gsd-pi 2.80.0-dev.2be7caf18 → 2.80.0-dev.3065f7bfa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +178 -108
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- 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-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-start.js +197 -6
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +79 -24
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
- 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 +238 -28
- 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/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +66 -32
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- 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 +22 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- 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/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
- 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 +15 -15
- 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 +15 -15
- 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/package.json +3 -3
- package/packages/mcp-server/src/workflow-tools.test.ts +9 -2
- package/packages/native/tsconfig.tsbuildinfo +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-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +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 +41 -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-abort-order.test.ts +36 -0
- 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 +45 -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/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +251 -172
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
- package/src/resources/extensions/gsd/auto/session.ts +8 -0
- 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-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-start.ts +230 -9
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
- package/src/resources/extensions/gsd/auto.ts +87 -19
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
- 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 +283 -28
- 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/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +84 -39
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- 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 +23 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- 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 +417 -10
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- 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/compaction-snapshot.test.ts +3 -1
- 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-budget.test.ts +10 -1
- 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/dispatch-rule-coverage.test.ts +313 -0
- 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/integration/git-service.test.ts +54 -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 +239 -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/memory-decay-factor.test.ts +90 -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 +45 -5
- package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -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/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +38 -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 +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- 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 +167 -4
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
- /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
|
@@ -542,6 +542,60 @@ describe('git-service', async () => {
|
|
|
542
542
|
rmSync(repo, { recursive: true, force: true });
|
|
543
543
|
});
|
|
544
544
|
|
|
545
|
+
// Regression: #5500. The LLM occasionally hallucinates files in
|
|
546
|
+
// task.keyFiles that were never written. Pre-existing scoped-stage code
|
|
547
|
+
// ran `git add -- <every keyFile>` and failed the entire commit on the
|
|
548
|
+
// first missing path. Verify that valid paths still commit and missing
|
|
549
|
+
// ones are dropped silently.
|
|
550
|
+
test('GitServiceImpl: scoped staging drops missing keyFiles and commits the rest', () => {
|
|
551
|
+
const repo = initTempRepo();
|
|
552
|
+
const svc = new GitServiceImpl(repo);
|
|
553
|
+
|
|
554
|
+
createFile(repo, "src/index.ts", "export const ok = true;");
|
|
555
|
+
// Note: src/commands/list.ts is intentionally NOT created — the LLM
|
|
556
|
+
// claimed it wrote this file but didn't.
|
|
557
|
+
|
|
558
|
+
const msg = svc.autoCommit("execute-task", "M001/S01/T02", [], {
|
|
559
|
+
taskId: "S01/T02",
|
|
560
|
+
taskTitle: "wire up command list",
|
|
561
|
+
oneLiner: "Added list command stub",
|
|
562
|
+
keyFiles: ["src/index.ts", "src/commands/list.ts"],
|
|
563
|
+
});
|
|
564
|
+
assert.ok(msg !== null, "autoCommit succeeds when at least one keyFile exists");
|
|
565
|
+
|
|
566
|
+
const committed = run("git show --name-only --format= HEAD", repo);
|
|
567
|
+
assert.ok(committed.includes("src/index.ts"), "existing key file is committed");
|
|
568
|
+
assert.ok(!committed.includes("src/commands/list.ts"), "missing key file is silently dropped");
|
|
569
|
+
|
|
570
|
+
rmSync(repo, { recursive: true, force: true });
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// Regression: #5500. When ALL keyFiles are bogus, scopedStageTaskFiles
|
|
574
|
+
// must return false so autoCommit falls back to smartStage. The commit
|
|
575
|
+
// still goes out (using `git add -A` semantics) instead of failing.
|
|
576
|
+
test('GitServiceImpl: all missing keyFiles falls back to smartStage', () => {
|
|
577
|
+
const repo = initTempRepo();
|
|
578
|
+
const svc = new GitServiceImpl(repo);
|
|
579
|
+
|
|
580
|
+
createFile(repo, "src/actually-changed.ts", "export const real = true;");
|
|
581
|
+
|
|
582
|
+
const msg = svc.autoCommit("execute-task", "M001/S01/T03", [], {
|
|
583
|
+
taskId: "S01/T03",
|
|
584
|
+
taskTitle: "fix path handling",
|
|
585
|
+
oneLiner: "Hardened path resolution",
|
|
586
|
+
keyFiles: ["src/wrong/path-1.ts", "src/wrong/path-2.ts"],
|
|
587
|
+
});
|
|
588
|
+
assert.ok(msg !== null, "autoCommit falls back to smartStage when all keyFiles are missing");
|
|
589
|
+
|
|
590
|
+
const committed = run("git show --name-only --format= HEAD", repo);
|
|
591
|
+
assert.ok(
|
|
592
|
+
committed.includes("src/actually-changed.ts"),
|
|
593
|
+
"smartStage fallback stages real dirty files when scoped staging finds nothing",
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
rmSync(repo, { recursive: true, force: true });
|
|
597
|
+
});
|
|
598
|
+
|
|
545
599
|
// ─── GitServiceImpl: empty-after-staging guard ─────────────────────────
|
|
546
600
|
|
|
547
601
|
test('GitServiceImpl: empty-after-staging guard', () => {
|
|
@@ -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)}`,
|
|
@@ -399,6 +403,240 @@ test("runDispatch pauses when complete-milestone summary exists on disk but the
|
|
|
399
403
|
assert.equal(stopCalls, 0, "mismatch pause should not hard-stop the loop");
|
|
400
404
|
});
|
|
401
405
|
|
|
406
|
+
test("runDispatch pauses when execute-task artifacts exist but DB status is still open", async (t) => {
|
|
407
|
+
const capture = createEventCapture();
|
|
408
|
+
let pauseCalls = 0;
|
|
409
|
+
let stopCalls = 0;
|
|
410
|
+
let invalidateCalls = 0;
|
|
411
|
+
const base = join(tmpdir(), `gsd-stuck-execute-task-${randomUUID()}`);
|
|
412
|
+
t.after(() => {
|
|
413
|
+
closeDatabase();
|
|
414
|
+
rmSync(base, { recursive: true, force: true });
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
418
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
419
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
420
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
421
|
+
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
422
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
423
|
+
insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "First task", status: "pending" });
|
|
424
|
+
writeFileSync(
|
|
425
|
+
join(sliceDir, "S01-PLAN.md"),
|
|
426
|
+
[
|
|
427
|
+
"# S01",
|
|
428
|
+
"",
|
|
429
|
+
"## Tasks",
|
|
430
|
+
"",
|
|
431
|
+
"- [x] **T01: First task** `est:1h`",
|
|
432
|
+
"",
|
|
433
|
+
].join("\n"),
|
|
434
|
+
);
|
|
435
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n");
|
|
436
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone on disk.\n");
|
|
437
|
+
|
|
438
|
+
const deps = makeMockDeps(capture, {
|
|
439
|
+
pauseAuto: async () => { pauseCalls++; },
|
|
440
|
+
stopAuto: async () => { stopCalls++; },
|
|
441
|
+
invalidateAllCaches: () => { invalidateCalls++; },
|
|
442
|
+
resolveDispatch: async () => ({
|
|
443
|
+
action: "dispatch" as const,
|
|
444
|
+
unitType: "execute-task",
|
|
445
|
+
unitId: "M001/S01/T01",
|
|
446
|
+
prompt: "execute the task",
|
|
447
|
+
matchedRule: "executing → execute-task",
|
|
448
|
+
}),
|
|
449
|
+
});
|
|
450
|
+
const ic = makeIC(deps, {
|
|
451
|
+
s: {
|
|
452
|
+
...makeSession(),
|
|
453
|
+
basePath: base,
|
|
454
|
+
originalBasePath: base,
|
|
455
|
+
} as any,
|
|
456
|
+
});
|
|
457
|
+
const preData: PreDispatchData = {
|
|
458
|
+
state: {
|
|
459
|
+
phase: "executing",
|
|
460
|
+
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
461
|
+
activeSlice: { id: "S01", title: "Slice" },
|
|
462
|
+
activeTask: { id: "T01", title: "First task" },
|
|
463
|
+
registry: [{ id: "M001", status: "active" }],
|
|
464
|
+
blockers: [],
|
|
465
|
+
} as any,
|
|
466
|
+
mid: "M001",
|
|
467
|
+
midTitle: "Test Milestone",
|
|
468
|
+
};
|
|
469
|
+
const loopState: LoopState = {
|
|
470
|
+
recentUnits: [
|
|
471
|
+
{ key: "execute-task/M001/S01/T01" },
|
|
472
|
+
{ key: "execute-task/M001/S01/T01" },
|
|
473
|
+
],
|
|
474
|
+
stuckRecoveryAttempts: 0,
|
|
475
|
+
consecutiveFinalizeTimeouts: 0,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const result = await runDispatch(ic, preData, loopState);
|
|
479
|
+
|
|
480
|
+
assert.equal(result.action, "break");
|
|
481
|
+
assert.equal((result as any).reason, "execute-task-artifact-db-mismatch");
|
|
482
|
+
assert.equal(pauseCalls, 1, "execute-task disk/db mismatch should pause auto-mode");
|
|
483
|
+
assert.equal(stopCalls, 0, "execute-task disk/db mismatch should not hard-stop the loop");
|
|
484
|
+
assert.equal(invalidateCalls, 0, "mismatch should not clear caches and continue toward redispatch");
|
|
485
|
+
assert.equal(loopState.recentUnits.length, 3, "mismatch should keep the stuck window intact");
|
|
486
|
+
assert.equal(loopState.stuckRecoveryAttempts, 1, "mismatch should not reset the recovery counter");
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("runDispatch pauses at Level 2 when execute-task artifacts exist but DB status is still open", async (t) => {
|
|
490
|
+
const capture = createEventCapture();
|
|
491
|
+
let pauseCalls = 0;
|
|
492
|
+
let stopCalls = 0;
|
|
493
|
+
let invalidateCalls = 0;
|
|
494
|
+
const base = join(tmpdir(), `gsd-stuck-execute-task-l2-${randomUUID()}`);
|
|
495
|
+
t.after(() => {
|
|
496
|
+
closeDatabase();
|
|
497
|
+
rmSync(base, { recursive: true, force: true });
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
501
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
502
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
503
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
504
|
+
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
505
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
506
|
+
insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "First task", status: "pending" });
|
|
507
|
+
writeFileSync(
|
|
508
|
+
join(sliceDir, "S01-PLAN.md"),
|
|
509
|
+
"# S01\n\n## Tasks\n\n- [x] **T01: First task** `est:1h`\n",
|
|
510
|
+
);
|
|
511
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n");
|
|
512
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone on disk.\n");
|
|
513
|
+
|
|
514
|
+
const deps = makeMockDeps(capture, {
|
|
515
|
+
pauseAuto: async () => { pauseCalls++; },
|
|
516
|
+
stopAuto: async () => { stopCalls++; },
|
|
517
|
+
invalidateAllCaches: () => { invalidateCalls++; },
|
|
518
|
+
resolveDispatch: async () => ({
|
|
519
|
+
action: "dispatch" as const,
|
|
520
|
+
unitType: "execute-task",
|
|
521
|
+
unitId: "M001/S01/T01",
|
|
522
|
+
prompt: "execute the task",
|
|
523
|
+
matchedRule: "executing execute-task",
|
|
524
|
+
}),
|
|
525
|
+
});
|
|
526
|
+
const ic = makeIC(deps, {
|
|
527
|
+
s: {
|
|
528
|
+
...makeSession(),
|
|
529
|
+
basePath: base,
|
|
530
|
+
originalBasePath: base,
|
|
531
|
+
} as any,
|
|
532
|
+
});
|
|
533
|
+
const preData: PreDispatchData = {
|
|
534
|
+
state: {
|
|
535
|
+
phase: "executing",
|
|
536
|
+
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
537
|
+
activeSlice: { id: "S01", title: "Slice" },
|
|
538
|
+
activeTask: { id: "T01", title: "First task" },
|
|
539
|
+
registry: [{ id: "M001", status: "active" }],
|
|
540
|
+
blockers: [],
|
|
541
|
+
} as any,
|
|
542
|
+
mid: "M001",
|
|
543
|
+
midTitle: "Test Milestone",
|
|
544
|
+
};
|
|
545
|
+
const loopState: LoopState = {
|
|
546
|
+
recentUnits: [
|
|
547
|
+
{ key: "execute-task/M001/S01/T01" },
|
|
548
|
+
{ key: "execute-task/M001/S01/T01" },
|
|
549
|
+
],
|
|
550
|
+
stuckRecoveryAttempts: 1,
|
|
551
|
+
consecutiveFinalizeTimeouts: 0,
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const result = await runDispatch(ic, preData, loopState);
|
|
555
|
+
|
|
556
|
+
assert.equal(result.action, "break");
|
|
557
|
+
assert.equal((result as any).reason, "execute-task-artifact-db-mismatch");
|
|
558
|
+
assert.equal(pauseCalls, 1, "Level 2 execute-task disk/db mismatch should pause auto-mode");
|
|
559
|
+
assert.equal(stopCalls, 0, "Level 2 execute-task disk/db mismatch should not hard-stop the loop");
|
|
560
|
+
assert.equal(invalidateCalls, 1, "Level 2 should invalidate caches before the final artifact recheck");
|
|
561
|
+
assert.equal(loopState.recentUnits.length, 3, "Level 2 mismatch should keep the stuck window intact");
|
|
562
|
+
assert.equal(loopState.stuckRecoveryAttempts, 1, "Level 2 mismatch should not reset the recovery counter");
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("runDispatch clears execute-task stuck state when artifacts and DB status are complete", async (t) => {
|
|
566
|
+
const capture = createEventCapture();
|
|
567
|
+
let pauseCalls = 0;
|
|
568
|
+
let stopCalls = 0;
|
|
569
|
+
let invalidateCalls = 0;
|
|
570
|
+
const base = join(tmpdir(), `gsd-stuck-execute-task-complete-${randomUUID()}`);
|
|
571
|
+
t.after(() => {
|
|
572
|
+
closeDatabase();
|
|
573
|
+
rmSync(base, { recursive: true, force: true });
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
577
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
578
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
579
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
580
|
+
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
581
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
582
|
+
insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "First task", status: "complete" });
|
|
583
|
+
writeFileSync(
|
|
584
|
+
join(sliceDir, "S01-PLAN.md"),
|
|
585
|
+
"# S01\n\n## Tasks\n\n- [x] **T01: First task** `est:1h`\n",
|
|
586
|
+
);
|
|
587
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n");
|
|
588
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone on disk.\n");
|
|
589
|
+
|
|
590
|
+
const deps = makeMockDeps(capture, {
|
|
591
|
+
pauseAuto: async () => { pauseCalls++; },
|
|
592
|
+
stopAuto: async () => { stopCalls++; },
|
|
593
|
+
invalidateAllCaches: () => { invalidateCalls++; },
|
|
594
|
+
resolveDispatch: async () => ({
|
|
595
|
+
action: "dispatch" as const,
|
|
596
|
+
unitType: "execute-task",
|
|
597
|
+
unitId: "M001/S01/T01",
|
|
598
|
+
prompt: "execute the task",
|
|
599
|
+
matchedRule: "executing execute-task",
|
|
600
|
+
}),
|
|
601
|
+
});
|
|
602
|
+
const ic = makeIC(deps, {
|
|
603
|
+
s: {
|
|
604
|
+
...makeSession(),
|
|
605
|
+
basePath: base,
|
|
606
|
+
originalBasePath: base,
|
|
607
|
+
} as any,
|
|
608
|
+
});
|
|
609
|
+
const preData: PreDispatchData = {
|
|
610
|
+
state: {
|
|
611
|
+
phase: "executing",
|
|
612
|
+
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
613
|
+
activeSlice: { id: "S01", title: "Slice" },
|
|
614
|
+
activeTask: { id: "T01", title: "First task" },
|
|
615
|
+
registry: [{ id: "M001", status: "active" }],
|
|
616
|
+
blockers: [],
|
|
617
|
+
} as any,
|
|
618
|
+
mid: "M001",
|
|
619
|
+
midTitle: "Test Milestone",
|
|
620
|
+
};
|
|
621
|
+
const loopState: LoopState = {
|
|
622
|
+
recentUnits: [
|
|
623
|
+
{ key: "execute-task/M001/S01/T01" },
|
|
624
|
+
{ key: "execute-task/M001/S01/T01" },
|
|
625
|
+
],
|
|
626
|
+
stuckRecoveryAttempts: 0,
|
|
627
|
+
consecutiveFinalizeTimeouts: 0,
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const result = await runDispatch(ic, preData, loopState);
|
|
631
|
+
|
|
632
|
+
assert.equal(result.action, "continue");
|
|
633
|
+
assert.equal(pauseCalls, 0, "closed DB task should not pause auto-mode");
|
|
634
|
+
assert.equal(stopCalls, 0, "closed DB task should not hard-stop the loop");
|
|
635
|
+
assert.equal(invalidateCalls, 1, "closed DB task recovery should invalidate caches once");
|
|
636
|
+
assert.deepEqual(loopState.recentUnits, [], "closed DB task recovery should clear the stuck window");
|
|
637
|
+
assert.equal(loopState.stuckRecoveryAttempts, 0, "closed DB task recovery should reset the recovery counter");
|
|
638
|
+
});
|
|
639
|
+
|
|
402
640
|
test("runDispatch clears stuck state after Level 1 artifact recovery", async (t) => {
|
|
403
641
|
const capture = createEventCapture();
|
|
404
642
|
let invalidateCalls = 0;
|
|
@@ -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');
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// gsd-2 / memoryDecayFactor unit tests
|
|
2
|
+
//
|
|
3
|
+
// Pure-function boundary tests for the V28 time-decay scoring helper.
|
|
4
|
+
// The function maps last_hit_at → multiplier in [0.7, 1.0] used by
|
|
5
|
+
// queryMemoriesRanked to down-weight stale memories without fully suppressing
|
|
6
|
+
// them. These tests pin the contract:
|
|
7
|
+
//
|
|
8
|
+
// - null / invalid / future timestamps → 1.0 (no decay penalty)
|
|
9
|
+
// - 0 days ago → 1.0
|
|
10
|
+
// - linear decay between 0 and 90 days
|
|
11
|
+
// - 90+ days ago → 0.7 floor
|
|
12
|
+
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
|
|
16
|
+
import { memoryDecayFactor } from "../memory-store.ts";
|
|
17
|
+
|
|
18
|
+
const DAY_MS = 86_400_000;
|
|
19
|
+
|
|
20
|
+
function isoDaysAgo(days: number): string {
|
|
21
|
+
return new Date(Date.now() - days * DAY_MS).toISOString();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test("memoryDecayFactor: null lastHitAt returns 1.0 (never-hit = no decay)", () => {
|
|
25
|
+
assert.equal(memoryDecayFactor(null), 1.0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("memoryDecayFactor: invalid timestamp string returns 1.0 (defensive)", () => {
|
|
29
|
+
assert.equal(memoryDecayFactor("not-a-date"), 1.0);
|
|
30
|
+
assert.equal(memoryDecayFactor(""), 1.0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("memoryDecayFactor: future timestamp clamps to daysAgo=0 → 1.0", () => {
|
|
34
|
+
// Clock skew or manual DB edits can yield future last_hit_at values.
|
|
35
|
+
// The factor must stay within [0.7, 1.0] regardless.
|
|
36
|
+
const future = new Date(Date.now() + 30 * DAY_MS).toISOString();
|
|
37
|
+
const factor = memoryDecayFactor(future);
|
|
38
|
+
assert.ok(factor <= 1.0, `factor must not exceed 1.0, got ${factor}`);
|
|
39
|
+
assert.ok(factor >= 0.7, `factor must not fall below 0.7, got ${factor}`);
|
|
40
|
+
assert.equal(factor, 1.0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("memoryDecayFactor: 0 days ago returns 1.0", () => {
|
|
44
|
+
const factor = memoryDecayFactor(new Date().toISOString());
|
|
45
|
+
// Tiny clock drift between now-string and Date.now() inside the function;
|
|
46
|
+
// assert it's effectively 1.0 within float tolerance.
|
|
47
|
+
assert.ok(Math.abs(factor - 1.0) < 1e-6, `expected ≈1.0, got ${factor}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("memoryDecayFactor: 30 days ago returns ~0.90 (linear midpoint)", () => {
|
|
51
|
+
// Formula: 1.0 - 0.3 * (30/90) = 1.0 - 0.1 = 0.90
|
|
52
|
+
const factor = memoryDecayFactor(isoDaysAgo(30));
|
|
53
|
+
assert.ok(Math.abs(factor - 0.90) < 1e-3, `expected ≈0.90, got ${factor}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("memoryDecayFactor: 60 days ago returns ~0.80", () => {
|
|
57
|
+
// Formula: 1.0 - 0.3 * (60/90) = 1.0 - 0.2 = 0.80
|
|
58
|
+
const factor = memoryDecayFactor(isoDaysAgo(60));
|
|
59
|
+
assert.ok(Math.abs(factor - 0.80) < 1e-3, `expected ≈0.80, got ${factor}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("memoryDecayFactor: 90 days ago returns 0.70 (floor)", () => {
|
|
63
|
+
const factor = memoryDecayFactor(isoDaysAgo(90));
|
|
64
|
+
assert.ok(Math.abs(factor - 0.70) < 1e-3, `expected ≈0.70, got ${factor}`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("memoryDecayFactor: 180 days ago stays at 0.70 floor", () => {
|
|
68
|
+
const factor = memoryDecayFactor(isoDaysAgo(180));
|
|
69
|
+
assert.equal(factor, 0.70);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("memoryDecayFactor: result always in [0.7, 1.0] for any input", () => {
|
|
73
|
+
const samples: (string | null)[] = [
|
|
74
|
+
null,
|
|
75
|
+
"",
|
|
76
|
+
"garbage",
|
|
77
|
+
new Date(0).toISOString(),
|
|
78
|
+
isoDaysAgo(0),
|
|
79
|
+
isoDaysAgo(15),
|
|
80
|
+
isoDaysAgo(45),
|
|
81
|
+
isoDaysAgo(89),
|
|
82
|
+
isoDaysAgo(91),
|
|
83
|
+
isoDaysAgo(365),
|
|
84
|
+
new Date(Date.now() + 365 * DAY_MS).toISOString(),
|
|
85
|
+
];
|
|
86
|
+
for (const s of samples) {
|
|
87
|
+
const f = memoryDecayFactor(s);
|
|
88
|
+
assert.ok(f >= 0.7 && f <= 1.0, `factor out of [0.7, 1.0] for ${s}: ${f}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
@@ -70,6 +70,7 @@ function makeDeps(
|
|
|
70
70
|
getAutoWorktreePath: () => null,
|
|
71
71
|
autoCommitCurrentBranch: () => undefined,
|
|
72
72
|
getCurrentBranch: () => "worktree/M001",
|
|
73
|
+
checkoutBranch: () => undefined,
|
|
73
74
|
autoWorktreeBranch: (mid: string) => `worktree/${mid}`,
|
|
74
75
|
resolveMilestoneFile: () => null, // no roadmap → early return path
|
|
75
76
|
readFileSync: () => "",
|