aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package-lock.json362
-rw-r--r--package.json5
-rw-r--r--src/main/claude/index.ts142
-rw-r--r--src/main/claude/phases.ts104
-rw-r--r--src/main/db/index.ts31
-rw-r--r--src/main/db/projects.ts38
-rw-r--r--src/main/db/schema.ts35
-rw-r--r--src/main/db/sessions.ts106
8 files changed, 821 insertions, 2 deletions
diff --git a/package-lock.json b/package-lock.json
index 74bac1f..64d46a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,11 @@
9 "version": "1.0.0", 9 "version": "1.0.0",
10 "hasInstallScript": true, 10 "hasInstallScript": true,
11 "dependencies": { 11 "dependencies": {
12 "@anthropic-ai/claude-agent-sdk": "^0.2.63",
12 "better-sqlite3": "12.2.0", 13 "better-sqlite3": "12.2.0",
13 "react": "^19.1.1", 14 "react": "^19.1.1",
14 "react-dom": "^19.1.1" 15 "react-dom": "^19.1.1",
16 "uuid": "^13.0.0"
15 }, 17 },
16 "devDependencies": { 18 "devDependencies": {
17 "@electron/rebuild": "4.0.1", 19 "@electron/rebuild": "4.0.1",
@@ -19,6 +21,7 @@
19 "@types/node": "24.3.2", 21 "@types/node": "24.3.2",
20 "@types/react": "^19.1.13", 22 "@types/react": "^19.1.13",
21 "@types/react-dom": "^19.1.9", 23 "@types/react-dom": "^19.1.9",
24 "@types/uuid": "^10.0.0",
22 "@vitejs/plugin-react": "^5.0.2", 25 "@vitejs/plugin-react": "^5.0.2",
23 "concurrently": "^9.2.1", 26 "concurrently": "^9.2.1",
24 "electron": "38.1.0", 27 "electron": "38.1.0",
@@ -28,6 +31,29 @@
28 "wait-on": "^8.0.5" 31 "wait-on": "^8.0.5"
29 } 32 }
30 }, 33 },
34 "node_modules/@anthropic-ai/claude-agent-sdk": {
35 "version": "0.2.63",
36 "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.63.tgz",
37 "integrity": "sha512-ZNiaQb/v6xkbrGt3dtq5J0DGY+AaOhoehUyposa3msvlAlkTHWNGR+NhbCcTE0ML1U91xhPqMAAwZIUqrlkKyQ==",
38 "license": "SEE LICENSE IN README.md",
39 "engines": {
40 "node": ">=18.0.0"
41 },
42 "optionalDependencies": {
43 "@img/sharp-darwin-arm64": "^0.34.2",
44 "@img/sharp-darwin-x64": "^0.34.2",
45 "@img/sharp-linux-arm": "^0.34.2",
46 "@img/sharp-linux-arm64": "^0.34.2",
47 "@img/sharp-linux-x64": "^0.34.2",
48 "@img/sharp-linuxmusl-arm64": "^0.34.2",
49 "@img/sharp-linuxmusl-x64": "^0.34.2",
50 "@img/sharp-win32-arm64": "^0.34.2",
51 "@img/sharp-win32-x64": "^0.34.2"
52 },
53 "peerDependencies": {
54 "zod": "^4.0.0"
55 }
56 },
31 "node_modules/@babel/code-frame": { 57 "node_modules/@babel/code-frame": {
32 "version": "7.27.1", 58 "version": "7.27.1",
33 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 59 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1749,6 +1775,310 @@
1749 "@hapi/hoek": "^11.0.2" 1775 "@hapi/hoek": "^11.0.2"
1750 } 1776 }
1751 }, 1777 },
1778 "node_modules/@img/sharp-darwin-arm64": {
1779 "version": "0.34.5",
1780 "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
1781 "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
1782 "cpu": [
1783 "arm64"
1784 ],
1785 "license": "Apache-2.0",
1786 "optional": true,
1787 "os": [
1788 "darwin"
1789 ],
1790 "engines": {
1791 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1792 },
1793 "funding": {
1794 "url": "https://opencollective.com/libvips"
1795 },
1796 "optionalDependencies": {
1797 "@img/sharp-libvips-darwin-arm64": "1.2.4"
1798 }
1799 },
1800 "node_modules/@img/sharp-darwin-x64": {
1801 "version": "0.34.5",
1802 "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
1803 "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
1804 "cpu": [
1805 "x64"
1806 ],
1807 "license": "Apache-2.0",
1808 "optional": true,
1809 "os": [
1810 "darwin"
1811 ],
1812 "engines": {
1813 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1814 },
1815 "funding": {
1816 "url": "https://opencollective.com/libvips"
1817 },
1818 "optionalDependencies": {
1819 "@img/sharp-libvips-darwin-x64": "1.2.4"
1820 }
1821 },
1822 "node_modules/@img/sharp-libvips-darwin-arm64": {
1823 "version": "1.2.4",
1824 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
1825 "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
1826 "cpu": [
1827 "arm64"
1828 ],
1829 "license": "LGPL-3.0-or-later",
1830 "optional": true,
1831 "os": [
1832 "darwin"
1833 ],
1834 "funding": {
1835 "url": "https://opencollective.com/libvips"
1836 }
1837 },
1838 "node_modules/@img/sharp-libvips-darwin-x64": {
1839 "version": "1.2.4",
1840 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
1841 "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
1842 "cpu": [
1843 "x64"
1844 ],
1845 "license": "LGPL-3.0-or-later",
1846 "optional": true,
1847 "os": [
1848 "darwin"
1849 ],
1850 "funding": {
1851 "url": "https://opencollective.com/libvips"
1852 }
1853 },
1854 "node_modules/@img/sharp-libvips-linux-arm": {
1855 "version": "1.2.4",
1856 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
1857 "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
1858 "cpu": [
1859 "arm"
1860 ],
1861 "license": "LGPL-3.0-or-later",
1862 "optional": true,
1863 "os": [
1864 "linux"
1865 ],
1866 "funding": {
1867 "url": "https://opencollective.com/libvips"
1868 }
1869 },
1870 "node_modules/@img/sharp-libvips-linux-arm64": {
1871 "version": "1.2.4",
1872 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
1873 "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
1874 "cpu": [
1875 "arm64"
1876 ],
1877 "license": "LGPL-3.0-or-later",
1878 "optional": true,
1879 "os": [
1880 "linux"
1881 ],
1882 "funding": {
1883 "url": "https://opencollective.com/libvips"
1884 }
1885 },
1886 "node_modules/@img/sharp-libvips-linux-x64": {
1887 "version": "1.2.4",
1888 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
1889 "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
1890 "cpu": [
1891 "x64"
1892 ],
1893 "license": "LGPL-3.0-or-later",
1894 "optional": true,
1895 "os": [
1896 "linux"
1897 ],
1898 "funding": {
1899 "url": "https://opencollective.com/libvips"
1900 }
1901 },
1902 "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
1903 "version": "1.2.4",
1904 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
1905 "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
1906 "cpu": [
1907 "arm64"
1908 ],
1909 "license": "LGPL-3.0-or-later",
1910 "optional": true,
1911 "os": [
1912 "linux"
1913 ],
1914 "funding": {
1915 "url": "https://opencollective.com/libvips"
1916 }
1917 },
1918 "node_modules/@img/sharp-libvips-linuxmusl-x64": {
1919 "version": "1.2.4",
1920 "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
1921 "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
1922 "cpu": [
1923 "x64"
1924 ],
1925 "license": "LGPL-3.0-or-later",
1926 "optional": true,
1927 "os": [
1928 "linux"
1929 ],
1930 "funding": {
1931 "url": "https://opencollective.com/libvips"
1932 }
1933 },
1934 "node_modules/@img/sharp-linux-arm": {
1935 "version": "0.34.5",
1936 "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
1937 "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
1938 "cpu": [
1939 "arm"
1940 ],
1941 "license": "Apache-2.0",
1942 "optional": true,
1943 "os": [
1944 "linux"
1945 ],
1946 "engines": {
1947 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1948 },
1949 "funding": {
1950 "url": "https://opencollective.com/libvips"
1951 },
1952 "optionalDependencies": {
1953 "@img/sharp-libvips-linux-arm": "1.2.4"
1954 }
1955 },
1956 "node_modules/@img/sharp-linux-arm64": {
1957 "version": "0.34.5",
1958 "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
1959 "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
1960 "cpu": [
1961 "arm64"
1962 ],
1963 "license": "Apache-2.0",
1964 "optional": true,
1965 "os": [
1966 "linux"
1967 ],
1968 "engines": {
1969 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1970 },
1971 "funding": {
1972 "url": "https://opencollective.com/libvips"
1973 },
1974 "optionalDependencies": {
1975 "@img/sharp-libvips-linux-arm64": "1.2.4"
1976 }
1977 },
1978 "node_modules/@img/sharp-linux-x64": {
1979 "version": "0.34.5",
1980 "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
1981 "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
1982 "cpu": [
1983 "x64"
1984 ],
1985 "license": "Apache-2.0",
1986 "optional": true,
1987 "os": [
1988 "linux"
1989 ],
1990 "engines": {
1991 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1992 },
1993 "funding": {
1994 "url": "https://opencollective.com/libvips"
1995 },
1996 "optionalDependencies": {
1997 "@img/sharp-libvips-linux-x64": "1.2.4"
1998 }
1999 },
2000 "node_modules/@img/sharp-linuxmusl-arm64": {
2001 "version": "0.34.5",
2002 "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
2003 "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
2004 "cpu": [
2005 "arm64"
2006 ],
2007 "license": "Apache-2.0",
2008 "optional": true,
2009 "os": [
2010 "linux"
2011 ],
2012 "engines": {
2013 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
2014 },
2015 "funding": {
2016 "url": "https://opencollective.com/libvips"
2017 },
2018 "optionalDependencies": {
2019 "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
2020 }
2021 },
2022 "node_modules/@img/sharp-linuxmusl-x64": {
2023 "version": "0.34.5",
2024 "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
2025 "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
2026 "cpu": [
2027 "x64"
2028 ],
2029 "license": "Apache-2.0",
2030 "optional": true,
2031 "os": [
2032 "linux"
2033 ],
2034 "engines": {
2035 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
2036 },
2037 "funding": {
2038 "url": "https://opencollective.com/libvips"
2039 },
2040 "optionalDependencies": {
2041 "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
2042 }
2043 },
2044 "node_modules/@img/sharp-win32-arm64": {
2045 "version": "0.34.5",
2046 "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
2047 "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
2048 "cpu": [
2049 "arm64"
2050 ],
2051 "license": "Apache-2.0 AND LGPL-3.0-or-later",
2052 "optional": true,
2053 "os": [
2054 "win32"
2055 ],
2056 "engines": {
2057 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
2058 },
2059 "funding": {
2060 "url": "https://opencollective.com/libvips"
2061 }
2062 },
2063 "node_modules/@img/sharp-win32-x64": {
2064 "version": "0.34.5",
2065 "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
2066 "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
2067 "cpu": [
2068 "x64"
2069 ],
2070 "license": "Apache-2.0 AND LGPL-3.0-or-later",
2071 "optional": true,
2072 "os": [
2073 "win32"
2074 ],
2075 "engines": {
2076 "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
2077 },
2078 "funding": {
2079 "url": "https://opencollective.com/libvips"
2080 }
2081 },
1752 "node_modules/@isaacs/balanced-match": { 2082 "node_modules/@isaacs/balanced-match": {
1753 "version": "4.0.1", 2083 "version": "4.0.1",
1754 "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", 2084 "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
@@ -2624,6 +2954,13 @@
2624 "@types/node": "*" 2954 "@types/node": "*"
2625 } 2955 }
2626 }, 2956 },
2957 "node_modules/@types/uuid": {
2958 "version": "10.0.0",
2959 "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
2960 "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
2961 "dev": true,
2962 "license": "MIT"
2963 },
2627 "node_modules/@types/verror": { 2964 "node_modules/@types/verror": {
2628 "version": "1.10.11", 2965 "version": "1.10.11",
2629 "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", 2966 "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
@@ -7447,6 +7784,19 @@
7447 "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 7784 "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
7448 "license": "MIT" 7785 "license": "MIT"
7449 }, 7786 },
7787 "node_modules/uuid": {
7788 "version": "13.0.0",
7789 "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
7790 "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
7791 "funding": [
7792 "https://github.com/sponsors/broofa",
7793 "https://github.com/sponsors/ctavan"
7794 ],
7795 "license": "MIT",
7796 "bin": {
7797 "uuid": "dist-node/bin/uuid"
7798 }
7799 },
7450 "node_modules/verror": { 7800 "node_modules/verror": {
7451 "version": "1.10.1", 7801 "version": "1.10.1",
7452 "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", 7802 "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
@@ -7706,6 +8056,16 @@
7706 "funding": { 8056 "funding": {
7707 "url": "https://github.com/sponsors/sindresorhus" 8057 "url": "https://github.com/sponsors/sindresorhus"
7708 } 8058 }
8059 },
8060 "node_modules/zod": {
8061 "version": "4.3.6",
8062 "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
8063 "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
8064 "license": "MIT",
8065 "peer": true,
8066 "funding": {
8067 "url": "https://github.com/sponsors/colinhacks"
8068 }
7709 } 8069 }
7710 } 8070 }
7711} 8071}
diff --git a/package.json b/package.json
index 2f7e388..a76759a 100644
--- a/package.json
+++ b/package.json
@@ -43,9 +43,11 @@
43 } 43 }
44 }, 44 },
45 "dependencies": { 45 "dependencies": {
46 "@anthropic-ai/claude-agent-sdk": "^0.2.63",
46 "better-sqlite3": "12.2.0", 47 "better-sqlite3": "12.2.0",
47 "react": "^19.1.1", 48 "react": "^19.1.1",
48 "react-dom": "^19.1.1" 49 "react-dom": "^19.1.1",
50 "uuid": "^13.0.0"
49 }, 51 },
50 "devDependencies": { 52 "devDependencies": {
51 "@electron/rebuild": "4.0.1", 53 "@electron/rebuild": "4.0.1",
@@ -53,6 +55,7 @@
53 "@types/node": "24.3.2", 55 "@types/node": "24.3.2",
54 "@types/react": "^19.1.13", 56 "@types/react": "^19.1.13",
55 "@types/react-dom": "^19.1.9", 57 "@types/react-dom": "^19.1.9",
58 "@types/uuid": "^10.0.0",
56 "@vitejs/plugin-react": "^5.0.2", 59 "@vitejs/plugin-react": "^5.0.2",
57 "concurrently": "^9.2.1", 60 "concurrently": "^9.2.1",
58 "electron": "38.1.0", 61 "electron": "38.1.0",
diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts
new file mode 100644
index 0000000..34a914e
--- /dev/null
+++ b/src/main/claude/index.ts
@@ -0,0 +1,142 @@
1import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk";
2import type { Session } from "../db/sessions";
3import { getPhaseConfig, getNextPhase, getArtifactFilename } from "./phases";
4import type { Phase, UserPermissionMode } from "./phases";
5import { getProject } from "../db/projects";
6import { updateSession } from "../db/sessions";
7import fs from "node:fs";
8import path from "node:path";
9
10// Track active queries by session ID
11const activeQueries = new Map<string, Query>();
12
13function ensureArtifactDir(projectPath: string): void {
14 const dir = path.join(projectPath, ".claude-flow");
15 if (!fs.existsSync(dir)) {
16 fs.mkdirSync(dir, { recursive: true });
17 }
18}
19
20export interface SendMessageOptions {
21 session: Session;
22 message: string;
23 onMessage: (msg: SDKMessage) => void;
24}
25
26export async function sendMessage({
27 session,
28 message,
29 onMessage,
30}: SendMessageOptions): Promise<void> {
31 const project = getProject(session.project_id);
32 if (!project) throw new Error("Project not found");
33
34 ensureArtifactDir(project.path);
35
36 const phaseConfig = getPhaseConfig(
37 session.phase as Phase,
38 session.permission_mode as UserPermissionMode
39 );
40
41 const q = query({
42 prompt: message,
43 options: {
44 cwd: project.path,
45 resume: session.claude_session_id ?? undefined,
46 tools: phaseConfig.tools,
47 permissionMode: phaseConfig.permissionMode,
48 // Add system prompt via extraArgs since there's no direct option
49 extraArgs: {
50 "system-prompt": phaseConfig.systemPrompt,
51 },
52 },
53 });
54
55 activeQueries.set(session.id, q);
56
57 try {
58 for await (const msg of q) {
59 // Capture session ID from init message
60 if (msg.type === "system" && msg.subtype === "init") {
61 if (!session.claude_session_id) {
62 updateSession(session.id, { claude_session_id: msg.session_id });
63 }
64 }
65 onMessage(msg);
66 }
67 } finally {
68 activeQueries.delete(session.id);
69 }
70}
71
72export function interruptSession(sessionId: string): void {
73 const q = activeQueries.get(sessionId);
74 if (q) {
75 q.close();
76 activeQueries.delete(sessionId);
77 }
78}
79
80/**
81 * Trigger a review: Claude reads the document and addresses user annotations
82 */
83export async function triggerReview(
84 session: Session,
85 onMessage: (msg: SDKMessage) => void
86): Promise<void> {
87 const docName = getArtifactFilename(session.phase as Phase);
88 const message = `I've updated .claude-flow/${docName} with annotations. Read the file, find all my inline notes (marked with // REVIEW:, // NOTE:, TODO:, or similar), address each one, and update the document accordingly. Do not implement anything yet.`;
89
90 await sendMessage({ session, message, onMessage });
91}
92
93/**
94 * Advance to the next phase
95 */
96export function advancePhase(session: Session): Phase | null {
97 const nextPhase = getNextPhase(session.phase as Phase);
98 if (nextPhase) {
99 updateSession(session.id, { phase: nextPhase });
100 }
101 return nextPhase;
102}
103
104/**
105 * Read an artifact file from the project's .claude-flow directory
106 */
107export function readArtifact(
108 projectPath: string,
109 filename: string
110): string | null {
111 const filePath = path.join(projectPath, ".claude-flow", filename);
112 if (fs.existsSync(filePath)) {
113 return fs.readFileSync(filePath, "utf-8");
114 }
115 return null;
116}
117
118/**
119 * Write an artifact file to the project's .claude-flow directory
120 */
121export function writeArtifact(
122 projectPath: string,
123 filename: string,
124 content: string
125): void {
126 const dir = path.join(projectPath, ".claude-flow");
127 if (!fs.existsSync(dir)) {
128 fs.mkdirSync(dir, { recursive: true });
129 }
130 fs.writeFileSync(path.join(dir, filename), content, "utf-8");
131}
132
133/**
134 * Get the initial message for a phase
135 */
136export function getPhaseInitialMessage(phase: Phase): string {
137 return getPhaseConfig(phase).initialMessage;
138}
139
140// Re-export types
141export type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
142export type { Phase, UserPermissionMode } from "./phases";
diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts
new file mode 100644
index 0000000..d503f3a
--- /dev/null
+++ b/src/main/claude/phases.ts
@@ -0,0 +1,104 @@
1import type { PermissionMode } from "@anthropic-ai/claude-agent-sdk";
2
3export type Phase = "research" | "plan" | "implement";
4export type UserPermissionMode = "acceptEdits" | "bypassPermissions";
5
6export interface PhaseConfig {
7 systemPrompt: string;
8 tools: string[];
9 permissionMode: PermissionMode;
10 initialMessage: string;
11}
12
13export const phaseConfigs: Record<Phase, PhaseConfig> = {
14 research: {
15 permissionMode: "plan",
16 tools: ["Read", "Glob", "Grep", "Bash", "Write"],
17 initialMessage:
18 "What areas of the codebase should I research? What are you trying to build?",
19 systemPrompt: `You are in RESEARCH mode.
20
21Your job is to deeply understand the codebase before any changes are made.
22
23When the user tells you what to research:
241. Read files thoroughly — understand all intricacies
252. Write your findings to .claude-flow/research.md
263. Format it as clear, readable markdown
27
28Rules:
29- DO NOT make any code changes
30- DO NOT modify any files except .claude-flow/research.md
31- Be thorough — surface-level reading is not acceptable
32
33When the user clicks "Review", read .claude-flow/research.md for their annotations and update accordingly.
34When the user clicks "Submit", they're ready to move to planning.`,
35 },
36
37 plan: {
38 permissionMode: "plan",
39 tools: ["Read", "Glob", "Grep", "Write"],
40 initialMessage:
41 "I'll create a detailed implementation plan based on my research. Give me a moment...",
42 systemPrompt: `You are in PLANNING mode.
43
44Based on the research in .claude-flow/research.md, write a detailed implementation plan.
45
46Write the plan to .claude-flow/plan.md with:
47- Detailed explanation of the approach
48- Specific code snippets showing proposed changes
49- File paths that will be modified
50- Trade-offs and considerations
51- A granular TODO list with checkboxes
52
53Rules:
54- DO NOT implement anything
55- DO NOT modify any source files
56- Only write to .claude-flow/plan.md
57
58The plan should be detailed enough that implementation becomes mechanical.
59
60When the user clicks "Review", read .claude-flow/plan.md for their annotations and update accordingly.
61When the user clicks "Submit", begin implementation.`,
62 },
63
64 implement: {
65 permissionMode: "acceptEdits",
66 tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
67 initialMessage:
68 "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.",
69 systemPrompt: `You are in IMPLEMENTATION mode. The plan has been approved.
70
71Read .claude-flow/plan.md and execute it:
72- Follow the plan exactly
73- Mark tasks complete (- [x]) as you finish them
74- Run typecheck/lint continuously if available
75- Do not add unnecessary comments
76- Do not stop until all tasks are complete
77
78If you encounter issues not covered by the plan, stop and ask.`,
79 },
80};
81
82export function getPhaseConfig(
83 phase: Phase,
84 userPermissionMode?: UserPermissionMode
85): PhaseConfig {
86 const config = { ...phaseConfigs[phase] };
87 if (phase === "implement" && userPermissionMode) {
88 config.permissionMode = userPermissionMode;
89 }
90 return config;
91}
92
93export function getNextPhase(phase: Phase): Phase | null {
94 const transitions: Record<Phase, Phase | null> = {
95 research: "plan",
96 plan: "implement",
97 implement: null,
98 };
99 return transitions[phase];
100}
101
102export function getArtifactFilename(phase: Phase): string {
103 return phase === "research" ? "research.md" : "plan.md";
104}
diff --git a/src/main/db/index.ts b/src/main/db/index.ts
new file mode 100644
index 0000000..a77cdd4
--- /dev/null
+++ b/src/main/db/index.ts
@@ -0,0 +1,31 @@
1import Database from "better-sqlite3";
2import { app } from "electron";
3import path from "node:path";
4import fs from "node:fs";
5import { initSchema } from "./schema";
6
7let db: Database.Database | null = null;
8
9export function getDb(): Database.Database {
10 if (db) return db;
11
12 const dbDir = app.getPath("userData");
13 if (!fs.existsSync(dbDir)) {
14 fs.mkdirSync(dbDir, { recursive: true });
15 }
16
17 const dbPath = path.join(dbDir, "claude-flow.db");
18 db = new Database(dbPath);
19 db.pragma("journal_mode = WAL");
20 db.pragma("foreign_keys = ON");
21
22 initSchema(db);
23 return db;
24}
25
26export function closeDb() {
27 if (db) {
28 db.close();
29 db = null;
30 }
31}
diff --git a/src/main/db/projects.ts b/src/main/db/projects.ts
new file mode 100644
index 0000000..88ef2f6
--- /dev/null
+++ b/src/main/db/projects.ts
@@ -0,0 +1,38 @@
1import { getDb } from "./index";
2import { v4 as uuid } from "uuid";
3
4export interface Project {
5 id: string;
6 name: string;
7 path: string;
8 created_at: number;
9 updated_at: number;
10}
11
12export function listProjects(): Project[] {
13 return getDb()
14 .prepare("SELECT * FROM projects ORDER BY updated_at DESC")
15 .all() as Project[];
16}
17
18export function getProject(id: string): Project | undefined {
19 return getDb()
20 .prepare("SELECT * FROM projects WHERE id = ?")
21 .get(id) as Project | undefined;
22}
23
24export function createProject(name: string, projectPath: string): Project {
25 const db = getDb();
26 const id = uuid();
27 const now = Math.floor(Date.now() / 1000);
28
29 db.prepare(
30 "INSERT INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
31 ).run(id, name, projectPath, now, now);
32
33 return { id, name, path: projectPath, created_at: now, updated_at: now };
34}
35
36export function deleteProject(id: string): void {
37 getDb().prepare("DELETE FROM projects WHERE id = ?").run(id);
38}
diff --git a/src/main/db/schema.ts b/src/main/db/schema.ts
new file mode 100644
index 0000000..c2093f9
--- /dev/null
+++ b/src/main/db/schema.ts
@@ -0,0 +1,35 @@
1import Database from "better-sqlite3";
2
3export function initSchema(db: Database.Database) {
4 db.exec(`
5 CREATE TABLE IF NOT EXISTS projects (
6 id TEXT PRIMARY KEY,
7 name TEXT NOT NULL,
8 path TEXT NOT NULL,
9 created_at INTEGER NOT NULL DEFAULT (unixepoch()),
10 updated_at INTEGER NOT NULL DEFAULT (unixepoch())
11 );
12
13 CREATE TABLE IF NOT EXISTS sessions (
14 id TEXT PRIMARY KEY,
15 project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
16 name TEXT NOT NULL,
17 phase TEXT NOT NULL DEFAULT 'research',
18 claude_session_id TEXT,
19 permission_mode TEXT NOT NULL DEFAULT 'acceptEdits',
20 created_at INTEGER NOT NULL DEFAULT (unixepoch()),
21 updated_at INTEGER NOT NULL DEFAULT (unixepoch())
22 );
23
24 CREATE TABLE IF NOT EXISTS messages (
25 id TEXT PRIMARY KEY,
26 session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
27 role TEXT NOT NULL,
28 content TEXT NOT NULL,
29 created_at INTEGER NOT NULL DEFAULT (unixepoch())
30 );
31
32 CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
33 CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
34 `);
35}
diff --git a/src/main/db/sessions.ts b/src/main/db/sessions.ts
new file mode 100644
index 0000000..684bb9e
--- /dev/null
+++ b/src/main/db/sessions.ts
@@ -0,0 +1,106 @@
1import { getDb } from "./index";
2import { v4 as uuid } from "uuid";
3
4export type Phase = "research" | "plan" | "implement";
5export type PermissionMode = "acceptEdits" | "bypassPermissions";
6
7export interface Session {
8 id: string;
9 project_id: string;
10 name: string;
11 phase: Phase;
12 claude_session_id: string | null;
13 permission_mode: PermissionMode;
14 created_at: number;
15 updated_at: number;
16}
17
18export interface Message {
19 id: string;
20 session_id: string;
21 role: "user" | "assistant";
22 content: string;
23 created_at: number;
24}
25
26export function listSessions(projectId: string): Session[] {
27 return getDb()
28 .prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY updated_at DESC")
29 .all(projectId) as Session[];
30}
31
32export function getSession(id: string): Session | undefined {
33 return getDb()
34 .prepare("SELECT * FROM sessions WHERE id = ?")
35 .get(id) as Session | undefined;
36}
37
38export function createSession(projectId: string, name: string): Session {
39 const db = getDb();
40 const id = uuid();
41 const now = Math.floor(Date.now() / 1000);
42
43 db.prepare(
44 `INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at)
45 VALUES (?, ?, ?, 'research', 'acceptEdits', ?, ?)`
46 ).run(id, projectId, name, now, now);
47
48 return {
49 id,
50 project_id: projectId,
51 name,
52 phase: "research",
53 claude_session_id: null,
54 permission_mode: "acceptEdits",
55 created_at: now,
56 updated_at: now,
57 };
58}
59
60export function updateSession(
61 id: string,
62 updates: Partial<Pick<Session, "name" | "phase" | "claude_session_id" | "permission_mode">>
63): void {
64 const db = getDb();
65 const sets: string[] = [];
66 const values: any[] = [];
67
68 for (const [key, value] of Object.entries(updates)) {
69 if (value !== undefined) {
70 sets.push(`${key} = ?`);
71 values.push(value);
72 }
73 }
74
75 if (sets.length > 0) {
76 sets.push("updated_at = ?");
77 values.push(Math.floor(Date.now() / 1000));
78 values.push(id);
79 db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values);
80 }
81}
82
83export function deleteSession(id: string): void {
84 getDb().prepare("DELETE FROM sessions WHERE id = ?").run(id);
85}
86
87// Messages
88export function listMessages(sessionId: string): Message[] {
89 return getDb()
90 .prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC")
91 .all(sessionId) as Message[];
92}
93
94export function addMessage(sessionId: string, role: Message["role"], content: string): Message {
95 const db = getDb();
96 const id = uuid();
97 const now = Math.floor(Date.now() / 1000);
98
99 db.prepare(
100 "INSERT INTO messages (id, session_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)"
101 ).run(id, sessionId, role, content, now);
102
103 db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
104
105 return { id, session_id: sessionId, role, content, created_at: now };
106}