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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// GSD-2 + Worktree dispatch guard: degrade empty worktrees over real project roots.
|
|
2
|
+
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
|
|
6
|
+
import { shouldDegradeEmptyWorktreeToProjectRoot } from "../auto/phases.ts";
|
|
7
|
+
import type { ProjectClassification } from "../detection.ts";
|
|
8
|
+
|
|
9
|
+
function classification(kind: ProjectClassification["kind"]): ProjectClassification {
|
|
10
|
+
return {
|
|
11
|
+
kind,
|
|
12
|
+
signals: {
|
|
13
|
+
detectedFiles: [],
|
|
14
|
+
isGitRepo: true,
|
|
15
|
+
isMonorepo: false,
|
|
16
|
+
xcodePlatforms: [],
|
|
17
|
+
hasCI: false,
|
|
18
|
+
hasTests: false,
|
|
19
|
+
verificationCommands: [],
|
|
20
|
+
},
|
|
21
|
+
trackedFiles: [],
|
|
22
|
+
untrackedFiles: [],
|
|
23
|
+
contentFiles: [],
|
|
24
|
+
markers: [],
|
|
25
|
+
reason: kind,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("worktree project-root degradation", () => {
|
|
30
|
+
test("degrades when worktree is greenfield but project root has content", () => {
|
|
31
|
+
assert.equal(
|
|
32
|
+
shouldDegradeEmptyWorktreeToProjectRoot(
|
|
33
|
+
classification("greenfield"),
|
|
34
|
+
classification("typed-existing"),
|
|
35
|
+
),
|
|
36
|
+
true,
|
|
37
|
+
);
|
|
38
|
+
assert.equal(
|
|
39
|
+
shouldDegradeEmptyWorktreeToProjectRoot(
|
|
40
|
+
classification("greenfield"),
|
|
41
|
+
classification("untyped-existing"),
|
|
42
|
+
),
|
|
43
|
+
true,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("keeps true greenfield worktrees in worktree mode", () => {
|
|
48
|
+
assert.equal(
|
|
49
|
+
shouldDegradeEmptyWorktreeToProjectRoot(
|
|
50
|
+
classification("greenfield"),
|
|
51
|
+
classification("greenfield"),
|
|
52
|
+
),
|
|
53
|
+
false,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("does not degrade when project root classification is invalid", () => {
|
|
58
|
+
assert.equal(
|
|
59
|
+
shouldDegradeEmptyWorktreeToProjectRoot(
|
|
60
|
+
classification("greenfield"),
|
|
61
|
+
classification("invalid-repo"),
|
|
62
|
+
),
|
|
63
|
+
false,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: WorktreeResolver unit and regression tests.
|
|
1
3
|
import test from "node:test";
|
|
2
4
|
import assert from "node:assert/strict";
|
|
3
5
|
import { mkdtempSync, rmSync, mkdirSync, realpathSync } from "node:fs";
|
|
@@ -9,6 +11,17 @@ import {
|
|
|
9
11
|
type NotifyCtx,
|
|
10
12
|
} from "../worktree-resolver.js";
|
|
11
13
|
import { AutoSession } from "../auto/session.js";
|
|
14
|
+
import {
|
|
15
|
+
closeDatabase,
|
|
16
|
+
insertMilestone,
|
|
17
|
+
openDatabase,
|
|
18
|
+
} from "../gsd-db.js";
|
|
19
|
+
import { registerAutoWorker } from "../db/auto-workers.js";
|
|
20
|
+
import {
|
|
21
|
+
claimMilestoneLease,
|
|
22
|
+
getMilestoneLease,
|
|
23
|
+
releaseMilestoneLease,
|
|
24
|
+
} from "../db/milestone-leases.js";
|
|
12
25
|
|
|
13
26
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
14
27
|
|
|
@@ -19,11 +32,12 @@ interface CallLog {
|
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
function makeSession(
|
|
22
|
-
overrides?: Partial<
|
|
35
|
+
overrides?: Partial<AutoSession>,
|
|
23
36
|
): AutoSession {
|
|
24
37
|
const s = new AutoSession();
|
|
25
38
|
s.basePath = overrides?.basePath ?? "/project";
|
|
26
39
|
s.originalBasePath = overrides?.originalBasePath ?? "/project";
|
|
40
|
+
Object.assign(s, overrides);
|
|
27
41
|
return s;
|
|
28
42
|
}
|
|
29
43
|
|
|
@@ -104,6 +118,9 @@ function makeDeps(
|
|
|
104
118
|
calls.push({ fn: "getCurrentBranch", args: [basePath] });
|
|
105
119
|
return "main";
|
|
106
120
|
},
|
|
121
|
+
checkoutBranch: (basePath: string, branch: string) => {
|
|
122
|
+
calls.push({ fn: "checkoutBranch", args: [basePath, branch] });
|
|
123
|
+
},
|
|
107
124
|
autoWorktreeBranch: (milestoneId: string) => {
|
|
108
125
|
calls.push({ fn: "autoWorktreeBranch", args: [milestoneId] });
|
|
109
126
|
return `milestone/${milestoneId}`;
|
|
@@ -182,6 +199,17 @@ function findCalls(calls: CallLog[], fn: string): CallLog[] {
|
|
|
182
199
|
return calls.filter((c) => c.fn === fn);
|
|
183
200
|
}
|
|
184
201
|
|
|
202
|
+
function makeDbBase(): string {
|
|
203
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-worktree-resolver-"));
|
|
204
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
205
|
+
return base;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function cleanupDbBase(base: string): void {
|
|
209
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
210
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
|
|
211
|
+
}
|
|
212
|
+
|
|
185
213
|
// ─── Getter Tests ────────────────────────────────────────────────────────────
|
|
186
214
|
|
|
187
215
|
test("workPath returns s.basePath", () => {
|
|
@@ -371,6 +399,43 @@ test("enterMilestone does not create double-nested worktree when originalBasePat
|
|
|
371
399
|
);
|
|
372
400
|
});
|
|
373
401
|
|
|
402
|
+
test("enterMilestone reacquires a released same-milestone lease before worktree entry", (t) => {
|
|
403
|
+
const base = makeDbBase();
|
|
404
|
+
t.after(() => cleanupDbBase(base));
|
|
405
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
406
|
+
insertMilestone({ id: "M001", title: "Test milestone", status: "active" });
|
|
407
|
+
|
|
408
|
+
const workerId = registerAutoWorker({ projectRootRealpath: base });
|
|
409
|
+
const originalClaim = claimMilestoneLease(workerId, "M001");
|
|
410
|
+
assert.equal(originalClaim.ok, true);
|
|
411
|
+
if (!originalClaim.ok) throw new Error("expected test lease claim");
|
|
412
|
+
assert.equal(releaseMilestoneLease(workerId, "M001", originalClaim.token), true);
|
|
413
|
+
|
|
414
|
+
const s = makeSession({
|
|
415
|
+
basePath: base,
|
|
416
|
+
originalBasePath: base,
|
|
417
|
+
workerId,
|
|
418
|
+
currentMilestoneId: "M001",
|
|
419
|
+
milestoneLeaseToken: originalClaim.token,
|
|
420
|
+
});
|
|
421
|
+
const deps = makeDeps({
|
|
422
|
+
createAutoWorktree: (basePath: string, milestoneId: string) => join(basePath, ".gsd", "worktrees", milestoneId),
|
|
423
|
+
});
|
|
424
|
+
const ctx = makeNotifyCtx();
|
|
425
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
426
|
+
|
|
427
|
+
resolver.enterMilestone("M001", ctx);
|
|
428
|
+
|
|
429
|
+
const row = getMilestoneLease("M001");
|
|
430
|
+
assert.ok(row);
|
|
431
|
+
assert.equal(row.worker_id, workerId);
|
|
432
|
+
assert.equal(row.status, "held");
|
|
433
|
+
assert.equal(row.fencing_token, originalClaim.token + 1);
|
|
434
|
+
assert.equal(s.milestoneLeaseToken, originalClaim.token + 1);
|
|
435
|
+
assert.equal(s.basePath, join(base, ".gsd", "worktrees", "M001"));
|
|
436
|
+
assert.equal(ctx.messages.some((m) => m.level === "error"), false);
|
|
437
|
+
});
|
|
438
|
+
|
|
374
439
|
// ─── enterMilestone Tests (branch mode) ──────────────────────────────────────
|
|
375
440
|
|
|
376
441
|
test("enterMilestone in branch mode calls enterBranchModeForMilestone and rebuilds GitService", () => {
|
|
@@ -740,21 +805,92 @@ test("mergeAndExit in branch mode merges when on milestone branch", () => {
|
|
|
740
805
|
assert.ok(ctx.messages.some((m) => m.msg.includes("branch mode")));
|
|
741
806
|
});
|
|
742
807
|
|
|
743
|
-
test("mergeAndExit in branch mode
|
|
808
|
+
test("mergeAndExit in branch mode checks out the milestone branch and merges (#5538-followup)", () => {
|
|
809
|
+
// Regression: previously this case silently returned without merging,
|
|
810
|
+
// stranding the milestone's commits on the branch (the test12345 repro).
|
|
811
|
+
// The fix forces a checkout first; merge proceeds when checkout succeeds.
|
|
744
812
|
const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
|
|
813
|
+
let currentBranch = "main";
|
|
814
|
+
const checkoutInvocations: Array<{ basePath: string; branch: string }> = [];
|
|
745
815
|
const deps = makeDeps({
|
|
746
816
|
isInAutoWorktree: () => false,
|
|
747
817
|
getIsolationMode: () => "branch",
|
|
748
|
-
getCurrentBranch: () =>
|
|
818
|
+
getCurrentBranch: () => currentBranch,
|
|
749
819
|
autoWorktreeBranch: () => "milestone/M001",
|
|
820
|
+
checkoutBranch: (basePath: string, branch: string) => {
|
|
821
|
+
checkoutInvocations.push({ basePath, branch });
|
|
822
|
+
currentBranch = branch;
|
|
823
|
+
},
|
|
750
824
|
});
|
|
751
825
|
const ctx = makeNotifyCtx();
|
|
752
826
|
const resolver = new WorktreeResolver(s, deps);
|
|
753
827
|
|
|
754
828
|
resolver.mergeAndExit("M001", ctx);
|
|
755
829
|
|
|
830
|
+
assert.equal(checkoutInvocations.length, 1, "must attempt checkout when on wrong branch");
|
|
831
|
+
assert.deepEqual(checkoutInvocations[0], { basePath: "/project", branch: "milestone/M001" });
|
|
832
|
+
assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 1);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
test("mergeAndExit in branch mode throws when checkout fails", () => {
|
|
836
|
+
// Regression for the silent-skip bug: if the working tree is on the wrong
|
|
837
|
+
// branch and checkout fails, we must throw so the caller pauses auto-mode
|
|
838
|
+
// — never silently advance with the milestone unmerged.
|
|
839
|
+
const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
|
|
840
|
+
const deps = makeDeps({
|
|
841
|
+
isInAutoWorktree: () => false,
|
|
842
|
+
getIsolationMode: () => "branch",
|
|
843
|
+
getCurrentBranch: () => "main",
|
|
844
|
+
autoWorktreeBranch: () => "milestone/M001",
|
|
845
|
+
checkoutBranch: () => {
|
|
846
|
+
throw new Error("dirty working tree blocks checkout");
|
|
847
|
+
},
|
|
848
|
+
});
|
|
849
|
+
const ctx = makeNotifyCtx();
|
|
850
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
851
|
+
|
|
852
|
+
assert.throws(
|
|
853
|
+
() => resolver.mergeAndExit("M001", ctx),
|
|
854
|
+
/dirty working tree blocks checkout/,
|
|
855
|
+
);
|
|
856
|
+
assert.equal(
|
|
857
|
+
findCalls(deps.calls, "mergeMilestoneToMain").length,
|
|
858
|
+
0,
|
|
859
|
+
"merge must not run when checkout failed",
|
|
860
|
+
);
|
|
861
|
+
const errorNotify = ctx.messages.find((m) => m.level === "error");
|
|
862
|
+
assert.ok(errorNotify, "an error notification must be emitted");
|
|
863
|
+
assert.match(errorNotify!.msg, /milestone\/M001 failed/);
|
|
864
|
+
assert.match(errorNotify!.msg, /Resolve manually/);
|
|
865
|
+
assert.equal(
|
|
866
|
+
ctx.messages.some((m) => m.level === "warning" && m.msg.includes("Milestone merge failed")),
|
|
867
|
+
false,
|
|
868
|
+
"checkout failures with explicit recovery guidance must not emit a duplicate warning",
|
|
869
|
+
);
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
test("mergeAndExit in branch mode throws when checkout reports success but HEAD is still wrong", () => {
|
|
873
|
+
// Defense in depth: even if checkoutBranch returns without throwing, we
|
|
874
|
+
// re-verify and throw if HEAD didn't actually move. Prevents merging on
|
|
875
|
+
// top of the wrong branch on platforms where the checkout is a no-op.
|
|
876
|
+
const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
|
|
877
|
+
const deps = makeDeps({
|
|
878
|
+
isInAutoWorktree: () => false,
|
|
879
|
+
getIsolationMode: () => "branch",
|
|
880
|
+
getCurrentBranch: () => "main", // never changes — simulates no-op checkout
|
|
881
|
+
autoWorktreeBranch: () => "milestone/M001",
|
|
882
|
+
checkoutBranch: () => {
|
|
883
|
+
// Pretend success — but getCurrentBranch will still return "main".
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
const ctx = makeNotifyCtx();
|
|
887
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
888
|
+
|
|
889
|
+
assert.throws(
|
|
890
|
+
() => resolver.mergeAndExit("M001", ctx),
|
|
891
|
+
/reported success but current branch is main/,
|
|
892
|
+
);
|
|
756
893
|
assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 0);
|
|
757
|
-
assert.equal(ctx.messages.length, 0);
|
|
758
894
|
});
|
|
759
895
|
|
|
760
896
|
test("mergeAndExit in branch mode handles merge failure gracefully", () => {
|
|
@@ -977,6 +1113,33 @@ test("mergeAndEnterNext enters next milestone even if merge fails", () => {
|
|
|
977
1113
|
);
|
|
978
1114
|
});
|
|
979
1115
|
|
|
1116
|
+
test("mergeAndEnterNext halts after branch-mode user-notified checkout failure", () => {
|
|
1117
|
+
const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
|
|
1118
|
+
const deps = makeDeps({
|
|
1119
|
+
isInAutoWorktree: () => false,
|
|
1120
|
+
getIsolationMode: () => "branch",
|
|
1121
|
+
getCurrentBranch: () => "main",
|
|
1122
|
+
autoWorktreeBranch: () => "milestone/M001",
|
|
1123
|
+
checkoutBranch: () => {
|
|
1124
|
+
throw new Error("dirty working tree blocks checkout");
|
|
1125
|
+
},
|
|
1126
|
+
});
|
|
1127
|
+
const ctx = makeNotifyCtx();
|
|
1128
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
1129
|
+
|
|
1130
|
+
assert.throws(
|
|
1131
|
+
() => resolver.mergeAndEnterNext("M001", "M002", ctx),
|
|
1132
|
+
/dirty working tree blocks checkout/,
|
|
1133
|
+
);
|
|
1134
|
+
assert.equal(
|
|
1135
|
+
findCalls(deps.calls, "enterBranchModeForMilestone").length,
|
|
1136
|
+
0,
|
|
1137
|
+
"must not enter the next milestone after a user-notified branch-mode failure",
|
|
1138
|
+
);
|
|
1139
|
+
assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 0);
|
|
1140
|
+
assert.ok(ctx.messages.some((m) => m.level === "error" && m.msg.includes("Resolve manually")));
|
|
1141
|
+
});
|
|
1142
|
+
|
|
980
1143
|
// ─── GitService Rebuild Atomicity ────────────────────────────────────────────
|
|
981
1144
|
|
|
982
1145
|
test("GitService is rebuilt with the NEW basePath after enterMilestone", () => {
|
|
@@ -308,6 +308,7 @@ function includeSupersededMemories(rankedActive: Memory[]): Memory[] {
|
|
|
308
308
|
scope: (row["scope"] as string) ?? "project",
|
|
309
309
|
tags,
|
|
310
310
|
structured_fields: structuredFields,
|
|
311
|
+
last_hit_at: (row["last_hit_at"] as string | null) ?? null,
|
|
311
312
|
};
|
|
312
313
|
});
|
|
313
314
|
} catch {
|
|
@@ -813,7 +813,7 @@ export async function executeMilestoneStatus(
|
|
|
813
813
|
|
|
814
814
|
return {
|
|
815
815
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
816
|
-
details: { operation: "milestone_status",
|
|
816
|
+
details: { operation: "milestone_status", ...result },
|
|
817
817
|
};
|
|
818
818
|
});
|
|
819
819
|
} catch (err) {
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from "./paths.js";
|
|
11
11
|
import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
12
12
|
import { parseUnitId } from "./unit-id.js";
|
|
13
|
+
import { getTask, isDbAvailable, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
|
|
14
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
13
15
|
|
|
14
16
|
// Per-record advisory lock — prevents read-modify-write races between
|
|
15
17
|
// concurrent writers updating disjoint fields of the same runtime record.
|
|
@@ -92,6 +94,7 @@ export interface ExecuteTaskRecoveryStatus {
|
|
|
92
94
|
summaryExists: boolean;
|
|
93
95
|
taskChecked: boolean;
|
|
94
96
|
nextActionAdvanced: boolean;
|
|
97
|
+
dbComplete: boolean;
|
|
95
98
|
mustHaveCount: number;
|
|
96
99
|
mustHavesMentionedInSummary: number;
|
|
97
100
|
}
|
|
@@ -213,6 +216,12 @@ export async function inspectExecuteTaskDurability(
|
|
|
213
216
|
const escapedTid = tid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
214
217
|
const taskChecked = !!planContent && new RegExp(`^- \\[[xX]\\] \\*\\*${escapedTid}:`, "m").test(planContent);
|
|
215
218
|
const nextActionAdvanced = !new RegExp(`Execute ${tid}\\b`).test(stateContent);
|
|
219
|
+
let dbComplete = false;
|
|
220
|
+
if (isDbAvailable()) {
|
|
221
|
+
refreshOpenDatabaseFromDisk();
|
|
222
|
+
const task = getTask(mid, sid, tid);
|
|
223
|
+
dbComplete = !!task && isClosedStatus(task.status);
|
|
224
|
+
}
|
|
216
225
|
|
|
217
226
|
// Must-have coverage: load task plan and count mentions in summary
|
|
218
227
|
let mustHaveCount = 0;
|
|
@@ -239,12 +248,14 @@ export async function inspectExecuteTaskDurability(
|
|
|
239
248
|
summaryExists,
|
|
240
249
|
taskChecked,
|
|
241
250
|
nextActionAdvanced,
|
|
251
|
+
dbComplete,
|
|
242
252
|
mustHaveCount,
|
|
243
253
|
mustHavesMentionedInSummary,
|
|
244
254
|
};
|
|
245
255
|
}
|
|
246
256
|
|
|
247
257
|
export function formatExecuteTaskRecoveryStatus(status: ExecuteTaskRecoveryStatus): string {
|
|
258
|
+
if (status.dbComplete) return "DB task status is closed";
|
|
248
259
|
const missing = [] as string[];
|
|
249
260
|
if (!status.summaryExists) missing.push(`summary missing (${status.summaryPath})`);
|
|
250
261
|
if (!status.taskChecked) missing.push(`task checkbox unchecked in ${status.planPath}`);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Shared capped workflow protocol and doctor-heal prompt payload helpers.
|
|
3
|
+
|
|
4
|
+
const DEFAULT_WORKFLOW_PROTOCOL_EXCERPT_CHARS = 4_000;
|
|
5
|
+
const MIN_WORKFLOW_PROTOCOL_EXCERPT_CHARS = 1_000;
|
|
6
|
+
const DEFAULT_DOCTOR_SUMMARY_CHARS = 2_400;
|
|
7
|
+
const DEFAULT_DOCTOR_ISSUE_CHARS = 300;
|
|
8
|
+
const DEFAULT_DOCTOR_MAX_ISSUES = 12;
|
|
9
|
+
const DEFAULT_DOCTOR_ISSUES_CHARS = 4_000;
|
|
10
|
+
|
|
11
|
+
export function buildWorkflowProtocolExcerpt(
|
|
12
|
+
workflow: string,
|
|
13
|
+
workflowPath: string,
|
|
14
|
+
opts: { maxChars?: number } = {},
|
|
15
|
+
): string {
|
|
16
|
+
const limit = opts.maxChars ?? getWorkflowProtocolExcerptLimit();
|
|
17
|
+
const trimmed = workflow.trim();
|
|
18
|
+
const excerpt = buildPrioritizedWorkflowExcerpt(trimmed, limit);
|
|
19
|
+
const truncated = trimmed.length > limit;
|
|
20
|
+
const lines = [
|
|
21
|
+
"## GSD Workflow Protocol Excerpt",
|
|
22
|
+
`Source: \`${workflowPath}\``,
|
|
23
|
+
"",
|
|
24
|
+
excerpt,
|
|
25
|
+
];
|
|
26
|
+
if (truncated) {
|
|
27
|
+
lines.push(
|
|
28
|
+
"",
|
|
29
|
+
"[Workflow Protocol Truncated]",
|
|
30
|
+
"The full workflow protocol remains available at the source path above. Read it only if this excerpt lacks a rule required for the dispatched task.",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildWorkflowDispatchContent(opts: {
|
|
37
|
+
workflow: string;
|
|
38
|
+
workflowPath: string;
|
|
39
|
+
task: string;
|
|
40
|
+
maxProtocolChars?: number;
|
|
41
|
+
}): string {
|
|
42
|
+
return [
|
|
43
|
+
"Read the following GSD workflow protocol excerpt and execute exactly. Use the source path for a full protocol read only if the excerpt lacks a required rule.",
|
|
44
|
+
"",
|
|
45
|
+
buildWorkflowProtocolExcerpt(opts.workflow, opts.workflowPath, { maxChars: opts.maxProtocolChars }),
|
|
46
|
+
"",
|
|
47
|
+
"## Your Task",
|
|
48
|
+
"",
|
|
49
|
+
opts.task.trim(),
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildDoctorHealSummary(reportText: string, opts: { maxChars?: number } = {}): string {
|
|
54
|
+
const limit = opts.maxChars ?? DEFAULT_DOCTOR_SUMMARY_CHARS;
|
|
55
|
+
const lines = reportText.split(/\r?\n/).map((line) => line.trimEnd());
|
|
56
|
+
const summaryLines = lines.filter((line) =>
|
|
57
|
+
line.length > 0 && (
|
|
58
|
+
/^#/.test(line) ||
|
|
59
|
+
/^(scope|status|summary|checks?|errors?|warnings?|fixes?|issues?)\b/i.test(line) ||
|
|
60
|
+
/doctor/i.test(line)
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
const selected = summaryLines.length > 0 ? summaryLines : lines.filter((line) => line.trim()).slice(0, 24);
|
|
64
|
+
return capText(selected.join("\n"), limit, "Full doctor report is available in the command output; use the structured issue list below for repairs.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildDoctorHealIssuePayload(
|
|
68
|
+
structuredIssues: string,
|
|
69
|
+
opts: { maxIssues?: number; maxIssueChars?: number; maxChars?: number } = {},
|
|
70
|
+
): string {
|
|
71
|
+
const maxIssues = opts.maxIssues ?? DEFAULT_DOCTOR_MAX_ISSUES;
|
|
72
|
+
const maxIssueChars = opts.maxIssueChars ?? DEFAULT_DOCTOR_ISSUE_CHARS;
|
|
73
|
+
const maxChars = opts.maxChars ?? DEFAULT_DOCTOR_ISSUES_CHARS;
|
|
74
|
+
const blocks = splitIssueBlocks(structuredIssues);
|
|
75
|
+
const topBlocks = blocks.slice(0, maxIssues).map((block) =>
|
|
76
|
+
capText(block, maxIssueChars, "Issue details truncated; inspect the relevant artifact before editing."),
|
|
77
|
+
);
|
|
78
|
+
if (blocks.length > maxIssues) {
|
|
79
|
+
topBlocks.push(`[${blocks.length - maxIssues} additional actionable issue(s) omitted from prompt. Re-run /gsd doctor heal after this repair pass.]`);
|
|
80
|
+
}
|
|
81
|
+
return capText(topBlocks.join("\n\n"), maxChars, "Structured issue list truncated; repair top actionable issues first and re-run doctor heal.");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getWorkflowProtocolExcerptLimit(): number {
|
|
85
|
+
const raw = process.env.PI_GSD_WORKFLOW_PROTOCOL_MAX_CHARS;
|
|
86
|
+
if (!raw) return DEFAULT_WORKFLOW_PROTOCOL_EXCERPT_CHARS;
|
|
87
|
+
const parsed = Number(raw);
|
|
88
|
+
if (!Number.isFinite(parsed) || parsed < MIN_WORKFLOW_PROTOCOL_EXCERPT_CHARS) {
|
|
89
|
+
return DEFAULT_WORKFLOW_PROTOCOL_EXCERPT_CHARS;
|
|
90
|
+
}
|
|
91
|
+
return Math.floor(parsed);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildPrioritizedWorkflowExcerpt(workflow: string, limit: number): string {
|
|
95
|
+
if (workflow.length <= limit) return workflow;
|
|
96
|
+
const sections = splitMarkdownSections(workflow);
|
|
97
|
+
const wanted = [
|
|
98
|
+
/^# /,
|
|
99
|
+
/^## Quick Start\b/i,
|
|
100
|
+
/^## The Hierarchy\b/i,
|
|
101
|
+
/^## The Phases\b/i,
|
|
102
|
+
/^### Phase 4: Execute\b/i,
|
|
103
|
+
/^### Phase 5: Verify\b/i,
|
|
104
|
+
/^### Observable Truths\b/i,
|
|
105
|
+
/^### Artifacts\b/i,
|
|
106
|
+
/^### Key Links\b/i,
|
|
107
|
+
/^### Phase 6: Summarize\b/i,
|
|
108
|
+
/^### Phase 7: Advance\b/i,
|
|
109
|
+
];
|
|
110
|
+
const selected: string[] = [];
|
|
111
|
+
const used = new Set<number>();
|
|
112
|
+
for (const pattern of wanted) {
|
|
113
|
+
const index = sections.findIndex((section, sectionIndex) =>
|
|
114
|
+
!used.has(sectionIndex) && pattern.test(section.heading),
|
|
115
|
+
);
|
|
116
|
+
if (index >= 0) {
|
|
117
|
+
used.add(index);
|
|
118
|
+
selected.push(sections[index].text);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const body = selected.length > 0 ? selected.join("\n\n") : workflow;
|
|
122
|
+
return capText(body, limit, "Workflow protocol excerpt capped; read the source path for omitted details.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function splitMarkdownSections(markdown: string): Array<{ heading: string; text: string }> {
|
|
126
|
+
const lines = markdown.split(/\r?\n/);
|
|
127
|
+
const sections: Array<{ heading: string; text: string }> = [];
|
|
128
|
+
let currentHeading = lines[0]?.startsWith("#") ? lines[0] : "# Preamble";
|
|
129
|
+
let current: string[] = [];
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (/^#{1,3}\s+/.test(line) && current.length > 0) {
|
|
132
|
+
sections.push({ heading: currentHeading, text: current.join("\n").trim() });
|
|
133
|
+
currentHeading = line;
|
|
134
|
+
current = [line];
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (/^#{1,3}\s+/.test(line)) currentHeading = line;
|
|
138
|
+
current.push(line);
|
|
139
|
+
}
|
|
140
|
+
if (current.length > 0) {
|
|
141
|
+
sections.push({ heading: currentHeading, text: current.join("\n").trim() });
|
|
142
|
+
}
|
|
143
|
+
return sections.filter((section) => section.text.length > 0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function splitIssueBlocks(structuredIssues: string): string[] {
|
|
147
|
+
const trimmed = structuredIssues.trim();
|
|
148
|
+
if (!trimmed) return ["No structured issue details were provided."];
|
|
149
|
+
const split = trimmed.split(/\n(?=(?:#{2,6}\s+|\d+\.\s+|- \*\*|- \[[ x]\]))/i)
|
|
150
|
+
.map((block) => block.trim())
|
|
151
|
+
.filter(Boolean);
|
|
152
|
+
return split.length > 0 ? split : [trimmed];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function capText(text: string, limit: number, notice: string): string {
|
|
156
|
+
if (text.length <= limit) return text;
|
|
157
|
+
const suffix = `\n\n[Truncated]\n${notice}`;
|
|
158
|
+
const headBudget = Math.max(0, limit - suffix.length);
|
|
159
|
+
return `${text.slice(0, headBudget).trimEnd()}${suffix}`;
|
|
160
|
+
}
|