@mrpatronz/nexusflow 1.3.3 → 1.6.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.
- package/README.md +50 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +85 -44
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +16 -73
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +58 -318
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/finish.d.ts +17 -0
- package/dist/commands/finish.d.ts.map +1 -0
- package/dist/commands/finish.js +173 -0
- package/dist/commands/finish.js.map +1 -0
- package/dist/commands/handoff.d.ts.map +1 -1
- package/dist/commands/handoff.js +11 -6
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/knowledge.d.ts +36 -0
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/commands/knowledge.js +184 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/open.d.ts.map +1 -1
- package/dist/commands/open.js +2 -4
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/refresh.d.ts +2 -1
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +35 -2
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/strategy.d.ts +37 -0
- package/dist/commands/strategy.d.ts.map +1 -0
- package/dist/commands/strategy.js +198 -0
- package/dist/commands/strategy.js.map +1 -0
- package/dist/core/commit.d.ts +41 -0
- package/dist/core/commit.d.ts.map +1 -0
- package/dist/core/commit.js +53 -0
- package/dist/core/commit.js.map +1 -0
- package/dist/core/config.d.ts +3 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +60 -32
- package/dist/core/config.js.map +1 -1
- package/dist/core/config.test.js +24 -0
- package/dist/core/config.test.js.map +1 -1
- package/dist/core/diff.d.ts +27 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +50 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/doctor.d.ts +34 -0
- package/dist/core/doctor.d.ts.map +1 -0
- package/dist/core/doctor.js +276 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/finish.d.ts +56 -0
- package/dist/core/finish.d.ts.map +1 -0
- package/dist/core/finish.js +121 -0
- package/dist/core/finish.js.map +1 -0
- package/dist/core/finish.test.d.ts +2 -0
- package/dist/core/finish.test.d.ts.map +1 -0
- package/dist/core/finish.test.js +110 -0
- package/dist/core/finish.test.js.map +1 -0
- package/dist/core/knowledge.d.ts +96 -0
- package/dist/core/knowledge.d.ts.map +1 -0
- package/dist/core/knowledge.js +299 -0
- package/dist/core/knowledge.js.map +1 -0
- package/dist/core/knowledge.test.d.ts +2 -0
- package/dist/core/knowledge.test.d.ts.map +1 -0
- package/dist/core/knowledge.test.js +157 -0
- package/dist/core/knowledge.test.js.map +1 -0
- package/dist/core/refresh.test.d.ts +2 -0
- package/dist/core/refresh.test.d.ts.map +1 -0
- package/dist/core/refresh.test.js +67 -0
- package/dist/core/refresh.test.js.map +1 -0
- package/dist/core/status.d.ts +45 -0
- package/dist/core/status.d.ts.map +1 -0
- package/dist/core/status.js +45 -0
- package/dist/core/status.js.map +1 -0
- package/dist/core/sync.test.d.ts +2 -0
- package/dist/core/sync.test.d.ts.map +1 -0
- package/dist/core/sync.test.js +86 -0
- package/dist/core/sync.test.js.map +1 -0
- package/dist/core/workspace.create.test.d.ts +2 -0
- package/dist/core/workspace.create.test.d.ts.map +1 -0
- package/dist/core/workspace.create.test.js +95 -0
- package/dist/core/workspace.create.test.js.map +1 -0
- package/dist/core/workspace.d.ts +9 -3
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +91 -13
- package/dist/core/workspace.js.map +1 -1
- package/dist/core/worktree.d.ts +11 -1
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +2 -0
- package/dist/core/worktree.js.map +1 -1
- package/dist/core/worktree.test.d.ts +2 -0
- package/dist/core/worktree.test.d.ts.map +1 -0
- package/dist/core/worktree.test.js +111 -0
- package/dist/core/worktree.test.js.map +1 -0
- package/dist/generators/base.js +1 -1
- package/dist/generators/index.d.ts +7 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +4 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/index.js +205 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +51 -282
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +45 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +413 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +43 -26
- package/dist/server.js.map +1 -1
- package/dist/utils/debug.d.ts +17 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +25 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/detect-ai.d.ts +14 -6
- package/dist/utils/detect-ai.d.ts.map +1 -1
- package/dist/utils/detect-ai.js +22 -9
- package/dist/utils/detect-ai.js.map +1 -1
- package/dist/utils/detect-ai.test.js +33 -0
- package/dist/utils/detect-ai.test.js.map +1 -1
- package/dist/utils/git.d.ts +13 -2
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +64 -5
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/git.test.js +46 -8
- package/dist/utils/git.test.js.map +1 -1
- package/dist/utils/multi-git.d.ts +40 -0
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +80 -0
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/multi-git.test.js +54 -1
- package/dist/utils/multi-git.test.js.map +1 -1
- package/dist/utils/open-editor.d.ts +20 -0
- package/dist/utils/open-editor.d.ts.map +1 -0
- package/dist/utils/open-editor.js +44 -0
- package/dist/utils/open-editor.js.map +1 -0
- package/dist/utils/pr.d.ts +50 -0
- package/dist/utils/pr.d.ts.map +1 -0
- package/dist/utils/pr.js +144 -0
- package/dist/utils/pr.js.map +1 -0
- package/dist/utils/pr.test.d.ts +2 -0
- package/dist/utils/pr.test.d.ts.map +1 -0
- package/dist/utils/pr.test.js +47 -0
- package/dist/utils/pr.test.js.map +1 -0
- package/dist/utils/prompts.d.ts +22 -1
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +145 -24
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/resolve-workspace.d.ts +30 -0
- package/dist/utils/resolve-workspace.d.ts.map +1 -0
- package/dist/utils/resolve-workspace.js +77 -0
- package/dist/utils/resolve-workspace.js.map +1 -0
- package/dist/utils/workflow-advisor.d.ts +2 -0
- package/dist/utils/workflow-advisor.d.ts.map +1 -1
- package/dist/utils/workflow-advisor.js +27 -4
- package/dist/utils/workflow-advisor.js.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { insertUnderHeading, formatEntry, parseKnowledgeEntries, addWorkspaceKnowledge, addBaseKnowledge, promoteKnowledge, readWorkspaceKnowledge, } from './knowledge.js';
|
|
3
|
+
import * as storage from './storage.js';
|
|
4
|
+
import * as workspace from './workspace.js';
|
|
5
|
+
import * as generators from '../generators/index.js';
|
|
6
|
+
vi.mock('./storage.js');
|
|
7
|
+
vi.mock('./workspace.js');
|
|
8
|
+
vi.mock('../generators/index.js');
|
|
9
|
+
// ─── Pure helpers ───────────────────────────────────────────────────────────
|
|
10
|
+
describe('insertUnderHeading', () => {
|
|
11
|
+
it('inserts under an existing heading and strips the placeholder', () => {
|
|
12
|
+
const md = `## Known Gotchas\n\n<!-- keep me -->\n\n_(No gotchas recorded yet.)_\n`;
|
|
13
|
+
const out = insertUnderHeading(md, ['Known Gotchas'], '- **2026-07-04:** watch the cache');
|
|
14
|
+
expect(out).toContain('- **2026-07-04:** watch the cache');
|
|
15
|
+
expect(out).not.toContain('No gotchas recorded yet');
|
|
16
|
+
expect(out).toContain('<!-- keep me -->'); // comments are preserved
|
|
17
|
+
});
|
|
18
|
+
it('matches a heading via an alias', () => {
|
|
19
|
+
const md = `## Discovered Gotchas & Watch-outs\n\n- None recorded yet.\n`;
|
|
20
|
+
const out = insertUnderHeading(md, ['Discovered Gotchas & Watch-outs', 'Known Gotchas'], '- **2026-07-04:** x');
|
|
21
|
+
expect(out).toContain('- **2026-07-04:** x');
|
|
22
|
+
expect(out).not.toContain('None recorded yet');
|
|
23
|
+
});
|
|
24
|
+
it('appends a new section when the heading is missing', () => {
|
|
25
|
+
const md = `# Workspace Knowledge — feat\n`;
|
|
26
|
+
const out = insertUnderHeading(md, ['Architecture Decisions'], '### 2026-07-04 — Chose X\n**Decision:** because reasons');
|
|
27
|
+
expect(out).toContain('## Architecture Decisions');
|
|
28
|
+
expect(out).toContain('**Decision:** because reasons');
|
|
29
|
+
});
|
|
30
|
+
it('preserves CRLF line endings', () => {
|
|
31
|
+
const md = `## Known Gotchas\r\n\r\n_(No gotchas recorded yet.)_\r\n`;
|
|
32
|
+
const out = insertUnderHeading(md, ['Known Gotchas'], '- foo');
|
|
33
|
+
expect(out.includes('\r\n')).toBe(true);
|
|
34
|
+
// No bare LF (every \n must be preceded by \r).
|
|
35
|
+
expect(/[^\r]\n/.test(out)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('passes through a leading YAML frontmatter block', () => {
|
|
38
|
+
const md = `---\ntags: ["x"]\ntype: workspace-context\n---\n\n## Known Gotchas\n\n_(No gotchas recorded yet.)_\n`;
|
|
39
|
+
const out = insertUnderHeading(md, ['Known Gotchas'], '- foo');
|
|
40
|
+
expect(out.startsWith('---')).toBe(true);
|
|
41
|
+
expect(out).toContain('tags: ["x"]');
|
|
42
|
+
expect(out).toContain('- foo');
|
|
43
|
+
});
|
|
44
|
+
it('does not destroy an existing entry when adding another', () => {
|
|
45
|
+
const md = `## Known Gotchas\n\n- **2026-07-01:** first\n`;
|
|
46
|
+
const out = insertUnderHeading(md, ['Known Gotchas'], '- **2026-07-04:** second');
|
|
47
|
+
expect(out).toContain('- **2026-07-01:** first');
|
|
48
|
+
expect(out).toContain('- **2026-07-04:** second');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('formatEntry', () => {
|
|
52
|
+
const ts = '2026-07-04T10:00:00.000Z';
|
|
53
|
+
it('formats a decision with a title as a dated ### block', () => {
|
|
54
|
+
const out = formatEntry({ type: 'decision', message: 'use worktrees', title: 'Worktrees', timestamp: ts }, 'workspace');
|
|
55
|
+
expect(out).toBe('### 2026-07-04 — Worktrees\n**Decision:** use worktrees');
|
|
56
|
+
});
|
|
57
|
+
it('formats a gotcha as a dated bullet', () => {
|
|
58
|
+
const out = formatEntry({ type: 'gotcha', message: 'EBUSY on Windows', timestamp: ts }, 'workspace');
|
|
59
|
+
expect(out).toBe('- **2026-07-04:** EBUSY on Windows');
|
|
60
|
+
});
|
|
61
|
+
it('formats progress as a checked item', () => {
|
|
62
|
+
const out = formatEntry({ type: 'progress', message: 'shipped rollback', timestamp: ts }, 'workspace');
|
|
63
|
+
expect(out).toBe('- [x] 2026-07-04 — shipped rollback');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('parseKnowledgeEntries', () => {
|
|
67
|
+
it('parses bullet entries and decision blocks with their types', () => {
|
|
68
|
+
const md = [
|
|
69
|
+
'## Known Gotchas',
|
|
70
|
+
'',
|
|
71
|
+
'- **2026-07-04:** watch out',
|
|
72
|
+
'',
|
|
73
|
+
'## Architecture Decisions',
|
|
74
|
+
'',
|
|
75
|
+
'### 2026-07-04 — Chose X',
|
|
76
|
+
'**Decision:** because',
|
|
77
|
+
'',
|
|
78
|
+
].join('\n');
|
|
79
|
+
const parsed = parseKnowledgeEntries(md);
|
|
80
|
+
expect(parsed).toContainEqual({
|
|
81
|
+
section: 'Known Gotchas',
|
|
82
|
+
type: 'gotcha',
|
|
83
|
+
text: '- **2026-07-04:** watch out',
|
|
84
|
+
});
|
|
85
|
+
const decision = parsed.find((p) => p.type === 'decision');
|
|
86
|
+
expect(decision?.text).toContain('### 2026-07-04 — Chose X');
|
|
87
|
+
expect(decision?.text).toContain('**Decision:** because');
|
|
88
|
+
});
|
|
89
|
+
it('ignores placeholder and comment lines', () => {
|
|
90
|
+
const md = `## Known Gotchas\n\n<!-- hint -->\n\n_(No gotchas recorded yet.)_\n`;
|
|
91
|
+
expect(parseKnowledgeEntries(md)).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// ─── Adapter-routed I/O (in-memory storage mock) ────────────────────────────
|
|
95
|
+
describe('knowledge I/O', () => {
|
|
96
|
+
const ws = new Map();
|
|
97
|
+
const base = new Map();
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
vi.clearAllMocks();
|
|
100
|
+
ws.clear();
|
|
101
|
+
base.clear();
|
|
102
|
+
vi.mocked(workspace.loadFeatureConfig).mockResolvedValue({ id: 'feat' });
|
|
103
|
+
vi.mocked(generators.buildBaseKnowledgeContent).mockImplementation((repo) => `# Base Codebase Knowledge — ${repo}\n\n## Discovered Gotchas & Watch-outs\n- None recorded yet.\n\n## Architecture Decisions\n- None recorded yet.\n`);
|
|
104
|
+
vi.mocked(storage.workspaceFileExists).mockImplementation(async (_w, fid, fn) => ws.has(`${fid}/${fn}`));
|
|
105
|
+
vi.mocked(storage.readWorkspaceFile).mockImplementation(async (_w, fid, fn) => ws.get(`${fid}/${fn}`));
|
|
106
|
+
vi.mocked(storage.writeWorkspaceFile).mockImplementation(async (_w, fid, fn, c) => {
|
|
107
|
+
ws.set(`${fid}/${fn}`, c);
|
|
108
|
+
});
|
|
109
|
+
vi.mocked(storage.resolveWorkspaceFileUrl).mockImplementation((_w, fid, fn) => `ws:/${fid}/${fn}`);
|
|
110
|
+
vi.mocked(storage.baseFileExists).mockImplementation(async (_w, repo, fn) => base.has(`${repo}/${fn}`));
|
|
111
|
+
vi.mocked(storage.readBaseFile).mockImplementation(async (_w, repo, fn) => base.get(`${repo}/${fn}`));
|
|
112
|
+
vi.mocked(storage.writeBaseFile).mockImplementation(async (_w, repo, fn, c) => {
|
|
113
|
+
base.set(`${repo}/${fn}`, c);
|
|
114
|
+
});
|
|
115
|
+
vi.mocked(storage.resolveBaseFileUrl).mockImplementation((_w, repo, fn) => `base:/${repo}/${fn}`);
|
|
116
|
+
});
|
|
117
|
+
it('creates the workspace knowledge file when missing and records the entry', async () => {
|
|
118
|
+
const result = await addWorkspaceKnowledge('/wsp', {
|
|
119
|
+
type: 'decision',
|
|
120
|
+
message: 'store knowledge via the adapter',
|
|
121
|
+
title: 'Adapter routing',
|
|
122
|
+
});
|
|
123
|
+
expect(result.createdFile).toBe(true);
|
|
124
|
+
const content = ws.get('feat/nexusflow-knowledge.md');
|
|
125
|
+
expect(content).toContain('## Architecture Decisions');
|
|
126
|
+
expect(content).toContain('**Decision:** store knowledge via the adapter');
|
|
127
|
+
});
|
|
128
|
+
it('rejects an empty message', async () => {
|
|
129
|
+
await expect(addWorkspaceKnowledge('/wsp', { type: 'gotcha', message: ' ' })).rejects.toThrow(/cannot be empty/i);
|
|
130
|
+
});
|
|
131
|
+
it('adds to a repo base file, bootstrapping it from the template', async () => {
|
|
132
|
+
const result = await addBaseKnowledge('/wsp', 'api', { type: 'gotcha', message: 'flaky test on CI' });
|
|
133
|
+
expect(result.createdFile).toBe(true);
|
|
134
|
+
const content = base.get('api/nexusflow-knowledge.md');
|
|
135
|
+
expect(content).toContain('## Discovered Gotchas & Watch-outs');
|
|
136
|
+
expect(content).toContain('flaky test on CI');
|
|
137
|
+
});
|
|
138
|
+
it('promotes a gotcha to base knowledge (copy leaves the workspace entry)', async () => {
|
|
139
|
+
ws.set('feat/nexusflow-knowledge.md', `# Workspace Knowledge — feat\n\n## Known Gotchas\n\n- **2026-07-04:** shared learning\n`);
|
|
140
|
+
const entries = parseKnowledgeEntries((await readWorkspaceKnowledge('/wsp')));
|
|
141
|
+
const result = await promoteKnowledge('/wsp', { repoName: 'api', entries, mode: 'copy' });
|
|
142
|
+
expect(result.promotedCount).toBe(1);
|
|
143
|
+
expect(base.get('api/nexusflow-knowledge.md')).toContain('- **2026-07-04:** shared learning');
|
|
144
|
+
// Copy mode keeps the original.
|
|
145
|
+
expect(ws.get('feat/nexusflow-knowledge.md')).toContain('- **2026-07-04:** shared learning');
|
|
146
|
+
});
|
|
147
|
+
it('move mode replaces the promoted workspace entry with a note', async () => {
|
|
148
|
+
ws.set('feat/nexusflow-knowledge.md', `# Workspace Knowledge — feat\n\n## Known Gotchas\n\n- **2026-07-04:** shared learning\n`);
|
|
149
|
+
const entries = parseKnowledgeEntries((await readWorkspaceKnowledge('/wsp')));
|
|
150
|
+
await promoteKnowledge('/wsp', { repoName: 'api', entries, mode: 'move' });
|
|
151
|
+
const wsContent = ws.get('feat/nexusflow-knowledge.md');
|
|
152
|
+
expect(wsContent).not.toContain('shared learning');
|
|
153
|
+
expect(wsContent).toContain('Promoted to api base knowledge');
|
|
154
|
+
expect(base.get('api/nexusflow-knowledge.md')).toContain('shared learning');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=knowledge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge.test.js","sourceRoot":"","sources":["../../src/core/knowledge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.test.d.ts","sourceRoot":"","sources":["../../src/core/refresh.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { refreshWorkspace } from './refresh.js';
|
|
4
|
+
import * as workspace from './workspace.js';
|
|
5
|
+
import * as analyzers from '../analyzers/index.js';
|
|
6
|
+
import * as generators from '../generators/index.js';
|
|
7
|
+
import * as handoff from '../commands/handoff.js';
|
|
8
|
+
vi.mock('node:fs/promises');
|
|
9
|
+
vi.mock('./workspace.js');
|
|
10
|
+
vi.mock('../analyzers/index.js');
|
|
11
|
+
vi.mock('../generators/index.js');
|
|
12
|
+
vi.mock('../commands/handoff.js');
|
|
13
|
+
const feature = {
|
|
14
|
+
id: 'feat',
|
|
15
|
+
branchName: 'feat',
|
|
16
|
+
description: 'd',
|
|
17
|
+
repos: ['/ws/a', '/ws/b'],
|
|
18
|
+
assistants: ['claude'],
|
|
19
|
+
workspacePath: '/ws',
|
|
20
|
+
createdAt: '2026-07-04T00:00:00Z',
|
|
21
|
+
};
|
|
22
|
+
describe('refreshWorkspace', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
vi.mocked(workspace.loadFeatureConfig).mockResolvedValue(feature);
|
|
26
|
+
vi.mocked(workspace.resolveRepoInfos).mockResolvedValue([
|
|
27
|
+
{ name: 'a', path: '/ws/a', defaultBranch: 'main' },
|
|
28
|
+
{ name: 'b', path: '/ws/b', defaultBranch: 'main' },
|
|
29
|
+
]);
|
|
30
|
+
vi.mocked(analyzers.analyzeAllReposCached).mockResolvedValue({ analysis: new Map(), analyzed: ['a'], reused: ['b'] });
|
|
31
|
+
vi.mocked(generators.generateContextFiles).mockResolvedValue(undefined);
|
|
32
|
+
vi.mocked(handoff.handoffCommand).mockResolvedValue(undefined);
|
|
33
|
+
// No handoff bundle by default.
|
|
34
|
+
vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT'));
|
|
35
|
+
});
|
|
36
|
+
it('throws when the workspace manifest is missing', async () => {
|
|
37
|
+
vi.mocked(workspace.loadFeatureConfig).mockResolvedValue(null);
|
|
38
|
+
await expect(refreshWorkspace('/ws')).rejects.toThrow(/Failed to load workspace configuration/);
|
|
39
|
+
});
|
|
40
|
+
it('passes analyzed/reused through to the report', async () => {
|
|
41
|
+
const report = await refreshWorkspace('/ws');
|
|
42
|
+
expect(report.analyzedRepos).toEqual(['a']);
|
|
43
|
+
expect(report.reusedRepos).toEqual(['b']);
|
|
44
|
+
});
|
|
45
|
+
it('limits map regeneration to changed repos by default', async () => {
|
|
46
|
+
await refreshWorkspace('/ws');
|
|
47
|
+
// 6th arg (changedRepos) is the analyzed list when not forcing.
|
|
48
|
+
expect(generators.generateContextFiles).toHaveBeenCalledWith(expect.any(Object), feature.assistants, '/ws', undefined, undefined, ['a']);
|
|
49
|
+
});
|
|
50
|
+
it('passes undefined changed-repos filter when force is set', async () => {
|
|
51
|
+
await refreshWorkspace('/ws', { force: true });
|
|
52
|
+
expect(generators.generateContextFiles).toHaveBeenCalledWith(expect.any(Object), feature.assistants, '/ws', undefined, undefined, undefined);
|
|
53
|
+
});
|
|
54
|
+
it('refreshes the handoff bundle when it exists and not baseOnly', async () => {
|
|
55
|
+
vi.mocked(fs.access).mockResolvedValue(undefined); // handoff file exists
|
|
56
|
+
const report = await refreshWorkspace('/ws');
|
|
57
|
+
expect(handoff.handoffCommand).toHaveBeenCalledWith('/ws');
|
|
58
|
+
expect(report.refreshedHandoff).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it('does not touch the handoff bundle when baseOnly', async () => {
|
|
61
|
+
vi.mocked(fs.access).mockResolvedValue(undefined);
|
|
62
|
+
const report = await refreshWorkspace('/ws', { baseOnly: true });
|
|
63
|
+
expect(handoff.handoffCommand).not.toHaveBeenCalled();
|
|
64
|
+
expect(report.refreshedHandoff).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=refresh.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.test.js","sourceRoot":"","sources":["../../src/core/refresh.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/status
|
|
3
|
+
* Headless cross-repo status report for a workspace. Backs the MCP
|
|
4
|
+
* `workspace_status` tool and the `finish` preflight, and can back a GUI
|
|
5
|
+
* status route. Throws only if the workspace manifest cannot be read.
|
|
6
|
+
*/
|
|
7
|
+
import { type RepoStatusFile } from '../utils/multi-git.js';
|
|
8
|
+
/** Full status for a single repo in a workspace. */
|
|
9
|
+
export interface RepoStatusReport {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
/** Current branch, or `null` when the repo is in a detached-HEAD state. */
|
|
13
|
+
branch: string | null;
|
|
14
|
+
/** The feature branch the workspace expects every repo to be on. */
|
|
15
|
+
expectedBranch: string;
|
|
16
|
+
onExpectedBranch: boolean;
|
|
17
|
+
dirty: boolean;
|
|
18
|
+
changedFiles: RepoStatusFile[];
|
|
19
|
+
/** Commits ahead of origin, or `null` when the branch was never pushed. */
|
|
20
|
+
ahead: number | null;
|
|
21
|
+
/** Commits behind origin, or `null` when the branch was never pushed. */
|
|
22
|
+
behind: number | null;
|
|
23
|
+
remoteUrl: string | null;
|
|
24
|
+
defaultBranch: string;
|
|
25
|
+
}
|
|
26
|
+
/** Aggregate workspace status. */
|
|
27
|
+
export interface WorkspaceStatusReport {
|
|
28
|
+
workspacePath: string;
|
|
29
|
+
branchName: string;
|
|
30
|
+
repos: RepoStatusReport[];
|
|
31
|
+
/** True when no repo has working-tree changes. */
|
|
32
|
+
allClean: boolean;
|
|
33
|
+
/** True when every repo is pushed (ahead === 0). A never-pushed branch counts as NOT pushed. */
|
|
34
|
+
allPushed: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Gathers branch, dirtiness, ahead/behind, and remote info for every repo in
|
|
38
|
+
* the workspace.
|
|
39
|
+
*
|
|
40
|
+
* @param workspacePath - Absolute path to the workspace directory.
|
|
41
|
+
* @returns The aggregate {@link WorkspaceStatusReport}.
|
|
42
|
+
* @throws If `nexusflow.json` cannot be read or parsed.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getWorkspaceStatusReport(workspacePath: string): Promise<WorkspaceStatusReport>;
|
|
45
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/core/status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yEAAyE;IACzE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,kCAAkC;AAClC,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,qBAAqB,CAAC,CAkChC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/status
|
|
3
|
+
* Headless cross-repo status report for a workspace. Backs the MCP
|
|
4
|
+
* `workspace_status` tool and the `finish` preflight, and can back a GUI
|
|
5
|
+
* status route. Throws only if the workspace manifest cannot be read.
|
|
6
|
+
*/
|
|
7
|
+
import { getWorkspaceRepos, getRepoBranch, getRepoStatus, getAheadBehind, getRemoteUrl, } from '../utils/multi-git.js';
|
|
8
|
+
/**
|
|
9
|
+
* Gathers branch, dirtiness, ahead/behind, and remote info for every repo in
|
|
10
|
+
* the workspace.
|
|
11
|
+
*
|
|
12
|
+
* @param workspacePath - Absolute path to the workspace directory.
|
|
13
|
+
* @returns The aggregate {@link WorkspaceStatusReport}.
|
|
14
|
+
* @throws If `nexusflow.json` cannot be read or parsed.
|
|
15
|
+
*/
|
|
16
|
+
export async function getWorkspaceStatusReport(workspacePath) {
|
|
17
|
+
const repos = await getWorkspaceRepos(workspacePath);
|
|
18
|
+
const reports = await Promise.all(repos.map(async (repo) => {
|
|
19
|
+
const [branch, status, remoteUrl, aheadBehind] = await Promise.all([
|
|
20
|
+
getRepoBranch(repo.path),
|
|
21
|
+
getRepoStatus(repo.path),
|
|
22
|
+
getRemoteUrl(repo.path),
|
|
23
|
+
getAheadBehind(repo.path, repo.branchName),
|
|
24
|
+
]);
|
|
25
|
+
return {
|
|
26
|
+
name: repo.name,
|
|
27
|
+
path: repo.path,
|
|
28
|
+
branch,
|
|
29
|
+
expectedBranch: repo.branchName,
|
|
30
|
+
onExpectedBranch: branch === repo.branchName,
|
|
31
|
+
dirty: status.hasChanges,
|
|
32
|
+
changedFiles: status.files,
|
|
33
|
+
ahead: aheadBehind.ahead,
|
|
34
|
+
behind: aheadBehind.behind,
|
|
35
|
+
remoteUrl,
|
|
36
|
+
defaultBranch: repo.defaultBranch,
|
|
37
|
+
};
|
|
38
|
+
}));
|
|
39
|
+
const branchName = repos[0]?.branchName ?? '';
|
|
40
|
+
const allClean = reports.every((r) => !r.dirty);
|
|
41
|
+
// `ahead === null` means the branch was never pushed, which is not "pushed".
|
|
42
|
+
const allPushed = reports.every((r) => r.ahead === 0);
|
|
43
|
+
return { workspacePath, branchName, repos: reports, allClean, allPushed };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/core/status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,GAEb,MAAM,uBAAuB,CAAC;AAgC/B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,aAAqB;IAErB,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAErD,MAAM,OAAO,GAAuB,MAAM,OAAO,CAAC,GAAG,CACnD,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAA6B,EAAE;QAClD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACjE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YACvB,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC;SAC3C,CAAC,CAAC;QAEH,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM;YACN,cAAc,EAAE,IAAI,CAAC,UAAU;YAC/B,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAC,UAAU;YAC5C,KAAK,EAAE,MAAM,CAAC,UAAU;YACxB,YAAY,EAAE,MAAM,CAAC,KAAK;YAC1B,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,SAAS;YACT,aAAa,EAAE,IAAI,CAAC,aAAa;SAClC,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,IAAI,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAChD,6EAA6E;IAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;IAEtD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.test.d.ts","sourceRoot":"","sources":["../../src/core/sync.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { syncWorkspace } from './sync.js';
|
|
3
|
+
import * as workspace from './workspace.js';
|
|
4
|
+
import * as workspaceState from './workspace-state.js';
|
|
5
|
+
import * as multiGit from '../utils/multi-git.js';
|
|
6
|
+
import * as analyzers from '../analyzers/index.js';
|
|
7
|
+
import * as generators from '../generators/index.js';
|
|
8
|
+
vi.mock('./workspace.js');
|
|
9
|
+
vi.mock('./workspace-state.js');
|
|
10
|
+
vi.mock('../utils/multi-git.js');
|
|
11
|
+
vi.mock('../analyzers/index.js');
|
|
12
|
+
vi.mock('../generators/index.js');
|
|
13
|
+
const feature = {
|
|
14
|
+
id: 'feat',
|
|
15
|
+
branchName: 'feat',
|
|
16
|
+
description: 'd',
|
|
17
|
+
repos: ['/ws/a', '/ws/b'],
|
|
18
|
+
assistants: ['claude'],
|
|
19
|
+
workspacePath: '/ws',
|
|
20
|
+
createdAt: '2026-07-04T00:00:00Z',
|
|
21
|
+
};
|
|
22
|
+
function wsRepo(name) {
|
|
23
|
+
return { name, path: `/ws/${name}`, branchName: 'feat', defaultBranch: 'main' };
|
|
24
|
+
}
|
|
25
|
+
function rebase(status) {
|
|
26
|
+
return { success: status !== 'conflict' && status !== 'error', status, message: status };
|
|
27
|
+
}
|
|
28
|
+
describe('syncWorkspace', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
vi.mocked(workspace.loadFeatureConfig).mockResolvedValue(feature);
|
|
32
|
+
vi.mocked(workspace.resolveRepoInfos).mockResolvedValue([
|
|
33
|
+
{ name: 'a', path: '/ws/a', defaultBranch: 'main' },
|
|
34
|
+
{ name: 'b', path: '/ws/b', defaultBranch: 'main' },
|
|
35
|
+
]);
|
|
36
|
+
vi.mocked(workspaceState.recordRepoSync).mockResolvedValue(undefined);
|
|
37
|
+
vi.mocked(multiGit.getWorkspaceRepos).mockResolvedValue([wsRepo('a'), wsRepo('b')]);
|
|
38
|
+
vi.mocked(analyzers.analyzeAllReposCached).mockResolvedValue({ analysis: new Map(), analyzed: ['a'], reused: ['b'] });
|
|
39
|
+
vi.mocked(generators.generateContextFiles).mockResolvedValue(undefined);
|
|
40
|
+
});
|
|
41
|
+
it('throws when the workspace manifest is missing', async () => {
|
|
42
|
+
vi.mocked(workspace.loadFeatureConfig).mockResolvedValue(null);
|
|
43
|
+
await expect(syncWorkspace('/ws')).rejects.toThrow(/Failed to load workspace configuration/);
|
|
44
|
+
});
|
|
45
|
+
it('classifies per-repo outcomes into counts', async () => {
|
|
46
|
+
vi.mocked(multiGit.rebaseRepo)
|
|
47
|
+
.mockResolvedValueOnce(rebase('conflict'))
|
|
48
|
+
.mockResolvedValueOnce(rebase('error'));
|
|
49
|
+
const report = await syncWorkspace('/ws');
|
|
50
|
+
expect(report.conflictCount).toBe(1);
|
|
51
|
+
expect(report.errorCount).toBe(1);
|
|
52
|
+
expect(report.syncedCount).toBe(0);
|
|
53
|
+
expect(report.repos.map((r) => r.status)).toEqual(['conflict', 'error']);
|
|
54
|
+
});
|
|
55
|
+
it('records each repo sync outcome to workspace state', async () => {
|
|
56
|
+
vi.mocked(multiGit.rebaseRepo).mockResolvedValue(rebase('up-to-date'));
|
|
57
|
+
await syncWorkspace('/ws');
|
|
58
|
+
expect(workspaceState.recordRepoSync).toHaveBeenCalledTimes(2);
|
|
59
|
+
expect(workspaceState.recordRepoSync).toHaveBeenCalledWith('/ws', 'a', expect.objectContaining({ status: 'up-to-date' }));
|
|
60
|
+
});
|
|
61
|
+
it('does not regenerate context when every repo is up to date', async () => {
|
|
62
|
+
vi.mocked(multiGit.rebaseRepo).mockResolvedValue(rebase('up-to-date'));
|
|
63
|
+
const report = await syncWorkspace('/ws');
|
|
64
|
+
expect(report.contextRefreshed).toBe(false);
|
|
65
|
+
expect(analyzers.analyzeAllReposCached).not.toHaveBeenCalled();
|
|
66
|
+
expect(generators.generateContextFiles).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('regenerates context when a repo was rebased', async () => {
|
|
69
|
+
vi.mocked(multiGit.rebaseRepo)
|
|
70
|
+
.mockResolvedValueOnce(rebase('rebased'))
|
|
71
|
+
.mockResolvedValueOnce(rebase('up-to-date'));
|
|
72
|
+
const report = await syncWorkspace('/ws');
|
|
73
|
+
expect(report.contextRefreshed).toBe(true);
|
|
74
|
+
expect(analyzers.analyzeAllReposCached).toHaveBeenCalled();
|
|
75
|
+
expect(generators.generateContextFiles).toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
it('tolerates a generator failure without failing the sync', async () => {
|
|
78
|
+
vi.mocked(multiGit.rebaseRepo).mockResolvedValue(rebase('rebased'));
|
|
79
|
+
vi.mocked(generators.generateContextFiles).mockRejectedValue(new Error('gen boom'));
|
|
80
|
+
const report = await syncWorkspace('/ws');
|
|
81
|
+
// The rebases already happened; a regen failure just leaves contextRefreshed false.
|
|
82
|
+
expect(report.contextRefreshed).toBe(false);
|
|
83
|
+
expect(report.syncedCount).toBe(2);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=sync.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.test.js","sourceRoot":"","sources":["../../src/core/sync.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.create.test.d.ts","sourceRoot":"","sources":["../../src/core/workspace.create.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { createWorkspace } from './workspace.js';
|
|
7
|
+
import * as worktree from './worktree.js';
|
|
8
|
+
vi.mock('execa');
|
|
9
|
+
vi.mock('./worktree.js');
|
|
10
|
+
/** Unique temp workspace path per test (no Date.now/random needed). */
|
|
11
|
+
let counter = 0;
|
|
12
|
+
let workspacePath = '';
|
|
13
|
+
function feature(repos) {
|
|
14
|
+
return {
|
|
15
|
+
id: 'feat-branch',
|
|
16
|
+
branchName: 'feat-branch',
|
|
17
|
+
description: 'test',
|
|
18
|
+
repos: repos.map((r) => path.join(workspacePath, r.name)),
|
|
19
|
+
originalRepos: repos.map((r) => r.path),
|
|
20
|
+
assistants: ['claude'],
|
|
21
|
+
workspacePath,
|
|
22
|
+
createdAt: '2026-07-04T00:00:00.000Z',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function repoInfos(...names) {
|
|
26
|
+
return names.map((name) => ({ name, path: path.join('/src', name), defaultBranch: 'main' }));
|
|
27
|
+
}
|
|
28
|
+
/** Args of every recorded `git` execa call. */
|
|
29
|
+
function gitCalls() {
|
|
30
|
+
return vi.mocked(execa).mock.calls.map((c) => c[1]);
|
|
31
|
+
}
|
|
32
|
+
describe('createWorkspace rollback', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
counter += 1;
|
|
36
|
+
workspacePath = path.join(os.tmpdir(), `nexusflow-create-test-${process.pid}-${counter}`);
|
|
37
|
+
// Non-worktree git calls (init, branch -D, prune) just succeed.
|
|
38
|
+
vi.mocked(execa).mockResolvedValue({ stdout: '' });
|
|
39
|
+
vi.mocked(worktree.removeWorktree).mockResolvedValue(undefined);
|
|
40
|
+
});
|
|
41
|
+
afterEach(async () => {
|
|
42
|
+
await fs.rm(workspacePath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch(() => { });
|
|
43
|
+
});
|
|
44
|
+
it('creates worktrees for all repos and saves the manifest on success', async () => {
|
|
45
|
+
vi.mocked(worktree.createWorktree).mockResolvedValue({ createdBranch: true });
|
|
46
|
+
const repos = repoInfos('api', 'web');
|
|
47
|
+
await createWorkspace(feature(repos), repos);
|
|
48
|
+
expect(worktree.createWorktree).toHaveBeenCalledTimes(2);
|
|
49
|
+
expect(worktree.removeWorktree).not.toHaveBeenCalled();
|
|
50
|
+
const manifest = await fs.readFile(path.join(workspacePath, 'nexusflow.json'), 'utf-8');
|
|
51
|
+
expect(JSON.parse(manifest).branchName).toBe('feat-branch');
|
|
52
|
+
});
|
|
53
|
+
it('rolls back created worktrees and branches when a later worktree fails', async () => {
|
|
54
|
+
vi.mocked(worktree.createWorktree)
|
|
55
|
+
.mockResolvedValueOnce({ createdBranch: true }) // api ok
|
|
56
|
+
.mockRejectedValueOnce(new Error('fatal: worktree add failed')); // web fails
|
|
57
|
+
const repos = repoInfos('api', 'web');
|
|
58
|
+
await expect(createWorkspace(feature(repos), repos)).rejects.toThrow(/worktree add failed/);
|
|
59
|
+
// Only the successfully-created worktree (api) is removed.
|
|
60
|
+
expect(worktree.removeWorktree).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(worktree.removeWorktree).toHaveBeenCalledWith(path.join('/src', 'api'), path.join(workspacePath, 'api'), true);
|
|
62
|
+
// Its run-created branch is deleted.
|
|
63
|
+
expect(gitCalls()).toContainEqual(['branch', '-D', 'feat-branch']);
|
|
64
|
+
// The workspace directory is gone.
|
|
65
|
+
await expect(fs.access(workspacePath)).rejects.toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
it('does not delete a branch that already existed (was only checked out)', async () => {
|
|
68
|
+
vi.mocked(worktree.createWorktree)
|
|
69
|
+
.mockResolvedValueOnce({ createdBranch: false }) // api reused an existing branch
|
|
70
|
+
.mockRejectedValueOnce(new Error('boom'));
|
|
71
|
+
const repos = repoInfos('api', 'web');
|
|
72
|
+
await expect(createWorkspace(feature(repos), repos)).rejects.toThrow(/boom/);
|
|
73
|
+
expect(worktree.removeWorktree).toHaveBeenCalledTimes(1);
|
|
74
|
+
expect(gitCalls().some((c) => c[0] === 'branch' && c[1] === '-D')).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
it('fails fast when the workspace directory already exists and is non-empty', async () => {
|
|
77
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
78
|
+
await fs.writeFile(path.join(workspacePath, 'existing.txt'), 'x', 'utf-8');
|
|
79
|
+
const repos = repoInfos('api');
|
|
80
|
+
await expect(createWorkspace(feature(repos), repos)).rejects.toThrow(/already exists and is not empty/);
|
|
81
|
+
expect(worktree.createWorktree).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
it('completes rollback even when removeWorktree fails (prunes and removes the dir)', async () => {
|
|
84
|
+
vi.mocked(worktree.createWorktree)
|
|
85
|
+
.mockResolvedValueOnce({ createdBranch: true })
|
|
86
|
+
.mockRejectedValueOnce(new Error('kaboom'));
|
|
87
|
+
vi.mocked(worktree.removeWorktree).mockRejectedValue(new Error('worktree locked'));
|
|
88
|
+
const repos = repoInfos('api', 'web');
|
|
89
|
+
await expect(createWorkspace(feature(repos), repos)).rejects.toThrow(/kaboom/);
|
|
90
|
+
// Falls back to `git worktree prune` when removal fails.
|
|
91
|
+
expect(gitCalls()).toContainEqual(['worktree', 'prune']);
|
|
92
|
+
await expect(fs.access(workspacePath)).rejects.toBeTruthy();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=workspace.create.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.create.test.js","sourceRoot":"","sources":["../../src/core/workspace.create.test.ts"],"names":[],"mappings":"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"}
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -19,11 +19,17 @@ export declare function getWorkspacePath(workspacesDir: string, branchName: stri
|
|
|
19
19
|
* 2. Creates a git worktree for every repo in the feature.
|
|
20
20
|
* 3. Saves the feature manifest (`nexusflow.json`).
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* If any worktree fails, the partially-created workspace is rolled back
|
|
23
|
+
* automatically (worktrees removed, run-created branches deleted, directory
|
|
24
|
+
* removed) and the original error is rethrown, so a failed `create` never
|
|
25
|
+
* leaves debris behind.
|
|
26
|
+
*
|
|
27
|
+
* @param feature - The feature definition.
|
|
28
|
+
* @param repos - Resolved repo metadata for every repo in the feature.
|
|
29
|
+
* @param onProgress - Optional per-repo progress callback (repo name, index, total).
|
|
24
30
|
* @returns The absolute path to the newly created workspace.
|
|
25
31
|
*/
|
|
26
|
-
export declare function createWorkspace(feature: Feature, repos: RepoInfo[]): Promise<string>;
|
|
32
|
+
export declare function createWorkspace(feature: Feature, repos: RepoInfo[], onProgress?: (repoName: string, index: number, total: number) => void): Promise<string>;
|
|
27
33
|
/**
|
|
28
34
|
* Lists all existing workspaces by reading `nexusflow.json` manifests from
|
|
29
35
|
* each subdirectory of {@link workspacesDir}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/core/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAoB,MAAM,aAAa,CAAC;AAWvE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/core/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAoB,MAAM,aAAa,CAAC;AAWvE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAmDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,QAAQ,EAAE,EACjB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACpE,OAAO,CAAC,MAAM,CAAC,CA4HjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,EAAE,CAAC,CAmCpB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAyBzB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBjF;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAOzE;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAE/E;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAoEf;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA6Df"}
|