gsd-pi 2.80.0-dev.96f4d89d5 → 2.80.0-dev.b2a374d5b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +37 -30
- package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +154 -8
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto.js +16 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
- package/dist/resources/extensions/gsd/detection.js +106 -0
- package/dist/resources/extensions/gsd/gsd-db.js +102 -2
- package/dist/resources/extensions/gsd/guided-flow.js +63 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- 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 +13 -13
- 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 +13 -13
- 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 +4 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -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 +17 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
- 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 +7 -0
- 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/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +50 -19
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +77 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +68 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +68 -19
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +88 -13
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +91 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +17 -7
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +19 -7
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +36 -27
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +42 -28
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
- package/src/resources/extensions/gsd/auto-start.ts +7 -6
- package/src/resources/extensions/gsd/auto.ts +23 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
- package/src/resources/extensions/gsd/detection.ts +128 -0
- package/src/resources/extensions/gsd/gsd-db.ts +119 -1
- package/src/resources/extensions/gsd/guided-flow.ts +66 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
- package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8b59c93905f68a66
|
|
@@ -28,7 +28,7 @@ Then do the thing `STATE.md` says to do next.
|
|
|
28
28
|
## The Hierarchy
|
|
29
29
|
|
|
30
30
|
```
|
|
31
|
-
Milestone → a shippable version (
|
|
31
|
+
Milestone → a shippable version (1-10 slices, sized to the work)
|
|
32
32
|
Slice → one demoable vertical capability (1-7 tasks)
|
|
33
33
|
Task → one context-window-sized unit of work (fits in one session)
|
|
34
34
|
```
|
|
@@ -331,7 +331,7 @@ The **Don't Hand-Roll** and **Common Pitfalls** sections prevent the most expens
|
|
|
331
331
|
|
|
332
332
|
**For a milestone (roadmap):**
|
|
333
333
|
1. Read `M###-CONTEXT.md`, `M###-RESEARCH.md`, and `.gsd/DECISIONS.md` if they exist.
|
|
334
|
-
2. Decompose the vision into
|
|
334
|
+
2. Decompose the vision into 1-10 demoable vertical slices. Prefer one slice for tiny, single-file, or static work unless the request clearly spans independent capabilities.
|
|
335
335
|
3. Order by risk (high-risk first to validate feasibility early).
|
|
336
336
|
4. Write `M###-ROADMAP.md` with checkboxes, risk levels, dependencies, demo sentences.
|
|
337
337
|
5. **Write the boundary map** — for each slice, specify what it produces (functions, types, interfaces, endpoints) and what it consumes from upstream slices. This forces interface thinking before implementation and enables deterministic verification that slices actually connect.
|
|
@@ -25,6 +25,8 @@ import { normalizeRealPath } from "../paths.js";
|
|
|
25
25
|
import { decideCooldownRecovery, decideDispatchClaim, decideEngineDispatch, decideFinalizeResult, decideInfrastructureError, decideIterationErrorRecovery, decideMemoryPressure, decideModelPolicyBlocked, decideMinRequestInterval, decideWorkflowLoop, formatDispatchExceptionSummary, formatUnhandledDispatchErrorSummary, resolveUnitRequestTimestamp, shouldUseCustomEnginePath, } from "./workflow-kernel.js";
|
|
26
26
|
import { hydrateCustomVerifyRetryCounts, saveCustomVerifyRetryCounts, } from "./custom-verify-retry-store.js";
|
|
27
27
|
import { settleDispatchCompleted, settleDispatchFailed, } from "./workflow-dispatch-ledger.js";
|
|
28
|
+
import { emitOpenUnitEndForUnit } from "../crash-recovery.js";
|
|
29
|
+
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
28
30
|
import { openDispatchClaim } from "./workflow-dispatch-claim.js";
|
|
29
31
|
import { completeWorkflowIteration } from "./workflow-iteration-completion.js";
|
|
30
32
|
import { createWorkflowJournalReporter } from "./workflow-journal-reporter.js";
|
|
@@ -133,6 +135,24 @@ async function enforceMinRequestInterval(s, prefs) {
|
|
|
133
135
|
await new Promise(r => setTimeout(r, decision.waitMs));
|
|
134
136
|
}
|
|
135
137
|
}
|
|
138
|
+
function closeOutCrashedUnit(s, iterData, err) {
|
|
139
|
+
const summary = formatDispatchExceptionSummary({ error: err });
|
|
140
|
+
try {
|
|
141
|
+
emitOpenUnitEndForUnit(s.basePath, iterData.unitType, iterData.unitId, "cancelled", {
|
|
142
|
+
message: summary,
|
|
143
|
+
category: "unit-exception",
|
|
144
|
+
isTransient: false,
|
|
145
|
+
});
|
|
146
|
+
writeUnitRuntimeRecord(s.basePath, iterData.unitType, iterData.unitId, s.currentUnit?.startedAt ?? Date.now(), {
|
|
147
|
+
phase: "crashed",
|
|
148
|
+
lastProgressAt: Date.now(),
|
|
149
|
+
lastProgressKind: "unit-exception",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (closeoutErr) {
|
|
153
|
+
logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
136
156
|
/**
|
|
137
157
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
138
158
|
* runUnit → finalize → repeat. Exits when s.active becomes false or a
|
|
@@ -392,7 +412,17 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
392
412
|
}
|
|
393
413
|
// ── Unit execution (shared with dev path) ──
|
|
394
414
|
await enforceMinRequestInterval(s, prefs);
|
|
395
|
-
|
|
415
|
+
let unitPhaseResult;
|
|
416
|
+
try {
|
|
417
|
+
unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, undefined, unitDispatchDeps);
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
closeOutCrashedUnit(s, iterData, err);
|
|
424
|
+
throw err;
|
|
425
|
+
}
|
|
396
426
|
if (unitPhaseResult.action === "next") {
|
|
397
427
|
const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
|
|
398
428
|
if (requestTimestamp !== undefined)
|
|
@@ -580,6 +610,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
580
610
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
581
611
|
throw err;
|
|
582
612
|
}
|
|
613
|
+
closeOutCrashedUnit(s, iterData, err);
|
|
583
614
|
dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
|
|
584
615
|
markFailed: markDispatchFailed,
|
|
585
616
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
@@ -13,13 +13,13 @@ import { detectStuck } from "./detect-stuck.js";
|
|
|
13
13
|
import { runUnit } from "./run-unit.js";
|
|
14
14
|
import { debugLog } from "../debug-logger.js";
|
|
15
15
|
import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
|
|
16
|
-
import {
|
|
16
|
+
import { classifyProject } from "../detection.js";
|
|
17
17
|
import { MergeConflictError } from "../git-service.js";
|
|
18
18
|
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
19
19
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
20
20
|
import { resumeAutoAfterProviderDelay } from "../bootstrap/provider-error-resume.js";
|
|
21
21
|
import { join, basename } from "node:path";
|
|
22
|
-
import { existsSync, cpSync
|
|
22
|
+
import { existsSync, cpSync } from "node:fs";
|
|
23
23
|
import { logWarning, logError, _resetLogs, drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
|
|
24
24
|
import { gsdRoot } from "../paths.js";
|
|
25
25
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
@@ -498,7 +498,7 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
498
498
|
}
|
|
499
499
|
// #2909: postflight — restore stashed changes after successful merge
|
|
500
500
|
if (preflightTransition.stashPushed) {
|
|
501
|
-
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
501
|
+
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightTransition.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
502
502
|
}
|
|
503
503
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
504
504
|
deps.invalidateAllCaches();
|
|
@@ -574,7 +574,7 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
574
574
|
}
|
|
575
575
|
// #2909: postflight — restore stashed changes after successful merge
|
|
576
576
|
if (preflightAllComplete.stashPushed) {
|
|
577
|
-
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
577
|
+
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightAllComplete.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
578
578
|
}
|
|
579
579
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
580
580
|
}
|
|
@@ -660,7 +660,7 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
660
660
|
}
|
|
661
661
|
// #2909: postflight — restore stashed changes after successful merge
|
|
662
662
|
if (preflightComplete.stashPushed) {
|
|
663
|
-
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
663
|
+
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightComplete.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
664
664
|
}
|
|
665
665
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
666
666
|
}
|
|
@@ -1084,8 +1084,9 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1084
1084
|
// Verify the working directory is a valid git checkout with project
|
|
1085
1085
|
// files before dispatching work. A broken worktree causes agents to
|
|
1086
1086
|
// hallucinate summaries since they cannot read or write any files.
|
|
1087
|
-
// Uses
|
|
1088
|
-
//
|
|
1087
|
+
// Uses project classification so project presence is not conflated with
|
|
1088
|
+
// ecosystem marker detection. Static/minimal repos become untyped-existing.
|
|
1089
|
+
let projectClassification = null;
|
|
1089
1090
|
if (s.basePath && unitType === "execute-task") {
|
|
1090
1091
|
const gitMarker = join(s.basePath, ".git");
|
|
1091
1092
|
const hasGit = deps.existsSync(gitMarker);
|
|
@@ -1096,30 +1097,26 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1096
1097
|
await deps.stopAuto(ctx, pi, msg);
|
|
1097
1098
|
return { action: "break", reason: "worktree-invalid" };
|
|
1098
1099
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1100
|
+
projectClassification = classifyProject(s.basePath);
|
|
1101
|
+
if (projectClassification.kind === "invalid-repo") {
|
|
1102
|
+
const msg = `Worktree health check failed: ${s.basePath} classified as invalid-repo (${projectClassification.reason}) — refusing to dispatch ${unitType} ${unitId}`;
|
|
1103
|
+
debugLog("runUnitPhase", { phase: "worktree-health-invalid-repo", basePath: s.basePath, classification: projectClassification });
|
|
1104
|
+
if (projectClassification.reason === "missing .git" && hasGit) {
|
|
1105
|
+
ctx.ui.notify(`Warning: ${s.basePath} project classification could not confirm .git; assuming it has no project content yet — proceeding as greenfield project because worktree health reported .git present`, "warning");
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
ctx.ui.notify(msg, "error");
|
|
1109
|
+
await deps.stopAuto(ctx, pi, msg);
|
|
1110
|
+
return { action: "break", reason: "worktree-invalid" };
|
|
1111
|
+
}
|
|
1107
1112
|
}
|
|
1108
|
-
|
|
1109
|
-
debugLog("runUnitPhase", { phase: "
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
? hasProjectFileInAncestor(s.basePath, deps.existsSync)
|
|
1116
|
-
: false;
|
|
1117
|
-
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
|
|
1118
|
-
// Greenfield projects won't have project files yet — the first task creates them.
|
|
1119
|
-
// Log a warning but allow execution to proceed. The .git check above is sufficient
|
|
1120
|
-
// to ensure we're in a valid working directory.
|
|
1121
|
-
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
|
|
1122
|
-
ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
|
|
1113
|
+
else if (projectClassification.kind === "greenfield") {
|
|
1114
|
+
debugLog("runUnitPhase", { phase: "worktree-health-greenfield", basePath: s.basePath, classification: projectClassification });
|
|
1115
|
+
ctx.ui.notify(`Warning: ${s.basePath} has no project content yet — proceeding as greenfield project`, "warning");
|
|
1116
|
+
}
|
|
1117
|
+
else if (projectClassification.kind === "untyped-existing") {
|
|
1118
|
+
debugLog("runUnitPhase", { phase: "worktree-health-untyped-existing", basePath: s.basePath, classification: projectClassification });
|
|
1119
|
+
ctx.ui.notify(`Notice: ${s.basePath} has existing project content but no recognized tooling markers — using generic file-level workflow guidance`, "info");
|
|
1123
1120
|
}
|
|
1124
1121
|
}
|
|
1125
1122
|
// Detect retry and capture previous tier for escalation
|
|
@@ -1182,6 +1179,16 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1182
1179
|
}
|
|
1183
1180
|
// Prompt injection
|
|
1184
1181
|
let finalPrompt = prompt;
|
|
1182
|
+
if (unitType === "execute-task") {
|
|
1183
|
+
projectClassification ??= classifyProject(s.basePath);
|
|
1184
|
+
if (projectClassification.kind === "untyped-existing") {
|
|
1185
|
+
const samples = projectClassification.contentFiles.slice(0, 8).join(", ") || "project files";
|
|
1186
|
+
finalPrompt +=
|
|
1187
|
+
"\n\n**Project classification:** Existing untyped project. No recognized build/tooling markers were detected, " +
|
|
1188
|
+
"so use generic file-level workflow guidance. Task plans and completion summaries must list every concrete " +
|
|
1189
|
+
`project file changed in \`files\` or \`expected_output\`. Detected content sample: ${samples}.`;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1185
1192
|
if (s.pendingVerificationRetry) {
|
|
1186
1193
|
const retryCtx = s.pendingVerificationRetry;
|
|
1187
1194
|
s.pendingVerificationRetry = null;
|
|
@@ -93,6 +93,16 @@ export function hasPendingDeepStage(prefs, basePath) {
|
|
|
93
93
|
const gate = getDeepStageGate(prefs, basePath);
|
|
94
94
|
return gate.status === "pending" || gate.status === "blocked";
|
|
95
95
|
}
|
|
96
|
+
export function shouldRunDeepProjectSetup(state, prefs, basePath, options = {}) {
|
|
97
|
+
if (options.hasSurvivorBranch === true)
|
|
98
|
+
return false;
|
|
99
|
+
if (state.phase !== "pre-planning" &&
|
|
100
|
+
state.phase !== "needs-discussion" &&
|
|
101
|
+
state.phase !== "planning") {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return hasPendingDeepStage(prefs, basePath);
|
|
105
|
+
}
|
|
96
106
|
function missingSliceStop(mid, phase) {
|
|
97
107
|
return {
|
|
98
108
|
action: "stop",
|
|
@@ -25,7 +25,7 @@ import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceh
|
|
|
25
25
|
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
26
26
|
import { syncStateToProjectRoot } from "./auto-worktree.js";
|
|
27
27
|
import { normalizeWorktreePathForCompare } from "./worktree-root.js";
|
|
28
|
-
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
28
|
+
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
|
|
29
29
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
30
30
|
import { consumeSignal } from "./session-status-io.js";
|
|
31
31
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
|
|
@@ -719,21 +719,21 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
719
719
|
}
|
|
720
720
|
}
|
|
721
721
|
// Evidence cross-reference (execute-task only)
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
// we can still detect units that claimed success but ran no commands.
|
|
722
|
+
// Only compare against concrete command evidence persisted by the task
|
|
723
|
+
// completion tool. A prose Verify field can be satisfied later by the
|
|
724
|
+
// host verification gate, so it is not enough to accuse the unit.
|
|
726
725
|
if (safetyConfig.evidence_cross_reference && s.currentUnit.type === "execute-task") {
|
|
727
726
|
try {
|
|
728
727
|
const actual = getEvidence();
|
|
729
728
|
const bashCalls = actual.filter(e => e.kind === "bash");
|
|
730
|
-
// If the task is marked complete but zero bash commands were run,
|
|
731
|
-
// it's suspicious — the LLM may have fabricated results.
|
|
732
729
|
if (sMid && sSid && sTid && isDbAvailable()) {
|
|
733
730
|
const taskRow = getTask(sMid, sSid, sTid);
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
731
|
+
const claimedCommands = getVerificationEvidence(sMid, sSid, sTid)
|
|
732
|
+
.map((row) => row.command)
|
|
733
|
+
.filter((command) => typeof command === "string" && command.trim().length > 0);
|
|
734
|
+
if (taskRow?.status === "complete" && claimedCommands.length > 0 && bashCalls.length === 0) {
|
|
735
|
+
logWarning("safety", "task claimed verification command evidence but no execution tool calls were recorded");
|
|
736
|
+
ctx.ui.notify(`Safety: task ${sTid} claimed command evidence but no execution tool calls were recorded`, "warning");
|
|
737
737
|
}
|
|
738
738
|
}
|
|
739
739
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* utility.
|
|
7
7
|
*/
|
|
8
8
|
import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
|
|
9
|
-
import { hasVerdict, getUatType } from "./verdict-parser.js";
|
|
9
|
+
import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
|
|
10
10
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
11
11
|
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
12
12
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
|
|
@@ -25,6 +25,7 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
25
25
|
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
26
26
|
import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
|
|
27
27
|
import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
|
|
28
|
+
import { classifyProject } from "./detection.js";
|
|
28
29
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
29
30
|
/**
|
|
30
31
|
* Historical static ceiling for the preamble cap. Kept as an upper bound even
|
|
@@ -62,6 +63,104 @@ function resolvePromptBudgets() {
|
|
|
62
63
|
function resolveSummaryBudgetChars() {
|
|
63
64
|
return resolvePromptBudgets().summaryBudgetChars;
|
|
64
65
|
}
|
|
66
|
+
function formatProjectClassificationForPlanning(classification) {
|
|
67
|
+
const sampleFiles = classification.contentFiles.slice(0, 8);
|
|
68
|
+
const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
|
|
69
|
+
const lines = [
|
|
70
|
+
"### Project Classification",
|
|
71
|
+
"",
|
|
72
|
+
`- **Kind:** ${classification.kind}`,
|
|
73
|
+
`- **Content files:** ${classification.contentFiles.length}`,
|
|
74
|
+
`- **Sample files:** ${sample}`,
|
|
75
|
+
`- **Reason:** ${classification.reason}`,
|
|
76
|
+
"",
|
|
77
|
+
];
|
|
78
|
+
if (classification.kind === "untyped-existing") {
|
|
79
|
+
if (classification.contentFiles.length <= 2) {
|
|
80
|
+
lines.push("**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.");
|
|
81
|
+
}
|
|
82
|
+
else if (classification.contentFiles.length <= 5) {
|
|
83
|
+
lines.push("**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.");
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
lines.push("**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (classification.kind === "greenfield") {
|
|
90
|
+
lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
|
|
91
|
+
}
|
|
92
|
+
else if (classification.kind === "typed-existing") {
|
|
93
|
+
lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
|
|
97
|
+
}
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
function normalizeArtifactRef(value) {
|
|
101
|
+
return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
|
|
102
|
+
}
|
|
103
|
+
function parseCoveredArtifacts(validationContent) {
|
|
104
|
+
const covered = new Set();
|
|
105
|
+
const lines = validationContent.split(/\r?\n/);
|
|
106
|
+
let inCoveredArtifacts = false;
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
|
|
109
|
+
inCoveredArtifacts = true;
|
|
110
|
+
const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
|
|
111
|
+
if (inline && inline !== "[]") {
|
|
112
|
+
inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!inCoveredArtifacts)
|
|
117
|
+
continue;
|
|
118
|
+
if (/^\S/.test(line) && !/^\s*-/.test(line))
|
|
119
|
+
break;
|
|
120
|
+
const item = line.match(/^\s*-\s*(.+)$/)?.[1];
|
|
121
|
+
if (item)
|
|
122
|
+
covered.add(normalizeArtifactRef(item));
|
|
123
|
+
}
|
|
124
|
+
return covered;
|
|
125
|
+
}
|
|
126
|
+
function isValidationFreshOrApplicable(validationContent, currentArtifacts) {
|
|
127
|
+
if (!validationContent)
|
|
128
|
+
return false;
|
|
129
|
+
if (!/validation_metadata:/i.test(validationContent))
|
|
130
|
+
return false;
|
|
131
|
+
const coveredArtifacts = parseCoveredArtifacts(validationContent);
|
|
132
|
+
if (coveredArtifacts.size === 0)
|
|
133
|
+
return false;
|
|
134
|
+
return currentArtifacts
|
|
135
|
+
.map(normalizeArtifactRef)
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.every((artifact) => coveredArtifacts.has(artifact));
|
|
138
|
+
}
|
|
139
|
+
function formatCloseoutReviewInstructions(validationContent, validationRel, currentArtifacts) {
|
|
140
|
+
const verdict = validationContent ? extractVerdict(validationContent) : null;
|
|
141
|
+
const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
|
|
142
|
+
if (verdict === "pass" && validationFresh) {
|
|
143
|
+
return [
|
|
144
|
+
"### Passing Validation Artifact",
|
|
145
|
+
"",
|
|
146
|
+
`A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
|
|
147
|
+
"",
|
|
148
|
+
"Do not delegate fresh reviewer/security/tester audits and do not redo the validation evidence review unless the artifact is internally inconsistent with the inlined summaries. Focus this unit on final milestone narrative, learnings, PROJECT/requirements updates, and `gsd_complete_milestone`.",
|
|
149
|
+
].join("\n");
|
|
150
|
+
}
|
|
151
|
+
if (verdict) {
|
|
152
|
+
return [
|
|
153
|
+
"### Validation Requires Attention",
|
|
154
|
+
"",
|
|
155
|
+
`A validation artifact is present at \`${validationRel}\` with verdict \`${verdict}\`, but it is missing freshness metadata or does not cover current milestone artifacts. Do not treat the milestone as complete unless the issues are resolved and evidence supports completion.`,
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
158
|
+
return [
|
|
159
|
+
"### No Passing Validation Artifact",
|
|
160
|
+
"",
|
|
161
|
+
`No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
|
|
162
|
+
].join("\n");
|
|
163
|
+
}
|
|
65
164
|
function capPreamble(preamble) {
|
|
66
165
|
// Cap inlined context at min(historical 30K ceiling, scaled inline budget).
|
|
67
166
|
// The ceiling preserves pre-fix behavior for large-window users; the scaled
|
|
@@ -1465,6 +1564,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
1465
1564
|
const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
|
|
1466
1565
|
if (researchAnchor)
|
|
1467
1566
|
inlined.push(formatAnchorForPrompt(researchAnchor));
|
|
1567
|
+
inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
|
|
1468
1568
|
inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
|
|
1469
1569
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
|
|
1470
1570
|
if (researchInline)
|
|
@@ -2017,6 +2117,9 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
2017
2117
|
const inlineLevel = level ?? resolveInlineLevel();
|
|
2018
2118
|
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
2019
2119
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
2120
|
+
const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
|
|
2121
|
+
const validationRel = relMilestoneFile(base, mid, "VALIDATION");
|
|
2122
|
+
const validationContent = validationPath ? await loadFile(validationPath) : null;
|
|
2020
2123
|
const inlined = [];
|
|
2021
2124
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
2022
2125
|
// Inline all slice summaries (deduplicated by slice ID)
|
|
@@ -2056,6 +2159,13 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
2056
2159
|
const pathList = summaryRelPaths.map(p => `- \`${p}\``).join("\n");
|
|
2057
2160
|
inlined.push(`### On-demand Slice Summaries\n\nExcerpted above. Read the full file for any slice when the excerpt's section heads don't carry enough narrative for the milestone summary you're drafting:\n\n${pathList}`);
|
|
2058
2161
|
}
|
|
2162
|
+
const validationContext = [
|
|
2163
|
+
formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
|
|
2164
|
+
];
|
|
2165
|
+
if (validationContent) {
|
|
2166
|
+
validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
|
|
2167
|
+
}
|
|
2168
|
+
inlined.unshift(...validationContext);
|
|
2059
2169
|
// Inline root GSD files (skip for minimal — completion can read these if needed)
|
|
2060
2170
|
if (inlineLevel !== "minimal") {
|
|
2061
2171
|
const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
|
|
@@ -12,7 +12,7 @@ import { appendEvent } from "./workflow-events.js";
|
|
|
12
12
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
13
13
|
import { clearParseCache } from "./files.js";
|
|
14
14
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
15
|
-
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
|
|
15
|
+
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk, getCompletedMilestoneTaskFileHints, getMilestoneCommitAttributionShas, recordMilestoneCommitAttribution } from "./gsd-db.js";
|
|
16
16
|
import { isValidationTerminal } from "./state.js";
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
18
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -110,17 +110,32 @@ export function hasImplementationArtifacts(basePath, milestoneId) {
|
|
|
110
110
|
// milestone commits instead of treating the self-diff as proof of no work.
|
|
111
111
|
if (changedFiles.length === 0) {
|
|
112
112
|
if (milestoneId && currentBranch === integrationBranch) {
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
113
|
+
const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
|
|
114
|
+
if (!milestoneEvidence.ok)
|
|
115
115
|
return "unknown";
|
|
116
|
-
if (
|
|
117
|
-
return classifyImplementationFiles(
|
|
116
|
+
if (milestoneEvidence.matched)
|
|
117
|
+
return classifyImplementationFiles(milestoneEvidence.files);
|
|
118
118
|
}
|
|
119
119
|
if (currentBranch && currentBranch !== "HEAD")
|
|
120
120
|
return "absent";
|
|
121
121
|
return "unknown";
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
const branchClassification = classifyImplementationFiles(changedFiles);
|
|
124
|
+
if (branchClassification === "present")
|
|
125
|
+
return "present";
|
|
126
|
+
// A completing milestone branch can have a non-empty diff containing only
|
|
127
|
+
// .gsd/ closeout files after implementation commits already landed on the
|
|
128
|
+
// recorded integration branch. In that topology, the branch diff alone is
|
|
129
|
+
// insufficient; use the same milestone-tagged evidence fallback as the
|
|
130
|
+
// self-diff retry path before declaring the milestone implementation-free.
|
|
131
|
+
if (milestoneId) {
|
|
132
|
+
const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
|
|
133
|
+
if (!milestoneEvidence.ok)
|
|
134
|
+
return "unknown";
|
|
135
|
+
if (milestoneEvidence.matched)
|
|
136
|
+
return classifyImplementationFiles(milestoneEvidence.files);
|
|
137
|
+
}
|
|
138
|
+
return "absent";
|
|
124
139
|
}
|
|
125
140
|
catch (e) {
|
|
126
141
|
// Non-fatal — if git operations fail, return unknown so callers can decide
|
|
@@ -148,6 +163,9 @@ function classifyImplementationFiles(files) {
|
|
|
148
163
|
function isImplementationPath(file) {
|
|
149
164
|
return !file.startsWith(".gsd/") && !file.startsWith(".gsd\\");
|
|
150
165
|
}
|
|
166
|
+
function normalizeRepoPath(file) {
|
|
167
|
+
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
168
|
+
}
|
|
151
169
|
/**
|
|
152
170
|
* Detect the main/master branch name.
|
|
153
171
|
*/
|
|
@@ -217,7 +235,7 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
217
235
|
]);
|
|
218
236
|
if (!scoped.ok)
|
|
219
237
|
return scoped;
|
|
220
|
-
if (scoped.matched)
|
|
238
|
+
if (scoped.matched && classifyImplementationFiles(scoped.files) === "present")
|
|
221
239
|
return scoped;
|
|
222
240
|
// Fallback (#5033): when .gsd/ is gitignored / external / untracked, the
|
|
223
241
|
// path-scoped scan matches no commits even though GSD-tagged commits
|
|
@@ -228,9 +246,137 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
228
246
|
// Intentionally unbounded — symmetric with the primary scan, and avoids
|
|
229
247
|
// reintroducing the rolling-depth failure class removed in #4699 where
|
|
230
248
|
// milestone evidence aged out behind unrelated activity.
|
|
231
|
-
|
|
249
|
+
const unscoped = scanGsdTaggedCommits(basePath, milestoneId, [
|
|
232
250
|
"log", "--format=%H%x1f%B%x1e", "HEAD",
|
|
233
251
|
]);
|
|
252
|
+
if (!unscoped.ok)
|
|
253
|
+
return scoped.matched ? scoped : unscoped;
|
|
254
|
+
if (!unscoped.matched)
|
|
255
|
+
return scoped;
|
|
256
|
+
return {
|
|
257
|
+
ok: true,
|
|
258
|
+
matched: true,
|
|
259
|
+
files: [...new Set([...scoped.files, ...unscoped.files])],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function getChangedFilesFromMilestoneEvidence(basePath, milestoneId) {
|
|
263
|
+
const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
|
|
264
|
+
if (!tagged.ok)
|
|
265
|
+
return tagged;
|
|
266
|
+
if (tagged.matched && classifyImplementationFiles(tagged.files) === "present")
|
|
267
|
+
return tagged;
|
|
268
|
+
const attributed = getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId);
|
|
269
|
+
if (!attributed.ok)
|
|
270
|
+
return tagged.matched ? tagged : attributed;
|
|
271
|
+
if (attributed.matched && classifyImplementationFiles(attributed.files) === "present")
|
|
272
|
+
return attributed;
|
|
273
|
+
const backfilled = backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId);
|
|
274
|
+
if (!backfilled.ok)
|
|
275
|
+
return tagged.matched ? tagged : attributed.matched ? attributed : backfilled;
|
|
276
|
+
if (!backfilled.matched) {
|
|
277
|
+
if (tagged.matched)
|
|
278
|
+
return tagged;
|
|
279
|
+
return attributed.matched ? attributed : backfilled;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
ok: true,
|
|
283
|
+
matched: true,
|
|
284
|
+
files: [...new Set([...tagged.files, ...attributed.files, ...backfilled.files])],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId) {
|
|
288
|
+
try {
|
|
289
|
+
const shas = getMilestoneCommitAttributionShas(milestoneId);
|
|
290
|
+
if (shas.length === 0)
|
|
291
|
+
return { ok: true, matched: false, files: [] };
|
|
292
|
+
const files = new Set();
|
|
293
|
+
let matched = false;
|
|
294
|
+
for (const sha of shas) {
|
|
295
|
+
if (!isFullCommitSha(sha))
|
|
296
|
+
continue;
|
|
297
|
+
const commitFiles = getChangedFilesForCommit(basePath, sha);
|
|
298
|
+
if (commitFiles.length === 0)
|
|
299
|
+
continue;
|
|
300
|
+
matched = true;
|
|
301
|
+
for (const file of commitFiles)
|
|
302
|
+
files.add(file);
|
|
303
|
+
}
|
|
304
|
+
return { ok: true, matched, files: [...files] };
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
logWarning("recovery", `milestone attribution scan failed: ${e.message}`);
|
|
308
|
+
return { ok: false, matched: false, files: [] };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId) {
|
|
312
|
+
try {
|
|
313
|
+
const milestone = getMilestone(milestoneId);
|
|
314
|
+
const milestoneStartedAt = milestone?.created_at ? Math.floor(Date.parse(milestone.created_at) / 1000) * 1000 : NaN;
|
|
315
|
+
if (!Number.isFinite(milestoneStartedAt))
|
|
316
|
+
return { ok: true, matched: false, files: [] };
|
|
317
|
+
const taskFileHints = getCompletedMilestoneTaskFileHints(milestoneId);
|
|
318
|
+
if (taskFileHints.length === 0)
|
|
319
|
+
return { ok: true, matched: false, files: [] };
|
|
320
|
+
const hintSet = new Set(taskFileHints.map(normalizeRepoPath).filter(Boolean));
|
|
321
|
+
if (hintSet.size === 0)
|
|
322
|
+
return { ok: true, matched: false, files: [] };
|
|
323
|
+
const records = getCommitRecords(basePath);
|
|
324
|
+
const files = new Set();
|
|
325
|
+
let matched = false;
|
|
326
|
+
for (const record of records) {
|
|
327
|
+
if (!isFullCommitSha(record.hash))
|
|
328
|
+
continue;
|
|
329
|
+
if (Date.parse(record.committedAt) < milestoneStartedAt)
|
|
330
|
+
continue;
|
|
331
|
+
if (record.parents.trim().split(/\s+/).filter(Boolean).length > 1)
|
|
332
|
+
continue;
|
|
333
|
+
if (commitMessageHasGsdTrailer(record.message))
|
|
334
|
+
continue;
|
|
335
|
+
const commitFiles = getChangedFilesForCommit(basePath, record.hash);
|
|
336
|
+
const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
|
|
337
|
+
if (implementationFiles.length === 0)
|
|
338
|
+
continue;
|
|
339
|
+
if (!implementationFiles.some((file) => hintSet.has(file)))
|
|
340
|
+
continue;
|
|
341
|
+
matched = true;
|
|
342
|
+
for (const file of implementationFiles)
|
|
343
|
+
files.add(file);
|
|
344
|
+
recordMilestoneCommitAttribution({
|
|
345
|
+
commitSha: record.hash,
|
|
346
|
+
milestoneId,
|
|
347
|
+
source: "backfill",
|
|
348
|
+
confidence: 0.8,
|
|
349
|
+
files: implementationFiles,
|
|
350
|
+
createdAt: new Date().toISOString(),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return { ok: true, matched, files: [...files] };
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
logWarning("recovery", `milestone attribution backfill failed: ${e.message}`);
|
|
357
|
+
return { ok: false, matched: false, files: [] };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function getCommitRecords(basePath) {
|
|
361
|
+
const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%x1e", "HEAD"], {
|
|
362
|
+
cwd: basePath,
|
|
363
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
364
|
+
encoding: "utf-8",
|
|
365
|
+
});
|
|
366
|
+
return logOutput
|
|
367
|
+
.split("\x1e")
|
|
368
|
+
.map((record) => record.trim())
|
|
369
|
+
.filter(Boolean)
|
|
370
|
+
.flatMap((record) => {
|
|
371
|
+
const parts = record.split("\x1f");
|
|
372
|
+
if (parts.length < 4)
|
|
373
|
+
return [];
|
|
374
|
+
const [hash, parents, committedAt, ...messageParts] = parts;
|
|
375
|
+
return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message: messageParts.join("\x1f") }];
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function isFullCommitSha(value) {
|
|
379
|
+
return /^[0-9a-f]{40}$/i.test(value);
|
|
234
380
|
}
|
|
235
381
|
function scanGsdTaggedCommits(basePath, milestoneId, gitArgs) {
|
|
236
382
|
try {
|
|
@@ -487,9 +487,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
487
487
|
hasSurvivorBranch = false;
|
|
488
488
|
}
|
|
489
489
|
const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
: false;
|
|
490
|
+
const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
|
|
491
|
+
const deepProjectStagePending = shouldRunDeepProjectSetup(state, effectivePrefs, base, { hasSurvivorBranch });
|
|
493
492
|
if (deepProjectStagePending) {
|
|
494
493
|
// Deep project-level setup runs before the first milestone exists. Let
|
|
495
494
|
// the auto loop dispatch workflow-preferences / project / requirements
|