@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.844675c9
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/project-sessions.js +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -9
- package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +659 -57
- package/dist/resources/extensions/gsd/auto-prompts.js +110 -1
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +5 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +29 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +24 -17
- package/dist/resources/extensions/gsd/auto.js +62 -464
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -1
- package/dist/resources/extensions/gsd/debug-logger.js +10 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +31 -32
- package/dist/resources/extensions/gsd/mcp-filter.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +45 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
- package/dist/resources/extensions/gsd/user-input-boundary.js +42 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +8 -8
- package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
- package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
- package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
- package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
- package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
- package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
- package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
- package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
- package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
- package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
- package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
- package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
- package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
- package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
- package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
- package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
- package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
- package/dist/web/standalone/node_modules/postcss/package.json +48 -48
- package/package.json +16 -11
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -34
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +12 -46
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/deps.js +10 -0
- package/scripts/link-workspace-packages.cjs +7 -40
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -8
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -2
- package/src/resources/extensions/gsd/auto/contracts.ts +8 -119
- package/src/resources/extensions/gsd/auto/orchestrator.ts +794 -58
- package/src/resources/extensions/gsd/auto-prompts.ts +114 -1
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +5 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +33 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +24 -16
- package/src/resources/extensions/gsd/auto.ts +81 -500
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
- package/src/resources/extensions/gsd/debug-logger.ts +11 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +38 -19
- package/src/resources/extensions/gsd/mcp-filter.ts +7 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +48 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
- package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +590 -855
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +19 -5
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
- package/src/resources/extensions/gsd/user-input-boundary.ts +37 -5
- package/dist/web/standalone/.next/server/chunks/678.js +0 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +0 -21
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +0 -213
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts +0 -28
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js +0 -249
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts +0 -19
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js +0 -797
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js.map +0 -1
- package/scripts/ensure-workspace-builds.cjs +0 -129
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → Qbr81pQ-pbQXP4bq2VXLv}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → Qbr81pQ-pbQXP4bq2VXLv}/_ssgManifest.js +0 -0
|
@@ -21,6 +21,14 @@ function runGit(args: string[], cwd: string): void {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function renderOutcomeWidget(widget: unknown): string {
|
|
25
|
+
const component = (widget as any)(
|
|
26
|
+
{ requestRender() {} },
|
|
27
|
+
{ fg: (_color: string, text: string) => text, bold: (text: string) => text },
|
|
28
|
+
);
|
|
29
|
+
return component.render(100).join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
test("cleanupAfterLoopExit preserves paused auto badge after provider pause", async () => {
|
|
25
33
|
const base = mkdtempSync(join(tmpdir(), "gsd-paused-cleanup-"));
|
|
26
34
|
const previousCwd = process.cwd();
|
|
@@ -167,13 +175,17 @@ test("cleanupAfterLoopExit preserves completion closeout surface after stopAuto
|
|
|
167
175
|
false,
|
|
168
176
|
"completion cleanup must not replace the closeout surface with a generic outcome card",
|
|
169
177
|
);
|
|
170
|
-
assert.equal(
|
|
178
|
+
assert.equal(
|
|
179
|
+
autoSession.completionStopInProgress,
|
|
180
|
+
true,
|
|
181
|
+
"completion closeout preservation must survive post-loop cleanup until the next agent turn",
|
|
182
|
+
);
|
|
171
183
|
} finally {
|
|
172
184
|
autoSession.reset();
|
|
173
185
|
}
|
|
174
186
|
});
|
|
175
187
|
|
|
176
|
-
test("cleanupAfterLoopExit
|
|
188
|
+
test("cleanupAfterLoopExit preserves completionStopInProgress even when preserveStepSurfaceAfterLoopExit is also set", async () => {
|
|
177
189
|
const statusCalls: unknown[] = [];
|
|
178
190
|
const widgetCalls: unknown[] = [];
|
|
179
191
|
|
|
@@ -196,8 +208,8 @@ test("cleanupAfterLoopExit clears completionStopInProgress even when preserveSte
|
|
|
196
208
|
|
|
197
209
|
assert.equal(
|
|
198
210
|
autoSession.completionStopInProgress,
|
|
199
|
-
|
|
200
|
-
"completionStopInProgress must
|
|
211
|
+
true,
|
|
212
|
+
"completionStopInProgress must survive cleanup even when preserveStepSurfaceAfterLoopExit was also set",
|
|
201
213
|
);
|
|
202
214
|
assert.equal(autoSession.preserveStepSurfaceAfterLoopExit, false);
|
|
203
215
|
} finally {
|
|
@@ -641,6 +653,10 @@ test("stopAuto foreground completion closeout reroots session and preserves the
|
|
|
641
653
|
false,
|
|
642
654
|
"foreground completion stop must not install a replacement roll-up widget over the transcript",
|
|
643
655
|
);
|
|
656
|
+
assert.ok(
|
|
657
|
+
widgetCalls.some(([key, value]) => key === "gsd-progress" && value === undefined),
|
|
658
|
+
"foreground completion stop must clear stale progress controls in the rerooted session",
|
|
659
|
+
);
|
|
644
660
|
assert.ok(
|
|
645
661
|
notifications.every(message => !message.includes("/gsd auto to resume")),
|
|
646
662
|
"completion stop notification must not tell users to resume a finished auto run",
|
|
@@ -649,10 +665,15 @@ test("stopAuto foreground completion closeout reroots session and preserves the
|
|
|
649
665
|
notifications.every(message => !message.includes("Auto-mode stopped") && !message.includes("Session:") && !message.includes("Debug log written")),
|
|
650
666
|
"completion stop must not append generic stop/session/debug notifications after the report",
|
|
651
667
|
);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
668
|
+
const outcome = widgetCalls.find(([key, value]) => key === "gsd-outcome" && typeof value === "function")?.[1];
|
|
669
|
+
assert.equal(
|
|
670
|
+
typeof outcome,
|
|
671
|
+
"function",
|
|
672
|
+
"foreground completion stop must install a durable closeout outcome in the rerooted session",
|
|
655
673
|
);
|
|
674
|
+
const rendered = renderOutcomeWidget(outcome);
|
|
675
|
+
assert.match(rendered, /Milestone M003 complete/);
|
|
676
|
+
assert.match(rendered, /Review the closeout/);
|
|
656
677
|
|
|
657
678
|
const widgetCallCountBeforePostLoopCleanup = widgetCalls.length;
|
|
658
679
|
await cleanupAfterLoopExit(ctx);
|
|
@@ -822,7 +843,7 @@ test("stopAuto closeout-transcript preservation suppresses generic stop widgets"
|
|
|
822
843
|
}
|
|
823
844
|
});
|
|
824
845
|
|
|
825
|
-
test("stopAuto foreground all-complete closeout
|
|
846
|
+
test("stopAuto foreground all-complete closeout installs a durable terminal outcome", async () => {
|
|
826
847
|
const base = mkdtempSync(join(tmpdir(), "gsd-all-complete-closeout-"));
|
|
827
848
|
const previousCwd = process.cwd();
|
|
828
849
|
const widgetCalls: Array<[string, unknown]> = [];
|
|
@@ -875,8 +896,15 @@ test("stopAuto foreground all-complete closeout preserves the transcript surface
|
|
|
875
896
|
false,
|
|
876
897
|
"foreground all-complete closeout must not replace the visible transcript with a roll-up widget",
|
|
877
898
|
);
|
|
878
|
-
const finalOutcome = widgetCalls.filter(([key]) => key === "gsd-outcome").at(-1);
|
|
879
|
-
assert.equal(
|
|
899
|
+
const finalOutcome = widgetCalls.filter(([key]) => key === "gsd-outcome").at(-1)?.[1];
|
|
900
|
+
assert.equal(
|
|
901
|
+
typeof finalOutcome,
|
|
902
|
+
"function",
|
|
903
|
+
"foreground all-complete closeout must install a durable terminal outcome",
|
|
904
|
+
);
|
|
905
|
+
const rendered = renderOutcomeWidget(finalOutcome);
|
|
906
|
+
assert.match(rendered, /All milestones complete/);
|
|
907
|
+
assert.match(rendered, /start new work/);
|
|
880
908
|
} finally {
|
|
881
909
|
try { closeDatabase(); } catch { /* noop */ }
|
|
882
910
|
autoSession.reset();
|
|
@@ -115,6 +115,21 @@ test('debugCount increments counters', () => {
|
|
|
115
115
|
assert.strictEqual(summary.dispatches, 5);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
test('debugCount tracks gitInvocations and writeDebugSummary reports it', () => {
|
|
119
|
+
const tmp = createTempGsdDir();
|
|
120
|
+
enableDebug(tmp);
|
|
121
|
+
|
|
122
|
+
debugCount('gitInvocations');
|
|
123
|
+
debugCount('gitInvocations', 13);
|
|
124
|
+
|
|
125
|
+
const logPath = writeDebugSummary()!;
|
|
126
|
+
const lines = readLogLines(logPath);
|
|
127
|
+
|
|
128
|
+
const summary = lines.find(l => l.event === 'debug-summary') as any;
|
|
129
|
+
assert.ok(summary, 'should have debug-summary event');
|
|
130
|
+
assert.strictEqual(summary.gitInvocations, 14);
|
|
131
|
+
});
|
|
132
|
+
|
|
118
133
|
test('debugPeak tracks max values', () => {
|
|
119
134
|
const tmp = createTempGsdDir();
|
|
120
135
|
enableDebug(tmp);
|
|
@@ -6,7 +6,8 @@ import { join } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
|
|
9
|
-
import { openDatabase, closeDatabase, getAllMilestones } from "../gsd-db.ts";
|
|
9
|
+
import { openDatabase, closeDatabase, getAllMilestones, getArtifact } from "../gsd-db.ts";
|
|
10
|
+
import { parseProject } from "../schemas/parsers.ts";
|
|
10
11
|
import { markApprovalGateVerified, clearDiscussionFlowState } from "../bootstrap/write-gate.ts";
|
|
11
12
|
import { executeSummarySave } from "../tools/workflow-tool-executors.ts";
|
|
12
13
|
|
|
@@ -107,3 +108,65 @@ test("executeSummarySave registers milestones when PROJECT.md uses canonical em-
|
|
|
107
108
|
assert.deepEqual(result.details.registeredMilestones, ["M001", "M002"]);
|
|
108
109
|
assert.equal(getAllMilestones().length, 2);
|
|
109
110
|
});
|
|
111
|
+
|
|
112
|
+
test("executeSummarySave self-heals the Milestone Sequence when DB already has milestones but content parses zero", async (t) => {
|
|
113
|
+
const base = setupBase(t);
|
|
114
|
+
|
|
115
|
+
// 1) First save registers M001 as complete; DB now holds the milestone with
|
|
116
|
+
// authoritative status.
|
|
117
|
+
const canonical = [
|
|
118
|
+
"# Project",
|
|
119
|
+
"",
|
|
120
|
+
"## Milestone Sequence",
|
|
121
|
+
"",
|
|
122
|
+
"<!-- Check off milestones as they complete. -->",
|
|
123
|
+
"",
|
|
124
|
+
"- [x] M001: Foo — bar",
|
|
125
|
+
"",
|
|
126
|
+
].join("\n");
|
|
127
|
+
await inProjectDir(base, () => executeSummarySave({ artifact_type: "PROJECT", content: canonical }, base));
|
|
128
|
+
assert.equal(getAllMilestones().length, 1);
|
|
129
|
+
|
|
130
|
+
// 2) A completion re-save reflows the sequence with an en-dash separator the
|
|
131
|
+
// canonical parser rejects → zero parseable lines, but the DB is intact.
|
|
132
|
+
// (A trailing non-bullet section prevents MILESTONE_LINE_RE from bridging
|
|
133
|
+
// onto a later bullet.) The checkbox here is deliberately WRONG (unchecked)
|
|
134
|
+
// to prove the rebuild takes status from the DB, not the malformed content.
|
|
135
|
+
const malformed = [
|
|
136
|
+
"# Project",
|
|
137
|
+
"",
|
|
138
|
+
"## Milestone Sequence",
|
|
139
|
+
"",
|
|
140
|
+
"<!-- Check off milestones as they complete. -->",
|
|
141
|
+
"",
|
|
142
|
+
"- [ ] M001: Foo – shipped bar", // en-dash U+2013, not accepted by MILESTONE_LINE_RE
|
|
143
|
+
"",
|
|
144
|
+
"## Notes",
|
|
145
|
+
"",
|
|
146
|
+
"Trailing prose with no list bullets.",
|
|
147
|
+
"",
|
|
148
|
+
].join("\n");
|
|
149
|
+
|
|
150
|
+
const result = await inProjectDir(base, () => executeSummarySave({ artifact_type: "PROJECT", content: malformed }, base));
|
|
151
|
+
|
|
152
|
+
// Non-fatal: the save succeeds and reports the self-heal.
|
|
153
|
+
assert.notEqual(result.isError, true);
|
|
154
|
+
assert.equal(result.details.milestoneSequenceSelfHealed, true);
|
|
155
|
+
assert.deepEqual(result.details.registeredMilestones, ["M001"]);
|
|
156
|
+
assert.equal(getAllMilestones().length, 1);
|
|
157
|
+
|
|
158
|
+
// The persisted PROJECT.md now parses canonically and preserves status + prose.
|
|
159
|
+
const persisted = getArtifact("PROJECT.md");
|
|
160
|
+
assert.ok(persisted);
|
|
161
|
+
const parsed = parseProject(persisted!.full_content);
|
|
162
|
+
assert.equal(parsed.milestones.length, 1);
|
|
163
|
+
const m001 = parsed.milestones.find(m => m.id === "M001");
|
|
164
|
+
assert.ok(m001);
|
|
165
|
+
// Status comes from the DB (M001 complete), not the unchecked box in the
|
|
166
|
+
// malformed content.
|
|
167
|
+
assert.equal(m001!.done, true);
|
|
168
|
+
assert.equal(m001!.title, "Foo");
|
|
169
|
+
assert.match(m001!.oneLiner, /shipped bar/);
|
|
170
|
+
// The HTML comment hint survives the rebuild.
|
|
171
|
+
assert.match(persisted!.full_content, /Check off milestones as they complete/);
|
|
172
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Integration regression for #549: merge_strategy: merge is not respected.
|
|
2
|
+
//
|
|
3
|
+
// mergeMilestoneToMain always called nativeMergeSquash regardless of the
|
|
4
|
+
// git.merge_strategy preference. When a user set `merge_strategy: merge`
|
|
5
|
+
// (to respect a global `merge.ff = false`), the squash path still ran.
|
|
6
|
+
//
|
|
7
|
+
// Fix (#549): an `effectiveStrategy` variable selects nativeMergeRegular
|
|
8
|
+
// (--no-ff --no-commit) when merge_strategy === "merge", leaving MERGE_HEAD
|
|
9
|
+
// so nativeCommit produces a real merge commit with two parents.
|
|
10
|
+
//
|
|
11
|
+
// These tests verify:
|
|
12
|
+
// 1. merge_strategy: merge → merge commit topology (two parents on main).
|
|
13
|
+
// 2. No merge_strategy set → squash commit topology (one parent on main).
|
|
14
|
+
// 3. Error messages reference the correct strategy label ("Merge" vs
|
|
15
|
+
// "Squash merge") so users are not misled when a conflict or dirty-tree
|
|
16
|
+
// error occurs under the merge strategy.
|
|
17
|
+
|
|
18
|
+
import { describe, test, afterEach } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
mkdtempSync,
|
|
24
|
+
realpathSync,
|
|
25
|
+
rmSync,
|
|
26
|
+
writeFileSync,
|
|
27
|
+
} from "node:fs";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
import { tmpdir } from "node:os";
|
|
30
|
+
import { execSync } from "node:child_process";
|
|
31
|
+
|
|
32
|
+
import { createAutoWorktree, mergeMilestoneToMain } from "../../auto-worktree.ts";
|
|
33
|
+
import { closeDatabase } from "../../gsd-db.ts";
|
|
34
|
+
import { getSliceBranchName } from "../../worktree.ts";
|
|
35
|
+
|
|
36
|
+
function run(cmd: string, cwd: string): string {
|
|
37
|
+
// Safe: all inputs are hardcoded test strings, not user input
|
|
38
|
+
return execSync(cmd, {
|
|
39
|
+
cwd,
|
|
40
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41
|
+
encoding: "utf-8",
|
|
42
|
+
}).trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createTempRepo(): string {
|
|
46
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "merge-strategy-test-")));
|
|
47
|
+
run("git init", dir);
|
|
48
|
+
run("git config user.email test@test.com", dir);
|
|
49
|
+
run("git config user.name Test", dir);
|
|
50
|
+
run("git config core.autocrlf false", dir);
|
|
51
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
52
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
53
|
+
writeFileSync(join(dir, ".gsd", "STATE.md"), "# State\n");
|
|
54
|
+
run("git add .", dir);
|
|
55
|
+
run("git commit -m init", dir);
|
|
56
|
+
run("git branch -M main", dir);
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeRoadmap(milestoneId: string, title: string): string {
|
|
61
|
+
return `# ${milestoneId}: ${title}\n\n## Slices\n- [x] **S01: Feature**\n`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function addSliceToMilestone(
|
|
65
|
+
repo: string,
|
|
66
|
+
wtPath: string,
|
|
67
|
+
milestoneId: string,
|
|
68
|
+
sliceId: string,
|
|
69
|
+
sliceTitle: string,
|
|
70
|
+
fileName: string,
|
|
71
|
+
): void {
|
|
72
|
+
const normalizedPath = wtPath.replaceAll("\\", "/");
|
|
73
|
+
const marker = "/.gsd/worktrees/";
|
|
74
|
+
const idx = normalizedPath.indexOf(marker);
|
|
75
|
+
const worktreeName =
|
|
76
|
+
idx !== -1 ? normalizedPath.slice(idx + marker.length).split("/")[0] : null;
|
|
77
|
+
|
|
78
|
+
const sliceBranch = getSliceBranchName(milestoneId, sliceId, worktreeName);
|
|
79
|
+
|
|
80
|
+
run(`git checkout -b ${sliceBranch}`, wtPath);
|
|
81
|
+
writeFileSync(join(wtPath, fileName), `export const feature = true;\n`);
|
|
82
|
+
run("git add .", wtPath);
|
|
83
|
+
run(`git commit -m "feat: ${sliceTitle}"`, wtPath);
|
|
84
|
+
run(`git checkout milestone/${milestoneId}`, wtPath);
|
|
85
|
+
run(
|
|
86
|
+
`git merge --no-ff ${sliceBranch} -m "feat(${milestoneId}/${sliceId}): ${sliceTitle}"`,
|
|
87
|
+
wtPath,
|
|
88
|
+
);
|
|
89
|
+
run(`git branch -d ${sliceBranch}`, wtPath);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Number of parents of the most recent commit on a branch. */
|
|
93
|
+
function parentCount(repo: string, branch: string): number {
|
|
94
|
+
const parents = run(`git log -1 --format=%P ${branch}`, repo);
|
|
95
|
+
return parents.trim() === "" ? 0 : parents.trim().split(/\s+/).length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
describe("mergeMilestoneToMain merge_strategy dispatch (#549)", { timeout: 300_000 }, () => {
|
|
99
|
+
const savedCwd = process.cwd();
|
|
100
|
+
const tempDirs: string[] = [];
|
|
101
|
+
|
|
102
|
+
function freshRepo(): string {
|
|
103
|
+
const d = createTempRepo();
|
|
104
|
+
tempDirs.push(d);
|
|
105
|
+
return d;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
process.chdir(savedCwd);
|
|
110
|
+
closeDatabase();
|
|
111
|
+
for (const d of tempDirs) {
|
|
112
|
+
if (existsSync(d)) rmSync(d, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
tempDirs.length = 0;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("#549: merge_strategy: merge produces a true merge commit with two parents", () => {
|
|
118
|
+
const repo = freshRepo();
|
|
119
|
+
|
|
120
|
+
// Write project preferences with merge_strategy: merge
|
|
121
|
+
writeFileSync(
|
|
122
|
+
join(repo, ".gsd", "PREFERENCES.md"),
|
|
123
|
+
"---\nversion: 1\ngit:\n merge_strategy: merge\n---\n",
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const wtPath = createAutoWorktree(repo, "M549");
|
|
127
|
+
addSliceToMilestone(repo, wtPath, "M549", "S01", "Feature A", "feature-a.ts");
|
|
128
|
+
|
|
129
|
+
const roadmap = makeRoadmap("M549", "Merge strategy milestone");
|
|
130
|
+
mergeMilestoneToMain(repo, "M549", roadmap);
|
|
131
|
+
|
|
132
|
+
assert.equal(
|
|
133
|
+
parentCount(repo, "main"),
|
|
134
|
+
2,
|
|
135
|
+
"merge_strategy: merge must produce a true merge commit with 2 parents on main",
|
|
136
|
+
);
|
|
137
|
+
assert.ok(existsSync(join(repo, "feature-a.ts")), "feature-a.ts present on main");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("default (no merge_strategy): squash commit has one parent on main", () => {
|
|
141
|
+
const repo = freshRepo();
|
|
142
|
+
// No PREFERENCES.md — default squash path
|
|
143
|
+
|
|
144
|
+
const wtPath = createAutoWorktree(repo, "M550");
|
|
145
|
+
addSliceToMilestone(repo, wtPath, "M550", "S01", "Feature B", "feature-b.ts");
|
|
146
|
+
|
|
147
|
+
const roadmap = makeRoadmap("M550", "Squash strategy milestone");
|
|
148
|
+
mergeMilestoneToMain(repo, "M550", roadmap);
|
|
149
|
+
|
|
150
|
+
assert.equal(
|
|
151
|
+
parentCount(repo, "main"),
|
|
152
|
+
1,
|
|
153
|
+
"default (squash) must produce a single-parent commit on main",
|
|
154
|
+
);
|
|
155
|
+
assert.ok(existsSync(join(repo, "feature-b.ts")), "feature-b.ts present on main");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Performance-contract test for the detectStaleRenders file-identity
|
|
3
|
+
// parse cache (#442 Phase 1.5). Asserts the observable contract: an unchanged
|
|
4
|
+
// projection is NOT re-parsed on the next dispatch (cache hit), while a changed
|
|
5
|
+
// projection IS re-parsed (cache miss on path+mtime+size). Modeled on the
|
|
6
|
+
// prepared-statement caching contract test in db-adapter.test.ts — it asserts
|
|
7
|
+
// external behavior (parse happened / did not), never internal wiring.
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice } from "../gsd-db.ts";
|
|
16
|
+
import { clearParseCache } from "../files.ts";
|
|
17
|
+
import { clearPathCache } from "../paths.ts";
|
|
18
|
+
import { invalidateStateCache } from "../state.ts";
|
|
19
|
+
import { detectStaleRenders } from "../markdown-renderer.ts";
|
|
20
|
+
import { enableDebug, disableDebug, getDebugCounters } from "../debug-logger.ts";
|
|
21
|
+
|
|
22
|
+
function roadmapMd(slices: Array<{ id: string; title: string; done: boolean }>): string {
|
|
23
|
+
const lines = ["# M001 Roadmap", "", "**Vision:** cache contract", "", "## Slices", ""];
|
|
24
|
+
for (const s of slices) lines.push(`- ${s.done ? "[x]" : "[ ]"} **${s.id}: ${s.title}** \`risk:medium\` \`depends:[]\``);
|
|
25
|
+
lines.push("");
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("#442: detectStaleRenders re-parses ROADMAP only when it changes on disk", (t) => {
|
|
30
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-parse-cache-"));
|
|
31
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
32
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
33
|
+
const roadmapPath = join(milestoneDir, "M001-ROADMAP.md");
|
|
34
|
+
t.after(() => {
|
|
35
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
36
|
+
disableDebug();
|
|
37
|
+
rmSync(base, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
41
|
+
insertMilestone({ id: "M001", title: "Cache", status: "active" });
|
|
42
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "medium", depends: [], sequence: 1 });
|
|
43
|
+
|
|
44
|
+
// Start from a cold cache so counts are attributable to this test.
|
|
45
|
+
clearParseCache();
|
|
46
|
+
clearPathCache();
|
|
47
|
+
invalidateStateCache();
|
|
48
|
+
|
|
49
|
+
writeFileSync(roadmapPath, roadmapMd([{ id: "S01", title: "Slice", done: false }]));
|
|
50
|
+
|
|
51
|
+
enableDebug(base); // resets counters
|
|
52
|
+
|
|
53
|
+
// First dispatch: cold cache → the roadmap is read + parsed.
|
|
54
|
+
detectStaleRenders(base);
|
|
55
|
+
const afterCold = getDebugCounters().parseRoadmapCalls;
|
|
56
|
+
assert.ok(afterCold > 0, "cold dispatch must parse the roadmap");
|
|
57
|
+
|
|
58
|
+
// Second dispatch, file unchanged: cache hit → NO new parse.
|
|
59
|
+
detectStaleRenders(base);
|
|
60
|
+
const afterWarm = getDebugCounters().parseRoadmapCalls;
|
|
61
|
+
assert.equal(afterWarm, afterCold, "unchanged projection must not be re-parsed");
|
|
62
|
+
|
|
63
|
+
// Change the file (different content AND size) WITHOUT clearing caches:
|
|
64
|
+
// the path+mtime+size identity changes, forcing a re-parse.
|
|
65
|
+
writeFileSync(
|
|
66
|
+
roadmapPath,
|
|
67
|
+
roadmapMd([
|
|
68
|
+
{ id: "S01", title: "Slice", done: true },
|
|
69
|
+
{ id: "S02", title: "Added Slice", done: false },
|
|
70
|
+
]),
|
|
71
|
+
);
|
|
72
|
+
detectStaleRenders(base);
|
|
73
|
+
const afterChange = getDebugCounters().parseRoadmapCalls;
|
|
74
|
+
assert.ok(afterChange > afterWarm, "a changed projection must be re-parsed");
|
|
75
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Regression tests for nativeMergeRegular (#549 — merge_strategy: merge).
|
|
2
|
+
//
|
|
3
|
+
// nativeMergeRegular is the new `git merge --no-ff --no-commit` path called
|
|
4
|
+
// when `git.merge_strategy: merge` is configured. These tests verify:
|
|
5
|
+
// 1. Success path: returns { success: true, conflicts: [] } and leaves MERGE_HEAD
|
|
6
|
+
// so nativeCommit can supply the commit message and git records two parents.
|
|
7
|
+
// 2. Dirty-tree path: returns __dirty_working_tree__ sentinel (matching
|
|
8
|
+
// nativeMergeSquash behaviour) when uncommitted changes would be overwritten.
|
|
9
|
+
// 3. Content-conflict path: returns the conflicted file names (not the dirty-tree
|
|
10
|
+
// sentinel) when both branches modified the same region of a file.
|
|
11
|
+
|
|
12
|
+
import { describe, test, afterEach } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
rmSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
} from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
23
|
+
|
|
24
|
+
import { nativeMergeRegular } from "../native-git-bridge.ts";
|
|
25
|
+
|
|
26
|
+
function git(cwd: string, ...args: string[]): string {
|
|
27
|
+
return execFileSync("git", args, {
|
|
28
|
+
cwd,
|
|
29
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
30
|
+
encoding: "utf-8",
|
|
31
|
+
}).trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeRepo(prefix: string): string {
|
|
35
|
+
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
36
|
+
git(dir, "init");
|
|
37
|
+
git(dir, "config", "user.email", "test@test.com");
|
|
38
|
+
git(dir, "config", "user.name", "Test");
|
|
39
|
+
git(dir, "config", "core.autocrlf", "false");
|
|
40
|
+
writeFileSync(join(dir, "README.md"), "# init\n");
|
|
41
|
+
git(dir, "add", "-A");
|
|
42
|
+
git(dir, "commit", "-m", "init");
|
|
43
|
+
git(dir, "branch", "-M", "main");
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("nativeMergeRegular (#549)", () => {
|
|
48
|
+
const tempDirs: string[] = [];
|
|
49
|
+
|
|
50
|
+
function freshRepo(prefix: string = "nmr-"): string {
|
|
51
|
+
const d = makeRepo(prefix);
|
|
52
|
+
tempDirs.push(d);
|
|
53
|
+
return d;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
for (const d of tempDirs) {
|
|
58
|
+
try {
|
|
59
|
+
git(d, "merge", "--abort");
|
|
60
|
+
} catch {
|
|
61
|
+
// no merge in progress — that is fine
|
|
62
|
+
}
|
|
63
|
+
if (existsSync(d)) rmSync(d, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
tempDirs.length = 0;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("success: returns { success: true, conflicts: [] } and sets MERGE_HEAD", () => {
|
|
69
|
+
const repo = freshRepo("nmr-success-");
|
|
70
|
+
git(repo, "checkout", "-b", "feature");
|
|
71
|
+
writeFileSync(join(repo, "feature.ts"), "export const a = 1;\n");
|
|
72
|
+
git(repo, "add", "feature.ts");
|
|
73
|
+
git(repo, "commit", "-m", "add feature");
|
|
74
|
+
git(repo, "checkout", "main");
|
|
75
|
+
|
|
76
|
+
const result = nativeMergeRegular(repo, "feature");
|
|
77
|
+
|
|
78
|
+
assert.equal(result.success, true);
|
|
79
|
+
assert.deepEqual(result.conflicts, []);
|
|
80
|
+
// --no-commit leaves MERGE_HEAD; nativeCommit sees it and creates two parents.
|
|
81
|
+
assert.ok(
|
|
82
|
+
existsSync(join(repo, ".git", "MERGE_HEAD")),
|
|
83
|
+
"MERGE_HEAD must exist after --no-ff --no-commit so the caller commits a true merge",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("dirty working tree: returns __dirty_working_tree__ sentinel", () => {
|
|
88
|
+
const repo = freshRepo("nmr-dirty-");
|
|
89
|
+
|
|
90
|
+
// feature branch introduces feature.ts
|
|
91
|
+
git(repo, "checkout", "-b", "feature");
|
|
92
|
+
writeFileSync(join(repo, "feature.ts"), "export const x = 1;\n");
|
|
93
|
+
git(repo, "add", "feature.ts");
|
|
94
|
+
git(repo, "commit", "-m", "add feature");
|
|
95
|
+
git(repo, "checkout", "main");
|
|
96
|
+
|
|
97
|
+
// dirty uncommitted local file that the merge would overwrite
|
|
98
|
+
writeFileSync(join(repo, "feature.ts"), "// local dirty version\n");
|
|
99
|
+
|
|
100
|
+
const result = nativeMergeRegular(repo, "feature");
|
|
101
|
+
|
|
102
|
+
assert.equal(result.success, false);
|
|
103
|
+
assert.ok(
|
|
104
|
+
result.conflicts.includes("__dirty_working_tree__"),
|
|
105
|
+
`expected __dirty_working_tree__ sentinel, got: ${JSON.stringify(result.conflicts)}`,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("content conflict: returns conflicted file names, not dirty-tree sentinel", () => {
|
|
110
|
+
const repo = freshRepo("nmr-conflict-");
|
|
111
|
+
|
|
112
|
+
// Diverging history: both main and feature modify the same file region.
|
|
113
|
+
writeFileSync(join(repo, "shared.ts"), "// base\nexport const v = 0;\n");
|
|
114
|
+
git(repo, "add", "shared.ts");
|
|
115
|
+
git(repo, "commit", "-m", "base version");
|
|
116
|
+
|
|
117
|
+
git(repo, "checkout", "-b", "feature");
|
|
118
|
+
writeFileSync(join(repo, "shared.ts"), "// feature version\nexport const v = 2;\n");
|
|
119
|
+
git(repo, "add", "shared.ts");
|
|
120
|
+
git(repo, "commit", "-m", "feature changes shared");
|
|
121
|
+
|
|
122
|
+
git(repo, "checkout", "main");
|
|
123
|
+
writeFileSync(join(repo, "shared.ts"), "// main version\nexport const v = 1;\n");
|
|
124
|
+
git(repo, "add", "shared.ts");
|
|
125
|
+
git(repo, "commit", "-m", "main changes shared");
|
|
126
|
+
|
|
127
|
+
const result = nativeMergeRegular(repo, "feature");
|
|
128
|
+
|
|
129
|
+
assert.equal(result.success, false);
|
|
130
|
+
assert.ok(
|
|
131
|
+
result.conflicts.includes("shared.ts"),
|
|
132
|
+
`expected shared.ts in conflicts, got: ${JSON.stringify(result.conflicts)}`,
|
|
133
|
+
);
|
|
134
|
+
assert.ok(
|
|
135
|
+
!result.conflicts.includes("__dirty_working_tree__"),
|
|
136
|
+
"content conflict must not be misclassified as dirty-tree",
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
});
|