@mrpatronz/nexusflow 0.2.18 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/README.md +27 -3
  2. package/dist/analyzers/index.d.ts +23 -0
  3. package/dist/analyzers/index.d.ts.map +1 -1
  4. package/dist/analyzers/index.js +62 -0
  5. package/dist/analyzers/index.js.map +1 -1
  6. package/dist/commands/commands.test.js +20 -6
  7. package/dist/commands/commands.test.js.map +1 -1
  8. package/dist/commands/commit.d.ts +2 -0
  9. package/dist/commands/commit.d.ts.map +1 -1
  10. package/dist/commands/commit.js +27 -5
  11. package/dist/commands/commit.js.map +1 -1
  12. package/dist/commands/diff.d.ts +9 -2
  13. package/dist/commands/diff.d.ts.map +1 -1
  14. package/dist/commands/diff.js +36 -12
  15. package/dist/commands/diff.js.map +1 -1
  16. package/dist/commands/doctor.js +2 -2
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/handoff.js +2 -2
  19. package/dist/commands/handoff.js.map +1 -1
  20. package/dist/commands/refresh.d.ts +4 -2
  21. package/dist/commands/refresh.d.ts.map +1 -1
  22. package/dist/commands/refresh.js +20 -36
  23. package/dist/commands/refresh.js.map +1 -1
  24. package/dist/commands/schedule.d.ts +35 -0
  25. package/dist/commands/schedule.d.ts.map +1 -0
  26. package/dist/commands/schedule.js +151 -0
  27. package/dist/commands/schedule.js.map +1 -0
  28. package/dist/commands/sync.d.ts.map +1 -1
  29. package/dist/commands/sync.js +58 -50
  30. package/dist/commands/sync.js.map +1 -1
  31. package/dist/core/analysis-cache.d.ts +58 -0
  32. package/dist/core/analysis-cache.d.ts.map +1 -0
  33. package/dist/core/analysis-cache.js +116 -0
  34. package/dist/core/analysis-cache.js.map +1 -0
  35. package/dist/core/analysis-cache.test.d.ts +2 -0
  36. package/dist/core/analysis-cache.test.d.ts.map +1 -0
  37. package/dist/core/analysis-cache.test.js +108 -0
  38. package/dist/core/analysis-cache.test.js.map +1 -0
  39. package/dist/core/refresh.d.ts +41 -0
  40. package/dist/core/refresh.d.ts.map +1 -0
  41. package/dist/core/refresh.js +60 -0
  42. package/dist/core/refresh.js.map +1 -0
  43. package/dist/core/scheduler.d.ts +117 -0
  44. package/dist/core/scheduler.d.ts.map +1 -0
  45. package/dist/core/scheduler.js +247 -0
  46. package/dist/core/scheduler.js.map +1 -0
  47. package/dist/core/scheduler.test.d.ts +2 -0
  48. package/dist/core/scheduler.test.d.ts.map +1 -0
  49. package/dist/core/scheduler.test.js +111 -0
  50. package/dist/core/scheduler.test.js.map +1 -0
  51. package/dist/core/sync.d.ts +50 -0
  52. package/dist/core/sync.d.ts.map +1 -0
  53. package/dist/core/sync.js +92 -0
  54. package/dist/core/sync.js.map +1 -0
  55. package/dist/core/workspace-state.d.ts +51 -0
  56. package/dist/core/workspace-state.d.ts.map +1 -0
  57. package/dist/core/workspace-state.js +108 -0
  58. package/dist/core/workspace-state.js.map +1 -0
  59. package/dist/core/workspace-state.test.d.ts +2 -0
  60. package/dist/core/workspace-state.test.d.ts.map +1 -0
  61. package/dist/core/workspace-state.test.js +84 -0
  62. package/dist/core/workspace-state.test.js.map +1 -0
  63. package/dist/generators/index.d.ts +7 -1
  64. package/dist/generators/index.d.ts.map +1 -1
  65. package/dist/generators/index.js +14 -1
  66. package/dist/generators/index.js.map +1 -1
  67. package/dist/gui/assets/index-Bdbld6yY.css +1 -0
  68. package/dist/gui/assets/index-D8iJPvnk.js +26 -0
  69. package/dist/gui/index.html +2 -2
  70. package/dist/index.js +107 -4
  71. package/dist/index.js.map +1 -1
  72. package/dist/mcp/server.d.ts.map +1 -1
  73. package/dist/mcp/server.js +42 -0
  74. package/dist/mcp/server.js.map +1 -1
  75. package/dist/server.d.ts.map +1 -1
  76. package/dist/server.js +175 -13
  77. package/dist/server.js.map +1 -1
  78. package/dist/server.test.js +66 -0
  79. package/dist/server.test.js.map +1 -1
  80. package/dist/types.d.ts +31 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/utils/multi-git.d.ts +54 -8
  83. package/dist/utils/multi-git.d.ts.map +1 -1
  84. package/dist/utils/multi-git.js +129 -21
  85. package/dist/utils/multi-git.js.map +1 -1
  86. package/dist/utils/multi-git.test.d.ts +2 -0
  87. package/dist/utils/multi-git.test.d.ts.map +1 -0
  88. package/dist/utils/multi-git.test.js +125 -0
  89. package/dist/utils/multi-git.test.js.map +1 -0
  90. package/package.json +13 -1
  91. package/.agents/skills/ccc/SKILL.md +0 -71
  92. package/.agents/skills/ccc/references/management.md +0 -110
  93. package/.agents/skills/ccc/references/settings.md +0 -126
  94. package/.github/dependabot.yml +0 -52
  95. package/.github/workflows/ci.yml +0 -44
  96. package/.github/workflows/dependabot-merge.yml +0 -53
  97. package/.github/workflows/release-desktop.yml +0 -67
  98. package/.github/workflows/release-npm.yml +0 -65
  99. package/.github/workflows/release-vscode.yml +0 -61
  100. package/.vscode/launch.json +0 -17
  101. package/.vscode/tasks.json +0 -17
  102. package/GETTING_STARTED.md +0 -114
  103. package/desktop/build-installer.js +0 -228
  104. package/desktop/build.js +0 -118
  105. package/desktop/installer/nexusflow.iss +0 -35
  106. package/desktop/neutralino.config.json +0 -39
  107. package/desktop/package-lock.json +0 -1060
  108. package/desktop/package.json +0 -14
  109. package/dist/gui/assets/index-CB-jWded.css +0 -1
  110. package/dist/gui/assets/index-DDXpZZu3.js +0 -25
  111. package/extension/media/icon.svg +0 -1
  112. package/extension/package-lock.json +0 -6044
  113. package/extension/package.json +0 -117
  114. package/extension/src/extension.ts +0 -653
  115. package/extension/tsconfig.json +0 -21
  116. package/gui/README.md +0 -73
  117. package/gui/e2e/wizard.spec.ts +0 -346
  118. package/gui/eslint.config.js +0 -26
  119. package/gui/index.html +0 -17
  120. package/gui/package-lock.json +0 -3079
  121. package/gui/package.json +0 -36
  122. package/gui/playwright.config.ts +0 -41
  123. package/gui/public/app_logo.png +0 -0
  124. package/gui/public/favicon.svg +0 -1
  125. package/gui/public/icons.svg +0 -24
  126. package/gui/src/App.css +0 -1
  127. package/gui/src/App.tsx +0 -3632
  128. package/gui/src/assets/hero.png +0 -0
  129. package/gui/src/assets/react.svg +0 -1
  130. package/gui/src/assets/vite.svg +0 -1
  131. package/gui/src/features/changes/ChangesViewer.tsx +0 -388
  132. package/gui/src/features/knowledge/KnowledgeBase.tsx +0 -84
  133. package/gui/src/features/onboarding/OnboardingWizard.tsx +0 -230
  134. package/gui/src/features/plan/ImplementationPlan.tsx +0 -32
  135. package/gui/src/features/services/ServiceConsole.tsx +0 -250
  136. package/gui/src/features/sessions/SessionHistory.tsx +0 -195
  137. package/gui/src/features/workspace/WorkspaceList.tsx +0 -666
  138. package/gui/src/index.css +0 -152
  139. package/gui/src/main.tsx +0 -94
  140. package/gui/src/types.ts +0 -62
  141. package/gui/tsconfig.app.json +0 -25
  142. package/gui/tsconfig.json +0 -7
  143. package/gui/tsconfig.node.json +0 -24
  144. package/gui/vite.config.ts +0 -12
  145. package/resources/workflows/plan-implement-review.md +0 -8
  146. package/resources/workflows/research-verify.md +0 -6
  147. package/resources/workflows/solo-developer.md +0 -3
  148. package/scripts/simulate-workspaces.ts +0 -55
  149. package/skills-lock.json +0 -11
  150. package/src/analyzers/detect-apis.ts +0 -290
  151. package/src/analyzers/detect-deps.ts +0 -315
  152. package/src/analyzers/detect-existing.ts +0 -74
  153. package/src/analyzers/detect-ports.ts +0 -110
  154. package/src/analyzers/index.ts +0 -97
  155. package/src/analyzers/messaging-analyzer.ts +0 -254
  156. package/src/analyzers/readme-summarizer.ts +0 -102
  157. package/src/analyzers/run-analyzer.ts +0 -269
  158. package/src/analyzers/tech-stack.ts +0 -283
  159. package/src/commands/adapter.ts +0 -348
  160. package/src/commands/add-repo.ts +0 -156
  161. package/src/commands/commands.test.ts +0 -156
  162. package/src/commands/commit.ts +0 -131
  163. package/src/commands/config.ts +0 -52
  164. package/src/commands/create.ts +0 -204
  165. package/src/commands/desktop.ts +0 -128
  166. package/src/commands/diff.ts +0 -125
  167. package/src/commands/doctor.ts +0 -370
  168. package/src/commands/handoff.ts +0 -266
  169. package/src/commands/init.ts +0 -134
  170. package/src/commands/list.ts +0 -46
  171. package/src/commands/logs.ts +0 -63
  172. package/src/commands/mcp.ts +0 -94
  173. package/src/commands/open.ts +0 -129
  174. package/src/commands/refresh.ts +0 -137
  175. package/src/commands/remove.ts +0 -98
  176. package/src/commands/start.ts +0 -117
  177. package/src/commands/status.ts +0 -54
  178. package/src/commands/stop.ts +0 -55
  179. package/src/commands/sync.ts +0 -147
  180. package/src/commands/tui.ts +0 -484
  181. package/src/commands/ui.ts +0 -111
  182. package/src/core/adapters/local-storage.ts +0 -66
  183. package/src/core/adapters/obsidian-storage.ts +0 -151
  184. package/src/core/adapters/registry.ts +0 -44
  185. package/src/core/adapters/storage.test.ts +0 -174
  186. package/src/core/adapters/vault-storage.ts +0 -86
  187. package/src/core/config.test.ts +0 -96
  188. package/src/core/config.ts +0 -140
  189. package/src/core/graph.ts +0 -344
  190. package/src/core/plugins/index.ts +0 -17
  191. package/src/core/plugins/loader.ts +0 -36
  192. package/src/core/ports/storage.ts +0 -71
  193. package/src/core/scanner.test.ts +0 -61
  194. package/src/core/scanner.ts +0 -91
  195. package/src/core/storage.ts +0 -37
  196. package/src/core/workspace.ts +0 -487
  197. package/src/core/worktree.ts +0 -120
  198. package/src/generators/antigravity.ts +0 -32
  199. package/src/generators/base.ts +0 -243
  200. package/src/generators/claude.ts +0 -34
  201. package/src/generators/codex.ts +0 -48
  202. package/src/generators/copilot.ts +0 -56
  203. package/src/generators/cursor.ts +0 -44
  204. package/src/generators/diff-context.ts +0 -75
  205. package/src/generators/index.ts +0 -322
  206. package/src/generators/map-generator.test.ts +0 -159
  207. package/src/generators/map-generator.ts +0 -539
  208. package/src/generators/plan-generator.ts +0 -483
  209. package/src/generators/skills-generator.ts +0 -259
  210. package/src/index.ts +0 -539
  211. package/src/mcp/server.ts +0 -294
  212. package/src/orchestration/detect.ts +0 -313
  213. package/src/orchestration/index.ts +0 -7
  214. package/src/orchestration/runner.ts +0 -283
  215. package/src/server.test.ts +0 -618
  216. package/src/server.ts +0 -1314
  217. package/src/types.ts +0 -415
  218. package/src/utils/detect-ai.test.ts +0 -44
  219. package/src/utils/detect-ai.ts +0 -84
  220. package/src/utils/detect-editors.test.ts +0 -48
  221. package/src/utils/detect-editors.ts +0 -57
  222. package/src/utils/git.test.ts +0 -74
  223. package/src/utils/git.ts +0 -117
  224. package/src/utils/local-ai.test.ts +0 -130
  225. package/src/utils/local-ai.ts +0 -116
  226. package/src/utils/multi-git.ts +0 -313
  227. package/src/utils/prompts.ts +0 -209
  228. package/src/utils/session-finder.ts +0 -483
  229. package/src/utils/system-scanner.test.ts +0 -89
  230. package/src/utils/system-scanner.ts +0 -96
  231. package/src/utils/update-check.ts +0 -286
  232. package/src/utils/workflow-advisor.ts +0 -118
  233. package/src/utils/workflows.test.ts +0 -114
  234. package/src/utils/workflows.ts +0 -178
  235. package/tsconfig.json +0 -19
  236. package/vitest.config.ts +0 -14
@@ -1,487 +0,0 @@
1
- /**
2
- * @module core/workspace
3
- * Creates and manages NexusFlow workspaces — directories that group
4
- * worktrees for a multi-repo feature together with a `nexusflow.json`
5
- * manifest.
6
- */
7
-
8
- import * as fs from 'node:fs/promises';
9
- import * as path from 'node:path';
10
- import * as os from 'node:os';
11
- import { execa } from 'execa';
12
-
13
- import type { Feature, RepoInfo, WorkspaceContext } from '../types.js';
14
- import { createWorktree, removeWorktree } from './worktree.js';
15
- import { detectDefaultBranch } from '../utils/git.js';
16
- import { analyzeAllRepos } from '../analyzers/index.js';
17
- import { generateContextFiles } from '../generators/index.js';
18
- import { loadConfig } from './config.js';
19
- import { getActiveStorageProvider } from './adapters/registry.js';
20
- import { deleteWorkspaceFiles } from './storage.js';
21
-
22
- /** Name of the per-workspace manifest file. */
23
- const MANIFEST_FILE = 'nexusflow.json';
24
-
25
- /**
26
- * Derives the workspace directory path for a given branch name.
27
- *
28
- * @param workspacesDir - The root workspaces directory (e.g. ~/dev/workspaces).
29
- * @param branchName - The feature branch name used as the workspace folder.
30
- * @returns Absolute path to the workspace directory.
31
- */
32
- export function getWorkspacePath(
33
- workspacesDir: string,
34
- branchName: string,
35
- ): string {
36
- return path.join(workspacesDir, branchName);
37
- }
38
-
39
- /**
40
- * Creates a full workspace for a feature:
41
- * 1. Creates the workspace directory.
42
- * 2. Creates a git worktree for every repo in the feature.
43
- * 3. Saves the feature manifest (`nexusflow.json`).
44
- *
45
- * @param feature - The feature definition.
46
- * @param repos - Resolved repo metadata for every repo in the feature.
47
- * @returns The absolute path to the newly created workspace.
48
- */
49
- export async function createWorkspace(
50
- feature: Feature,
51
- repos: RepoInfo[],
52
- ): Promise<string> {
53
- const workspacePath = feature.workspacePath;
54
-
55
- // Ensure the workspace directory exists.
56
- await fs.mkdir(workspacePath, { recursive: true });
57
-
58
- // Initialize git repository at workspace root to prevent AI assistants (like Claude)
59
- // from climbing up to parent git repositories (main/master).
60
- try {
61
- await execa('git', ['init'], { cwd: workspacePath });
62
-
63
- // Write a .gitignore to ignore the sub-repositories
64
- const gitignoreContent = repos.map((repo) => `/${repo.name}/`).join('\n') + '\n';
65
- await fs.writeFile(path.join(workspacePath, '.gitignore'), gitignoreContent, 'utf-8');
66
- } catch (error) {
67
- // Silently ignore or log warning if git init fails
68
- console.warn('Warning: Failed to initialize git repository at workspace root:', error);
69
- }
70
-
71
- // Generate a .code-workspace file so VS Code opens each repo as a top-level workspace
72
- // folder with its own SCM provider. Without this, VS Code's git scanner respects the
73
- // root .gitignore (which lists every repo dir) and never discovers the worktrees.
74
- try {
75
- const workspaceName = path.basename(workspacePath);
76
- const codeWorkspace = {
77
- folders: [
78
- { path: '.', name: `${workspaceName} (workspace)` },
79
- ...repos.map((repo) => ({ path: repo.name, name: repo.name })),
80
- ],
81
- settings: {
82
- 'search.useIgnoreFiles': false,
83
- },
84
- };
85
- await fs.writeFile(
86
- path.join(workspacePath, `${workspaceName}.code-workspace`),
87
- JSON.stringify(codeWorkspace, null, 2) + '\n',
88
- 'utf-8',
89
- );
90
- } catch (error) {
91
- console.warn('Warning: Failed to create .code-workspace file:', error);
92
- }
93
-
94
- // Create .vscode/settings.json for editors that don't use the .code-workspace file
95
- try {
96
- const vscodeDir = path.join(workspacePath, '.vscode');
97
- await fs.mkdir(vscodeDir, { recursive: true });
98
- const settings = {
99
- 'search.useIgnoreFiles': false,
100
- };
101
- await fs.writeFile(
102
- path.join(vscodeDir, 'settings.json'),
103
- JSON.stringify(settings, null, 2) + '\n',
104
- 'utf-8',
105
- );
106
- } catch (error) {
107
- console.warn('Warning: Failed to create .vscode/settings.json:', error);
108
- }
109
-
110
- // Create .cursor/mcp.json for workspace-local Cursor MCP configuration
111
- try {
112
- const cursorDir = path.join(workspacePath, '.cursor');
113
- await fs.mkdir(cursorDir, { recursive: true });
114
- const cursorMcp = {
115
- "mcpServers": {
116
- "nexusflow": {
117
- "command": "npx",
118
- "args": ["-y", "@mrpatronz/nexusflow", "mcp", "start"]
119
- }
120
- }
121
- };
122
- await fs.writeFile(
123
- path.join(cursorDir, 'mcp.json'),
124
- JSON.stringify(cursorMcp, null, 2) + '\n',
125
- 'utf-8'
126
- );
127
- } catch (error) {
128
- console.warn('Warning: Failed to create .cursor/mcp.json:', error);
129
- }
130
-
131
- // Create a worktree for each repo inside the workspace.
132
- for (const repo of repos) {
133
- const worktreeTarget = path.join(workspacePath, repo.name);
134
- await createWorktree(
135
- repo.path,
136
- worktreeTarget,
137
- feature.branchName,
138
- repo.defaultBranch,
139
- );
140
- }
141
-
142
- // Persist the feature manifest.
143
- await saveFeatureConfig(workspacePath, feature);
144
-
145
- // Exclude NexusFlow files from git
146
- await excludeNexusFlowFiles(workspacePath, feature);
147
-
148
- return workspacePath;
149
- }
150
-
151
- /**
152
- * Lists all existing workspaces by reading `nexusflow.json` manifests from
153
- * each subdirectory of {@link workspacesDir}.
154
- *
155
- * Directories that do not contain a valid manifest are silently skipped.
156
- *
157
- * @param workspacesDir - The root workspaces directory.
158
- * @returns An array of {@link Feature} objects for each discovered workspace.
159
- */
160
- export async function listWorkspaces(
161
- workspacesDir: string,
162
- ): Promise<Feature[]> {
163
- const features: Feature[] = [];
164
-
165
- async function scan(dir: string, depth: number) {
166
- if (depth > 3) return;
167
-
168
- let entries: import('node:fs').Dirent[];
169
- try {
170
- entries = await fs.readdir(dir, { withFileTypes: true });
171
- } catch {
172
- return;
173
- }
174
-
175
- // Check if current directory contains a nexusflow.json manifest
176
- const hasManifest = entries.some(
177
- (e) => e.isFile() && e.name === MANIFEST_FILE,
178
- );
179
- if (hasManifest) {
180
- const loaded = await loadFeatureConfig(dir);
181
- if (loaded) {
182
- features.push(loaded);
183
- return; // Workspaces do not nest.
184
- }
185
- }
186
-
187
- // Otherwise, recursively scan subdirectories
188
- for (const entry of entries) {
189
- if (entry.isDirectory()) {
190
- await scan(path.join(dir, entry.name), depth + 1);
191
- }
192
- }
193
- }
194
-
195
- await scan(workspacesDir, 1);
196
- return features;
197
- }
198
-
199
- /**
200
- * Saves a {@link Feature} as `nexusflow.json` inside the given workspace.
201
- *
202
- * @param workspacePath - Absolute path to the workspace directory.
203
- * @param feature - The feature definition to persist.
204
- */
205
- export async function saveFeatureConfig(
206
- workspacePath: string,
207
- feature: Feature,
208
- ): Promise<void> {
209
- const data = JSON.stringify(feature, null, 2) + '\n';
210
- const adapter = getActiveStorageProvider();
211
- await adapter.writeWorkspaceFile(workspacePath, feature.id, MANIFEST_FILE, data);
212
- }
213
-
214
- /**
215
- * Loads a {@link Feature} from the `nexusflow.json` manifest inside a
216
- * workspace directory.
217
- *
218
- * @param workspacePath - Absolute path to the workspace directory.
219
- * @returns The loaded feature, or `null` if the manifest doesn't exist or is
220
- * invalid.
221
- */
222
- export async function loadFeatureConfig(
223
- workspacePath: string,
224
- ): Promise<Feature | null> {
225
- const featureId = path.basename(workspacePath);
226
- const adapter = getActiveStorageProvider();
227
-
228
- // 1. Try reading via the storage adapter first
229
- try {
230
- const raw = await adapter.readWorkspaceFile(workspacePath, featureId, MANIFEST_FILE);
231
- return JSON.parse(raw) as Feature;
232
- } catch {}
233
-
234
- // 2. Try direct local check
235
- const manifestPath = path.join(workspacePath, MANIFEST_FILE);
236
- try {
237
- const raw = await fs.readFile(manifestPath, 'utf-8');
238
- return JSON.parse(raw) as Feature;
239
- } catch {}
240
-
241
- // 3. Try direct vault check
242
- const vaultManifest = path.join(os.homedir(), '.nexusflow', 'vault', featureId, MANIFEST_FILE);
243
- try {
244
- const raw = await fs.readFile(vaultManifest, 'utf-8');
245
- return JSON.parse(raw) as Feature;
246
- } catch {}
247
-
248
- // 4. Traverse parent directories
249
- const rootDir = await findWorkspaceRoot(workspacePath);
250
- if (rootDir && rootDir !== workspacePath) {
251
- return loadFeatureConfig(rootDir);
252
- }
253
-
254
- return null;
255
- }
256
-
257
- /**
258
- * Traverses up parent directories to find a directory containing `nexusflow.json`.
259
- *
260
- * @param startPath - Path to start searching from.
261
- * @returns Absolute path to the workspace root directory, or null if not found.
262
- */
263
- export async function findWorkspaceRoot(startPath: string): Promise<string | null> {
264
- let current = path.resolve(startPath);
265
- while (true) {
266
- const manifestPath = path.join(current, MANIFEST_FILE);
267
- try {
268
- await fs.access(manifestPath);
269
- return current;
270
- } catch {
271
- const parent = path.dirname(current);
272
- if (parent === current) {
273
- break; // Reached root directory
274
- }
275
- current = parent;
276
- }
277
- }
278
- return null;
279
- }
280
-
281
- /**
282
- * Resolves a repo path to a full RepoInfo object.
283
- *
284
- * @param repoPath - Absolute path to the original repository.
285
- */
286
- export async function resolveRepoInfo(repoPath: string): Promise<RepoInfo> {
287
- const defaultBranch = await detectDefaultBranch(repoPath);
288
- return {
289
- name: path.basename(repoPath),
290
- path: repoPath,
291
- defaultBranch,
292
- };
293
- }
294
-
295
- /**
296
- * Deletes a workspace cleanly, removing all associated git worktrees first,
297
- * then deleting the directory from disk.
298
- *
299
- * @param workspacePath - Absolute path to the workspace directory.
300
- */
301
- export async function deleteWorkspace(
302
- workspacePath: string,
303
- ): Promise<void> {
304
- const feature = await loadFeatureConfig(workspacePath);
305
- if (feature) {
306
- try {
307
- await deleteWorkspaceFiles(workspacePath, feature.id);
308
- } catch (error) {
309
- console.warn(`Warning: failed to delete workspace context files from storage:`, error);
310
- }
311
-
312
- const origRepos = feature.originalRepos || [];
313
- for (let i = 0; i < feature.repos.length; i++) {
314
- const worktreePath = feature.repos[i]!;
315
- const originalPath = origRepos[i] || worktreePath;
316
- const repoName = path.basename(worktreePath);
317
- try {
318
- await removeWorktree(originalPath, worktreePath, true);
319
- } catch (error) {
320
- console.warn(`Warning: failed to remove worktree for ${repoName} in ${originalPath}:`, error);
321
- try {
322
- await execa('git', ['worktree', 'prune'], { cwd: originalPath });
323
- } catch (pruneError) {
324
- console.warn(`Warning: failed to prune worktrees in ${originalPath}:`, pruneError);
325
- }
326
- }
327
- }
328
- } else {
329
- // Manifest is missing. Try to detect worktrees by scanning subdirectories
330
- try {
331
- const entries = await fs.readdir(workspacePath, { withFileTypes: true });
332
- for (const entry of entries) {
333
- if (entry.isDirectory()) {
334
- const subPath = path.join(workspacePath, entry.name);
335
- const gitFilePath = path.join(subPath, '.git');
336
- try {
337
- const stat = await fs.stat(gitFilePath);
338
- if (stat.isFile()) {
339
- const content = await fs.readFile(gitFilePath, 'utf-8');
340
- const match = content.match(/gitdir:\s*(.+)\.git\/worktrees/);
341
- if (match && match[1]) {
342
- const mainRepoPath = path.resolve(match[1].trim());
343
- await removeWorktree(mainRepoPath, subPath, true);
344
- }
345
- }
346
- } catch {}
347
- }
348
- }
349
- } catch {}
350
- }
351
-
352
- // Delete the directory itself
353
- try {
354
- await fs.rm(workspacePath, { recursive: true, force: true });
355
- } catch (error) {
356
- console.error(`Failed to delete workspace directory ${workspacePath}:`, error);
357
- throw error;
358
- }
359
- }
360
-
361
- /**
362
- * Adds a repository to an existing workspace.
363
- *
364
- * @param workspacePath - Absolute path to the workspace directory.
365
- * @param repoPath - Absolute path to the repository to add.
366
- */
367
- export async function addRepoToWorkspace(
368
- workspacePath: string,
369
- repoPath: string,
370
- ): Promise<void> {
371
- const feature = await loadFeatureConfig(workspacePath);
372
- if (!feature) {
373
- throw new Error(`Workspace manifest not found at ${workspacePath}`);
374
- }
375
-
376
- const newRepoInfo = await resolveRepoInfo(repoPath);
377
- const worktreeTarget = path.join(workspacePath, newRepoInfo.name);
378
-
379
- if (feature.repos.includes(worktreeTarget)) {
380
- throw new Error(`Repository ${repoPath} is already in the workspace`);
381
- }
382
-
383
- // 1. Create the worktree
384
- await createWorktree(
385
- newRepoInfo.path,
386
- worktreeTarget,
387
- feature.branchName,
388
- newRepoInfo.defaultBranch,
389
- );
390
-
391
- // 2. Update manifest
392
- feature.repos.push(worktreeTarget);
393
- if (!feature.originalRepos) {
394
- feature.originalRepos = [];
395
- }
396
- feature.originalRepos.push(repoPath);
397
- await saveFeatureConfig(workspacePath, feature);
398
-
399
- // 3. Update .gitignore at workspace root
400
- try {
401
- const gitignorePath = path.join(workspacePath, '.gitignore');
402
- let gitignoreContent = '';
403
- try {
404
- gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
405
- } catch {}
406
-
407
- const entry = `/${newRepoInfo.name}/`;
408
- if (!gitignoreContent.includes(entry)) {
409
- gitignoreContent = gitignoreContent.trim() + '\n' + entry + '\n';
410
- await fs.writeFile(gitignorePath, gitignoreContent, 'utf-8');
411
- }
412
- } catch (error) {
413
- console.warn('Warning: Failed to update .gitignore:', error);
414
- }
415
-
416
- // 4. Re-run analysis, update configs, and repack workspace
417
- const allRepos = await Promise.all(feature.repos.map(resolveRepoInfo));
418
- const workspaceRepos = allRepos.map((repo) => ({
419
- ...repo,
420
- path: path.join(workspacePath, repo.name),
421
- }));
422
- const analysis = await analyzeAllRepos(workspaceRepos);
423
- const ctx: WorkspaceContext = {
424
- feature,
425
- repos: workspaceRepos,
426
- analysis,
427
- };
428
-
429
- await generateContextFiles(ctx, feature.assistants, workspacePath);
430
- await excludeNexusFlowFiles(workspacePath, feature);
431
- }
432
-
433
- /**
434
- * Automatically adds NexusFlow workspace context and config files to git's local
435
- * exclude list for each repository so they never show up in `git status` or get committed.
436
- */
437
- async function excludeNexusFlowFiles(workspacePath: string, feature: Feature): Promise<void> {
438
- const excludeEntries = [
439
- 'CLAUDE.md',
440
- 'AGENTS.md',
441
- 'WORKSPACE.md',
442
- 'nexusflow-knowledge.md',
443
- 'nexusflow-plan.md',
444
- 'nexusflow-conventions-*.md',
445
- 'nexusflow-map-*.md',
446
- 'nexusflow-diff-context.md',
447
- '.cursor/rules/',
448
- '.vscode/settings.json'
449
- ];
450
-
451
- for (const repoPath of feature.repos) {
452
- try {
453
- const gitDir = path.join(repoPath, '.git');
454
- let worktreeGitDir = gitDir;
455
- const stat = await fs.stat(gitDir);
456
- if (stat.isFile()) {
457
- const fileContent = await fs.readFile(gitDir, 'utf8');
458
- const match = fileContent.match(/gitdir:\s*(.+)/);
459
- if (match) {
460
- worktreeGitDir = match[1].trim();
461
- }
462
- }
463
-
464
- const excludeFilePath = path.join(worktreeGitDir, 'info', 'exclude');
465
- await fs.mkdir(path.dirname(excludeFilePath), { recursive: true });
466
-
467
- let excludeContent = '';
468
- try {
469
- excludeContent = await fs.readFile(excludeFilePath, 'utf8');
470
- } catch {}
471
-
472
- let updated = false;
473
- for (const entry of excludeEntries) {
474
- if (!excludeContent.includes(entry)) {
475
- excludeContent = excludeContent.trim() + '\n' + entry + '\n';
476
- updated = true;
477
- }
478
- }
479
-
480
- if (updated) {
481
- await fs.writeFile(excludeFilePath, excludeContent.trim() + '\n', 'utf8');
482
- }
483
- } catch {
484
- // Ignore if not a git repo or directory missing
485
- }
486
- }
487
- }
@@ -1,120 +0,0 @@
1
- /**
2
- * @module core/worktree
3
- * Git worktree operations — create and remove worktrees for feature branches.
4
- */
5
-
6
- import { execa } from 'execa';
7
-
8
- /**
9
- * Creates a new git worktree for a feature branch.
10
- *
11
- * Runs:
12
- * 1. `git fetch origin` — ensure we have the latest remote refs.
13
- * 2. `git worktree add <targetPath> -b <branchName> origin/<baseBranch>`
14
- *
15
- * @param repoPath - Absolute path to the main repo checkout.
16
- * @param targetPath - Absolute path where the worktree should be created.
17
- * @param branchName - Name of the new local branch to create.
18
- * @param baseBranch - Remote branch to base the new branch on (e.g. 'main').
19
- */
20
- export async function createWorktree(
21
- repoPath: string,
22
- targetPath: string,
23
- branchName: string,
24
- baseBranch: string,
25
- ): Promise<void> {
26
- let fetched = false;
27
- // Fetch latest remote state.
28
- try {
29
- await execa('git', ['fetch', 'origin'], { cwd: repoPath });
30
- fetched = true;
31
- } catch {
32
- // Silently ignore fetch failures (e.g., offline or no remote origin)
33
- }
34
-
35
- // Update local base branch, main, and master to keep them in sync with remote before branching
36
- if (fetched) {
37
- try {
38
- const { stdout: currentBranchRaw } = await execa('git', ['branch', '--show-current'], { cwd: repoPath });
39
- const currentBranch = currentBranchRaw.trim();
40
-
41
- const branchesToUpdate = Array.from(new Set([baseBranch, 'main', 'master']));
42
-
43
- for (const branch of branchesToUpdate) {
44
- try {
45
- if (currentBranch === branch) {
46
- // Safe fast-forward pull for the currently checked-out branch
47
- await execa('git', ['pull', '--ff-only'], { cwd: repoPath });
48
- } else {
49
- // Fast-forward local ref from remote ref without checkout
50
- await execa('git', ['fetch', 'origin', `${branch}:${branch}`], { cwd: repoPath });
51
- }
52
- } catch {
53
- // Ignore individual branch update failures
54
- }
55
- }
56
- } catch {
57
- // Ignore failures
58
- }
59
- }
60
-
61
- // Determine starting point: remote branch if fetched successfully and exists, else local branch.
62
- let startPoint = baseBranch;
63
- if (fetched) {
64
- try {
65
- await execa('git', ['rev-parse', '--verify', `origin/${baseBranch}`], { cwd: repoPath });
66
- startPoint = `origin/${baseBranch}`;
67
- } catch {
68
- // remote tracking branch does not exist, fallback to local branch
69
- }
70
- }
71
-
72
- // Check if the target branch already exists locally
73
- let branchExists = false;
74
- try {
75
- await execa('git', ['rev-parse', '--verify', branchName], { cwd: repoPath });
76
- branchExists = true;
77
- } catch {
78
- // Branch does not exist locally
79
- }
80
-
81
- // Create the worktree
82
- if (branchExists) {
83
- // If the branch already exists, checkout the existing branch
84
- await execa(
85
- 'git',
86
- ['worktree', 'add', targetPath, branchName],
87
- { cwd: repoPath },
88
- );
89
- } else {
90
- // If the branch does not exist, create a new branch based on the start point without tracking it
91
- await execa(
92
- 'git',
93
- ['worktree', 'add', '--no-track', targetPath, '-b', branchName, startPoint],
94
- { cwd: repoPath },
95
- );
96
- }
97
- }
98
-
99
- /**
100
- * Removes an existing git worktree.
101
- *
102
- * Runs `git worktree remove [--force] <worktreePath>` from the main repo.
103
- *
104
- * @param repoPath - Absolute path to the main repo checkout.
105
- * @param worktreePath - Absolute path to the worktree to remove.
106
- * @param force - Whether to force removal (cleanly removes modified files).
107
- */
108
- export async function removeWorktree(
109
- repoPath: string,
110
- worktreePath: string,
111
- force = false,
112
- ): Promise<void> {
113
- const args = ['worktree', 'remove'];
114
- if (force) {
115
- args.push('--force');
116
- }
117
- args.push(worktreePath);
118
- await execa('git', args, { cwd: repoPath });
119
- }
120
-
@@ -1,32 +0,0 @@
1
- /**
2
- * @module generators/antigravity
3
- * Generates an AGENTS.md file for Antigravity.
4
- */
5
-
6
- import path from 'node:path';
7
- import fse from 'fs-extra';
8
- import type { WorkspaceContext } from '../types.js';
9
- import { buildContextContent } from './base.js';
10
-
11
- /**
12
- * Generates an `AGENTS.md` file at the workspace root.
13
- *
14
- * Antigravity reads this file for persistent context about the workspace.
15
- *
16
- * @param ctx - The workspace context (feature + repos + analysis).
17
- * @param workspacePath - Absolute path to the workspace root directory.
18
- */
19
- export async function generateAntigravityConfig(
20
- ctx: WorkspaceContext,
21
- workspacePath: string,
22
- ): Promise<void> {
23
- const content = buildContextContent(ctx);
24
- const filePath = path.join(workspacePath, 'AGENTS.md');
25
-
26
- try {
27
- await fse.writeFile(filePath, content, 'utf-8');
28
- } catch (error) {
29
- const message = error instanceof Error ? error.message : String(error);
30
- throw new Error(`Failed to write AGENTS.md: ${message}`);
31
- }
32
- }