@opengsd/gsd-pi 1.9.0 → 1.10.0
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 +10 -10
- package/dist/headless.js +2 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto/unit-phase.js +37 -5
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -8
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
- package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -5
- package/dist/resources/extensions/gsd/auto-start.js +29 -9
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +40 -0
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +33 -7
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -24
- package/dist/resources/extensions/gsd/commands-verdict.js +10 -9
- package/dist/resources/extensions/gsd/compat/compat-marker.js +80 -5
- package/dist/resources/extensions/gsd/constants.js +12 -13
- package/dist/resources/extensions/gsd/context-masker.js +97 -21
- package/dist/resources/extensions/gsd/db/engine.js +5 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +24 -8
- package/dist/resources/extensions/gsd/error-classifier.js +12 -2
- package/dist/resources/extensions/gsd/exec-sandbox.js +1 -0
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/git-service.js +19 -7
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/guided-flow.js +11 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +5 -21
- package/dist/resources/extensions/gsd/md-importer.js +5 -6
- package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
- package/dist/resources/extensions/gsd/model-router.js +11 -0
- package/dist/resources/extensions/gsd/paths.js +66 -15
- package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
- package/dist/resources/extensions/gsd/preferences-types.js +5 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
- package/dist/resources/extensions/gsd/preferences.js +17 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
- package/dist/resources/extensions/gsd/skill-activation.js +17 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +98 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +4 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +5 -2
- package/dist/resources/extensions/gsd/stop-notice.js +9 -0
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -5
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
- package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
- package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
- package/dist/resources/extensions/gsd/workspace.js +2 -1
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/shared/interview-ui.js +43 -6
- 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 +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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 +8 -8
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +7 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +1 -0
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +9 -3
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +121 -15
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +272 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/README.md +7 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +5 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- 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/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +76 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +70 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +6 -1
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +103 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto/unit-phase.ts +41 -5
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +118 -19
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
- package/src/resources/extensions/gsd/auto-runtime-state.ts +12 -7
- package/src/resources/extensions/gsd/auto-start.ts +31 -8
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +56 -0
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +20 -7
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -12
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -29
- package/src/resources/extensions/gsd/commands-verdict.ts +16 -9
- package/src/resources/extensions/gsd/compat/compat-marker.ts +99 -5
- package/src/resources/extensions/gsd/constants.ts +14 -13
- package/src/resources/extensions/gsd/context-masker.ts +98 -2
- package/src/resources/extensions/gsd/db/engine.ts +5 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +30 -7
- package/src/resources/extensions/gsd/error-classifier.ts +13 -2
- package/src/resources/extensions/gsd/exec-sandbox.ts +1 -0
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/git-service.ts +19 -6
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/guided-flow.ts +11 -4
- package/src/resources/extensions/gsd/markdown-renderer.ts +5 -23
- package/src/resources/extensions/gsd/md-importer.ts +5 -5
- package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
- package/src/resources/extensions/gsd/model-router.ts +12 -0
- package/src/resources/extensions/gsd/paths.ts +73 -15
- package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
- package/src/resources/extensions/gsd/preferences.ts +25 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
- package/src/resources/extensions/gsd/skill-activation.ts +17 -3
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +132 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +4 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +4 -2
- package/src/resources/extensions/gsd/stop-notice.ts +11 -0
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +76 -1
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
- package/src/resources/extensions/gsd/tests/db-engine-cjs-compat.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/external-markdown-edit.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
- package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +20 -5
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
- package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
- package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
- package/src/resources/extensions/gsd/workspace.ts +2 -1
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/shared/interview-ui.ts +47 -8
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +29 -0
- /package/dist/web/standalone/.next/static/{BE5XoklToI6lZjRzpXih8 → EAhnhYMt5UVOiG9-iPDjc}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BE5XoklToI6lZjRzpXih8 → EAhnhYMt5UVOiG9-iPDjc}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { GSD_CONTEXT_MESSAGE_SENTINEL } from "./constants.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Observation masking for GSD auto-mode sessions.
|
|
3
5
|
*
|
|
@@ -75,6 +77,26 @@ function findTurnBoundary(messages: MaskableMessage[], keepRecentTurns: number):
|
|
|
75
77
|
return 0;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
function countRealUserTurns(messages: MaskableMessage[]): number {
|
|
81
|
+
let count = 0;
|
|
82
|
+
for (const m of messages) {
|
|
83
|
+
if (m.role === "user" && !isBashResultUserMessage(m)) count++;
|
|
84
|
+
}
|
|
85
|
+
return count;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Quantizes how many recent turns stay unmasked so the boundary only moves
|
|
90
|
+
* forward once per full block of `keepRecentTurns` new turns, instead of
|
|
91
|
+
* every turn. Keeps the masked prefix byte-stable within a block, which is
|
|
92
|
+
* what lets the LLM provider's prompt cache survive across turns.
|
|
93
|
+
*/
|
|
94
|
+
function quantizedKeepTurns(totalTurns: number, keepRecentTurns: number): number {
|
|
95
|
+
if (keepRecentTurns <= 0 || totalTurns < 2 * keepRecentTurns) return totalTurns;
|
|
96
|
+
const maskedTurns = Math.floor((totalTurns - keepRecentTurns) / keepRecentTurns) * keepRecentTurns;
|
|
97
|
+
return totalTurns - maskedTurns;
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
/**
|
|
79
101
|
* Detect user messages that originated from bashExecution.
|
|
80
102
|
* After convertToLlm, these are {role: "user", content: [{type:"text", text:"Ran `cmd`\n..."}]}.
|
|
@@ -95,7 +117,8 @@ function isMaskableMessage(m: MaskableMessage): boolean {
|
|
|
95
117
|
|
|
96
118
|
export function createObservationMask(keepRecentTurns: number = 8) {
|
|
97
119
|
return (messages: MaskableMessage[]): MaskableMessage[] => {
|
|
98
|
-
const
|
|
120
|
+
const totalTurns = countRealUserTurns(messages);
|
|
121
|
+
const boundary = findTurnBoundary(messages, quantizedKeepTurns(totalTurns, keepRecentTurns));
|
|
99
122
|
if (boundary === 0) return messages;
|
|
100
123
|
|
|
101
124
|
return messages.map((m, i) => {
|
|
@@ -126,6 +149,14 @@ function findResponsesTurnBoundary(items: ResponsesInputItem[], keepRecentTurns:
|
|
|
126
149
|
return 0;
|
|
127
150
|
}
|
|
128
151
|
|
|
152
|
+
function countResponsesRealUserTurns(items: ResponsesInputItem[]): number {
|
|
153
|
+
let count = 0;
|
|
154
|
+
for (const item of items) {
|
|
155
|
+
if (item.role === "user" && !isResponsesBashResultUserItem(item)) count++;
|
|
156
|
+
}
|
|
157
|
+
return count;
|
|
158
|
+
}
|
|
159
|
+
|
|
129
160
|
/**
|
|
130
161
|
* Observation masking for OpenAI/Codex Responses API payloads.
|
|
131
162
|
*
|
|
@@ -135,7 +166,8 @@ function findResponsesTurnBoundary(items: ResponsesInputItem[], keepRecentTurns:
|
|
|
135
166
|
*/
|
|
136
167
|
export function createResponsesInputObservationMask(keepRecentTurns: number = 8) {
|
|
137
168
|
return (items: ResponsesInputItem[]): ResponsesInputItem[] => {
|
|
138
|
-
const
|
|
169
|
+
const totalTurns = countResponsesRealUserTurns(items);
|
|
170
|
+
const boundary = findResponsesTurnBoundary(items, quantizedKeepTurns(totalTurns, keepRecentTurns));
|
|
139
171
|
if (boundary === 0) return items;
|
|
140
172
|
|
|
141
173
|
return items.map((item, i) => {
|
|
@@ -219,3 +251,67 @@ export function truncateResponsesInputResultItems(items: ResponsesInputItem[], m
|
|
|
219
251
|
return item;
|
|
220
252
|
});
|
|
221
253
|
}
|
|
254
|
+
|
|
255
|
+
// GSD injects at most one context message per turn (memory/guided/forensics —
|
|
256
|
+
// see buildContextMessage in bootstrap/system-context.ts), marked with
|
|
257
|
+
// GSD_CONTEXT_MESSAGE_SENTINEL. convertToLlm strips the distinguishing
|
|
258
|
+
// customType before the payload reaches this hook, so detection is by content
|
|
259
|
+
// prefix instead. Pre-sentinel session history lacks the sentinel, so we also
|
|
260
|
+
// match the stable *bracketed* GSD block labels those injections begin with,
|
|
261
|
+
// letting resumed sessions dedupe older memory/guided blocks.
|
|
262
|
+
//
|
|
263
|
+
// Only GSD-specific bracketed markers belong here — never generic prose. A
|
|
264
|
+
// message is dropped from the payload when it matches, so a natural-language
|
|
265
|
+
// prefix (e.g. the forensics prompt "Debug GSD itself.") could silently delete
|
|
266
|
+
// a real user message that happens to start the same way. Legacy forensics
|
|
267
|
+
// injections without memory are therefore left un-deduped by design; those
|
|
268
|
+
// with memory still match via the "[GSD Context Metadata]" wrapper.
|
|
269
|
+
const LEGACY_GSD_CONTEXT_INJECTION_PREFIXES = [
|
|
270
|
+
"[GSD Context Metadata]",
|
|
271
|
+
"[MEMORY — Critical and prompt-relevant memories from the GSD memory store]",
|
|
272
|
+
"[GSD Guided Execute Context]",
|
|
273
|
+
] as const;
|
|
274
|
+
|
|
275
|
+
function isGsdContextInjectionText(text: string | undefined): boolean {
|
|
276
|
+
if (typeof text !== "string") return false;
|
|
277
|
+
if (text.startsWith(GSD_CONTEXT_MESSAGE_SENTINEL)) return true;
|
|
278
|
+
return LEGACY_GSD_CONTEXT_INJECTION_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isGsdContextInjectionMessage(m: MaskableMessage): boolean {
|
|
282
|
+
if (m.role !== "user") return false;
|
|
283
|
+
return isGsdContextInjectionText(firstTextFromContent(m.content));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Removes every GSD context-injection user message except the latest one.
|
|
288
|
+
* Each turn re-injects a near-identical memory/guided/forensics block; left
|
|
289
|
+
* in place they duplicate verbatim across an N-turn session. Removal (not
|
|
290
|
+
* masking) — an empty placeholder still costs tokens and shifts message
|
|
291
|
+
* positions, breaking cache byte-stability. Pure function: stored history is
|
|
292
|
+
* never mutated, only the outgoing payload array.
|
|
293
|
+
*/
|
|
294
|
+
export function filterSupersededContextInjections(messages: MaskableMessage[]): MaskableMessage[] {
|
|
295
|
+
let lastIndex = -1;
|
|
296
|
+
for (let i = 0; i < messages.length; i++) {
|
|
297
|
+
if (isGsdContextInjectionMessage(messages[i])) lastIndex = i;
|
|
298
|
+
}
|
|
299
|
+
if (lastIndex === -1) return messages;
|
|
300
|
+
return messages.filter((m, i) => i === lastIndex || !isGsdContextInjectionMessage(m));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isResponsesGsdContextInjectionItem(item: ResponsesInputItem): boolean {
|
|
304
|
+
if (item.role !== "user") return false;
|
|
305
|
+
return isGsdContextInjectionText(firstTextFromContent(item.content));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function filterSupersededResponsesContextInjections(
|
|
309
|
+
items: ResponsesInputItem[],
|
|
310
|
+
): ResponsesInputItem[] {
|
|
311
|
+
let lastIndex = -1;
|
|
312
|
+
for (let i = 0; i < items.length; i++) {
|
|
313
|
+
if (isResponsesGsdContextInjectionItem(items[i])) lastIndex = i;
|
|
314
|
+
}
|
|
315
|
+
if (lastIndex === -1) return items;
|
|
316
|
+
return items.filter((item, i) => i === lastIndex || !isResponsesGsdContextInjectionItem(item));
|
|
317
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// writer layer.
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
14
|
import { GSDError, GSD_STALE_STATE } from "../errors.js";
|
|
15
15
|
import type { GsdWorkspace, MilestoneScope } from "../workspace.js";
|
|
16
16
|
import { logError, logWarning } from "../workflow-logger.js";
|
|
@@ -71,7 +71,10 @@ let _gsdRequire: ReturnType<typeof createRequire> | null | undefined;
|
|
|
71
71
|
function getGsdRequire(): ReturnType<typeof createRequire> | null {
|
|
72
72
|
if (_gsdRequire !== undefined) return _gsdRequire;
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
// Next.js may emit this module into a CommonJS chunk. Avoid ESM-only module
|
|
75
|
+
// metadata syntax here; it is a hard parse error there.
|
|
76
|
+
const packageRoot = process.env.GSD_WEB_PACKAGE_ROOT || process.env.GSD_PKG_ROOT || process.cwd();
|
|
77
|
+
_gsdRequire = createRequire(resolve(packageRoot, "package.json"));
|
|
75
78
|
} catch {
|
|
76
79
|
_gsdRequire = null;
|
|
77
80
|
}
|
|
@@ -357,6 +357,28 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
|
|
|
357
357
|
- `"replace"` requires `prompt`.
|
|
358
358
|
- `"skip"` is valid with no additional fields.
|
|
359
359
|
|
|
360
|
+
- `planning_subagents`: object — project-local widening for read-only planning subagent dispatch. Supported unit keys are `plan-milestone` and `plan-slice`; each has:
|
|
361
|
+
- `allowed`: string[] — additional planning-safe agents for that unit.
|
|
362
|
+
|
|
363
|
+
Agents must already be classified by GSD as read-only planning specialists: `mnemo`, `scout`, `planner`, `reviewer`, `security`, or `tester`. The write gate still blocks source writes outside `.gsd/**`, unrestricted bash, stale subagent calls without agent identities, agents outside the global read-only registry, and agents not listed for the active unit.
|
|
364
|
+
|
|
365
|
+
Example:
|
|
366
|
+
|
|
367
|
+
```yaml
|
|
368
|
+
planning_subagents:
|
|
369
|
+
plan-milestone:
|
|
370
|
+
allowed:
|
|
371
|
+
- scout
|
|
372
|
+
- planner
|
|
373
|
+
- security
|
|
374
|
+
plan-slice:
|
|
375
|
+
allowed:
|
|
376
|
+
- scout
|
|
377
|
+
- planner
|
|
378
|
+
- reviewer
|
|
379
|
+
- security
|
|
380
|
+
```
|
|
381
|
+
|
|
360
382
|
**Known unit types for `before`/`after`:** `research-milestone`, `plan-milestone`, `research-slice`, `plan-slice`, `execute-task`, `complete-slice`, `replan-slice`, `reassess-roadmap`, `run-uat`.
|
|
361
383
|
|
|
362
384
|
- `experimental`: opt-in experimental features. All features here are **off by default** — you must explicitly set each one to `true` to enable it. Features in this block may change or be removed without a deprecation cycle while in experimental status. Keys:
|
|
@@ -15,13 +15,13 @@ import {
|
|
|
15
15
|
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
16
16
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
17
17
|
import {
|
|
18
|
-
buildTaskFileName,
|
|
19
18
|
gsdProjectionRoot,
|
|
20
19
|
gsdRoot,
|
|
21
20
|
resolveGsdPathContract,
|
|
22
21
|
resolveMilestoneFile,
|
|
23
22
|
resolveMilestonePath,
|
|
24
23
|
resolveSliceFile,
|
|
24
|
+
resolveTaskFile,
|
|
25
25
|
} from "./paths.js";
|
|
26
26
|
import { deriveState } from "./state.js";
|
|
27
27
|
import { isClosedStatus } from "./status-guards.js";
|
|
@@ -95,6 +95,25 @@ function reportCheckboxDbStatusDivergence(
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function bareDuplicateMilestoneId(milestoneId: string): string | null {
|
|
99
|
+
const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
|
|
100
|
+
return match?.[1]?.toUpperCase() ?? null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function checkboxDbStatusMilestoneIds(basePath: string, milestoneIds: string[]): string[] {
|
|
104
|
+
if (!hasFlatPhaseLayout(basePath)) return milestoneIds;
|
|
105
|
+
|
|
106
|
+
// Flat-phase directories are keyed by phase number. A restored
|
|
107
|
+
// M003-xxxxxx row alongside M003 is a stale duplicate for the same
|
|
108
|
+
// phases/03-* projection, so checking both can compare DB status against two
|
|
109
|
+
// competing PLAN files and report a persistent false divergence.
|
|
110
|
+
const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
|
|
111
|
+
return milestoneIds.filter((milestoneId) => {
|
|
112
|
+
const bareId = bareDuplicateMilestoneId(milestoneId);
|
|
113
|
+
return !bareId || !allIds.has(bareId);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
function checkProjectionCheckboxDbStatus(basePath: string, milestoneIds: string[], issues: DoctorIssue[]): void {
|
|
99
118
|
for (const milestoneId of milestoneIds) {
|
|
100
119
|
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
@@ -208,10 +227,8 @@ function resolveFlatPhaseArtifactDiskPath(basePath: string, row: ArtifactRow): s
|
|
|
208
227
|
if (!artifactType) return null;
|
|
209
228
|
|
|
210
229
|
if (row.slice_id && row.task_id) {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
|
|
214
|
-
return existsSync(candidate) ? candidate : null;
|
|
230
|
+
const candidate = resolveTaskFile(basePath, row.milestone_id, row.slice_id, row.task_id, artifactType);
|
|
231
|
+
return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
|
|
215
232
|
}
|
|
216
233
|
|
|
217
234
|
const candidate = row.slice_id
|
|
@@ -436,7 +453,9 @@ function staleMilestonesArtifactRowFixable(basePath: string, row: ArtifactRow, a
|
|
|
436
453
|
|
|
437
454
|
function stalePhasesArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
|
|
438
455
|
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
439
|
-
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
|
|
456
|
+
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
|
|
457
|
+
(hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
|
|
458
|
+
hasPresentMilestonesReplacement(basePath, row, artifactRows));
|
|
440
459
|
}
|
|
441
460
|
|
|
442
461
|
function staleArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
|
|
@@ -809,7 +828,11 @@ export async function checkEngineHealth(
|
|
|
809
828
|
// block doctor.
|
|
810
829
|
try {
|
|
811
830
|
if (isDbAvailable()) {
|
|
812
|
-
checkProjectionCheckboxDbStatus(
|
|
831
|
+
checkProjectionCheckboxDbStatus(
|
|
832
|
+
basePath,
|
|
833
|
+
checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)),
|
|
834
|
+
issues,
|
|
835
|
+
);
|
|
813
836
|
}
|
|
814
837
|
} catch {
|
|
815
838
|
// Non-fatal: checkbox-vs-DB divergence check must never block doctor
|
|
@@ -65,8 +65,12 @@ const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|web
|
|
|
65
65
|
// transient errors so auto-mode can retry with reduced budget or fall back to a larger-context model.
|
|
66
66
|
// See: https://github.com/open-gsd/gsd-pi/issues/4528
|
|
67
67
|
const SERVER_RE = /internal(?: server)?[ _-]?error|server[ _-]?error|500|502|503|overloaded|server_error|api_error|service.?unavailable|context (?:window|length) exceed|context window exceed/i;
|
|
68
|
+
// Provider adapters throw this when an upstream stop reason reaches Pi before
|
|
69
|
+
// the adapter knows how to map it. Keep the match scoped so plain "unknown"
|
|
70
|
+
// runtime errors still pause for inspection instead of being retried forever.
|
|
71
|
+
const PROVIDER_STOP_REASON_RE = /unhandled stop reason:\s*(?:abort|unknown)\b/i;
|
|
68
72
|
// ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
|
|
69
|
-
const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
73
|
+
const CONNECTION_RE = /terminated|\babort\b|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
70
74
|
// Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
|
|
71
75
|
// This eliminates the need to enumerate every error message variant individually.
|
|
72
76
|
const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
|
|
@@ -103,7 +107,7 @@ const UNSUPPORTED_MODEL_SCOPE_RE = /\b(?:account|plan|tier|subscription)\b/i;
|
|
|
103
107
|
* 3. Network (ECONNRESET, ETIMEDOUT, socket hang up, fetch failed, dns)
|
|
104
108
|
* 4. Stream truncation (malformed JSON from mid-stream cut)
|
|
105
109
|
* 5. Server (500/502/503, overloaded, server_error)
|
|
106
|
-
* 6. Connection (terminated, ECONNREFUSED, EPIPE, other side closed)
|
|
110
|
+
* 6. Connection (terminated, abort/unknown stop reason, ECONNREFUSED, EPIPE, other side closed)
|
|
107
111
|
* 7. Model error (400 invalid argument / payload rejected for this model)
|
|
108
112
|
* 8. Unknown
|
|
109
113
|
*/
|
|
@@ -163,6 +167,13 @@ export function classifyError(errorMsg: string, retryAfterMs?: number): ErrorCla
|
|
|
163
167
|
return { kind: "rate-limit", retryAfterMs: delayMs };
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
// Provider stop-reason adapter gaps are usually request/stream failures from
|
|
171
|
+
// the current model. Treat the scoped adapter message as fallback-eligible,
|
|
172
|
+
// but keep generic unknown errors on the indefinite pause path.
|
|
173
|
+
if (PROVIDER_STOP_REASON_RE.test(errorMsg)) {
|
|
174
|
+
return { kind: "connection", retryAfterMs: retryAfterMs ?? 15_000 };
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
// 3. Network errors — same-model retry candidate
|
|
167
178
|
if (NETWORK_RE.test(errorMsg)) {
|
|
168
179
|
// Exclude if also matches permanent signals (already handled above for
|
|
@@ -155,6 +155,19 @@ function isInsideFlatProjectionBackup(backupDir: string, src: string): boolean {
|
|
|
155
155
|
return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function removeFlatProjectionBackup(backupDir: string): void {
|
|
159
|
+
const phaseBackupDir = join(backupDir, "__phases");
|
|
160
|
+
if (!existsSync(phaseBackupDir)) return;
|
|
161
|
+
try {
|
|
162
|
+
removePathWithRetries(phaseBackupDir);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
logWarning(
|
|
165
|
+
"migration",
|
|
166
|
+
`flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${(err as Error).message}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
function restoreFlatProjectionFromBackup(basePath: string, backupDir: string): void {
|
|
159
172
|
const phaseBackupDir = join(backupDir, "__phases");
|
|
160
173
|
if (!existsSync(phaseBackupDir)) return;
|
|
@@ -494,4 +507,5 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
|
|
|
494
507
|
`flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${(err as Error).message}`,
|
|
495
508
|
);
|
|
496
509
|
}
|
|
510
|
+
removeFlatProjectionBackup(backupDir);
|
|
497
511
|
}
|
|
@@ -385,10 +385,16 @@ const runtimeFilesCleanedUpRepos = new Set<string>();
|
|
|
385
385
|
// ─── Integration Branch Metadata ───────────────────────────────────────────
|
|
386
386
|
|
|
387
387
|
/**
|
|
388
|
-
* Path to the milestone metadata file
|
|
389
|
-
* Format: .gsd
|
|
388
|
+
* Path to the milestone integration-branch metadata file.
|
|
389
|
+
* Format: .gsd/<MID>-META.json (deliberately OUTSIDE milestones/<MID>/ so it
|
|
390
|
+
* cannot poison isLegacyMilestonesLayout — see ADR-045).
|
|
390
391
|
*/
|
|
391
|
-
function milestoneMetaPath(basePath: string, milestoneId: string): string {
|
|
392
|
+
export function milestoneMetaPath(basePath: string, milestoneId: string): string {
|
|
393
|
+
return join(gsdRoot(basePath), `${milestoneId}-META.json`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Legacy location (pre-ADR-045). Read-only fallback for existing on-disk trees. */
|
|
397
|
+
function legacyMilestoneMetaPath(basePath: string, milestoneId: string): string {
|
|
392
398
|
return join(gsdRoot(basePath), "milestones", milestoneId, `${milestoneId}-META.json`);
|
|
393
399
|
}
|
|
394
400
|
|
|
@@ -398,8 +404,12 @@ function milestoneMetaPath(basePath: string, milestoneId: string): string {
|
|
|
398
404
|
*/
|
|
399
405
|
export function readIntegrationBranch(basePath: string, milestoneId: string): string | null {
|
|
400
406
|
try {
|
|
401
|
-
|
|
402
|
-
if (!existsSync(metaFile))
|
|
407
|
+
let metaFile = milestoneMetaPath(basePath, milestoneId);
|
|
408
|
+
if (!existsSync(metaFile)) {
|
|
409
|
+
const legacy = legacyMilestoneMetaPath(basePath, milestoneId);
|
|
410
|
+
if (!existsSync(legacy)) return null;
|
|
411
|
+
metaFile = legacy;
|
|
412
|
+
}
|
|
403
413
|
const data = JSON.parse(readFileSync(metaFile, "utf-8"));
|
|
404
414
|
const branch = data?.integrationBranch;
|
|
405
415
|
if (typeof branch === "string" && branch.trim() !== "" && VALID_BRANCH_NAME.test(branch)) {
|
|
@@ -451,7 +461,10 @@ export function writeIntegrationBranch(
|
|
|
451
461
|
if (existingBranch === branch) return;
|
|
452
462
|
|
|
453
463
|
const metaFile = milestoneMetaPath(basePath, milestoneId);
|
|
454
|
-
|
|
464
|
+
// Ensure the flat .gsd/ root exists (never milestones/<MID>/ — that would
|
|
465
|
+
// poison layout detection, ADR-045). In production .gsd/ already exists; this
|
|
466
|
+
// guards fresh trees so the write below can't ENOENT.
|
|
467
|
+
mkdirSync(gsdRoot(basePath), { recursive: true });
|
|
455
468
|
|
|
456
469
|
// Merge with existing metadata if present
|
|
457
470
|
let existing: Record<string, unknown> = {};
|
|
@@ -67,6 +67,13 @@ export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
|
|
|
67
67
|
|
|
68
68
|
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
69
69
|
import { applyStatusTransition } from "./db/writers/status.js";
|
|
70
|
+
import {
|
|
71
|
+
LAYOUT_SEGMENTS,
|
|
72
|
+
derivePhaseSlug,
|
|
73
|
+
milestoneIdToPhaseNum,
|
|
74
|
+
milestoneIdUniqueSuffix,
|
|
75
|
+
phaseDirName,
|
|
76
|
+
} from "./layout-policy.js";
|
|
70
77
|
|
|
71
78
|
export function insertDecision(d: Omit<Decision, "seq">): void {
|
|
72
79
|
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -206,6 +213,47 @@ export function insertArtifact(a: {
|
|
|
206
213
|
});
|
|
207
214
|
}
|
|
208
215
|
|
|
216
|
+
function canonicalPhaseDirNameForDb(milestoneId: string, title: string): string {
|
|
217
|
+
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
218
|
+
const slug = derivePhaseSlug(title || milestoneId);
|
|
219
|
+
const suffix = milestoneIdUniqueSuffix(milestoneId);
|
|
220
|
+
return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function reconcileMilestonePhaseArtifactPaths(milestoneId: string, title: string): void {
|
|
224
|
+
const db = getDbOrNull()!;
|
|
225
|
+
const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
|
|
226
|
+
const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
|
|
227
|
+
const staleRows = db.prepare(
|
|
228
|
+
`SELECT path
|
|
229
|
+
FROM artifacts
|
|
230
|
+
WHERE milestone_id = :milestone_id
|
|
231
|
+
AND path LIKE :phase_prefix`,
|
|
232
|
+
).all({
|
|
233
|
+
":milestone_id": milestoneId,
|
|
234
|
+
":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
|
|
235
|
+
}) as Array<{ path: string }>;
|
|
236
|
+
|
|
237
|
+
const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
|
|
238
|
+
const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
|
|
239
|
+
const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
|
|
240
|
+
|
|
241
|
+
for (const row of staleRows) {
|
|
242
|
+
const parts = row.path.split("/");
|
|
243
|
+
if (parts.length < 3) continue;
|
|
244
|
+
if (parts[0] !== LAYOUT_SEGMENTS.level1) continue;
|
|
245
|
+
if (parts[1] === canonicalDir) continue;
|
|
246
|
+
if (!parts[1]?.startsWith(`${phaseNumPrefix}-`)) continue;
|
|
247
|
+
|
|
248
|
+
const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
|
|
249
|
+
if (existingPath.get({ ":path": newPath })) {
|
|
250
|
+
deletePath.run({ ":path": row.path });
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
updatePath.run({ ":new_path": newPath, ":old_path": row.path });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
209
257
|
export interface MilestonePlanningRecord {
|
|
210
258
|
vision: string;
|
|
211
259
|
successCriteria: string[];
|
|
@@ -287,39 +335,43 @@ export function insertMilestone(m: {
|
|
|
287
335
|
|
|
288
336
|
export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string; depends_on?: string[] }): void {
|
|
289
337
|
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
338
|
+
transaction(() => {
|
|
339
|
+
getDbOrNull()!.prepare(
|
|
340
|
+
`UPDATE milestones SET
|
|
341
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
342
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
343
|
+
depends_on = COALESCE(:depends_on, depends_on),
|
|
344
|
+
vision = COALESCE(:vision, vision),
|
|
345
|
+
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
346
|
+
key_risks = COALESCE(:key_risks, key_risks),
|
|
347
|
+
proof_strategy = COALESCE(:proof_strategy, proof_strategy),
|
|
348
|
+
verification_contract = COALESCE(:verification_contract, verification_contract),
|
|
349
|
+
verification_integration = COALESCE(:verification_integration, verification_integration),
|
|
350
|
+
verification_operational = COALESCE(:verification_operational, verification_operational),
|
|
351
|
+
verification_uat = COALESCE(:verification_uat, verification_uat),
|
|
352
|
+
definition_of_done = COALESCE(:definition_of_done, definition_of_done),
|
|
353
|
+
requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
|
|
354
|
+
boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
|
|
355
|
+
WHERE id = :id`,
|
|
356
|
+
).run({
|
|
357
|
+
":id": milestoneId,
|
|
358
|
+
":title": planning.title ?? "",
|
|
359
|
+
":status": planning.status ?? "",
|
|
360
|
+
":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
|
|
361
|
+
":vision": planning.vision ?? null,
|
|
362
|
+
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
363
|
+
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
364
|
+
":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
|
|
365
|
+
":verification_contract": planning.verificationContract ?? null,
|
|
366
|
+
":verification_integration": planning.verificationIntegration ?? null,
|
|
367
|
+
":verification_operational": planning.verificationOperational ?? null,
|
|
368
|
+
":verification_uat": planning.verificationUat ?? null,
|
|
369
|
+
":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
|
|
370
|
+
":requirement_coverage": planning.requirementCoverage ?? null,
|
|
371
|
+
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
372
|
+
});
|
|
373
|
+
const finalTitle = planning.title?.trim();
|
|
374
|
+
if (finalTitle) reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
|
|
323
375
|
});
|
|
324
376
|
}
|
|
325
377
|
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
buildPlanMilestonePrompt,
|
|
27
27
|
buildPlanSlicePrompt,
|
|
28
28
|
buildSkillActivationBlock,
|
|
29
|
+
capPreamble,
|
|
29
30
|
} from "./auto-prompts.js";
|
|
30
31
|
import { deriveState, isGhostMilestone } from "./state.js";
|
|
31
32
|
import { invalidateAllCaches } from "./cache.js";
|
|
@@ -1199,7 +1200,9 @@ function resolveDiscussSliceBasePath(basePath: string, milestoneId: string): str
|
|
|
1199
1200
|
* slice summaries so the agent can ask grounded UX/behaviour questions
|
|
1200
1201
|
* without wasting a turn reading files.
|
|
1201
1202
|
*/
|
|
1202
|
-
|
|
1203
|
+
// Exported for tests (`prompt-budget-enforcement.test.ts`) — verifies the
|
|
1204
|
+
// inlined-context cap. Not part of the public flow surface otherwise.
|
|
1205
|
+
export async function buildDiscussSlicePrompt(
|
|
1203
1206
|
mid: string,
|
|
1204
1207
|
sid: string,
|
|
1205
1208
|
sTitle: string,
|
|
@@ -1262,8 +1265,12 @@ async function buildDiscussSlicePrompt(
|
|
|
1262
1265
|
}
|
|
1263
1266
|
}
|
|
1264
1267
|
|
|
1268
|
+
// Cap the inlined block: it grows unbounded with each completed slice's full
|
|
1269
|
+
// SUMMARY. `capPreamble` truncates at `### ` section boundaries — roadmap and
|
|
1270
|
+
// milestone context come first (survive), completed-slice summaries come last
|
|
1271
|
+
// (dropped first) when the executor's window can't hold everything.
|
|
1265
1272
|
const inlinedContext = inlined.length > 0
|
|
1266
|
-
? `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`
|
|
1273
|
+
? capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`)
|
|
1267
1274
|
: `## Inlined Context\n\n_(no context files found yet — go in blind and ask broad questions)_`;
|
|
1268
1275
|
|
|
1269
1276
|
const sliceDirPath = `.gsd/milestones/${mid}/slices/${sid}`;
|
|
@@ -1275,7 +1282,7 @@ async function buildDiscussSlicePrompt(
|
|
|
1275
1282
|
: "";
|
|
1276
1283
|
|
|
1277
1284
|
const inlinedTemplates = inlineTemplate("slice-context", "Slice Context");
|
|
1278
|
-
return loadPrompt("guided-discuss-slice", {
|
|
1285
|
+
return prependLanguageDirective(base, loadPrompt("guided-discuss-slice", {
|
|
1279
1286
|
milestoneId: mid,
|
|
1280
1287
|
sliceId: sid,
|
|
1281
1288
|
sliceTitle: sTitle,
|
|
@@ -1286,7 +1293,7 @@ async function buildDiscussSlicePrompt(
|
|
|
1286
1293
|
inlinedTemplates,
|
|
1287
1294
|
structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
|
|
1288
1295
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
|
|
1289
|
-
});
|
|
1296
|
+
}));
|
|
1290
1297
|
}
|
|
1291
1298
|
|
|
1292
1299
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { readFileSync, existsSync, mkdirSync, statSync, unlinkSync } from "node:fs";
|
|
13
13
|
import { logWarning } from "./workflow-logger.js";
|
|
14
14
|
import { isClosedStatus } from "./status-guards.js";
|
|
15
|
-
import { dirname, join
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
16
|
import {
|
|
17
17
|
getAllMilestones,
|
|
18
18
|
getMilestone,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
resolveTasksDir,
|
|
41
41
|
targetMilestoneFile,
|
|
42
42
|
targetSliceFile,
|
|
43
|
+
targetTaskFile,
|
|
43
44
|
gsdProjectionRoot,
|
|
44
45
|
gsdRoot,
|
|
45
46
|
buildTaskFileName,
|
|
@@ -49,7 +50,7 @@ import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.j
|
|
|
49
50
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
50
51
|
import { invalidateStateCache } from "./state.js";
|
|
51
52
|
import { clearPathCache, milestonesDir, legacyMilestonesDir, isLegacyMilestonesLayout, resolveMilestonePath, relSliceFile, canonicalPhaseDirName } from "./paths.js";
|
|
52
|
-
import { readCompatMarker, writeCompatMarker, computeProjectionSha } from "./compat/compat-marker.js";
|
|
53
|
+
import { readCompatMarker, writeCompatMarker, computeProjectionSha, deriveCompatProjectionKey } from "./compat/compat-marker.js";
|
|
53
54
|
import type { RiskLevel } from "./types.js";
|
|
54
55
|
import {
|
|
55
56
|
phaseDirName,
|
|
@@ -108,13 +109,7 @@ function flushProjectionWritesToMarker(): void {
|
|
|
108
109
|
*/
|
|
109
110
|
function toArtifactPath(absPath: string, basePath: string): string {
|
|
110
111
|
const projectionRoot = gsdProjectionRoot(basePath);
|
|
111
|
-
|
|
112
|
-
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
113
|
-
? projectionRoot
|
|
114
|
-
: gsdRoot(basePath);
|
|
115
|
-
const rel = relative(root, absPath);
|
|
116
|
-
// Normalize to forward slashes for consistent DB keys
|
|
117
|
-
return rel.replace(/\\/g, "/");
|
|
112
|
+
return deriveCompatProjectionKey(absPath, [projectionRoot, gsdRoot(basePath)]);
|
|
118
113
|
}
|
|
119
114
|
|
|
120
115
|
/**
|
|
@@ -756,20 +751,7 @@ export async function renderTaskSummary(
|
|
|
756
751
|
return false; // No summary to render — skip silently
|
|
757
752
|
}
|
|
758
753
|
|
|
759
|
-
|
|
760
|
-
const slicePath = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
761
|
-
if (!slicePath) {
|
|
762
|
-
process.stderr.write(
|
|
763
|
-
`markdown-renderer: cannot resolve slice path for ${milestoneId}/${sliceId}\n`,
|
|
764
|
-
);
|
|
765
|
-
return false;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
// Use the tasks/ subdir when it exists (legacy layout), otherwise use the
|
|
769
|
-
// slice path directly (flat-phase: task summaries live alongside plan files).
|
|
770
|
-
const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId) ?? slicePath;
|
|
771
|
-
const fileName = buildTaskFileName(taskId, "SUMMARY");
|
|
772
|
-
const absPath = join(tasksDir, fileName);
|
|
754
|
+
const absPath = targetTaskFile(basePath, milestoneId, sliceId, taskId, "SUMMARY", getMilestone(milestoneId)?.title);
|
|
773
755
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
774
756
|
|
|
775
757
|
await writeAndStore(absPath, artifactPath, task.full_summary_md, {
|