gsd-pi 2.80.0-dev.cf9433f56 → 2.80.0-dev.d4fc28e6b
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.js +0 -19
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +29 -0
- package/dist/resources/extensions/gsd/auto/loop.js +71 -8
- package/dist/resources/extensions/gsd/auto/phases.js +150 -94
- package/dist/resources/extensions/gsd/auto/resolve.js +12 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -30
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +33 -1
- package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +9 -1
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
- package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
- package/dist/resources/extensions/gsd/auto-start.js +197 -6
- package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +18 -22
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +86 -19
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
- package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +298 -54
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +53 -0
- package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
- package/dist/resources/extensions/gsd/guided-flow.js +47 -28
- package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
- package/dist/resources/extensions/gsd/pre-execution-checks.js +15 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +35 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/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 +12 -12
- 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/welcome-screen.d.ts +2 -0
- package/dist/welcome-screen.js +9 -7
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +2 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
- package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
- package/packages/pi-agent-core/dist/token-audit.js +221 -0
- package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
- package/packages/pi-agent-core/dist/types.d.ts +9 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-agent-core/src/agent.ts +8 -0
- package/packages/pi-agent-core/src/index.ts +2 -0
- package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
- package/packages/pi-agent-core/src/token-audit.ts +287 -0
- package/packages/pi-agent-core/src/types.ts +14 -0
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +36 -7
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -6
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +32 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +40 -7
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
- package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
- package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +30 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +26 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/loop.ts +84 -8
- package/src/resources/extensions/gsd/auto/phases.ts +218 -154
- package/src/resources/extensions/gsd/auto/resolve.ts +19 -0
- package/src/resources/extensions/gsd/auto/run-unit.ts +10 -29
- package/src/resources/extensions/gsd/auto/session.ts +8 -0
- package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +63 -1
- package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +14 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
- package/src/resources/extensions/gsd/auto-start.ts +230 -9
- package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
- package/src/resources/extensions/gsd/auto.ts +18 -18
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +100 -18
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
- package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +347 -54
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +66 -0
- package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
- package/src/resources/extensions/gsd/guided-flow.ts +52 -35
- package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
- package/src/resources/extensions/gsd/pre-execution-checks.ts +16 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +361 -10
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
- package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
- package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +166 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +104 -3
- package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +49 -4
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
- /package/dist/web/standalone/.next/static/{-5nHJWzSdG-WkPMul_khA → cWaxzf-sdbSSbbwYu8q7a}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-5nHJWzSdG-WkPMul_khA → cWaxzf-sdbSSbbwYu8q7a}/_ssgManifest.js +0 -0
|
@@ -131,9 +131,11 @@ test("postflightPopStash — restores stashed changes and emits info notificatio
|
|
|
131
131
|
run('git commit -m "simulate merge"', repo);
|
|
132
132
|
|
|
133
133
|
const postNotifications: Array<{ msg: string; level: string }> = [];
|
|
134
|
-
postflightPopStash(repo, "M004", preflight.stashMarker, (msg, level) => {
|
|
134
|
+
const postflight = postflightPopStash(repo, "M004", preflight.stashMarker, (msg, level) => {
|
|
135
135
|
postNotifications.push({ msg, level });
|
|
136
136
|
});
|
|
137
|
+
assert.equal(postflight.restored, true, "postflight must report successful restore");
|
|
138
|
+
assert.equal(postflight.needsManualRecovery, false, "successful restore must not need manual recovery");
|
|
137
139
|
|
|
138
140
|
// The stashed README.md change must be restored
|
|
139
141
|
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
@@ -171,7 +173,8 @@ test("preflight + merge + postflight round-trip preserves uncommitted changes",
|
|
|
171
173
|
run('git commit -m "feat: add feature"', repo);
|
|
172
174
|
|
|
173
175
|
// Postflight: pop stash
|
|
174
|
-
postflightPopStash(repo, "M005", preflight.stashMarker, () => {});
|
|
176
|
+
const postflight = postflightPopStash(repo, "M005", preflight.stashMarker, () => {});
|
|
177
|
+
assert.equal(postflight.needsManualRecovery, false, "clean restore must not stop auto-mode");
|
|
175
178
|
|
|
176
179
|
// README.md must still have our local content
|
|
177
180
|
const restored = readFileSync(join(repo, "README.md"), "utf-8");
|
|
@@ -197,9 +200,12 @@ test("postflightPopStash conflict warning names the exact stash ref", () => {
|
|
|
197
200
|
run('git commit -m "simulate conflicting merge"', repo);
|
|
198
201
|
|
|
199
202
|
const notifications: Array<{ msg: string; level: string }> = [];
|
|
200
|
-
postflightPopStash(repo, "M005C", preflight.stashMarker, (msg, level) => {
|
|
203
|
+
const postflight = postflightPopStash(repo, "M005C", preflight.stashMarker, (msg, level) => {
|
|
201
204
|
notifications.push({ msg, level });
|
|
202
205
|
});
|
|
206
|
+
assert.equal(postflight.restored, false, "conflicted restore must report restored=false");
|
|
207
|
+
assert.equal(postflight.needsManualRecovery, true, "conflicted restore must require manual recovery");
|
|
208
|
+
assert.match(postflight.message, /failed after merge of milestone M005C/);
|
|
203
209
|
|
|
204
210
|
const warning = notifications.find((n) => n.level === "warning")?.msg ?? "";
|
|
205
211
|
assert.match(warning, /git stash pop stash@\{\d+\}/);
|
|
@@ -219,7 +225,8 @@ test("postflightPopStash restores the matching GSD stash, not stash@{0}", () =>
|
|
|
219
225
|
writeFileSync(join(repo, "other.txt"), "other stash\n");
|
|
220
226
|
run('git stash push --include-untracked -m "unrelated newer stash"', repo);
|
|
221
227
|
|
|
222
|
-
postflightPopStash(repo, "M006", preflight.stashMarker, () => {});
|
|
228
|
+
const postflight = postflightPopStash(repo, "M006", preflight.stashMarker, () => {});
|
|
229
|
+
assert.equal(postflight.needsManualRecovery, false, "targeted restore must not need manual recovery");
|
|
223
230
|
|
|
224
231
|
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
225
232
|
assert.equal(content.replace(/\r\n/g, "\n"), "# target stash\n");
|
|
@@ -242,7 +249,8 @@ test("postflightPopStash restores the exact preflight marker when another same-m
|
|
|
242
249
|
writeFileSync(join(repo, "same-milestone.txt"), "newer same milestone stash\n");
|
|
243
250
|
run('git stash push --include-untracked -m "gsd-preflight-stash [gsd-preflight-stash:M007:other]"', repo);
|
|
244
251
|
|
|
245
|
-
postflightPopStash(repo, "M007", preflight.stashMarker, () => {});
|
|
252
|
+
const postflight = postflightPopStash(repo, "M007", preflight.stashMarker, () => {});
|
|
253
|
+
assert.equal(postflight.needsManualRecovery, false, "exact marker restore must not need manual recovery");
|
|
246
254
|
|
|
247
255
|
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
248
256
|
assert.equal(content.replace(/\r\n/g, "\n"), "# target stash\n");
|
|
@@ -260,7 +268,8 @@ test("postflightPopStash falls back to milestone marker prefix when exact marker
|
|
|
260
268
|
writeFileSync(join(repo, "README.md"), "# fallback stash\n");
|
|
261
269
|
run('git stash push --include-untracked -m "gsd-preflight-stash [gsd-preflight-stash:M008:fallback]"', repo);
|
|
262
270
|
|
|
263
|
-
postflightPopStash(repo, "M008", undefined, () => {});
|
|
271
|
+
const postflight = postflightPopStash(repo, "M008", undefined, () => {});
|
|
272
|
+
assert.equal(postflight.needsManualRecovery, false, "fallback marker restore must not need manual recovery");
|
|
264
273
|
|
|
265
274
|
const content = readFileSync(join(repo, "README.md"), "utf-8");
|
|
266
275
|
assert.equal(content.replace(/\r\n/g, "\n"), "# fallback stash\n");
|
|
@@ -261,3 +261,34 @@ test("#4780 closer prompt: uses excerpts + lists on-demand slice SUMMARY paths",
|
|
|
261
261
|
`closer prompt length ${prompt.length} should be < raw summary size ${rawSize} + 20KB headroom`,
|
|
262
262
|
);
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
test("complete-milestone prompt caps repeated inlined context around 20k chars", async (t) => {
|
|
266
|
+
const base = createBase();
|
|
267
|
+
t.after(() => cleanup(base));
|
|
268
|
+
invalidateAllCaches();
|
|
269
|
+
|
|
270
|
+
writeRoadmap(base, makeRoadmap());
|
|
271
|
+
writeSummary(base, "S01", makeFatSummary("S01"));
|
|
272
|
+
writeSummary(base, "S02", makeFatSummary("S02"));
|
|
273
|
+
writeFileSync(
|
|
274
|
+
join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
|
|
275
|
+
"# M001 Context\n\n" + "Large milestone context body. ".repeat(1200),
|
|
276
|
+
);
|
|
277
|
+
writeFileSync(
|
|
278
|
+
join(base, ".gsd", "KNOWLEDGE.md"),
|
|
279
|
+
"# Project Knowledge\n\n## Patterns\n\n### Test Milestone shared\n" + "Large scoped knowledge body. ".repeat(1200),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const prompt = await buildCompleteMilestonePrompt("M001", "Test Milestone", base);
|
|
283
|
+
const contextStart = prompt.indexOf("## Inlined Context (preloaded");
|
|
284
|
+
const contextEnd = prompt.indexOf("## Steps", contextStart);
|
|
285
|
+
assert.ok(contextStart >= 0, "prompt should include inlined context");
|
|
286
|
+
assert.ok(contextEnd > contextStart, "prompt should include steps after inlined context");
|
|
287
|
+
|
|
288
|
+
const inlinedContext = prompt.slice(contextStart, contextEnd);
|
|
289
|
+
assert.ok(
|
|
290
|
+
inlinedContext.length <= 21_000,
|
|
291
|
+
`inlined context ${inlinedContext.length} chars should stay near the 20k cap`,
|
|
292
|
+
);
|
|
293
|
+
assert.match(inlinedContext, /\[\.\.\.truncated \d+ sections\]/);
|
|
294
|
+
});
|
|
@@ -113,8 +113,9 @@ test("#4782 phase 3: buildCompleteSlicePrompt composes roadmap → plan → task
|
|
|
113
113
|
"task summaries precede slice-summary template",
|
|
114
114
|
);
|
|
115
115
|
|
|
116
|
-
// Task
|
|
117
|
-
assert.match(prompt,
|
|
116
|
+
// Task summary excerpt is inlined; full narrative remains on-demand.
|
|
117
|
+
assert.match(prompt, /### Task Summary: T01 \(excerpt\)/);
|
|
118
|
+
assert.doesNotMatch(prompt, /Task one did the thing/);
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
test("#4782 phase 3: buildCompleteSlicePrompt handles missing task summaries gracefully", async (t) => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// GSD-2 + context-store.test.ts — Regression coverage for DB-backed context query helpers.
|
|
2
|
+
|
|
1
3
|
import { describe, test, afterEach } from "node:test";
|
|
2
4
|
import assert from "node:assert/strict";
|
|
3
5
|
import {
|
|
@@ -362,7 +364,11 @@ describe("context-store: sub-5ms query timing", () => {
|
|
|
362
364
|
|
|
363
365
|
assert.strictEqual(decisions.length, 50, `got ${decisions.length} decisions (expected 50)`);
|
|
364
366
|
assert.strictEqual(requirements.length, 50, `got ${requirements.length} requirements (expected 50)`);
|
|
365
|
-
|
|
367
|
+
const maxLatencyMs = process.env.NODE_V8_COVERAGE ? 15 : 5;
|
|
368
|
+
assert.ok(
|
|
369
|
+
elapsed < maxLatencyMs,
|
|
370
|
+
`query latency ${elapsed.toFixed(2)}ms should be < ${maxLatencyMs}ms`,
|
|
371
|
+
);
|
|
366
372
|
});
|
|
367
373
|
});
|
|
368
374
|
|
|
@@ -192,7 +192,11 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
|
|
|
192
192
|
resolveMilestoneFile: () => null,
|
|
193
193
|
reconcileMergeState: () => "clean",
|
|
194
194
|
preflightCleanRoot: () => ({ stashPushed: false, summary: "" }),
|
|
195
|
-
postflightPopStash: () => {
|
|
195
|
+
postflightPopStash: () => ({
|
|
196
|
+
restored: true,
|
|
197
|
+
needsManualRecovery: false,
|
|
198
|
+
message: "restored",
|
|
199
|
+
}),
|
|
196
200
|
getLedger: () => null,
|
|
197
201
|
getProjectTotals: () => ({ cost: 0 }),
|
|
198
202
|
formatCost: (c: number) => `$${c.toFixed(2)}`,
|
|
@@ -39,6 +39,7 @@ test("execute-task prompt renders compact execution and completion gates", async
|
|
|
39
39
|
taskPlanPath: ".gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md",
|
|
40
40
|
priorTaskLines: "- None",
|
|
41
41
|
skillActivation: "Load relevant skills.",
|
|
42
|
+
inlinedTemplates: "### Output Template: Task Summary\nSource: `templates/task-summary.md`",
|
|
42
43
|
templatesDir: join(fixtureRoot, "templates"),
|
|
43
44
|
taskSummaryTemplatePath: "C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-summary.md",
|
|
44
45
|
verificationBudget: "~10K chars",
|
|
@@ -46,12 +47,14 @@ test("execute-task prompt renders compact execution and completion gates", async
|
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
assert.match(prompt, /You execute\./);
|
|
49
|
-
assert.match(prompt, /Call `memory_query
|
|
50
|
+
assert.match(prompt, /Call `memory_query`.*only when no injected memory block exists/s);
|
|
50
51
|
assert.match(prompt, /Before any `Write` that creates an artifact or output file/);
|
|
51
52
|
assert.match(prompt, /Build real behavior/);
|
|
52
53
|
assert.match(prompt, /Background process rule/);
|
|
53
54
|
assert.match(prompt, /blocker_discovered: true/);
|
|
54
|
-
assert.match(prompt, /
|
|
55
|
+
assert.match(prompt, /Use the inlined Task Summary template below/);
|
|
56
|
+
assert.match(prompt, /Read `C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-summary\.md` only if the inlined template is absent or visibly truncated/);
|
|
57
|
+
assert.match(prompt, /### Output Template: Task Summary/);
|
|
55
58
|
assert.doesNotMatch(prompt, /\{\{templatesDir\}\}\/task-summary\.md/);
|
|
56
59
|
assert.match(prompt, /Call `gsd_task_complete`/);
|
|
57
60
|
assert.match(prompt, /Do not run git commands/);
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// GSD-2 + src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts
|
|
2
|
+
// Regression: when createAutoWorktree reuses an existing milestone branch,
|
|
3
|
+
// it must be fast-forwarded onto integration so the next milestone forks
|
|
4
|
+
// from up-to-date code (#5538-followup).
|
|
5
|
+
|
|
6
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { basename, join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
fastForwardReusedMilestoneBranchIfSafe,
|
|
15
|
+
_isBranchCheckedOutElsewhere,
|
|
16
|
+
} from "../auto-worktree.js";
|
|
17
|
+
|
|
18
|
+
const NO_PROMPT_ENV = {
|
|
19
|
+
...process.env,
|
|
20
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
21
|
+
GIT_AUTHOR_NAME: "test",
|
|
22
|
+
GIT_AUTHOR_EMAIL: "test@example.com",
|
|
23
|
+
GIT_COMMITTER_NAME: "test",
|
|
24
|
+
GIT_COMMITTER_EMAIL: "test@example.com",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function git(cwd: string, ...args: string[]): string {
|
|
28
|
+
return execFileSync("git", args, {
|
|
29
|
+
cwd,
|
|
30
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
env: NO_PROMPT_ENV,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function rev(cwd: string, ref: string): string {
|
|
37
|
+
return git(cwd, "rev-parse", ref).trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("fastForwardReusedMilestoneBranchIfSafe", () => {
|
|
41
|
+
let repo: string;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
repo = mkdtempSync(join(tmpdir(), "ff-reused-branch-"));
|
|
45
|
+
git(repo, "init", "-q", "-b", "main");
|
|
46
|
+
git(repo, "config", "user.email", "test@example.com");
|
|
47
|
+
git(repo, "config", "user.name", "test");
|
|
48
|
+
writeFileSync(join(repo, "seed.txt"), "seed\n");
|
|
49
|
+
git(repo, "add", "seed.txt");
|
|
50
|
+
git(repo, "commit", "-q", "-m", "initial");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
rmSync(repo, { recursive: true, force: true });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("fast-forwards a milestone branch that is strictly behind integration (regression: stale base)", () => {
|
|
58
|
+
// Create milestone/M001 from main's initial commit, then advance main.
|
|
59
|
+
git(repo, "branch", "milestone/M001");
|
|
60
|
+
const m001Initial = rev(repo, "milestone/M001");
|
|
61
|
+
|
|
62
|
+
writeFileSync(join(repo, "seed.txt"), "advanced\n");
|
|
63
|
+
git(repo, "add", "seed.txt");
|
|
64
|
+
git(repo, "commit", "-q", "-m", "main moved forward");
|
|
65
|
+
const mainTip = rev(repo, "main");
|
|
66
|
+
|
|
67
|
+
assert.notEqual(m001Initial, mainTip, "main must be ahead before the test");
|
|
68
|
+
|
|
69
|
+
fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
|
|
70
|
+
|
|
71
|
+
assert.equal(
|
|
72
|
+
rev(repo, "milestone/M001"),
|
|
73
|
+
mainTip,
|
|
74
|
+
"milestone/M001 must be fast-forwarded to main's tip",
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("does not touch a milestone branch that has its own commits ahead", () => {
|
|
79
|
+
// Branch from main, add a unique commit, then advance main.
|
|
80
|
+
git(repo, "checkout", "-q", "-b", "milestone/M001");
|
|
81
|
+
writeFileSync(join(repo, "milestone-only.txt"), "milestone work\n");
|
|
82
|
+
git(repo, "add", "milestone-only.txt");
|
|
83
|
+
git(repo, "commit", "-q", "-m", "M001 work");
|
|
84
|
+
const milestoneTip = rev(repo, "milestone/M001");
|
|
85
|
+
|
|
86
|
+
git(repo, "checkout", "-q", "main");
|
|
87
|
+
writeFileSync(join(repo, "seed.txt"), "advanced\n");
|
|
88
|
+
git(repo, "add", "seed.txt");
|
|
89
|
+
git(repo, "commit", "-q", "-m", "main moved forward");
|
|
90
|
+
|
|
91
|
+
fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
|
|
92
|
+
|
|
93
|
+
assert.equal(
|
|
94
|
+
rev(repo, "milestone/M001"),
|
|
95
|
+
milestoneTip,
|
|
96
|
+
"diverged milestone branch must NOT be touched (would lose work)",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("is a no-op when milestone branch is already up-to-date with main", () => {
|
|
101
|
+
git(repo, "branch", "milestone/M001");
|
|
102
|
+
const before = rev(repo, "milestone/M001");
|
|
103
|
+
|
|
104
|
+
fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
|
|
105
|
+
|
|
106
|
+
assert.equal(rev(repo, "milestone/M001"), before, "ref must not move");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("does nothing when the milestone branch does not exist", () => {
|
|
110
|
+
// Should silently return — no error, no side effects.
|
|
111
|
+
assert.doesNotThrow(() =>
|
|
112
|
+
fastForwardReusedMilestoneBranchIfSafe(repo, "M999", "milestone/M999"),
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("does nothing in a non-git directory", () => {
|
|
117
|
+
const nonRepo = mkdtempSync(join(tmpdir(), "ff-not-a-repo-"));
|
|
118
|
+
try {
|
|
119
|
+
assert.doesNotThrow(() =>
|
|
120
|
+
fastForwardReusedMilestoneBranchIfSafe(nonRepo, "M001", "milestone/M001"),
|
|
121
|
+
);
|
|
122
|
+
} finally {
|
|
123
|
+
rmSync(nonRepo, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("skips fast-forward when branch is checked out in another worktree (peer-review regression)", () => {
|
|
128
|
+
// Codex peer review caught: `nativeUpdateRef` succeeds even when the
|
|
129
|
+
// branch is checked out in a linked worktree, leaving that worktree's
|
|
130
|
+
// HEAD inconsistent with its index/work tree. The fix calls
|
|
131
|
+
// `nativeWorktreeList` first and skips the FF if any worktree owns the
|
|
132
|
+
// target branch. This test sets up the exact scenario.
|
|
133
|
+
git(repo, "branch", "milestone/M001");
|
|
134
|
+
const m001Initial = rev(repo, "milestone/M001");
|
|
135
|
+
|
|
136
|
+
// Add a linked worktree that checks out milestone/M001.
|
|
137
|
+
const wtPath = join(repo, "..", `${basename(repo)}-wt`);
|
|
138
|
+
git(repo, "worktree", "add", wtPath, "milestone/M001");
|
|
139
|
+
|
|
140
|
+
// Advance main so a fast-forward would otherwise apply.
|
|
141
|
+
writeFileSync(join(repo, "seed.txt"), "advanced\n");
|
|
142
|
+
git(repo, "add", "seed.txt");
|
|
143
|
+
git(repo, "commit", "-q", "-m", "main moved forward");
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
fastForwardReusedMilestoneBranchIfSafe(repo, "M001", "milestone/M001");
|
|
147
|
+
|
|
148
|
+
assert.equal(
|
|
149
|
+
rev(repo, "milestone/M001"),
|
|
150
|
+
m001Initial,
|
|
151
|
+
"milestone/M001 must NOT move while a linked worktree has it checked out",
|
|
152
|
+
);
|
|
153
|
+
} finally {
|
|
154
|
+
git(repo, "worktree", "remove", "--force", wtPath);
|
|
155
|
+
rmSync(wtPath, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("_isBranchCheckedOutElsewhere", () => {
|
|
161
|
+
let repo: string;
|
|
162
|
+
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
repo = mkdtempSync(join(tmpdir(), "is-checked-out-"));
|
|
165
|
+
git(repo, "init", "-q", "-b", "main");
|
|
166
|
+
git(repo, "config", "user.email", "test@example.com");
|
|
167
|
+
git(repo, "config", "user.name", "test");
|
|
168
|
+
writeFileSync(join(repo, "seed.txt"), "seed\n");
|
|
169
|
+
git(repo, "add", "seed.txt");
|
|
170
|
+
git(repo, "commit", "-q", "-m", "initial");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
afterEach(() => {
|
|
174
|
+
rmSync(repo, { recursive: true, force: true });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("returns true when branch is checked out in a linked worktree", () => {
|
|
178
|
+
git(repo, "branch", "milestone/M001");
|
|
179
|
+
const wtPath = join(repo, "..", `${basename(repo)}-wt`);
|
|
180
|
+
git(repo, "worktree", "add", wtPath, "milestone/M001");
|
|
181
|
+
try {
|
|
182
|
+
assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M001"), true);
|
|
183
|
+
} finally {
|
|
184
|
+
git(repo, "worktree", "remove", "--force", wtPath);
|
|
185
|
+
rmSync(wtPath, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("returns true when branch is checked out in the main worktree itself", () => {
|
|
190
|
+
// The default checkout. `git worktree list --porcelain` reports the
|
|
191
|
+
// primary worktree too, so a branch checked out there counts as
|
|
192
|
+
// "checked out elsewhere" relative to a fresh ref update intent.
|
|
193
|
+
git(repo, "checkout", "-q", "-b", "milestone/M002");
|
|
194
|
+
assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M002"), true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("returns false when branch exists but is not checked out anywhere", () => {
|
|
198
|
+
git(repo, "branch", "milestone/M003");
|
|
199
|
+
assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M003"), false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("returns false for an unknown branch in a clean repo", () => {
|
|
203
|
+
assert.equal(_isBranchCheckedOutElsewhere(repo, "milestone/M999"), false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("returns false on a non-git directory (empty worktree list)", () => {
|
|
207
|
+
// nativeWorktreeList does not throw on a non-repo — it returns []. The
|
|
208
|
+
// parent function `fastForwardReusedMilestoneBranchIfSafe` never reaches
|
|
209
|
+
// this code path on a non-repo because `nativeBranchExists` short-circuits
|
|
210
|
+
// earlier. Documenting actual behavior so future readers don't expect a
|
|
211
|
+
// fail-safe `true` here.
|
|
212
|
+
const nonRepo = mkdtempSync(join(tmpdir(), "is-checked-out-not-repo-"));
|
|
213
|
+
try {
|
|
214
|
+
assert.equal(_isBranchCheckedOutElsewhere(nonRepo, "milestone/M001"), false);
|
|
215
|
+
} finally {
|
|
216
|
+
rmSync(nonRepo, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// GSD-2 + src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts
|
|
2
|
+
// Regression: a thrown error from `_mergeBranchMode` (made fail-loud in
|
|
3
|
+
// commit 68ef58a3c) must be caught at the survivor-finalize call site so
|
|
4
|
+
// bootstrap surfaces an error notify instead of an unhandled exception
|
|
5
|
+
// propagating to the slash-command caller (#5549 post-merge audit, R2).
|
|
6
|
+
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
|
|
10
|
+
import { _finalizeSurvivorBranch } from "../auto-start.js";
|
|
11
|
+
import type { WorktreeResolver } from "../worktree-resolver.js";
|
|
12
|
+
|
|
13
|
+
interface FakeResolverState {
|
|
14
|
+
mergeCalls: Array<{ milestoneId: string }>;
|
|
15
|
+
shouldThrow?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fakeResolver(state: FakeResolverState): WorktreeResolver {
|
|
19
|
+
return {
|
|
20
|
+
mergeAndExit: (milestoneId: string) => {
|
|
21
|
+
state.mergeCalls.push({ milestoneId });
|
|
22
|
+
if (state.shouldThrow) throw state.shouldThrow;
|
|
23
|
+
},
|
|
24
|
+
} as unknown as WorktreeResolver;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FakeUiState {
|
|
28
|
+
notifications: Array<{ message: string; level: string }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fakeUi(state: FakeUiState): {
|
|
32
|
+
notify: (msg: string, level?: "info" | "warning" | "error" | "success") => void;
|
|
33
|
+
} {
|
|
34
|
+
return {
|
|
35
|
+
notify: (message: string, level?: "info" | "warning" | "error" | "success") => {
|
|
36
|
+
state.notifications.push({ message, level: level ?? "info" });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("happy path: survivor merge runs, returns merged:true, info notify announces the merge", () => {
|
|
42
|
+
const resolverState: FakeResolverState = { mergeCalls: [] };
|
|
43
|
+
const uiState: FakeUiState = { notifications: [] };
|
|
44
|
+
const result = _finalizeSurvivorBranch(
|
|
45
|
+
fakeResolver(resolverState),
|
|
46
|
+
"M001",
|
|
47
|
+
fakeUi(uiState),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(result, { merged: true });
|
|
51
|
+
assert.deepEqual(resolverState.mergeCalls, [{ milestoneId: "M001" }]);
|
|
52
|
+
assert.equal(uiState.notifications.length, 1);
|
|
53
|
+
assert.equal(uiState.notifications[0].level, "info");
|
|
54
|
+
assert.match(
|
|
55
|
+
uiState.notifications[0].message,
|
|
56
|
+
/Milestone M001 is complete but branch\/worktree was not finalized/,
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("regression: thrown error from mergeAndExit (e.g. wrong-branch) is caught and surfaced as error notify", () => {
|
|
61
|
+
// Pre-PR-5549 commit 5: `_mergeBranchMode` returned false silently.
|
|
62
|
+
// Post-commit 5: it throws. Without this fix, the throw at auto-start.ts
|
|
63
|
+
// line ~810 would bubble through `bootstrapAutoSession` to
|
|
64
|
+
// `startAutoDetached`'s top-level .catch as an unhandled-exception log
|
|
65
|
+
// — observable as a stack trace instead of a clean failure notification.
|
|
66
|
+
const boom = new Error("dirty working tree blocks checkout");
|
|
67
|
+
const resolverState: FakeResolverState = { mergeCalls: [], shouldThrow: boom };
|
|
68
|
+
const uiState: FakeUiState = { notifications: [] };
|
|
69
|
+
|
|
70
|
+
// Must not throw.
|
|
71
|
+
const result = _finalizeSurvivorBranch(
|
|
72
|
+
fakeResolver(resolverState),
|
|
73
|
+
"M001",
|
|
74
|
+
fakeUi(uiState),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
assert.equal(result.merged, false);
|
|
78
|
+
assert.equal(result.error, boom);
|
|
79
|
+
|
|
80
|
+
// Two notifies: info (announce) + error (the failure detail).
|
|
81
|
+
assert.equal(uiState.notifications.length, 2);
|
|
82
|
+
assert.equal(uiState.notifications[0].level, "info");
|
|
83
|
+
assert.equal(uiState.notifications[1].level, "error");
|
|
84
|
+
assert.match(
|
|
85
|
+
uiState.notifications[1].message,
|
|
86
|
+
/Survivor-branch finalization for M001 failed/,
|
|
87
|
+
);
|
|
88
|
+
assert.match(uiState.notifications[1].message, /dirty working tree blocks checkout/);
|
|
89
|
+
assert.match(uiState.notifications[1].message, /Resolve manually/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("non-Error thrown values are stringified into the user-facing message", () => {
|
|
93
|
+
const resolverState: FakeResolverState = {
|
|
94
|
+
mergeCalls: [],
|
|
95
|
+
shouldThrow: "git lock contention",
|
|
96
|
+
};
|
|
97
|
+
const uiState: FakeUiState = { notifications: [] };
|
|
98
|
+
|
|
99
|
+
const result = _finalizeSurvivorBranch(
|
|
100
|
+
fakeResolver(resolverState),
|
|
101
|
+
"M001",
|
|
102
|
+
fakeUi(uiState),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
assert.equal(result.merged, false);
|
|
106
|
+
assert.equal(result.error, resolverState.shouldThrow);
|
|
107
|
+
assert.equal(uiState.notifications[1].level, "error");
|
|
108
|
+
assert.match(uiState.notifications[1].message, /git lock contention/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("inner notifications from mergeAndExit's NotifyCtx reach the same UI", () => {
|
|
112
|
+
// The resolver's NotifyCtx must be wired to ui.notify so messages emitted
|
|
113
|
+
// inside mergeAndExit (e.g. "Milestone Mxxx merged. Pushed to remote.")
|
|
114
|
+
// appear in the same UI stream as the outer notifies.
|
|
115
|
+
const uiState: FakeUiState = { notifications: [] };
|
|
116
|
+
const ui = fakeUi(uiState);
|
|
117
|
+
|
|
118
|
+
const resolver = {
|
|
119
|
+
mergeAndExit: (_milestoneId: string, ctx: { notify: (msg: string, level?: "info" | "warning" | "error" | "success") => void }) => {
|
|
120
|
+
ctx.notify("Milestone M001 merged.", "success");
|
|
121
|
+
},
|
|
122
|
+
} as unknown as WorktreeResolver;
|
|
123
|
+
|
|
124
|
+
const result = _finalizeSurvivorBranch(resolver, "M001", ui);
|
|
125
|
+
|
|
126
|
+
assert.equal(result.merged, true);
|
|
127
|
+
// 1: outer announce
|
|
128
|
+
// 2: inner success
|
|
129
|
+
assert.equal(uiState.notifications.length, 2);
|
|
130
|
+
assert.equal(uiState.notifications[1].level, "success");
|
|
131
|
+
assert.equal(uiState.notifications[1].message, "Milestone M001 merged.");
|
|
132
|
+
});
|
|
@@ -52,9 +52,12 @@ describe('isolation:none stale branch guard (#3675)', () => {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
test('guard is wrapped in try-catch (non-fatal)', () => {
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
// Pin to the specific guard usage (`currentBranch.startsWith(...)`) so
|
|
56
|
+
// unrelated occurrences of `startsWith("milestone/")` elsewhere in the
|
|
57
|
+
// file (e.g. branch-name iteration helpers added later) don't shift the
|
|
58
|
+
// index and break this structural assertion.
|
|
59
|
+
const milestoneIdx = source.indexOf('currentBranch.startsWith("milestone/")');
|
|
60
|
+
assert.ok(milestoneIdx > 0, 'isolation:none guard milestone/ check should exist');
|
|
58
61
|
const before = source.slice(Math.max(0, milestoneIdx - 500), milestoneIdx);
|
|
59
62
|
assert.match(before, /try\s*\{/,
|
|
60
63
|
'milestone branch guard should be inside a try block');
|
|
@@ -85,7 +85,11 @@ function makeMockDeps(
|
|
|
85
85
|
resolveMilestoneFile: () => null,
|
|
86
86
|
reconcileMergeState: () => "clean",
|
|
87
87
|
preflightCleanRoot: () => ({ stashPushed: false, summary: "" }),
|
|
88
|
-
postflightPopStash: () => {
|
|
88
|
+
postflightPopStash: () => ({
|
|
89
|
+
restored: true,
|
|
90
|
+
needsManualRecovery: false,
|
|
91
|
+
message: "restored",
|
|
92
|
+
}),
|
|
89
93
|
getLedger: () => ({ units: [] }),
|
|
90
94
|
getProjectTotals: () => ({ cost: 0 }),
|
|
91
95
|
formatCost: (c: number) => `$${c.toFixed(2)}`,
|
|
@@ -52,6 +52,16 @@ async function executeToolInDir(tool: any, params: Record<string, unknown>, dir:
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
async function executeToolWithContextRoot(tool: any, params: Record<string, unknown>, processDir: string, contextRoot: string) {
|
|
56
|
+
const originalCwd = process.cwd();
|
|
57
|
+
try {
|
|
58
|
+
process.chdir(processDir);
|
|
59
|
+
return await tool.execute("test-call-id", params, undefined, undefined, { cwd: contextRoot });
|
|
60
|
+
} finally {
|
|
61
|
+
process.chdir(originalCwd);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
// ─── Registration ─────────────────────────────────────────────────────────────
|
|
56
66
|
|
|
57
67
|
test("registerJournalTools registers gsd_journal_query tool", () => {
|
|
@@ -126,6 +136,28 @@ test("gsd_journal_query respects limit parameter", async () => {
|
|
|
126
136
|
}
|
|
127
137
|
});
|
|
128
138
|
|
|
139
|
+
test("gsd_journal_query uses context cwd instead of process cwd", async () => {
|
|
140
|
+
const processBase = makeTmpBase();
|
|
141
|
+
const contextBase = makeTmpBase();
|
|
142
|
+
try {
|
|
143
|
+
emitJournalEvent(processBase, makeEntry({ seq: 0, flowId: "process-flow" }));
|
|
144
|
+
emitJournalEvent(contextBase, makeEntry({ seq: 0, flowId: "context-flow" }));
|
|
145
|
+
|
|
146
|
+
const pi = makeMockPi();
|
|
147
|
+
registerJournalTools(pi);
|
|
148
|
+
const tool = pi.tools[0];
|
|
149
|
+
|
|
150
|
+
const result = await executeToolWithContextRoot(tool, { limit: 5 }, processBase, contextBase);
|
|
151
|
+
const entries = JSON.parse(result.content[0].text) as JournalEntry[];
|
|
152
|
+
|
|
153
|
+
assert.equal(entries.length, 1);
|
|
154
|
+
assert.equal(entries[0].flowId, "context-flow");
|
|
155
|
+
} finally {
|
|
156
|
+
cleanup(processBase);
|
|
157
|
+
cleanup(contextBase);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
129
161
|
// ─── Error Handling ───────────────────────────────────────────────────────────
|
|
130
162
|
|
|
131
163
|
test("gsd_journal_query handles errors gracefully", async () => {
|
|
@@ -249,6 +249,28 @@ test('loadKnowledgeBlock: reports globalSizeKb above 4KB threshold', () => {
|
|
|
249
249
|
rmSync(tmp, { recursive: true, force: true });
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
+
test('loadKnowledgeBlock: caps repeated system prompt knowledge by default with source path', () => {
|
|
253
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-kb-')));
|
|
254
|
+
const gsdHome = join(tmp, 'home');
|
|
255
|
+
const cwd = join(tmp, 'project');
|
|
256
|
+
mkdirSync(join(cwd, '.gsd'), { recursive: true });
|
|
257
|
+
mkdirSync(join(gsdHome, 'agent'), { recursive: true });
|
|
258
|
+
writeFileSync(join(cwd, '.gsd', 'KNOWLEDGE.md'), `K001: ${'large project knowledge '.repeat(1200)}`);
|
|
259
|
+
|
|
260
|
+
const original = process.env.PI_GSD_KNOWLEDGE_MAX_CHARS;
|
|
261
|
+
delete process.env.PI_GSD_KNOWLEDGE_MAX_CHARS;
|
|
262
|
+
try {
|
|
263
|
+
const result = loadKnowledgeBlock(gsdHome, cwd);
|
|
264
|
+
assert.ok(result.block.includes('Source: `'));
|
|
265
|
+
assert.ok(result.block.length <= 12_500, `knowledge block ${result.block.length} should stay near default cap`);
|
|
266
|
+
assert.ok(result.block.includes('[Knowledge Truncated]'));
|
|
267
|
+
} finally {
|
|
268
|
+
if (original === undefined) delete process.env.PI_GSD_KNOWLEDGE_MAX_CHARS;
|
|
269
|
+
else process.env.PI_GSD_KNOWLEDGE_MAX_CHARS = original;
|
|
270
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
252
274
|
// ─── inlineKnowledgeBudgeted — issue #4719 ─────────────────────────────────
|
|
253
275
|
// Milestone-phase prompts must not inject the full KNOWLEDGE.md. The budgeted
|
|
254
276
|
// helper scopes by milestone-level keywords and caps the injected size.
|
|
@@ -315,6 +337,31 @@ test('inlineKnowledgeBudgeted: caps payload below budget for large files', async
|
|
|
315
337
|
rmSync(tmp, { recursive: true, force: true });
|
|
316
338
|
});
|
|
317
339
|
|
|
340
|
+
test('inlineKnowledgeBudgeted: default budget keeps auto prompt knowledge compact', async () => {
|
|
341
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-knowledge-')));
|
|
342
|
+
const gsdDir = join(tmp, '.gsd');
|
|
343
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
344
|
+
|
|
345
|
+
const entries = Array.from({ length: 300 }, (_, i) =>
|
|
346
|
+
`### Entry ${i}: shared topic\n${'default budget filler '.repeat(25)}\n`,
|
|
347
|
+
).join('\n');
|
|
348
|
+
writeFileSync(join(gsdDir, 'KNOWLEDGE.md'), `# Project Knowledge\n\n## Patterns\n\n${entries}`);
|
|
349
|
+
|
|
350
|
+
const result = await inlineKnowledgeBudgeted(tmp, ['shared']);
|
|
351
|
+
assert.ok(result !== null, 'should return content');
|
|
352
|
+
assert.ok(
|
|
353
|
+
result!.length <= 12_500,
|
|
354
|
+
`default payload ${result!.length} chars should stay near the 12k budget`,
|
|
355
|
+
);
|
|
356
|
+
assert.match(
|
|
357
|
+
result!,
|
|
358
|
+
/\[\.\.\.truncated \d+ chars; rerun with narrower scope if needed\]/,
|
|
359
|
+
'should include truncation note when default budget is exceeded',
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
363
|
+
});
|
|
364
|
+
|
|
318
365
|
test('inlineKnowledgeBudgeted: returns null when no KNOWLEDGE.md exists', async () => {
|
|
319
366
|
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-knowledge-')));
|
|
320
367
|
const gsdDir = join(tmp, '.gsd');
|