gsd-pi 2.8.3 → 2.9.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 +2 -1
- package/dist/cli.js +5 -0
- package/dist/loader.js +1 -1
- package/dist/update-check.d.ts +24 -0
- package/dist/update-check.js +93 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js +758 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js +267 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js +101 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js +709 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js +64 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js +574 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +46 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/client.ts +880 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/config.ts +325 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/defaults.json +456 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/edits.ts +109 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/index.ts +943 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp.md +33 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/types.ts +421 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/utils.ts +682 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/index.ts +10 -0
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +59 -2
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js +758 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.js +267 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.js +101 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js +709 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js +64 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +574 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +4 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +46 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +880 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +325 -0
- package/packages/pi-coding-agent/src/core/lsp/defaults.json +456 -0
- package/packages/pi-coding-agent/src/core/lsp/edits.ts +109 -0
- package/packages/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +943 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +33 -0
- package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +421 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +682 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/tools/index.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +59 -2
- package/src/resources/extensions/ask-user-questions.ts +2 -2
- package/src/resources/extensions/bg-shell/index.ts +34 -37
- package/src/resources/extensions/browser-tools/core.d.ts +205 -0
- package/src/resources/extensions/browser-tools/index.ts +2 -2
- package/src/resources/extensions/browser-tools/refs.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/session.ts +1 -1
- package/src/resources/extensions/context7/index.ts +2 -2
- package/src/resources/extensions/get-secrets-from-user.ts +3 -2
- package/src/resources/extensions/google-search/index.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +41 -4
- package/src/resources/extensions/gsd/commands.ts +218 -3
- package/src/resources/extensions/gsd/doctor.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +116 -4
- package/src/resources/extensions/gsd/guided-flow.ts +19 -9
- package/src/resources/extensions/gsd/index.ts +17 -7
- package/src/resources/extensions/gsd/preferences.ts +1 -1
- package/src/resources/extensions/gsd/tests/git-service.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/next-milestone-id.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +352 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/worktree.ts +20 -1
- package/src/resources/extensions/mac-tools/index.ts +1 -1
- package/src/resources/extensions/search-the-web/format.ts +1 -1
- package/src/resources/extensions/search-the-web/index.ts +5 -5
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +7 -7
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +11 -11
- package/src/resources/extensions/search-the-web/tool-search.ts +10 -10
- package/src/resources/extensions/shared/interview-ui.ts +2 -2
|
@@ -127,7 +127,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
127
127
|
...params,
|
|
128
128
|
timeout: params.timeout ?? DEFAULT_BASH_TIMEOUT_SECS,
|
|
129
129
|
};
|
|
130
|
-
return baseBash.execute(toolCallId, paramsWithTimeout, signal, onUpdate, ctx);
|
|
130
|
+
return (baseBash as any).execute(toolCallId, paramsWithTimeout, signal, onUpdate, ctx);
|
|
131
131
|
},
|
|
132
132
|
};
|
|
133
133
|
pi.registerTool(dynamicBash as any);
|
|
@@ -148,7 +148,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
148
148
|
ctx?: any,
|
|
149
149
|
) => {
|
|
150
150
|
const fresh = createWriteTool(process.cwd());
|
|
151
|
-
return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
151
|
+
return (fresh as any).execute(toolCallId, params, signal, onUpdate, ctx);
|
|
152
152
|
},
|
|
153
153
|
};
|
|
154
154
|
pi.registerTool(dynamicWrite as any);
|
|
@@ -164,7 +164,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
164
164
|
ctx?: any,
|
|
165
165
|
) => {
|
|
166
166
|
const fresh = createReadTool(process.cwd());
|
|
167
|
-
return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
167
|
+
return (fresh as any).execute(toolCallId, params, signal, onUpdate, ctx);
|
|
168
168
|
},
|
|
169
169
|
};
|
|
170
170
|
pi.registerTool(dynamicRead as any);
|
|
@@ -180,7 +180,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
180
180
|
ctx?: any,
|
|
181
181
|
) => {
|
|
182
182
|
const fresh = createEditTool(process.cwd());
|
|
183
|
-
return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
183
|
+
return (fresh as any).execute(toolCallId, params, signal, onUpdate, ctx);
|
|
184
184
|
},
|
|
185
185
|
};
|
|
186
186
|
pi.registerTool(dynamicEdit as any);
|
|
@@ -325,14 +325,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
325
325
|
// If auto-mode is already running, advance to next unit
|
|
326
326
|
if (!isAutoActive()) return;
|
|
327
327
|
|
|
328
|
-
// If the agent was aborted (user pressed Escape)
|
|
329
|
-
//
|
|
330
|
-
//
|
|
328
|
+
// If the agent was aborted (user pressed Escape) or hit a provider
|
|
329
|
+
// error (fetch failure, rate limit, etc.), pause auto-mode instead of
|
|
330
|
+
// advancing. This preserves the conversation so the user can inspect
|
|
331
|
+
// what happened, interact with the agent, or resume.
|
|
331
332
|
const lastMsg = event.messages[event.messages.length - 1];
|
|
332
333
|
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
333
334
|
await pauseAuto(ctx, pi);
|
|
334
335
|
return;
|
|
335
336
|
}
|
|
337
|
+
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
|
|
338
|
+
const errorDetail =
|
|
339
|
+
"errorMessage" in lastMsg && lastMsg.errorMessage
|
|
340
|
+
? `: ${lastMsg.errorMessage}`
|
|
341
|
+
: "";
|
|
342
|
+
(ctx as any).log(`Auto-mode paused due to provider error${errorDetail}`);
|
|
343
|
+
await pauseAuto(ctx, pi);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
336
346
|
|
|
337
347
|
await handleAgentEnd(ctx, pi);
|
|
338
348
|
});
|
|
@@ -625,7 +625,7 @@ function validatePreferences(preferences: GSDPreferences): {
|
|
|
625
625
|
}
|
|
626
626
|
const validatedRule: GSDSkillRule = { when };
|
|
627
627
|
for (const action of SKILL_ACTIONS) {
|
|
628
|
-
const values = normalizeStringList((rule as Record<string, unknown>)[action]);
|
|
628
|
+
const values = normalizeStringList((rule as unknown as Record<string, unknown>)[action]);
|
|
629
629
|
if (values.length > 0) {
|
|
630
630
|
validatedRule[action as keyof GSDSkillRule] = values as never;
|
|
631
631
|
}
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
RUNTIME_EXCLUSION_PATHS,
|
|
10
10
|
VALID_BRANCH_NAME,
|
|
11
11
|
runGit,
|
|
12
|
+
readIntegrationBranch,
|
|
13
|
+
writeIntegrationBranch,
|
|
12
14
|
type GitPreferences,
|
|
13
15
|
type CommitOptions,
|
|
14
16
|
type MergeSliceResult,
|
|
@@ -1370,6 +1372,230 @@ async function main(): Promise<void> {
|
|
|
1370
1372
|
assert(true, "PreMergeCheckResult type exported and usable");
|
|
1371
1373
|
}
|
|
1372
1374
|
|
|
1375
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1376
|
+
// Integration branch — feature-branch workflow support
|
|
1377
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1378
|
+
|
|
1379
|
+
// ─── writeIntegrationBranch / readIntegrationBranch: round-trip ────────
|
|
1380
|
+
|
|
1381
|
+
console.log("\n=== Integration branch: write and read ===");
|
|
1382
|
+
|
|
1383
|
+
{
|
|
1384
|
+
const repo = initBranchTestRepo();
|
|
1385
|
+
|
|
1386
|
+
// Initially no integration branch
|
|
1387
|
+
assertEq(readIntegrationBranch(repo, "M001"), null, "readIntegrationBranch returns null when no metadata");
|
|
1388
|
+
|
|
1389
|
+
// Write integration branch
|
|
1390
|
+
writeIntegrationBranch(repo, "M001", "f-123-new-thing");
|
|
1391
|
+
assertEq(readIntegrationBranch(repo, "M001"), "f-123-new-thing", "readIntegrationBranch returns written branch");
|
|
1392
|
+
|
|
1393
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// ─── writeIntegrationBranch: idempotent — doesn't overwrite ───────────
|
|
1397
|
+
|
|
1398
|
+
console.log("\n=== Integration branch: idempotent write ===");
|
|
1399
|
+
|
|
1400
|
+
{
|
|
1401
|
+
const repo = initBranchTestRepo();
|
|
1402
|
+
|
|
1403
|
+
writeIntegrationBranch(repo, "M001", "f-123-first");
|
|
1404
|
+
writeIntegrationBranch(repo, "M001", "f-456-second"); // should NOT overwrite
|
|
1405
|
+
|
|
1406
|
+
assertEq(readIntegrationBranch(repo, "M001"), "f-123-first", "second write does not overwrite existing integration branch");
|
|
1407
|
+
|
|
1408
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// ─── writeIntegrationBranch: rejects slice branches ───────────────────
|
|
1412
|
+
|
|
1413
|
+
console.log("\n=== Integration branch: rejects slice branches ===");
|
|
1414
|
+
|
|
1415
|
+
{
|
|
1416
|
+
const repo = initBranchTestRepo();
|
|
1417
|
+
|
|
1418
|
+
writeIntegrationBranch(repo, "M001", "gsd/M001/S01");
|
|
1419
|
+
assertEq(readIntegrationBranch(repo, "M001"), null, "slice branches are not recorded as integration branch");
|
|
1420
|
+
|
|
1421
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// ─── writeIntegrationBranch: rejects invalid branch names ─────────────
|
|
1425
|
+
|
|
1426
|
+
console.log("\n=== Integration branch: rejects invalid names ===");
|
|
1427
|
+
|
|
1428
|
+
{
|
|
1429
|
+
const repo = initBranchTestRepo();
|
|
1430
|
+
|
|
1431
|
+
writeIntegrationBranch(repo, "M001", "bad; rm -rf /");
|
|
1432
|
+
assertEq(readIntegrationBranch(repo, "M001"), null, "invalid branch name is not recorded");
|
|
1433
|
+
|
|
1434
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// ─── getMainBranch: uses integration branch when milestone set ────────
|
|
1438
|
+
|
|
1439
|
+
console.log("\n=== getMainBranch: integration branch from milestone metadata ===");
|
|
1440
|
+
|
|
1441
|
+
{
|
|
1442
|
+
const repo = initBranchTestRepo();
|
|
1443
|
+
|
|
1444
|
+
// Create a feature branch
|
|
1445
|
+
run("git checkout -b f-123-feature", repo);
|
|
1446
|
+
run("git checkout main", repo);
|
|
1447
|
+
|
|
1448
|
+
// Write integration branch metadata
|
|
1449
|
+
writeIntegrationBranch(repo, "M001", "f-123-feature");
|
|
1450
|
+
|
|
1451
|
+
// Without milestone set, getMainBranch returns "main"
|
|
1452
|
+
const svc = new GitServiceImpl(repo);
|
|
1453
|
+
assertEq(svc.getMainBranch(), "main", "getMainBranch returns main when no milestone set");
|
|
1454
|
+
|
|
1455
|
+
// With milestone set, getMainBranch returns the integration branch
|
|
1456
|
+
svc.setMilestoneId("M001");
|
|
1457
|
+
assertEq(svc.getMainBranch(), "f-123-feature", "getMainBranch returns integration branch when milestone set");
|
|
1458
|
+
|
|
1459
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// ─── getMainBranch: main_branch pref still takes priority ─────────────
|
|
1463
|
+
|
|
1464
|
+
console.log("\n=== getMainBranch: main_branch pref overrides integration branch ===");
|
|
1465
|
+
|
|
1466
|
+
{
|
|
1467
|
+
const repo = initBranchTestRepo();
|
|
1468
|
+
|
|
1469
|
+
run("git checkout -b f-123-feature", repo);
|
|
1470
|
+
run("git checkout -b trunk", repo);
|
|
1471
|
+
run("git checkout main", repo);
|
|
1472
|
+
|
|
1473
|
+
writeIntegrationBranch(repo, "M001", "f-123-feature");
|
|
1474
|
+
|
|
1475
|
+
// Explicit preference still wins
|
|
1476
|
+
const svc = new GitServiceImpl(repo, { main_branch: "trunk" });
|
|
1477
|
+
svc.setMilestoneId("M001");
|
|
1478
|
+
assertEq(svc.getMainBranch(), "trunk", "main_branch preference overrides integration branch");
|
|
1479
|
+
|
|
1480
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// ─── getMainBranch: falls back when integration branch deleted ────────
|
|
1484
|
+
|
|
1485
|
+
console.log("\n=== getMainBranch: fallback when integration branch deleted ===");
|
|
1486
|
+
|
|
1487
|
+
{
|
|
1488
|
+
const repo = initBranchTestRepo();
|
|
1489
|
+
|
|
1490
|
+
// Write metadata pointing to a branch that doesn't exist
|
|
1491
|
+
writeIntegrationBranch(repo, "M001", "deleted-branch");
|
|
1492
|
+
|
|
1493
|
+
const svc = new GitServiceImpl(repo);
|
|
1494
|
+
svc.setMilestoneId("M001");
|
|
1495
|
+
assertEq(svc.getMainBranch(), "main", "getMainBranch falls back to main when integration branch no longer exists");
|
|
1496
|
+
|
|
1497
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// ─── End-to-end: feature branch workflow ──────────────────────────────
|
|
1501
|
+
|
|
1502
|
+
console.log("\n=== End-to-end: feature branch workflow ===");
|
|
1503
|
+
|
|
1504
|
+
{
|
|
1505
|
+
const repo = initBranchTestRepo();
|
|
1506
|
+
|
|
1507
|
+
// Simulate: user creates feature branch and starts GSD
|
|
1508
|
+
run("git checkout -b f-123-new-thing", repo);
|
|
1509
|
+
createFile(repo, "setup.txt", "initial setup");
|
|
1510
|
+
run("git add -A", repo);
|
|
1511
|
+
run("git commit -m 'initial feature setup'", repo);
|
|
1512
|
+
|
|
1513
|
+
// Record integration branch (this is what auto.ts does at startup)
|
|
1514
|
+
writeIntegrationBranch(repo, "M001", "f-123-new-thing");
|
|
1515
|
+
|
|
1516
|
+
// Create GitServiceImpl with milestone set
|
|
1517
|
+
const svc = new GitServiceImpl(repo);
|
|
1518
|
+
svc.setMilestoneId("M001");
|
|
1519
|
+
|
|
1520
|
+
// Verify getMainBranch returns the feature branch, not "main"
|
|
1521
|
+
assertEq(svc.getMainBranch(), "f-123-new-thing", "e2e: getMainBranch returns feature branch");
|
|
1522
|
+
|
|
1523
|
+
// Create slice branch — should branch from f-123-new-thing (current)
|
|
1524
|
+
svc.ensureSliceBranch("M001", "S01");
|
|
1525
|
+
assertEq(svc.getCurrentBranch(), "gsd/M001/S01", "e2e: slice branch created");
|
|
1526
|
+
|
|
1527
|
+
// The slice branch should have the feature branch's commit
|
|
1528
|
+
const log = run("git log --oneline", repo);
|
|
1529
|
+
assert(log.includes("initial feature setup"), "e2e: slice branch inherits feature branch content");
|
|
1530
|
+
|
|
1531
|
+
// Do work on the slice branch
|
|
1532
|
+
createFile(repo, "src/feature.ts", "export const feature = true;");
|
|
1533
|
+
svc.commit({ message: "feat: add feature module" });
|
|
1534
|
+
|
|
1535
|
+
// switchToMain should go to feature branch
|
|
1536
|
+
svc.switchToMain();
|
|
1537
|
+
assertEq(svc.getCurrentBranch(), "f-123-new-thing", "e2e: switchToMain goes to feature branch, not main");
|
|
1538
|
+
|
|
1539
|
+
// mergeSliceToMain should merge into feature branch
|
|
1540
|
+
const result = svc.mergeSliceToMain("M001", "S01", "Add feature module");
|
|
1541
|
+
assertEq(result.mergedCommitMessage, "feat(M001/S01): Add feature module", "e2e: merge commit message correct");
|
|
1542
|
+
assertEq(svc.getCurrentBranch(), "f-123-new-thing", "e2e: after merge, still on feature branch");
|
|
1543
|
+
|
|
1544
|
+
// The feature branch should have the merged work
|
|
1545
|
+
const files = run("git ls-files", repo);
|
|
1546
|
+
assert(files.includes("src/feature.ts"), "e2e: merged file exists on feature branch");
|
|
1547
|
+
|
|
1548
|
+
// Main should NOT have the merged work
|
|
1549
|
+
run("git checkout main", repo);
|
|
1550
|
+
const mainFiles = run("git ls-files", repo);
|
|
1551
|
+
assert(!mainFiles.includes("src/feature.ts"), "e2e: main does NOT have merged work — it stays on the feature branch");
|
|
1552
|
+
|
|
1553
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// ─── Per-milestone isolation: different milestones, different targets ──
|
|
1557
|
+
|
|
1558
|
+
console.log("\n=== Integration branch: per-milestone isolation ===");
|
|
1559
|
+
|
|
1560
|
+
{
|
|
1561
|
+
const repo = initBranchTestRepo();
|
|
1562
|
+
|
|
1563
|
+
run("git checkout -b feature-a", repo);
|
|
1564
|
+
run("git checkout -b feature-b", repo);
|
|
1565
|
+
run("git checkout main", repo);
|
|
1566
|
+
|
|
1567
|
+
writeIntegrationBranch(repo, "M001", "feature-a");
|
|
1568
|
+
writeIntegrationBranch(repo, "M002", "feature-b");
|
|
1569
|
+
|
|
1570
|
+
const svc = new GitServiceImpl(repo);
|
|
1571
|
+
|
|
1572
|
+
svc.setMilestoneId("M001");
|
|
1573
|
+
assertEq(svc.getMainBranch(), "feature-a", "M001 integration branch is feature-a");
|
|
1574
|
+
|
|
1575
|
+
svc.setMilestoneId("M002");
|
|
1576
|
+
assertEq(svc.getMainBranch(), "feature-b", "M002 integration branch is feature-b");
|
|
1577
|
+
|
|
1578
|
+
svc.setMilestoneId(null);
|
|
1579
|
+
assertEq(svc.getMainBranch(), "main", "no milestone set → falls back to main");
|
|
1580
|
+
|
|
1581
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// ─── Backward compatibility: no metadata → existing behavior ──────────
|
|
1585
|
+
|
|
1586
|
+
console.log("\n=== Integration branch: backward compat ===");
|
|
1587
|
+
|
|
1588
|
+
{
|
|
1589
|
+
const repo = initBranchTestRepo();
|
|
1590
|
+
const svc = new GitServiceImpl(repo);
|
|
1591
|
+
|
|
1592
|
+
// Set milestone but no metadata file exists
|
|
1593
|
+
svc.setMilestoneId("M001");
|
|
1594
|
+
assertEq(svc.getMainBranch(), "main", "backward compat: no metadata file → falls back to main");
|
|
1595
|
+
|
|
1596
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1373
1599
|
// ─── untrackRuntimeFiles: removes tracked runtime files from index ───
|
|
1374
1600
|
|
|
1375
1601
|
console.log("\n=== untrackRuntimeFiles ===");
|
|
@@ -354,8 +354,8 @@ async function main(): Promise<void> {
|
|
|
354
354
|
assert(state.phase !== undefined, 'pipeline: deriveState returns phase');
|
|
355
355
|
assert(state.activeMilestone !== null, 'pipeline: deriveState has activeMilestone');
|
|
356
356
|
assertEq(state.activeMilestone!.id, 'M001', 'pipeline: deriveState activeMilestone is M001');
|
|
357
|
-
assert(state.progress
|
|
358
|
-
assert(state.progress
|
|
357
|
+
assert(state.progress!.slices !== undefined, 'pipeline: deriveState has slices progress');
|
|
358
|
+
assert(state.progress!.tasks !== undefined, 'pipeline: deriveState has tasks progress');
|
|
359
359
|
|
|
360
360
|
} finally {
|
|
361
361
|
rmSync(base, { recursive: true, force: true });
|
|
@@ -317,7 +317,7 @@ function makeResearch(fileName: string, content: string): PlanningResearch {
|
|
|
317
317
|
assertEq(doneSlice?.tasks[0]?.summary?.duration, '2h', 'completion: summary duration from frontmatter');
|
|
318
318
|
assertEq(doneSlice?.tasks[0]?.summary?.provides, ['feature-01'], 'completion: summary provides from frontmatter');
|
|
319
319
|
assertEq(doneSlice?.tasks[0]?.summary?.keyFiles, ['file-01.ts'], 'completion: summary keyFiles from frontmatter');
|
|
320
|
-
assert(doneSlice?.tasks[0]?.summary?.whatHappened?.includes('Summary body'), 'completion: summary whatHappened from body');
|
|
320
|
+
assert(doneSlice?.tasks[0]?.summary?.whatHappened?.includes('Summary body') ?? false, 'completion: summary whatHappened from body');
|
|
321
321
|
assert(doneSlice?.summary !== null, 'completion: done slice has slice summary');
|
|
322
322
|
assert(activeSlice?.summary === null, 'completion: active slice has null summary');
|
|
323
323
|
assertEq(doneSlice?.tasks[0]?.estimate, '2h', 'completion: task estimate from summary duration');
|
|
@@ -234,18 +234,18 @@ async function main(): Promise<void> {
|
|
|
234
234
|
assertEq(state.activeSlice!.id, 'S02', 'incomplete: deriveState activeSlice is S02');
|
|
235
235
|
assert(state.activeTask !== null, 'incomplete: deriveState has activeTask');
|
|
236
236
|
assertEq(state.activeTask!.id, 'T03', 'incomplete: deriveState activeTask is T03');
|
|
237
|
-
assert(state.progress
|
|
238
|
-
assertEq(state.progress
|
|
239
|
-
assertEq(state.progress
|
|
240
|
-
assert(state.progress
|
|
237
|
+
assert(state.progress!.slices !== undefined, 'incomplete: deriveState has slices progress');
|
|
238
|
+
assertEq(state.progress!.slices!.done, 1, 'incomplete: deriveState slices done count');
|
|
239
|
+
assertEq(state.progress!.slices!.total, 2, 'incomplete: deriveState slices total count');
|
|
240
|
+
assert(state.progress!.tasks !== undefined, 'incomplete: deriveState has tasks progress');
|
|
241
241
|
// S02 has 1 task, 0 done (only active slice tasks counted)
|
|
242
|
-
assertEq(state.progress
|
|
243
|
-
assertEq(state.progress
|
|
242
|
+
assertEq(state.progress!.tasks!.done, 0, 'incomplete: deriveState tasks done (in active slice)');
|
|
243
|
+
assertEq(state.progress!.tasks!.total, 1, 'incomplete: deriveState tasks total (in active slice)');
|
|
244
244
|
// Requirements
|
|
245
|
-
assertEq(state.requirements
|
|
246
|
-
assertEq(state.requirements
|
|
247
|
-
assertEq(state.requirements
|
|
248
|
-
assertEq(state.requirements
|
|
245
|
+
assertEq(state.requirements!.active, 1, 'incomplete: deriveState requirements active');
|
|
246
|
+
assertEq(state.requirements!.validated, 1, 'incomplete: deriveState requirements validated');
|
|
247
|
+
assertEq(state.requirements!.deferred, 1, 'incomplete: deriveState requirements deferred');
|
|
248
|
+
assertEq(state.requirements!.outOfScope, 1, 'incomplete: deriveState requirements outOfScope');
|
|
249
249
|
|
|
250
250
|
// (f) generatePreview
|
|
251
251
|
console.log(' --- generatePreview ---');
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Tests for nextMilestoneId and maxMilestoneNum — milestone ID generation
|
|
2
|
+
// using max-based approach to avoid collisions after deletions.
|
|
3
|
+
//
|
|
4
|
+
// Sections:
|
|
5
|
+
// (a) Empty array returns M001
|
|
6
|
+
// (b) Sequential IDs return next in sequence
|
|
7
|
+
// (c) IDs with gaps (deletion) use max, not fill
|
|
8
|
+
// (d) Non-numeric directory names mixed in are ignored
|
|
9
|
+
|
|
10
|
+
import { nextMilestoneId, maxMilestoneNum } from '../guided-flow.ts';
|
|
11
|
+
|
|
12
|
+
// ─── Assertion helpers ─────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
|
|
17
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
18
|
+
if (JSON.stringify(actual) === JSON.stringify(expected)) {
|
|
19
|
+
passed++;
|
|
20
|
+
} else {
|
|
21
|
+
failed++;
|
|
22
|
+
console.error(` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Tests ─────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
async function main(): Promise<void> {
|
|
29
|
+
console.log('nextMilestoneId / maxMilestoneNum tests');
|
|
30
|
+
|
|
31
|
+
// (a) Empty array → M001
|
|
32
|
+
{
|
|
33
|
+
assertEq(maxMilestoneNum([]), 0, 'maxMilestoneNum([]) === 0');
|
|
34
|
+
assertEq(nextMilestoneId([]), 'M001', 'nextMilestoneId([]) === "M001"');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// (b) Sequential IDs → next in sequence
|
|
38
|
+
{
|
|
39
|
+
assertEq(
|
|
40
|
+
nextMilestoneId(['M001', 'M002', 'M003']),
|
|
41
|
+
'M004',
|
|
42
|
+
'sequential IDs return M004',
|
|
43
|
+
);
|
|
44
|
+
assertEq(maxMilestoneNum(['M001', 'M002', 'M003']), 3, 'max of sequential is 3');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// (c) IDs with gaps (deletion scenario) → uses max, not fill
|
|
48
|
+
{
|
|
49
|
+
assertEq(
|
|
50
|
+
nextMilestoneId(['M001', 'M003']),
|
|
51
|
+
'M004',
|
|
52
|
+
'gap scenario returns M004, not M002',
|
|
53
|
+
);
|
|
54
|
+
assertEq(maxMilestoneNum(['M001', 'M003']), 3, 'max with gap is 3');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// (d) Non-numeric directory names mixed in are ignored
|
|
58
|
+
{
|
|
59
|
+
assertEq(
|
|
60
|
+
nextMilestoneId(['M001', 'notes', '.DS_Store', 'M003']),
|
|
61
|
+
'M004',
|
|
62
|
+
'non-numeric names ignored, returns M004',
|
|
63
|
+
);
|
|
64
|
+
assertEq(
|
|
65
|
+
maxMilestoneNum(['M001', 'notes', '.DS_Store', 'M003']),
|
|
66
|
+
3,
|
|
67
|
+
'max ignores non-numeric entries',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
72
|
+
// Results
|
|
73
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
console.log(`\n${'='.repeat(40)}`);
|
|
76
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
77
|
+
if (failed > 0) {
|
|
78
|
+
process.exit(1);
|
|
79
|
+
} else {
|
|
80
|
+
console.log('All tests passed');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch((error) => {
|
|
85
|
+
console.error(error);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|