gsd-pi 2.49.0-dev.de3d9f6 → 2.50.0-dev.9476db8
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/headless-ui.js +12 -2
- package/dist/headless.js +29 -13
- package/dist/resources/extensions/gsd/auto/infra-errors.js +1 -0
- package/dist/resources/extensions/gsd/auto/phases.js +11 -11
- package/dist/resources/extensions/gsd/auto/resolve.js +2 -2
- package/dist/resources/extensions/gsd/auto/run-unit.js +2 -2
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +8 -10
- package/dist/resources/extensions/gsd/auto-dashboard.js +6 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +33 -21
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -24
- package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
- package/dist/resources/extensions/gsd/auto-recovery.js +62 -184
- package/dist/resources/extensions/gsd/auto-start.js +4 -31
- package/dist/resources/extensions/gsd/auto-timers.js +2 -2
- package/dist/resources/extensions/gsd/auto-verification.js +4 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +257 -113
- package/dist/resources/extensions/gsd/auto.js +7 -5
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +89 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -1
- package/dist/resources/extensions/gsd/branch-patterns.js +13 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +5 -1234
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +168 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +28 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +405 -0
- package/dist/resources/extensions/gsd/doctor-global-checks.js +74 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +600 -0
- package/dist/resources/extensions/gsd/doctor.js +9 -1
- package/dist/resources/extensions/gsd/extension-manifest.json +1 -1
- package/dist/resources/extensions/gsd/git-service.js +9 -10
- package/dist/resources/extensions/gsd/gsd-db.js +124 -1
- package/dist/resources/extensions/gsd/guided-flow-queue.js +10 -11
- package/dist/resources/extensions/gsd/markdown-renderer.js +33 -5
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +9 -8
- package/dist/resources/extensions/gsd/prompts/execute-task.md +16 -13
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +8 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/repo-identity.js +29 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +2 -2
- package/dist/resources/extensions/gsd/session-forensics.js +6 -11
- package/dist/resources/extensions/gsd/session-lock.js +67 -56
- package/dist/resources/extensions/gsd/state.js +34 -7
- package/dist/resources/extensions/gsd/templates/milestone-summary.md +8 -0
- package/dist/resources/extensions/gsd/templates/plan.md +16 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +13 -0
- package/dist/resources/extensions/gsd/templates/slice-summary.md +9 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +24 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -3
- package/dist/resources/extensions/gsd/verdict-parser.js +84 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
- package/dist/resources/extensions/gsd/worktree.js +3 -2
- package/dist/resources/extensions/remote-questions/config.js +3 -5
- package/dist/resources/extensions/search-the-web/native-search.js +8 -3
- package/dist/resources/extensions/search-the-web/tool-search.js +19 -2
- package/dist/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.7c75ac378de0f2b5.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-0a4cd455ec4197d2.js → webpack-2473ce2c3879fff4.js} +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +39 -10
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +39 -8
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.js +8 -3
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +9 -2
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.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 +7 -32
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js +5 -0
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +0 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/blob-store.ts +6 -3
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +9 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +7 -32
- package/packages/pi-coding-agent/src/modes/rpc/jsonl.ts +6 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/infra-errors.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +10 -11
- package/src/resources/extensions/gsd/auto/resolve.ts +3 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +2 -2
- package/src/resources/extensions/gsd/auto/session.ts +5 -0
- package/src/resources/extensions/gsd/auto/types.ts +13 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +19 -21
- package/src/resources/extensions/gsd/auto-dashboard.ts +5 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +39 -21
- package/src/resources/extensions/gsd/auto-loop.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -28
- package/src/resources/extensions/gsd/auto-prompts.ts +113 -19
- package/src/resources/extensions/gsd/auto-recovery.ts +65 -199
- package/src/resources/extensions/gsd/auto-start.ts +7 -27
- package/src/resources/extensions/gsd/auto-timers.ts +2 -2
- package/src/resources/extensions/gsd/auto-verification.ts +4 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +305 -108
- package/src/resources/extensions/gsd/auto.ts +11 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +93 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/branch-patterns.ts +16 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +5 -1291
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +182 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +30 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +415 -0
- package/src/resources/extensions/gsd/doctor-global-checks.ts +84 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +626 -0
- package/src/resources/extensions/gsd/doctor.ts +9 -1
- package/src/resources/extensions/gsd/extension-manifest.json +1 -1
- package/src/resources/extensions/gsd/git-service.ts +7 -15
- package/src/resources/extensions/gsd/gsd-db.ts +150 -2
- package/src/resources/extensions/gsd/guided-flow-queue.ts +11 -12
- package/src/resources/extensions/gsd/markdown-renderer.ts +37 -4
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +37 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +9 -8
- package/src/resources/extensions/gsd/prompts/execute-task.md +16 -13
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +8 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/repo-identity.ts +28 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +2 -2
- package/src/resources/extensions/gsd/session-forensics.ts +6 -11
- package/src/resources/extensions/gsd/session-lock.ts +92 -64
- package/src/resources/extensions/gsd/state.ts +38 -5
- package/src/resources/extensions/gsd/templates/milestone-summary.md +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +16 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +13 -0
- package/src/resources/extensions/gsd/templates/slice-summary.md +9 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +24 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +1 -81
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +9 -12
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +115 -1
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +65 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +347 -0
- package/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +223 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +44 -4
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +0 -16
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +204 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -3
- package/src/resources/extensions/gsd/types.ts +30 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +95 -0
- package/src/resources/extensions/gsd/verification-gate.ts +0 -2
- package/src/resources/extensions/gsd/worktree-resolver.ts +31 -0
- package/src/resources/extensions/gsd/worktree.ts +3 -2
- package/src/resources/extensions/remote-questions/config.ts +3 -5
- package/src/resources/extensions/search-the-web/native-search.ts +8 -3
- package/src/resources/extensions/search-the-web/tool-search.ts +22 -2
- package/src/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +0 -191
- package/dist/resources/extensions/gsd/resource-version.js +0 -97
- package/dist/web/standalone/.next/static/chunks/4024.11ca5c01938e5948.js +0 -9
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -234
- package/src/resources/extensions/gsd/resource-version.ts +0 -101
- /package/dist/web/standalone/.next/static/{ceckLbAMjhzHaQ3RPtJnT → MkE9kzqUGny3-cSE0GNnm}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ceckLbAMjhzHaQ3RPtJnT → MkE9kzqUGny3-cSE0GNnm}/_ssgManifest.js +0 -0
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
insertTask,
|
|
7
7
|
upsertSlicePlanning,
|
|
8
8
|
upsertTaskPlanning,
|
|
9
|
+
insertGateRow,
|
|
9
10
|
_getAdapter,
|
|
10
11
|
} from "../gsd-db.js";
|
|
12
|
+
import type { GateId } from "../types.js";
|
|
11
13
|
import { invalidateStateCache } from "../state.js";
|
|
12
14
|
import { renderPlanFromDb } from "../markdown-renderer.js";
|
|
13
15
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
@@ -190,6 +192,20 @@ export async function handlePlanSlice(
|
|
|
190
192
|
fullPlanMd: task.fullPlanMd,
|
|
191
193
|
});
|
|
192
194
|
}
|
|
195
|
+
|
|
196
|
+
// Seed quality gate rows inside the transaction — all-or-nothing with
|
|
197
|
+
// the plan data so a crash can't leave orphaned gates without tasks.
|
|
198
|
+
const sliceGates: GateId[] = ["Q3", "Q4"];
|
|
199
|
+
for (const gid of sliceGates) {
|
|
200
|
+
insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: gid, scope: "slice" });
|
|
201
|
+
}
|
|
202
|
+
const taskGates: GateId[] = ["Q5", "Q6", "Q7"];
|
|
203
|
+
for (const task of params.tasks) {
|
|
204
|
+
for (const gid of taskGates) {
|
|
205
|
+
insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: gid, scope: "task", taskId: task.taskId });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: "Q8", scope: "slice" });
|
|
193
209
|
});
|
|
194
210
|
} catch (err) {
|
|
195
211
|
return { error: `db write failed: ${(err as Error).message}` };
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
|
15
15
|
import { saveFile, clearParseCache } from "../files.js";
|
|
16
16
|
import { invalidateStateCache } from "../state.js";
|
|
17
|
+
import { VALIDATION_VERDICTS, isValidMilestoneVerdict } from "../verdict-parser.js";
|
|
17
18
|
|
|
18
19
|
export interface ValidateMilestoneParams {
|
|
19
20
|
milestoneId: string;
|
|
@@ -71,9 +72,8 @@ export async function handleValidateMilestone(
|
|
|
71
72
|
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
|
72
73
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return { error: `verdict must be one of: ${validVerdicts.join(", ")}` };
|
|
75
|
+
if (!isValidMilestoneVerdict(params.verdict)) {
|
|
76
|
+
return { error: `verdict must be one of: ${VALIDATION_VERDICTS.join(", ")}` };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// ── Filesystem render ──────────────────────────────────────────────────
|
|
@@ -11,6 +11,7 @@ export type Phase =
|
|
|
11
11
|
| "discussing"
|
|
12
12
|
| "researching"
|
|
13
13
|
| "planning"
|
|
14
|
+
| "evaluating-gates"
|
|
14
15
|
| "executing"
|
|
15
16
|
| "verifying"
|
|
16
17
|
| "summarizing"
|
|
@@ -557,3 +558,32 @@ export interface CompleteSliceParams {
|
|
|
557
558
|
/** Optional caller-provided reason this action was triggered */
|
|
558
559
|
triggerReason?: string;
|
|
559
560
|
}
|
|
561
|
+
|
|
562
|
+
// ─── Quality Gates ───────────────────────────────────────────────────────
|
|
563
|
+
|
|
564
|
+
export type GateId = "Q3" | "Q4" | "Q5" | "Q6" | "Q7" | "Q8";
|
|
565
|
+
export type GateScope = "slice" | "task";
|
|
566
|
+
export type GateStatus = "pending" | "complete" | "omitted";
|
|
567
|
+
export type GateVerdict = "pass" | "flag" | "omitted" | "";
|
|
568
|
+
|
|
569
|
+
export interface GateRow {
|
|
570
|
+
milestone_id: string;
|
|
571
|
+
slice_id: string;
|
|
572
|
+
gate_id: GateId;
|
|
573
|
+
scope: GateScope;
|
|
574
|
+
task_id: string;
|
|
575
|
+
status: GateStatus;
|
|
576
|
+
verdict: GateVerdict;
|
|
577
|
+
rationale: string;
|
|
578
|
+
findings: string;
|
|
579
|
+
evaluated_at: string | null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** Configuration for parallel quality gate evaluation during slice planning. */
|
|
583
|
+
export interface GateEvaluationConfig {
|
|
584
|
+
enabled: boolean;
|
|
585
|
+
/** Which slice-scoped gates to evaluate in parallel. Default: ['Q3', 'Q4']. */
|
|
586
|
+
slice_gates?: string[];
|
|
587
|
+
/** Whether to evaluate task-level gates (Q5/Q6/Q7) via reactive-execute. Default: true when enabled. */
|
|
588
|
+
task_gates?: boolean;
|
|
589
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized verdict extraction, normalization, and schema validation.
|
|
3
|
+
*
|
|
4
|
+
* All verdict-related logic lives here so that normalization rules
|
|
5
|
+
* (e.g. `passed` → `pass`) are applied consistently across the codebase.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { extractUatType } from "./files.js";
|
|
9
|
+
import type { UatType } from "./files.js";
|
|
10
|
+
|
|
11
|
+
// ── Verdict extraction ──────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract and normalize the `verdict` value from YAML frontmatter.
|
|
15
|
+
*
|
|
16
|
+
* Normalization:
|
|
17
|
+
* - lowercased
|
|
18
|
+
* - `passed` → `pass`
|
|
19
|
+
*
|
|
20
|
+
* Returns `undefined` when frontmatter is absent or has no `verdict` field.
|
|
21
|
+
*/
|
|
22
|
+
export function extractVerdict(content: string): string | undefined {
|
|
23
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
24
|
+
if (!fmMatch) return undefined;
|
|
25
|
+
const verdictMatch = fmMatch[1].match(/verdict:\s*([\w-]+)/i);
|
|
26
|
+
if (!verdictMatch) return undefined;
|
|
27
|
+
let v = verdictMatch[1].toLowerCase();
|
|
28
|
+
if (v === "passed") v = "pass";
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns `true` when the content's frontmatter contains a `verdict` field.
|
|
34
|
+
*/
|
|
35
|
+
export function hasVerdict(content: string): boolean {
|
|
36
|
+
return /verdict:\s*[\w-]+/i.test(content);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── UAT verdict schema ──────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Base verdicts that are always acceptable for UAT results.
|
|
43
|
+
*/
|
|
44
|
+
export const UAT_ACCEPTABLE_VERDICTS: readonly string[] = ["pass", "passed"];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* UAT types whose results may legitimately produce a `partial` verdict
|
|
48
|
+
* when all automatable checks pass but human-only checks remain.
|
|
49
|
+
*/
|
|
50
|
+
const PARTIAL_ELIGIBLE_UAT_TYPES: readonly UatType[] = [
|
|
51
|
+
"mixed",
|
|
52
|
+
"human-experience",
|
|
53
|
+
"live-runtime",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check whether a verdict is acceptable for a given UAT type.
|
|
58
|
+
*
|
|
59
|
+
* `pass` / `passed` are always acceptable. `partial` is acceptable only for
|
|
60
|
+
* UAT types that include non-automatable human checks.
|
|
61
|
+
*/
|
|
62
|
+
export function isAcceptableUatVerdict(verdict: string, uatType: UatType | undefined): boolean {
|
|
63
|
+
if (UAT_ACCEPTABLE_VERDICTS.includes(verdict)) return true;
|
|
64
|
+
if (verdict === "partial" && uatType && (PARTIAL_ELIGIBLE_UAT_TYPES as readonly string[]).includes(uatType)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Milestone validation verdict schema ─────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Valid verdicts for the `validate-milestone` tool.
|
|
74
|
+
*/
|
|
75
|
+
export const VALIDATION_VERDICTS = ["pass", "needs-attention", "needs-remediation"] as const;
|
|
76
|
+
export type ValidationVerdict = (typeof VALIDATION_VERDICTS)[number];
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check whether a string is a valid milestone validation verdict.
|
|
80
|
+
*/
|
|
81
|
+
export function isValidMilestoneVerdict(verdict: string): verdict is ValidationVerdict {
|
|
82
|
+
return (VALIDATION_VERDICTS as readonly string[]).includes(verdict);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── UAT type helper ─────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract the UAT type from content, defaulting to `"artifact-driven"`.
|
|
89
|
+
*
|
|
90
|
+
* The `"artifact-driven"` fallback is the original default used throughout
|
|
91
|
+
* the codebase when a UAT file lacks an explicit `## UAT Type` section.
|
|
92
|
+
*/
|
|
93
|
+
export function getUatType(content: string): UatType {
|
|
94
|
+
return extractUatType(content) ?? "artifact-driven";
|
|
95
|
+
}
|
|
@@ -150,6 +150,18 @@ export class WorktreeResolver {
|
|
|
150
150
|
*/
|
|
151
151
|
enterMilestone(milestoneId: string, ctx: NotifyCtx): void {
|
|
152
152
|
this.validateMilestoneId(milestoneId);
|
|
153
|
+
|
|
154
|
+
// If worktree creation failed earlier this session, skip all future attempts
|
|
155
|
+
if (this.s.isolationDegraded) {
|
|
156
|
+
debugLog("WorktreeResolver", {
|
|
157
|
+
action: "enterMilestone",
|
|
158
|
+
milestoneId,
|
|
159
|
+
skipped: true,
|
|
160
|
+
reason: "isolation-degraded",
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
153
165
|
if (!this.deps.shouldUseWorktreeIsolation()) {
|
|
154
166
|
debugLog("WorktreeResolver", {
|
|
155
167
|
action: "enterMilestone",
|
|
@@ -220,6 +232,9 @@ export class WorktreeResolver {
|
|
|
220
232
|
`Auto-worktree creation for ${milestoneId} failed: ${msg}. Continuing in project root.`,
|
|
221
233
|
"warning",
|
|
222
234
|
);
|
|
235
|
+
// Degrade isolation for the rest of this session so mergeAndExit
|
|
236
|
+
// doesn't try to merge a nonexistent worktree branch (#2483)
|
|
237
|
+
this.s.isolationDegraded = true;
|
|
223
238
|
// Do NOT update s.basePath — stay in project root
|
|
224
239
|
}
|
|
225
240
|
}
|
|
@@ -304,6 +319,22 @@ export class WorktreeResolver {
|
|
|
304
319
|
*/
|
|
305
320
|
mergeAndExit(milestoneId: string, ctx: NotifyCtx): void {
|
|
306
321
|
this.validateMilestoneId(milestoneId);
|
|
322
|
+
|
|
323
|
+
// If worktree creation failed earlier, skip merge — work is on current branch (#2483)
|
|
324
|
+
if (this.s.isolationDegraded) {
|
|
325
|
+
debugLog("WorktreeResolver", {
|
|
326
|
+
action: "mergeAndExit",
|
|
327
|
+
milestoneId,
|
|
328
|
+
skipped: true,
|
|
329
|
+
reason: "isolation-degraded",
|
|
330
|
+
});
|
|
331
|
+
ctx.notify(
|
|
332
|
+
`Skipping worktree merge for ${milestoneId} — isolation was degraded (worktree creation failed earlier). Work is on the current branch.`,
|
|
333
|
+
"info",
|
|
334
|
+
);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
307
338
|
const mode = this.deps.getIsolationMode();
|
|
308
339
|
debugLog("WorktreeResolver", {
|
|
309
340
|
action: "mergeAndExit",
|
|
@@ -235,8 +235,9 @@ export function getSliceBranchName(milestoneId: string, sliceId: string, worktre
|
|
|
235
235
|
return `gsd/${milestoneId}/${sliceId}`;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
/**
|
|
239
|
-
export
|
|
238
|
+
/** Re-export for backward compatibility — canonical definition in branch-patterns.ts */
|
|
239
|
+
export { SLICE_BRANCH_RE } from "./branch-patterns.js";
|
|
240
|
+
import { SLICE_BRANCH_RE } from "./branch-patterns.js";
|
|
240
241
|
|
|
241
242
|
/**
|
|
242
243
|
* Parse a slice branch name into its components.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Remote Questions — configuration resolution and validation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { AuthStorage } from "@gsd/pi-coding-agent";
|
|
6
6
|
import { loadEffectiveGSDPreferences, type RemoteQuestionsConfig } from "../gsd/preferences.js";
|
|
7
7
|
import type { RemoteChannel } from "./types.js";
|
|
8
8
|
|
|
@@ -54,9 +54,7 @@ function hydrateRemoteTokensFromAuth(): void {
|
|
|
54
54
|
if (needed.length === 0) return;
|
|
55
55
|
|
|
56
56
|
try {
|
|
57
|
-
const
|
|
58
|
-
const authPath = join(process.env.HOME ?? "~", ".gsd", "agent", "auth.json");
|
|
59
|
-
const auth = AuthStorage.create(authPath);
|
|
57
|
+
const auth = AuthStorage.create();
|
|
60
58
|
|
|
61
59
|
for (const [providerId, envVar] of needed) {
|
|
62
60
|
try {
|
|
@@ -72,7 +70,7 @@ function hydrateRemoteTokensFromAuth(): void {
|
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
} catch {
|
|
75
|
-
// AuthStorage unavailable
|
|
73
|
+
// AuthStorage unavailable or auth.json missing/unreadable — skip silently.
|
|
76
74
|
}
|
|
77
75
|
}
|
|
78
76
|
|
|
@@ -176,11 +176,15 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
176
176
|
);
|
|
177
177
|
payload.tools = tools;
|
|
178
178
|
|
|
179
|
-
// ── Session-level search budget (#1309)
|
|
179
|
+
// ── Session-level search budget (#1309, #compaction-safe) ─────────────
|
|
180
180
|
// Count web_search_tool_result blocks in the conversation history to
|
|
181
181
|
// determine how many native searches have already been used this session.
|
|
182
182
|
// The Anthropic API's max_uses resets per request, so without this guard,
|
|
183
183
|
// pause_turn → resubmit cycles allow unlimited total searches.
|
|
184
|
+
//
|
|
185
|
+
// Use the monotonic high-water mark: take the max of the history count
|
|
186
|
+
// and the running counter. This prevents budget resets when context
|
|
187
|
+
// compaction removes web_search_tool_result blocks from history.
|
|
184
188
|
if (Array.isArray(messages)) {
|
|
185
189
|
let historySearchCount = 0;
|
|
186
190
|
for (const msg of messages) {
|
|
@@ -192,8 +196,9 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
|
-
//
|
|
196
|
-
|
|
199
|
+
// High-water mark: never decrease the counter, even if compaction
|
|
200
|
+
// removes web_search_tool_result blocks from the visible history.
|
|
201
|
+
sessionSearchCount = Math.max(sessionSearchCount, historySearchCount);
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
const remaining = Math.max(0, MAX_NATIVE_SEARCHES_PER_SESSION - sessionSearchCount);
|
|
@@ -106,14 +106,20 @@ searchCache.startPurgeInterval(60_000);
|
|
|
106
106
|
|
|
107
107
|
// Consecutive duplicate search guard (#949)
|
|
108
108
|
// Tracks recent query keys to detect and break search loops.
|
|
109
|
-
const MAX_CONSECUTIVE_DUPES =
|
|
109
|
+
const MAX_CONSECUTIVE_DUPES = 1;
|
|
110
110
|
let lastSearchKey = "";
|
|
111
111
|
let consecutiveDupeCount = 0;
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// Session-level total search budget (all queries, not just duplicates).
|
|
114
|
+
// Prevents unbounded search accumulation across varied queries.
|
|
115
|
+
const MAX_SEARCHES_PER_SESSION = 15;
|
|
116
|
+
let sessionTotalSearches = 0;
|
|
117
|
+
|
|
118
|
+
/** Reset session-scoped search guard state (both duplicate and budget). */
|
|
114
119
|
export function resetSearchLoopGuardState(): void {
|
|
115
120
|
lastSearchKey = "";
|
|
116
121
|
consecutiveDupeCount = 0;
|
|
122
|
+
sessionTotalSearches = 0;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
// Summarizer responses: max 50 entries, 15-minute TTL
|
|
@@ -357,6 +363,17 @@ export function registerSearchTool(pi: ExtensionAPI) {
|
|
|
357
363
|
};
|
|
358
364
|
}
|
|
359
365
|
|
|
366
|
+
// ------------------------------------------------------------------
|
|
367
|
+
// Session-level search budget
|
|
368
|
+
// ------------------------------------------------------------------
|
|
369
|
+
if (sessionTotalSearches >= MAX_SEARCHES_PER_SESSION) {
|
|
370
|
+
return {
|
|
371
|
+
content: [{ type: "text" as const, text: `⚠️ Search budget exhausted: ${sessionTotalSearches}/${MAX_SEARCHES_PER_SESSION} searches used this session. The information you need should already be in previous search results. Stop searching and use those results to proceed with your task.` }],
|
|
372
|
+
isError: true,
|
|
373
|
+
details: { errorKind: "budget_exhausted", error: `Session search budget exhausted (${MAX_SEARCHES_PER_SESSION})` } satisfies Partial<SearchDetails>,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
360
377
|
const count = params.count ?? 5;
|
|
361
378
|
const wantSummary = params.summary ?? false;
|
|
362
379
|
|
|
@@ -410,6 +427,9 @@ export function registerSearchTool(pi: ExtensionAPI) {
|
|
|
410
427
|
consecutiveDupeCount = 1;
|
|
411
428
|
}
|
|
412
429
|
|
|
430
|
+
// Count every search that passes the guards toward the session budget.
|
|
431
|
+
sessionTotalSearches++;
|
|
432
|
+
|
|
413
433
|
const cached = searchCache.get(cacheKey);
|
|
414
434
|
|
|
415
435
|
if (cached) {
|
|
@@ -103,9 +103,12 @@ gh issue list -R gsd-build/gsd-2
|
|
|
103
103
|
gh issue list -R gsd-build/gsd-2 --label "priority:p1" --state open
|
|
104
104
|
|
|
105
105
|
# Create issue with labels and milestone
|
|
106
|
+
# NOTE: Do NOT use labels for issue classification (bug, feature, etc.)
|
|
107
|
+
# Use labels for metadata (priority, status, auto-generated) only.
|
|
108
|
+
# Issue classification uses GitHub Issue Types, set via GraphQL after creation.
|
|
106
109
|
gh issue create -R gsd-build/gsd-2 \
|
|
107
110
|
--title "feat: add feature X" \
|
|
108
|
-
--label "priority:p1"
|
|
111
|
+
--label "priority:p1" \
|
|
109
112
|
--milestone "v1.0"
|
|
110
113
|
|
|
111
114
|
# View issue
|
|
@@ -120,6 +123,24 @@ gh issue edit <number> -R gsd-build/gsd-2 \
|
|
|
120
123
|
--remove-label "status:needs-grooming"
|
|
121
124
|
```
|
|
122
125
|
|
|
126
|
+
### Issue Types (Classification)
|
|
127
|
+
|
|
128
|
+
`gh issue create` has no `--type` flag. Issue types (Bug, Feature Request, etc.) are set via GraphQL after creation:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Step 1: Create the issue (returns URL)
|
|
132
|
+
ISSUE_URL=$(gh issue create -R gsd-build/gsd-2 \
|
|
133
|
+
--title "..." --body "...")
|
|
134
|
+
|
|
135
|
+
# Step 2: Set the issue type via GraphQL
|
|
136
|
+
ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
|
|
137
|
+
ISSUE_ID=$(gh api graphql -f query='{ repository(owner:"gsd-build",name:"gsd-2") { issue(number:'"$ISSUE_NUM"') { id } } }' --jq '.data.repository.issue.id')
|
|
138
|
+
TYPE_ID=$(gh api graphql -f query='{ repository(owner:"gsd-build",name:"gsd-2") { issueTypes(first:20) { nodes { id name } } } }' --jq '.data.repository.issueTypes.nodes[] | select(.name=="Bug") | .id')
|
|
139
|
+
gh api graphql -f query='mutation { updateIssue(input:{id:"'"$ISSUE_ID"'",issueTypeId:"'"$TYPE_ID"'"}) { issue { number } } }'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Replace `"Bug"` with the appropriate type name (`"Feature Request"`, `"Task"`, etc.).
|
|
143
|
+
|
|
123
144
|
### Labels
|
|
124
145
|
|
|
125
146
|
```bash
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worktree ↔ project root state synchronization for auto-mode.
|
|
3
|
-
*
|
|
4
|
-
* When auto-mode runs inside a worktree, dispatch-critical state files
|
|
5
|
-
* (.gsd/ metadata) diverge between the worktree (where work happens)
|
|
6
|
-
* and the project root (where startAutoMode reads initial state on restart).
|
|
7
|
-
* Without syncing, restarting auto-mode reads stale state from the project
|
|
8
|
-
* root and re-dispatches already-completed units.
|
|
9
|
-
*
|
|
10
|
-
* Also contains resource staleness detection and stale worktree escape.
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
|
|
13
|
-
import { join, sep as pathSep } from "node:path";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
|
-
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
16
|
-
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
17
|
-
// ─── Project Root → Worktree Sync ─────────────────────────────────────────
|
|
18
|
-
/**
|
|
19
|
-
* Sync milestone artifacts from project root INTO worktree before deriveState.
|
|
20
|
-
* Covers the case where the LLM wrote artifacts to the main repo filesystem
|
|
21
|
-
* (e.g. via absolute paths) but the worktree has stale data. Also deletes
|
|
22
|
-
* gsd.db in the worktree so it rebuilds from fresh disk state (#853).
|
|
23
|
-
* Non-fatal — sync failure should never block dispatch.
|
|
24
|
-
*/
|
|
25
|
-
export function syncProjectRootToWorktree(projectRoot, worktreePath, milestoneId) {
|
|
26
|
-
if (!worktreePath || !projectRoot || worktreePath === projectRoot)
|
|
27
|
-
return;
|
|
28
|
-
if (!milestoneId)
|
|
29
|
-
return;
|
|
30
|
-
const prGsd = join(projectRoot, ".gsd");
|
|
31
|
-
const wtGsd = join(worktreePath, ".gsd");
|
|
32
|
-
// Copy milestone directory from project root to worktree if the project root
|
|
33
|
-
// has newer artifacts (e.g. slices that don't exist in the worktree yet)
|
|
34
|
-
safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId));
|
|
35
|
-
// Delete worktree gsd.db so it rebuilds from the freshly synced files.
|
|
36
|
-
// Stale DB rows are the root cause of the infinite skip loop (#853).
|
|
37
|
-
try {
|
|
38
|
-
const wtDb = join(wtGsd, "gsd.db");
|
|
39
|
-
if (existsSync(wtDb)) {
|
|
40
|
-
unlinkSync(wtDb);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
/* non-fatal */
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
// ─── Worktree → Project Root Sync ─────────────────────────────────────────
|
|
48
|
-
/**
|
|
49
|
-
* Sync dispatch-critical .gsd/ state files from worktree to project root.
|
|
50
|
-
* Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
|
|
51
|
-
* Copies: STATE.md + active milestone directory (roadmap, slice plans, task summaries).
|
|
52
|
-
* Non-fatal — sync failure should never block dispatch.
|
|
53
|
-
*/
|
|
54
|
-
export function syncStateToProjectRoot(worktreePath, projectRoot, milestoneId) {
|
|
55
|
-
if (!worktreePath || !projectRoot || worktreePath === projectRoot)
|
|
56
|
-
return;
|
|
57
|
-
if (!milestoneId)
|
|
58
|
-
return;
|
|
59
|
-
const wtGsd = join(worktreePath, ".gsd");
|
|
60
|
-
const prGsd = join(projectRoot, ".gsd");
|
|
61
|
-
// 1. STATE.md — the quick-glance status used by initial deriveState()
|
|
62
|
-
safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
|
|
63
|
-
// 2. Milestone directory — ROADMAP, slice PLANs, task summaries
|
|
64
|
-
// Copy the entire milestone .gsd subtree so deriveState reads current checkboxes
|
|
65
|
-
safeCopyRecursive(join(wtGsd, "milestones", milestoneId), join(prGsd, "milestones", milestoneId), { force: true });
|
|
66
|
-
// 3. metrics.json — session cost/token tracking (#2313).
|
|
67
|
-
// Without this, metrics accumulated in the worktree are invisible from the
|
|
68
|
-
// project root and never appear in the dashboard or skill-health reports.
|
|
69
|
-
safeCopy(join(wtGsd, "metrics.json"), join(prGsd, "metrics.json"), { force: true });
|
|
70
|
-
// 4. Runtime records — unit dispatch state used by selfHealRuntimeRecords().
|
|
71
|
-
// Without this, a crash during a unit leaves the runtime record only in the
|
|
72
|
-
// worktree. If the next session resolves basePath before worktree re-entry,
|
|
73
|
-
// selfHeal can't find or clear the stale record (#769).
|
|
74
|
-
safeCopyRecursive(join(wtGsd, "runtime", "units"), join(prGsd, "runtime", "units"), { force: true });
|
|
75
|
-
}
|
|
76
|
-
// ─── Resource Staleness ───────────────────────────────────────────────────
|
|
77
|
-
/**
|
|
78
|
-
* Read the resource version (semver) from the managed-resources manifest.
|
|
79
|
-
* Uses gsdVersion instead of syncedAt so that launching a second session
|
|
80
|
-
* doesn't falsely trigger staleness (#804).
|
|
81
|
-
*/
|
|
82
|
-
export function readResourceVersion() {
|
|
83
|
-
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
|
|
84
|
-
const manifestPath = join(agentDir, "managed-resources.json");
|
|
85
|
-
try {
|
|
86
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
87
|
-
return typeof manifest?.gsdVersion === "string"
|
|
88
|
-
? manifest.gsdVersion
|
|
89
|
-
: null;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Check if managed resources have been updated since session start.
|
|
97
|
-
* Returns a warning message if stale, null otherwise.
|
|
98
|
-
*/
|
|
99
|
-
export function checkResourcesStale(versionOnStart) {
|
|
100
|
-
if (versionOnStart === null)
|
|
101
|
-
return null;
|
|
102
|
-
const current = readResourceVersion();
|
|
103
|
-
if (current === null)
|
|
104
|
-
return null;
|
|
105
|
-
if (current !== versionOnStart) {
|
|
106
|
-
return "GSD resources were updated since this session started. Restart gsd to load the new code.";
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
// ─── Stale Worktree Escape ────────────────────────────────────────────────
|
|
111
|
-
/**
|
|
112
|
-
* Detect and escape a stale worktree cwd (#608).
|
|
113
|
-
*
|
|
114
|
-
* After milestone completion + merge, the worktree directory is removed but
|
|
115
|
-
* the process cwd may still point inside `.gsd/worktrees/<MID>/`.
|
|
116
|
-
* When a new session starts, `process.cwd()` is passed as `base` to startAuto
|
|
117
|
-
* and all subsequent writes land in the wrong directory. This function detects
|
|
118
|
-
* that scenario and chdir back to the project root.
|
|
119
|
-
*
|
|
120
|
-
* Returns the corrected base path.
|
|
121
|
-
*/
|
|
122
|
-
export function escapeStaleWorktree(base) {
|
|
123
|
-
// Direct layout: /.gsd/worktrees/
|
|
124
|
-
const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
125
|
-
let idx = base.indexOf(directMarker);
|
|
126
|
-
if (idx === -1) {
|
|
127
|
-
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
128
|
-
const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`);
|
|
129
|
-
const match = base.match(symlinkRe);
|
|
130
|
-
if (!match || match.index === undefined)
|
|
131
|
-
return base;
|
|
132
|
-
idx = match.index;
|
|
133
|
-
}
|
|
134
|
-
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
135
|
-
const projectRoot = base.slice(0, idx);
|
|
136
|
-
// Guard: If the candidate project root's .gsd IS the user-level ~/.gsd,
|
|
137
|
-
// the string-slice heuristic matched the wrong /.gsd/ boundary. This happens
|
|
138
|
-
// when .gsd is a symlink into ~/.gsd/projects/<hash> and process.cwd()
|
|
139
|
-
// resolved through the symlink. Returning ~ would be catastrophic (#1676).
|
|
140
|
-
const candidateGsd = join(projectRoot, ".gsd").replaceAll("\\", "/");
|
|
141
|
-
const gsdHomePath = gsdHome.replaceAll("\\", "/");
|
|
142
|
-
if (candidateGsd === gsdHomePath || candidateGsd.startsWith(gsdHomePath + "/")) {
|
|
143
|
-
// Don't chdir to home — return base unchanged.
|
|
144
|
-
// resolveProjectRoot() in worktree.ts has the full git-file-based recovery
|
|
145
|
-
// and will be called by the caller (startAuto → projectRoot()).
|
|
146
|
-
return base;
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
process.chdir(projectRoot);
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
// If chdir fails, return the original — caller will handle errors downstream
|
|
153
|
-
return base;
|
|
154
|
-
}
|
|
155
|
-
return projectRoot;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Clean stale runtime unit files for completed milestones.
|
|
159
|
-
*
|
|
160
|
-
* After restart, stale runtime/units/*.json from prior milestones can
|
|
161
|
-
* cause deriveState to resume the wrong milestone (#887). Removes files
|
|
162
|
-
* for milestones that have a SUMMARY (fully complete).
|
|
163
|
-
*/
|
|
164
|
-
export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
|
|
165
|
-
const runtimeUnitsDir = join(gsdRootPath, "runtime", "units");
|
|
166
|
-
if (!existsSync(runtimeUnitsDir))
|
|
167
|
-
return 0;
|
|
168
|
-
let cleaned = 0;
|
|
169
|
-
try {
|
|
170
|
-
for (const file of readdirSync(runtimeUnitsDir)) {
|
|
171
|
-
if (!file.endsWith(".json"))
|
|
172
|
-
continue;
|
|
173
|
-
const midMatch = file.match(/(M\d+(?:-[a-z0-9]{6})?)/);
|
|
174
|
-
if (!midMatch)
|
|
175
|
-
continue;
|
|
176
|
-
if (hasMilestoneSummary(midMatch[1])) {
|
|
177
|
-
try {
|
|
178
|
-
unlinkSync(join(runtimeUnitsDir, file));
|
|
179
|
-
cleaned++;
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
/* non-fatal */
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
/* non-fatal */
|
|
189
|
-
}
|
|
190
|
-
return cleaned;
|
|
191
|
-
}
|