gsd-pi 2.41.0-dev.0acbce9 → 2.41.0-dev.3557dc4
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/dist/cli-web-branch.d.ts +6 -0
- package/dist/cli-web-branch.js +17 -0
- package/dist/onboarding.js +2 -1
- package/dist/resources/extensions/gsd/auto/loop.js +9 -1
- package/dist/resources/extensions/gsd/auto/phases.js +26 -8
- package/dist/resources/extensions/gsd/auto-dashboard.js +6 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
- package/dist/resources/extensions/gsd/auto-start.js +8 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
- package/dist/resources/extensions/gsd/auto.js +36 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +8 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/context-store.js +4 -3
- package/dist/resources/extensions/gsd/db-writer.js +5 -2
- package/dist/resources/extensions/gsd/detection.js +1 -1
- package/dist/resources/extensions/gsd/doctor.js +11 -1
- package/dist/resources/extensions/gsd/exit-command.js +12 -2
- package/dist/resources/extensions/gsd/export.js +9 -13
- package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
- package/dist/resources/extensions/gsd/files.js +28 -11
- package/dist/resources/extensions/gsd/forensics.js +10 -3
- package/dist/resources/extensions/gsd/git-service.js +5 -1
- package/dist/resources/extensions/gsd/gsd-db.js +25 -8
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +7 -3
- package/dist/resources/extensions/gsd/journal.js +85 -0
- package/dist/resources/extensions/gsd/md-importer.js +5 -0
- package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +2 -2
- package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +1 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/repo-identity.js +46 -2
- package/dist/resources/extensions/gsd/rule-registry.js +489 -0
- package/dist/resources/extensions/gsd/rule-types.js +6 -0
- package/dist/resources/extensions/gsd/service-tier.js +138 -0
- package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
- package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
- package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
- package/dist/resources/extensions/gsd/worktree-resolver.js +19 -2
- package/dist/resources/extensions/subagent/index.js +7 -3
- package/dist/resources/extensions/voice/index.js +4 -4
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/chunks/229.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +29 -7
- package/package.json +1 -1
- package/packages/native/src/__tests__/text.test.mjs +33 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
- package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -1
- package/src/resources/extensions/gsd/auto/loop.ts +10 -1
- package/src/resources/extensions/gsd/auto/phases.ts +28 -8
- package/src/resources/extensions/gsd/auto/types.ts +4 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +7 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
- package/src/resources/extensions/gsd/auto-start.ts +8 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
- package/src/resources/extensions/gsd/auto.ts +40 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +8 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/context-store.ts +4 -3
- package/src/resources/extensions/gsd/db-writer.ts +6 -2
- package/src/resources/extensions/gsd/detection.ts +1 -1
- package/src/resources/extensions/gsd/doctor.ts +12 -1
- package/src/resources/extensions/gsd/exit-command.ts +14 -2
- package/src/resources/extensions/gsd/export.ts +8 -15
- package/src/resources/extensions/gsd/extension-manifest.json +2 -2
- package/src/resources/extensions/gsd/files.ts +29 -12
- package/src/resources/extensions/gsd/forensics.ts +9 -3
- package/src/resources/extensions/gsd/git-service.ts +5 -4
- package/src/resources/extensions/gsd/gsd-db.ts +37 -8
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +7 -3
- package/src/resources/extensions/gsd/journal.ts +134 -0
- package/src/resources/extensions/gsd/md-importer.ts +6 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +1 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/repo-identity.ts +47 -2
- package/src/resources/extensions/gsd/rule-registry.ts +599 -0
- package/src/resources/extensions/gsd/rule-types.ts +68 -0
- package/src/resources/extensions/gsd/service-tier.ts +171 -0
- package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
- package/src/resources/extensions/gsd/templates/decisions.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +386 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -3
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
- package/src/resources/extensions/gsd/types.ts +3 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
- package/src/resources/extensions/gsd/worktree-resolver.ts +30 -9
- package/src/resources/extensions/subagent/index.ts +7 -3
- package/src/resources/extensions/voice/index.ts +4 -4
- package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
- /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → JBSIr4fSfHXs5g5x2ZBSC}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → JBSIr4fSfHXs5g5x2ZBSC}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// tool-naming — Verifies canonical + alias tool registration for GSD DB tools.
|
|
2
|
+
//
|
|
3
|
+
// Each of the 4 DB tools must register under its canonical gsd_concept_action name
|
|
4
|
+
// AND under the old gsd_action_concept name as a backward-compatible alias.
|
|
5
|
+
// The alias must share the exact same execute function reference as the canonical tool.
|
|
6
|
+
|
|
7
|
+
import { createTestContext } from './test-helpers.ts';
|
|
8
|
+
import { registerDbTools } from '../bootstrap/db-tools.ts';
|
|
9
|
+
|
|
10
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
11
|
+
|
|
12
|
+
// ─── Mock PI ──────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
function makeMockPi() {
|
|
15
|
+
const tools: any[] = [];
|
|
16
|
+
return {
|
|
17
|
+
registerTool: (tool: any) => tools.push(tool),
|
|
18
|
+
tools,
|
|
19
|
+
} as any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Rename map ───────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const RENAME_MAP: Array<{ canonical: string; alias: string }> = [
|
|
25
|
+
{ canonical: "gsd_decision_save", alias: "gsd_save_decision" },
|
|
26
|
+
{ canonical: "gsd_requirement_update", alias: "gsd_update_requirement" },
|
|
27
|
+
{ canonical: "gsd_summary_save", alias: "gsd_save_summary" },
|
|
28
|
+
{ canonical: "gsd_milestone_generate_id", alias: "gsd_generate_milestone_id" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// ─── Registration count ──────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
console.log('\n── Tool naming: registration count ──');
|
|
34
|
+
|
|
35
|
+
const pi = makeMockPi();
|
|
36
|
+
registerDbTools(pi);
|
|
37
|
+
|
|
38
|
+
assertEq(pi.tools.length, 8, 'Should register exactly 8 tools (4 canonical + 4 aliases)');
|
|
39
|
+
|
|
40
|
+
// ─── Both names exist for each pair ──────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
console.log('\n── Tool naming: canonical and alias names exist ──');
|
|
43
|
+
|
|
44
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
45
|
+
const canonicalTool = pi.tools.find((t: any) => t.name === canonical);
|
|
46
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
47
|
+
|
|
48
|
+
assertTrue(canonicalTool !== undefined, `Canonical tool "${canonical}" should be registered`);
|
|
49
|
+
assertTrue(aliasTool !== undefined, `Alias tool "${alias}" should be registered`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Execute function identity ───────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
console.log('\n── Tool naming: execute function identity (===) ──');
|
|
55
|
+
|
|
56
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
57
|
+
const canonicalTool = pi.tools.find((t: any) => t.name === canonical);
|
|
58
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
59
|
+
|
|
60
|
+
if (canonicalTool && aliasTool) {
|
|
61
|
+
assertTrue(
|
|
62
|
+
canonicalTool.execute === aliasTool.execute,
|
|
63
|
+
`"${canonical}" and "${alias}" should share the same execute function reference`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Alias descriptions include "(alias for ...)" ───────────────────────────
|
|
69
|
+
|
|
70
|
+
console.log('\n── Tool naming: alias descriptions ──');
|
|
71
|
+
|
|
72
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
73
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
74
|
+
|
|
75
|
+
if (aliasTool) {
|
|
76
|
+
assertTrue(
|
|
77
|
+
aliasTool.description.includes(`alias for ${canonical}`),
|
|
78
|
+
`Alias "${alias}" description should include "alias for ${canonical}"`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Canonical tools have proper promptGuidelines ────────────────────────────
|
|
84
|
+
|
|
85
|
+
console.log('\n── Tool naming: canonical promptGuidelines use canonical name ──');
|
|
86
|
+
|
|
87
|
+
for (const { canonical } of RENAME_MAP) {
|
|
88
|
+
const canonicalTool = pi.tools.find((t: any) => t.name === canonical);
|
|
89
|
+
|
|
90
|
+
if (canonicalTool) {
|
|
91
|
+
const guidelinesText = canonicalTool.promptGuidelines.join(' ');
|
|
92
|
+
assertTrue(
|
|
93
|
+
guidelinesText.includes(canonical),
|
|
94
|
+
`Canonical tool "${canonical}" promptGuidelines should reference its own name`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Alias promptGuidelines direct to canonical ──────────────────────────────
|
|
100
|
+
|
|
101
|
+
console.log('\n── Tool naming: alias promptGuidelines redirect to canonical ──');
|
|
102
|
+
|
|
103
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
104
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
105
|
+
|
|
106
|
+
if (aliasTool) {
|
|
107
|
+
const guidelinesText = aliasTool.promptGuidelines.join(' ');
|
|
108
|
+
assertTrue(
|
|
109
|
+
guidelinesText.includes(`Alias for ${canonical}`),
|
|
110
|
+
`Alias "${alias}" promptGuidelines should say "Alias for ${canonical}"`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
|
|
117
|
+
report();
|
|
@@ -15,6 +15,7 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const hooksPath = join(__dirname, "..", "post-unit-hooks.ts");
|
|
18
|
+
const registryPath = join(__dirname, "..", "rule-registry.ts");
|
|
18
19
|
const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts");
|
|
19
20
|
|
|
20
21
|
// After decomposition, triage/dispatch logic lives in auto-post-unit.ts
|
|
@@ -25,7 +26,11 @@ const autoSrc = [
|
|
|
25
26
|
postUnitSrc,
|
|
26
27
|
readFileSync(join(__dirname, "..", "auto-start.ts"), "utf-8"),
|
|
27
28
|
].join("\n");
|
|
28
|
-
|
|
29
|
+
// Hook exclusion logic lives in the rule-registry (facade delegates there)
|
|
30
|
+
const hooksSrc = [
|
|
31
|
+
readFileSync(hooksPath, "utf-8"),
|
|
32
|
+
readFileSync(registryPath, "utf-8"),
|
|
33
|
+
].join("\n");
|
|
29
34
|
const autoPromptsSrc = (() => { try { return readFileSync(autoPromptsPath, "utf-8"); } catch { return autoSrc; } })();
|
|
30
35
|
|
|
31
36
|
// ─── Hook exclusion ──────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* windows-path-normalization.test.ts — Verify Windows backslash paths are
|
|
3
|
+
* normalised to forward slashes before embedding in bash command strings.
|
|
4
|
+
*
|
|
5
|
+
* Regression test for #1436: on Windows, `cd C:\Users\user\project` in bash
|
|
6
|
+
* strips backslashes (escape characters), producing `C:Usersuserproject`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
10
|
+
|
|
11
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
12
|
+
|
|
13
|
+
// ─── shellEscape + path normalization ──────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
// Replicate the shellEscape helper from cmux/index.ts
|
|
16
|
+
function shellEscape(value: string): string {
|
|
17
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The bashPath pattern used in subagent/index.ts
|
|
21
|
+
function bashPath(p: string): string {
|
|
22
|
+
return shellEscape(p.replaceAll("\\", "/"));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log("\n=== Windows backslash path normalization (#1436) ===");
|
|
26
|
+
|
|
27
|
+
// Backslash paths are converted to forward slashes
|
|
28
|
+
assertEq(
|
|
29
|
+
bashPath("C:\\Users\\user\\project"),
|
|
30
|
+
"'C:/Users/user/project'",
|
|
31
|
+
"backslash path normalised to forward slashes in shell-escaped string",
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Unix paths pass through unchanged
|
|
35
|
+
assertEq(
|
|
36
|
+
bashPath("/home/user/project"),
|
|
37
|
+
"'/home/user/project'",
|
|
38
|
+
"Unix path unchanged",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Mixed separators are normalised
|
|
42
|
+
assertEq(
|
|
43
|
+
bashPath("C:\\Users/user\\project/src"),
|
|
44
|
+
"'C:/Users/user/project/src'",
|
|
45
|
+
"mixed separators normalised",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Paths with single quotes are still properly escaped
|
|
49
|
+
assertEq(
|
|
50
|
+
bashPath("C:\\Users\\o'brien\\project"),
|
|
51
|
+
"'C:/Users/o'\\''brien/project'",
|
|
52
|
+
"single quote in path is escaped after normalisation",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// UNC paths
|
|
56
|
+
assertEq(
|
|
57
|
+
bashPath("\\\\server\\share\\dir"),
|
|
58
|
+
"'//server/share/dir'",
|
|
59
|
+
"UNC path normalised",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Empty string
|
|
63
|
+
assertEq(
|
|
64
|
+
bashPath(""),
|
|
65
|
+
"''",
|
|
66
|
+
"empty string handled",
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// ─── cd command construction ───────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
console.log("\n=== cd command construction with normalised paths ===");
|
|
72
|
+
|
|
73
|
+
const windowsCwd = "C:\\Users\\user\\project\\.gsd\\worktrees\\M001";
|
|
74
|
+
const cdCommand = `cd ${bashPath(windowsCwd)}`;
|
|
75
|
+
assertEq(
|
|
76
|
+
cdCommand,
|
|
77
|
+
"cd 'C:/Users/user/project/.gsd/worktrees/M001'",
|
|
78
|
+
"cd command uses forward slashes for Windows worktree path",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Verify the mangled form from #1436 is NOT produced
|
|
82
|
+
assertTrue(
|
|
83
|
+
!cdCommand.includes("C:Users"),
|
|
84
|
+
"mangled path C:Usersuserproject must not appear",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// ─── Worktree teardown orphan detection ────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
console.log("\n=== teardown orphan warning path formatting ===");
|
|
90
|
+
|
|
91
|
+
const windowsWtDir = "C:\\Users\\user\\project\\.gsd\\worktrees\\M001";
|
|
92
|
+
const helpCommand = `rm -rf "${windowsWtDir.replaceAll("\\", "/")}"`;
|
|
93
|
+
assertEq(
|
|
94
|
+
helpCommand,
|
|
95
|
+
'rm -rf "C:/Users/user/project/.gsd/worktrees/M001"',
|
|
96
|
+
"orphan cleanup help command uses forward slashes",
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
report();
|
|
@@ -47,6 +47,7 @@ function seedMainDb(dbPath: string): void {
|
|
|
47
47
|
choice: 'node:sqlite',
|
|
48
48
|
rationale: 'Built-in',
|
|
49
49
|
revisable: 'yes',
|
|
50
|
+
made_by: 'agent',
|
|
50
51
|
superseded_by: null,
|
|
51
52
|
});
|
|
52
53
|
insertRequirement({
|
|
@@ -182,6 +183,7 @@ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
|
|
|
182
183
|
choice: 'WAL',
|
|
183
184
|
rationale: 'Performance',
|
|
184
185
|
revisable: 'yes',
|
|
186
|
+
made_by: 'agent',
|
|
185
187
|
superseded_by: null,
|
|
186
188
|
});
|
|
187
189
|
closeDatabase();
|
|
@@ -357,6 +359,7 @@ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
|
|
|
357
359
|
choice: 'yes',
|
|
358
360
|
rationale: 'Robustness',
|
|
359
361
|
revisable: 'no',
|
|
362
|
+
made_by: 'agent',
|
|
360
363
|
superseded_by: null,
|
|
361
364
|
});
|
|
362
365
|
closeDatabase();
|
|
@@ -395,6 +398,7 @@ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
|
|
|
395
398
|
choice: 'works',
|
|
396
399
|
rationale: 'Verify DETACH cleanup',
|
|
397
400
|
revisable: 'no',
|
|
401
|
+
made_by: 'agent',
|
|
398
402
|
superseded_by: null,
|
|
399
403
|
});
|
|
400
404
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-health-dispatch.test.ts — Regression tests for the worktree health
|
|
3
|
+
* check in auto/phases.ts (#1833, #1843).
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the pre-dispatch health check recognises non-JS project types
|
|
6
|
+
* (Rust, Go, Python, etc.) via the shared PROJECT_FILES list from detection.ts,
|
|
7
|
+
* rather than hard-coding package.json / src/ only.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { PROJECT_FILES } from "../detection.js";
|
|
18
|
+
|
|
19
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/** Create a minimal git repo and return its path. */
|
|
22
|
+
function createGitRepo(): string {
|
|
23
|
+
const dir = mkdtempSync(join(tmpdir(), "wt-dispatch-test-"));
|
|
24
|
+
// All execSync calls use hardcoded strings only — no user input, no injection risk.
|
|
25
|
+
execSync("git init", { cwd: dir, stdio: "ignore" });
|
|
26
|
+
execSync("git config user.email test@test.com", { cwd: dir, stdio: "ignore" });
|
|
27
|
+
execSync("git config user.name Test", { cwd: dir, stdio: "ignore" });
|
|
28
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
29
|
+
execSync("git add . && git commit -m init", { cwd: dir, stdio: "ignore" });
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Simulate the health check logic from auto/phases.ts.
|
|
35
|
+
*
|
|
36
|
+
* Returns true when the directory would PASS the health check (dispatch
|
|
37
|
+
* proceeds), false when it would FAIL (dispatch blocked).
|
|
38
|
+
*
|
|
39
|
+
* This mirrors the fixed logic: .git must exist, AND at least one
|
|
40
|
+
* PROJECT_FILES entry or a src/ directory must exist.
|
|
41
|
+
*/
|
|
42
|
+
function wouldPassHealthCheck(basePath: string, existsSyncFn: (p: string) => boolean): boolean {
|
|
43
|
+
const hasGit = existsSyncFn(join(basePath, ".git"));
|
|
44
|
+
if (!hasGit) return false;
|
|
45
|
+
|
|
46
|
+
for (const file of PROJECT_FILES) {
|
|
47
|
+
if (existsSyncFn(join(basePath, file))) return true;
|
|
48
|
+
}
|
|
49
|
+
if (existsSyncFn(join(basePath, "src"))) return true;
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
import { existsSync } from "node:fs";
|
|
55
|
+
|
|
56
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
test("PROJECT_FILES is exported and contains expected multi-ecosystem entries", () => {
|
|
59
|
+
assert.ok(Array.isArray(PROJECT_FILES), "PROJECT_FILES is an array");
|
|
60
|
+
assert.ok(PROJECT_FILES.length >= 17, `expected >= 17 entries, got ${PROJECT_FILES.length}`);
|
|
61
|
+
// Spot-check key ecosystems
|
|
62
|
+
assert.ok(PROJECT_FILES.includes("Cargo.toml"), "includes Rust marker");
|
|
63
|
+
assert.ok(PROJECT_FILES.includes("go.mod"), "includes Go marker");
|
|
64
|
+
assert.ok(PROJECT_FILES.includes("pyproject.toml"), "includes Python marker");
|
|
65
|
+
assert.ok(PROJECT_FILES.includes("package.json"), "includes JS marker");
|
|
66
|
+
assert.ok(PROJECT_FILES.includes("pom.xml"), "includes Java marker");
|
|
67
|
+
assert.ok(PROJECT_FILES.includes("Package.swift"), "includes Swift marker");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("health check passes for Rust project (Cargo.toml, no package.json)", () => {
|
|
71
|
+
const dir = createGitRepo();
|
|
72
|
+
try {
|
|
73
|
+
writeFileSync(join(dir, "Cargo.toml"), "[package]\nname = \"test\"\n");
|
|
74
|
+
mkdirSync(join(dir, "crates"), { recursive: true });
|
|
75
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Rust project should pass health check");
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(dir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("health check passes for Go project (go.mod, no package.json)", () => {
|
|
82
|
+
const dir = createGitRepo();
|
|
83
|
+
try {
|
|
84
|
+
writeFileSync(join(dir, "go.mod"), "module example.com/test\n\ngo 1.21\n");
|
|
85
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Go project should pass health check");
|
|
86
|
+
} finally {
|
|
87
|
+
rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("health check passes for Python project (pyproject.toml, no package.json)", () => {
|
|
92
|
+
const dir = createGitRepo();
|
|
93
|
+
try {
|
|
94
|
+
writeFileSync(join(dir, "pyproject.toml"), "[project]\nname = \"test\"\n");
|
|
95
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Python project should pass health check");
|
|
96
|
+
} finally {
|
|
97
|
+
rmSync(dir, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("health check passes for Java project (pom.xml, no package.json)", () => {
|
|
102
|
+
const dir = createGitRepo();
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(join(dir, "pom.xml"), "<project></project>\n");
|
|
105
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Java project should pass health check");
|
|
106
|
+
} finally {
|
|
107
|
+
rmSync(dir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("health check passes for Swift project (Package.swift, no package.json)", () => {
|
|
112
|
+
const dir = createGitRepo();
|
|
113
|
+
try {
|
|
114
|
+
writeFileSync(join(dir, "Package.swift"), "// swift-tools-version:5.7\n");
|
|
115
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Swift project should pass health check");
|
|
116
|
+
} finally {
|
|
117
|
+
rmSync(dir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("health check passes for C/C++ project (CMakeLists.txt, no package.json)", () => {
|
|
122
|
+
const dir = createGitRepo();
|
|
123
|
+
try {
|
|
124
|
+
writeFileSync(join(dir, "CMakeLists.txt"), "cmake_minimum_required(VERSION 3.20)\n");
|
|
125
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "C/C++ project should pass health check");
|
|
126
|
+
} finally {
|
|
127
|
+
rmSync(dir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("health check passes for Elixir project (mix.exs, no package.json)", () => {
|
|
132
|
+
const dir = createGitRepo();
|
|
133
|
+
try {
|
|
134
|
+
writeFileSync(join(dir, "mix.exs"), "defmodule Test.MixProject do\nend\n");
|
|
135
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Elixir project should pass health check");
|
|
136
|
+
} finally {
|
|
137
|
+
rmSync(dir, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("health check passes for JS project (package.json, backward compat)", () => {
|
|
142
|
+
const dir = createGitRepo();
|
|
143
|
+
try {
|
|
144
|
+
writeFileSync(join(dir, "package.json"), '{"name":"test"}\n');
|
|
145
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "JS project should pass health check");
|
|
146
|
+
} finally {
|
|
147
|
+
rmSync(dir, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("health check passes for src/-only project (backward compat)", () => {
|
|
152
|
+
const dir = createGitRepo();
|
|
153
|
+
try {
|
|
154
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
155
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "src/-only project should pass health check");
|
|
156
|
+
} finally {
|
|
157
|
+
rmSync(dir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("health check fails for directory with no .git", () => {
|
|
162
|
+
const dir = mkdtempSync(join(tmpdir(), "wt-dispatch-test-nogit-"));
|
|
163
|
+
try {
|
|
164
|
+
writeFileSync(join(dir, "Cargo.toml"), "[package]\nname = \"test\"\n");
|
|
165
|
+
assert.ok(!wouldPassHealthCheck(dir, existsSync), "no-git directory should fail health check");
|
|
166
|
+
} finally {
|
|
167
|
+
rmSync(dir, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("health check fails for empty git repo with no project files", () => {
|
|
172
|
+
const dir = createGitRepo();
|
|
173
|
+
try {
|
|
174
|
+
assert.ok(!wouldPassHealthCheck(dir, existsSync), "empty git repo should fail health check");
|
|
175
|
+
} finally {
|
|
176
|
+
rmSync(dir, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
@@ -52,7 +52,7 @@ function makeDeps(
|
|
|
52
52
|
fn: "mergeMilestoneToMain",
|
|
53
53
|
args: [basePath, milestoneId, roadmapContent],
|
|
54
54
|
});
|
|
55
|
-
return { pushed: false };
|
|
55
|
+
return { pushed: false, codeFilesChanged: true };
|
|
56
56
|
},
|
|
57
57
|
syncWorktreeStateBack: (
|
|
58
58
|
mainBasePath: string,
|
|
@@ -424,7 +424,7 @@ test("mergeAndExit in worktree mode shows pushed status", () => {
|
|
|
424
424
|
const deps = makeDeps({
|
|
425
425
|
isInAutoWorktree: () => true,
|
|
426
426
|
getIsolationMode: () => "worktree",
|
|
427
|
-
mergeMilestoneToMain: () => ({ pushed: true }),
|
|
427
|
+
mergeMilestoneToMain: () => ({ pushed: true, codeFilesChanged: true }),
|
|
428
428
|
});
|
|
429
429
|
const ctx = makeNotifyCtx();
|
|
430
430
|
const resolver = new WorktreeResolver(s, deps);
|
|
@@ -659,6 +659,81 @@ test("mergeAndExit in none mode is a no-op", () => {
|
|
|
659
659
|
assert.equal(ctx.messages.length, 0);
|
|
660
660
|
});
|
|
661
661
|
|
|
662
|
+
// ─── #1906 — metadata-only merge warning ────────────────────────────────────
|
|
663
|
+
|
|
664
|
+
test("mergeAndExit warns when merge contains no code changes (#1906)", () => {
|
|
665
|
+
const s = makeSession({
|
|
666
|
+
basePath: "/project/.gsd/worktrees/M001",
|
|
667
|
+
originalBasePath: "/project",
|
|
668
|
+
});
|
|
669
|
+
const deps = makeDeps({
|
|
670
|
+
isInAutoWorktree: () => true,
|
|
671
|
+
getIsolationMode: () => "worktree",
|
|
672
|
+
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
|
|
673
|
+
});
|
|
674
|
+
const ctx = makeNotifyCtx();
|
|
675
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
676
|
+
|
|
677
|
+
resolver.mergeAndExit("M001", ctx);
|
|
678
|
+
|
|
679
|
+
assert.ok(
|
|
680
|
+
ctx.messages.some((m) => m.msg.includes("NO code changes") && m.level === "warning"),
|
|
681
|
+
"must emit warning when only .gsd/ metadata was merged",
|
|
682
|
+
);
|
|
683
|
+
assert.ok(
|
|
684
|
+
!ctx.messages.some((m) => m.msg.includes("merged to main") && m.level === "info"),
|
|
685
|
+
"must NOT emit success-style info notification for metadata-only merge",
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("mergeAndExit emits info when merge contains code changes (#1906)", () => {
|
|
690
|
+
const s = makeSession({
|
|
691
|
+
basePath: "/project/.gsd/worktrees/M001",
|
|
692
|
+
originalBasePath: "/project",
|
|
693
|
+
});
|
|
694
|
+
const deps = makeDeps({
|
|
695
|
+
isInAutoWorktree: () => true,
|
|
696
|
+
getIsolationMode: () => "worktree",
|
|
697
|
+
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
|
|
698
|
+
});
|
|
699
|
+
const ctx = makeNotifyCtx();
|
|
700
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
701
|
+
|
|
702
|
+
resolver.mergeAndExit("M001", ctx);
|
|
703
|
+
|
|
704
|
+
assert.ok(
|
|
705
|
+
ctx.messages.some((m) => m.msg.includes("merged to main") && m.level === "info"),
|
|
706
|
+
"must emit info notification when code files were merged",
|
|
707
|
+
);
|
|
708
|
+
assert.ok(
|
|
709
|
+
!ctx.messages.some((m) => m.msg.includes("NO code changes")),
|
|
710
|
+
"must NOT emit metadata-only warning when code files were merged",
|
|
711
|
+
);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("mergeAndExit branch mode warns when merge contains no code changes (#1906)", () => {
|
|
715
|
+
const s = makeSession({
|
|
716
|
+
basePath: "/project",
|
|
717
|
+
originalBasePath: "/project",
|
|
718
|
+
});
|
|
719
|
+
const deps = makeDeps({
|
|
720
|
+
isInAutoWorktree: () => false,
|
|
721
|
+
getIsolationMode: () => "branch",
|
|
722
|
+
getCurrentBranch: () => "milestone/M001",
|
|
723
|
+
autoWorktreeBranch: () => "milestone/M001",
|
|
724
|
+
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
|
|
725
|
+
});
|
|
726
|
+
const ctx = makeNotifyCtx();
|
|
727
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
728
|
+
|
|
729
|
+
resolver.mergeAndExit("M001", ctx);
|
|
730
|
+
|
|
731
|
+
assert.ok(
|
|
732
|
+
ctx.messages.some((m) => m.msg.includes("NO code changes") && m.level === "warning"),
|
|
733
|
+
"branch mode must emit warning when only .gsd/ metadata was merged",
|
|
734
|
+
);
|
|
735
|
+
});
|
|
736
|
+
|
|
662
737
|
// ─── mergeAndEnterNext Tests ─────────────────────────────────────────────────
|
|
663
738
|
|
|
664
739
|
test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
|
|
@@ -677,7 +752,7 @@ test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
|
|
|
677
752
|
_roadmap: string,
|
|
678
753
|
) => {
|
|
679
754
|
callOrder.push(`merge:${milestoneId}`);
|
|
680
|
-
return { pushed: false };
|
|
755
|
+
return { pushed: false, codeFilesChanged: true };
|
|
681
756
|
},
|
|
682
757
|
getAutoWorktreePath: () => null,
|
|
683
758
|
createAutoWorktree: (basePath: string, milestoneId: string) => {
|