@opengsd/gsd-pi 1.5.0-dev.4fb2d460 → 1.5.0-dev.c4c8885f
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/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
- package/dist/resources/extensions/gsd/auto-start.js +53 -30
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto.js +97 -31
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/paths.js +70 -49
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
- 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 +10 -10
- 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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +10 -10
- package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
- package/src/resources/extensions/gsd/auto-start.ts +57 -28
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto.ts +103 -30
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/paths.ts +87 -48
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-types.ts +24 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
|
@@ -38,9 +38,10 @@ import {
|
|
|
38
38
|
resolveSlicePath,
|
|
39
39
|
resolveTaskFile,
|
|
40
40
|
resolveTasksDir,
|
|
41
|
+
targetMilestoneFile,
|
|
42
|
+
targetSliceFile,
|
|
41
43
|
gsdProjectionRoot,
|
|
42
44
|
gsdRoot,
|
|
43
|
-
buildMilestoneFileName,
|
|
44
45
|
buildTaskFileName,
|
|
45
46
|
buildSliceFileName,
|
|
46
47
|
} from "./paths.js";
|
|
@@ -52,9 +53,6 @@ import { readCompatMarker, writeCompatMarker, computeProjectionSha } from "./com
|
|
|
52
53
|
import type { RiskLevel } from "./types.js";
|
|
53
54
|
import {
|
|
54
55
|
phaseDirName,
|
|
55
|
-
planFileName,
|
|
56
|
-
milestoneIdToPhaseNum,
|
|
57
|
-
sliceIdToPlanNum,
|
|
58
56
|
derivePhaseSlug,
|
|
59
57
|
} from "./layout-policy.js";
|
|
60
58
|
|
|
@@ -176,23 +174,6 @@ function sanitizeInlineRoadmapText(value: string | null | undefined): string {
|
|
|
176
174
|
.trim();
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
function resolveRoadmapProjectionPath(basePath: string, milestoneId: string): string {
|
|
180
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
181
|
-
const existing = resolveMilestonePath(basePath, milestoneId);
|
|
182
|
-
const legacyBase = legacyMilestonesDir(basePath);
|
|
183
|
-
const isLegacyLayout = existing
|
|
184
|
-
? existing.startsWith(legacyBase + "/") || existing.startsWith(legacyBase + "\\")
|
|
185
|
-
: isLegacyMilestonesLayout(basePath);
|
|
186
|
-
const phaseDir = existing ?? join(
|
|
187
|
-
isLegacyLayout ? legacyBase : milestonesDir(basePath),
|
|
188
|
-
isLegacyLayout ? milestoneId : canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title),
|
|
189
|
-
);
|
|
190
|
-
const roadmapFileName = isLegacyLayout
|
|
191
|
-
? `${milestoneId}-ROADMAP.md`
|
|
192
|
-
: `${String(phaseNum).padStart(2, "0")}-ROADMAP.md`;
|
|
193
|
-
return join(phaseDir, roadmapFileName);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
177
|
function isMilestoneFlatPhaseLayout(basePath: string, milestoneId: string): boolean {
|
|
197
178
|
const existing = resolveMilestonePath(basePath, milestoneId);
|
|
198
179
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
@@ -600,7 +581,7 @@ export async function renderRoadmapFromDb(
|
|
|
600
581
|
"projection",
|
|
601
582
|
`renderRoadmapFromDb skipped unplanned milestone ${milestoneId} (zero slices, empty vision) — refusing to write a stub ROADMAP`,
|
|
602
583
|
);
|
|
603
|
-
const absPath =
|
|
584
|
+
const absPath = targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestone.title);
|
|
604
585
|
if (existsSync(absPath)) {
|
|
605
586
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
606
587
|
unlinkSync(absPath);
|
|
@@ -614,7 +595,7 @@ export async function renderRoadmapFromDb(
|
|
|
614
595
|
return { skipped: "unplanned-milestone" };
|
|
615
596
|
}
|
|
616
597
|
|
|
617
|
-
const absPath =
|
|
598
|
+
const absPath = targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestone.title);
|
|
618
599
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
619
600
|
const content = renderRoadmapMarkdown(milestone, slices);
|
|
620
601
|
|
|
@@ -655,25 +636,20 @@ export async function renderMilestoneArtifactsFromDb(
|
|
|
655
636
|
const artifacts = getMilestoneScopedArtifacts(milestoneId);
|
|
656
637
|
if (artifacts.length === 0) return false;
|
|
657
638
|
|
|
658
|
-
const
|
|
659
|
-
join(
|
|
660
|
-
milestonesDir(basePath),
|
|
661
|
-
canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title),
|
|
662
|
-
);
|
|
663
|
-
mkdirSync(phaseDir, { recursive: true });
|
|
664
|
-
|
|
665
|
-
const legacyBase = legacyMilestonesDir(basePath);
|
|
666
|
-
const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
|
|
639
|
+
const milestone = getMilestone(milestoneId);
|
|
667
640
|
|
|
668
641
|
let wrote = false;
|
|
669
642
|
for (const artifact of artifacts) {
|
|
670
643
|
if (artifact.artifact_type === "ROADMAP") continue;
|
|
671
644
|
if (!artifact.full_content.trim()) continue;
|
|
672
645
|
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
646
|
+
const absPath = targetMilestoneFile(
|
|
647
|
+
basePath,
|
|
648
|
+
milestoneId,
|
|
649
|
+
artifact.artifact_type,
|
|
650
|
+
milestone?.title,
|
|
651
|
+
);
|
|
652
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
677
653
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
678
654
|
await writeAndStore(absPath, artifactPath, artifact.full_content, {
|
|
679
655
|
artifact_type: artifact.artifact_type,
|
|
@@ -824,22 +800,12 @@ export async function renderSliceSummary(
|
|
|
824
800
|
return false; // No slice data — skip silently
|
|
825
801
|
}
|
|
826
802
|
|
|
827
|
-
|
|
828
|
-
// finds existing dirs. In flat-phase the slice "path" is the phase dir.
|
|
829
|
-
let slicePath = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
830
|
-
if (!slicePath) {
|
|
831
|
-
const mDir = milestonesDir(basePath);
|
|
832
|
-
const dirName = canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title);
|
|
833
|
-
slicePath = join(mDir, dirName);
|
|
834
|
-
mkdirSync(slicePath, { recursive: true });
|
|
835
|
-
}
|
|
836
|
-
|
|
803
|
+
const milestoneTitle = getMilestone(milestoneId)?.title;
|
|
837
804
|
let wrote = false;
|
|
838
805
|
|
|
839
|
-
// Write SUMMARY — flat-phase: NN-MM-SUMMARY.md inside phase dir
|
|
840
806
|
if (slice.full_summary_md) {
|
|
841
|
-
const
|
|
842
|
-
|
|
807
|
+
const summaryAbs = targetSliceFile(basePath, milestoneId, sliceId, "SUMMARY", milestoneTitle);
|
|
808
|
+
mkdirSync(dirname(summaryAbs), { recursive: true });
|
|
843
809
|
const summaryArtifact = toArtifactPath(summaryAbs, basePath);
|
|
844
810
|
|
|
845
811
|
await writeAndStore(summaryAbs, summaryArtifact, slice.full_summary_md, {
|
|
@@ -850,10 +816,9 @@ export async function renderSliceSummary(
|
|
|
850
816
|
wrote = true;
|
|
851
817
|
}
|
|
852
818
|
|
|
853
|
-
// Write UAT — flat-phase: NN-MM-UAT.md inside phase dir
|
|
854
819
|
if (slice.full_uat_md) {
|
|
855
|
-
const
|
|
856
|
-
|
|
820
|
+
const uatAbs = targetSliceFile(basePath, milestoneId, sliceId, "UAT", milestoneTitle);
|
|
821
|
+
mkdirSync(dirname(uatAbs), { recursive: true });
|
|
857
822
|
const uatArtifact = toArtifactPath(uatAbs, basePath);
|
|
858
823
|
|
|
859
824
|
await writeAndStore(uatAbs, uatArtifact, slice.full_uat_md, {
|
|
@@ -1080,7 +1045,7 @@ function detectStaleRendersImpl(basePath: string): StaleEntry[] {
|
|
|
1080
1045
|
// TODO(flat-phase): roadmap checkbox parsing may not match flat-phase
|
|
1081
1046
|
// roadmap format, causing false-positive drift loops. Skip during transition.
|
|
1082
1047
|
/*
|
|
1083
|
-
const roadmapPath =
|
|
1048
|
+
const roadmapPath = targetMilestoneFile(basePath, milestone.id, "ROADMAP", milestone.title);
|
|
1084
1049
|
if (existsSync(roadmapPath)) {
|
|
1085
1050
|
try {
|
|
1086
1051
|
const parsed = parseProjectionByIdentity(roadmapPath, parseRoadmap) as ReturnType<typeof parseRoadmap>;
|
|
@@ -1172,36 +1137,28 @@ function detectStaleRendersImpl(basePath: string): StaleEntry[] {
|
|
|
1172
1137
|
}
|
|
1173
1138
|
}
|
|
1174
1139
|
|
|
1175
|
-
// Check missing slice summary/UAT files
|
|
1140
|
+
// Check missing slice summary/UAT files. Use the same target helper as
|
|
1141
|
+
// renderSliceSummary so detection and repair agree on flat-phase vs legacy
|
|
1142
|
+
// output locations.
|
|
1176
1143
|
const sliceRow = getSlice(milestone.id, slice.id);
|
|
1177
1144
|
if (sliceRow && sliceRow.status === "complete") {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
if (sliceRow.full_summary_md) {
|
|
1186
|
-
const summaryName = planFileName(phaseNum, planNum, "SUMMARY");
|
|
1187
|
-
const summaryAbsPath = join(slicePath, summaryName);
|
|
1188
|
-
if (!existsSync(summaryAbsPath)) {
|
|
1189
|
-
stale.push({
|
|
1190
|
-
path: summaryAbsPath,
|
|
1191
|
-
reason: `${slice.id} is complete with summary in DB but SUMMARY.md missing on disk`,
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1145
|
+
if (sliceRow.full_summary_md) {
|
|
1146
|
+
const summaryAbsPath = targetSliceFile(basePath, milestone.id, slice.id, "SUMMARY", milestone.title);
|
|
1147
|
+
if (!existsSync(summaryAbsPath)) {
|
|
1148
|
+
stale.push({
|
|
1149
|
+
path: summaryAbsPath,
|
|
1150
|
+
reason: `${slice.id} is complete with summary in DB but SUMMARY.md missing on disk`,
|
|
1151
|
+
});
|
|
1194
1152
|
}
|
|
1153
|
+
}
|
|
1195
1154
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1155
|
+
if (sliceRow.full_uat_md) {
|
|
1156
|
+
const uatAbsPath = targetSliceFile(basePath, milestone.id, slice.id, "UAT", milestone.title);
|
|
1157
|
+
if (!existsSync(uatAbsPath)) {
|
|
1158
|
+
stale.push({
|
|
1159
|
+
path: uatAbsPath,
|
|
1160
|
+
reason: `${slice.id} is complete with UAT in DB but UAT.md missing on disk`,
|
|
1161
|
+
});
|
|
1205
1162
|
}
|
|
1206
1163
|
}
|
|
1207
1164
|
}
|
|
@@ -1257,12 +1214,14 @@ export async function renderReplanFromDb(
|
|
|
1257
1214
|
sliceId: string,
|
|
1258
1215
|
replanData: ReplanData,
|
|
1259
1216
|
): Promise<{ replanPath: string; content: string }> {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1217
|
+
const absPath = targetSliceFile(
|
|
1218
|
+
basePath,
|
|
1219
|
+
milestoneId,
|
|
1220
|
+
sliceId,
|
|
1221
|
+
"REPLAN",
|
|
1222
|
+
getMilestone(milestoneId)?.title,
|
|
1223
|
+
);
|
|
1224
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
1266
1225
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
1267
1226
|
|
|
1268
1227
|
const lines: string[] = [];
|
|
@@ -1299,24 +1258,14 @@ export async function renderAssessmentFromDb(
|
|
|
1299
1258
|
sliceId: string,
|
|
1300
1259
|
assessmentData: AssessmentData,
|
|
1301
1260
|
): Promise<{ assessmentPath: string; content: string }> {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const isLegacyLayout = mDir
|
|
1311
|
-
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
1312
|
-
: false;
|
|
1313
|
-
const fallbackDir = join(milestonesDir(basePath), canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
|
|
1314
|
-
const slicePath = isLegacyLayout
|
|
1315
|
-
? (resolveSlicePath(basePath, milestoneId, sliceId) ?? fallbackDir)
|
|
1316
|
-
: (mDir ?? fallbackDir);
|
|
1317
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
1318
|
-
const planNum = sliceIdToPlanNum(sliceId);
|
|
1319
|
-
const absPath = join(slicePath, planFileName(phaseNum, planNum, "ASSESSMENT"));
|
|
1261
|
+
const absPath = targetSliceFile(
|
|
1262
|
+
basePath,
|
|
1263
|
+
milestoneId,
|
|
1264
|
+
sliceId,
|
|
1265
|
+
"ASSESSMENT",
|
|
1266
|
+
getMilestone(milestoneId)?.title,
|
|
1267
|
+
);
|
|
1268
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
1320
1269
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
1321
1270
|
|
|
1322
1271
|
const lines: string[] = [];
|
|
@@ -265,7 +265,7 @@ export function snapshotUnitMetrics(
|
|
|
265
265
|
}
|
|
266
266
|
saveLedger(basePath, ledger);
|
|
267
267
|
|
|
268
|
-
if (isUnifiedAuditEnabled()) {
|
|
268
|
+
if (isUnifiedAuditEnabled(basePath)) {
|
|
269
269
|
emitUokAuditEvent(
|
|
270
270
|
basePath,
|
|
271
271
|
buildAuditEnvelope({
|
|
@@ -458,7 +458,7 @@ export function snapshotUnitMetricsByScope(
|
|
|
458
458
|
}
|
|
459
459
|
saveLedger(base, scopedLedger);
|
|
460
460
|
|
|
461
|
-
if (isUnifiedAuditEnabled()) {
|
|
461
|
+
if (isUnifiedAuditEnabled(base)) {
|
|
462
462
|
emitUokAuditEvent(
|
|
463
463
|
base,
|
|
464
464
|
buildAuditEnvelope({
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { readdirSync, existsSync, realpathSync, statSync, Dirent } from "node:fs";
|
|
14
|
-
import { join, dirname, normalize, resolve } from "node:path";
|
|
14
|
+
import { join, dirname, normalize, relative, resolve } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
16
|
import { spawnSync } from "node:child_process";
|
|
17
17
|
import { nativeScanGsdTree, type GsdTreeEntry } from "./native-parser-bridge.js";
|
|
@@ -379,21 +379,26 @@ export function resolveGsdPathContract(
|
|
|
379
379
|
const resolvedWorkRoot = resolve(workRoot || process.cwd());
|
|
380
380
|
const isWorktree = isGsdWorktreePath(resolvedWorkRoot);
|
|
381
381
|
if (isWorktree && !originalProjectRoot?.trim()) {
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
382
|
+
const rawProjectGsd = resolveExternalStateProjectGsdFromWorktreePath(resolvedWorkRoot);
|
|
383
|
+
if (rawProjectGsd) {
|
|
384
|
+
// Canonicalize the `.gsd` root so the DB path matches what every other
|
|
385
|
+
// accessor (gsdRoot/gsdProjectionRoot) returns — otherwise an unresolved
|
|
386
|
+
// symlink (e.g. `/mnt/c/.../.gsd` → native ext4 on WSL) selects the wrong
|
|
387
|
+
// journal mode and yields a fragile, move-prone handle. See issue #1239.
|
|
388
|
+
const projectGsd = normalizeRealPath(rawProjectGsd);
|
|
384
389
|
return {
|
|
385
390
|
projectRoot: dirname(dirname(projectGsd)),
|
|
386
391
|
workRoot: resolvedWorkRoot,
|
|
387
392
|
projectGsd,
|
|
388
|
-
worktreeGsd: join(resolvedWorkRoot, ".gsd"),
|
|
393
|
+
worktreeGsd: normalizeRealPath(join(resolvedWorkRoot, ".gsd")),
|
|
389
394
|
projectDb: join(projectGsd, "gsd.db"),
|
|
390
395
|
isWorktree,
|
|
391
396
|
};
|
|
392
397
|
}
|
|
393
398
|
}
|
|
394
399
|
const projectRoot = resolve(resolveWorktreeProjectRoot(resolvedWorkRoot, originalProjectRoot));
|
|
395
|
-
const projectGsd = join(projectRoot, ".gsd");
|
|
396
|
-
const worktreeGsd = isWorktree ? join(resolvedWorkRoot, ".gsd") : null;
|
|
400
|
+
const projectGsd = normalizeRealPath(join(projectRoot, ".gsd"));
|
|
401
|
+
const worktreeGsd = isWorktree ? normalizeRealPath(join(resolvedWorkRoot, ".gsd")) : null;
|
|
397
402
|
|
|
398
403
|
return {
|
|
399
404
|
projectRoot,
|
|
@@ -916,10 +921,24 @@ export function resolveTaskFile(
|
|
|
916
921
|
basePath: string, milestoneId: string, sliceId: string,
|
|
917
922
|
taskId: string, suffix: string
|
|
918
923
|
): string | null {
|
|
924
|
+
const phaseDir = resolveMilestonePath(basePath, milestoneId);
|
|
925
|
+
if (!phaseDir) return null;
|
|
926
|
+
|
|
927
|
+
const legacyBase = legacyMilestonesDir(basePath);
|
|
928
|
+
const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
|
|
929
|
+
|
|
930
|
+
if (suffix !== "PLAN" && !isLegacy) {
|
|
931
|
+
const flatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
|
|
932
|
+
return existsSync(flatPath) ? flatPath : null;
|
|
933
|
+
}
|
|
934
|
+
|
|
919
935
|
const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
|
|
936
|
+
if (tDir) {
|
|
937
|
+
const file = resolveFile(tDir, taskId, suffix);
|
|
938
|
+
if (file) return join(tDir, file);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return null;
|
|
923
942
|
}
|
|
924
943
|
|
|
925
944
|
// ─── Relative Path Builders (for prompts — .gsd/milestones/...) ────────────
|
|
@@ -954,31 +973,41 @@ export function relMilestonePath(basePath: string, milestoneId: string, title?:
|
|
|
954
973
|
}
|
|
955
974
|
|
|
956
975
|
/**
|
|
957
|
-
* Build
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
* name when the file doesn't exist yet.
|
|
976
|
+
* Build the canonical absolute write target for a milestone file.
|
|
977
|
+
* Existing compatibility filenames are intentionally ignored; readers that need
|
|
978
|
+
* to preserve an existing file should call resolveMilestoneFile first.
|
|
961
979
|
*/
|
|
962
|
-
export function
|
|
963
|
-
basePath: string, milestoneId: string, suffix: string
|
|
980
|
+
export function targetMilestoneFile(
|
|
981
|
+
basePath: string, milestoneId: string, suffix: string, title?: string
|
|
964
982
|
): string {
|
|
965
|
-
const mRel = relMilestonePath(basePath, milestoneId);
|
|
966
|
-
// resolveMilestoneFile checks NN-SUFFIX.md (flat-phase) and MID-SUFFIX.md (legacy).
|
|
967
|
-
const absFile = resolveMilestoneFile(basePath, milestoneId, suffix);
|
|
968
|
-
if (absFile) {
|
|
969
|
-
const fileName = absFile.split(/[/\\]/).pop()!;
|
|
970
|
-
return `${mRel}/${fileName}`;
|
|
971
|
-
}
|
|
972
|
-
// File doesn't exist yet — use layout-aware fallback filename.
|
|
973
983
|
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
974
984
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
975
985
|
const isLegacy = mDir
|
|
976
986
|
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
977
|
-
:
|
|
987
|
+
: isLegacyMilestonesLayout(basePath);
|
|
988
|
+
const dir = mDir ?? join(
|
|
989
|
+
isLegacy ? legacyBase : milestonesDir(basePath),
|
|
990
|
+
isLegacy ? milestoneId : canonicalPhaseDirName(milestoneId, title),
|
|
991
|
+
);
|
|
978
992
|
const fileName = isLegacy
|
|
979
993
|
? `${milestoneId}-${suffix}.md`
|
|
980
994
|
: `${String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0")}-${suffix}.md`;
|
|
981
|
-
return
|
|
995
|
+
return join(dir, fileName);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Build relative .gsd/ path to a milestone file.
|
|
1000
|
+
* Preserves an existing compatibility filename when present; otherwise falls
|
|
1001
|
+
* back to the canonical write target.
|
|
1002
|
+
*/
|
|
1003
|
+
export function relMilestoneFile(
|
|
1004
|
+
basePath: string, milestoneId: string, suffix: string, title?: string
|
|
1005
|
+
): string {
|
|
1006
|
+
const rel = relative(
|
|
1007
|
+
gsdProjectionRoot(basePath),
|
|
1008
|
+
resolveMilestoneFile(basePath, milestoneId, suffix) ?? targetMilestoneFile(basePath, milestoneId, suffix, title),
|
|
1009
|
+
).replace(/\\/g, "/");
|
|
1010
|
+
return `.gsd/${rel}`;
|
|
982
1011
|
}
|
|
983
1012
|
|
|
984
1013
|
/**
|
|
@@ -1010,34 +1039,44 @@ export function relSlicePath(
|
|
|
1010
1039
|
}
|
|
1011
1040
|
|
|
1012
1041
|
/**
|
|
1013
|
-
* Build
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
* @param milestoneTitle - Optional milestone title forwarded to relSlicePath /
|
|
1017
|
-
* relMilestonePath for title-aware flat-phase fallback dir naming. Only used
|
|
1018
|
-
* when the phase directory does not yet exist on disk.
|
|
1042
|
+
* Build the canonical absolute write target for a slice file.
|
|
1043
|
+
* Existing compatibility filenames are intentionally ignored; readers that need
|
|
1044
|
+
* to preserve an existing file should call resolveSliceFile first.
|
|
1019
1045
|
*/
|
|
1020
|
-
export function
|
|
1046
|
+
export function targetSliceFile(
|
|
1021
1047
|
basePath: string, milestoneId: string, sliceId: string, suffix: string, milestoneTitle?: string
|
|
1022
1048
|
): string {
|
|
1023
|
-
const sRel = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
|
|
1024
|
-
const absPath = resolveSliceFile(basePath, milestoneId, sliceId, suffix);
|
|
1025
|
-
if (absPath) {
|
|
1026
|
-
const fileName = absPath.split(/[/\\]/).pop()!;
|
|
1027
|
-
return `${sRel}/${fileName}`;
|
|
1028
|
-
}
|
|
1029
|
-
// Fallback when file doesn't exist yet — use layout-aware filename.
|
|
1030
1049
|
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
1031
1050
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
1032
1051
|
const isLegacy = mDir
|
|
1033
1052
|
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
1034
|
-
:
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1053
|
+
: isLegacyMilestonesLayout(basePath);
|
|
1054
|
+
const milestoneDir = mDir
|
|
1055
|
+
?? dirname(targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestoneTitle));
|
|
1056
|
+
const slicesDir = join(milestoneDir, "slices");
|
|
1057
|
+
const dir = isLegacy
|
|
1058
|
+
? join(slicesDir, resolveDir(slicesDir, sliceId) ?? sliceId)
|
|
1059
|
+
: milestoneDir;
|
|
1060
|
+
const fileName = isLegacy
|
|
1061
|
+
? `${sliceId}-${suffix}.md`
|
|
1062
|
+
: planFileName(milestoneIdToPhaseNum(milestoneId), sliceIdToPlanNum(sliceId), suffix);
|
|
1063
|
+
return join(dir, fileName);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Build relative .gsd/ path to a slice file.
|
|
1068
|
+
* Preserves an existing compatibility filename when present; otherwise falls
|
|
1069
|
+
* back to the canonical write target.
|
|
1070
|
+
*/
|
|
1071
|
+
export function relSliceFile(
|
|
1072
|
+
basePath: string, milestoneId: string, sliceId: string, suffix: string, milestoneTitle?: string
|
|
1073
|
+
): string {
|
|
1074
|
+
const rel = relative(
|
|
1075
|
+
gsdProjectionRoot(basePath),
|
|
1076
|
+
resolveSliceFile(basePath, milestoneId, sliceId, suffix)
|
|
1077
|
+
?? targetSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle),
|
|
1078
|
+
).replace(/\\/g, "/");
|
|
1079
|
+
return `.gsd/${rel}`;
|
|
1041
1080
|
}
|
|
1042
1081
|
|
|
1043
1082
|
/**
|
|
@@ -1049,7 +1088,7 @@ export function relSliceFile(
|
|
|
1049
1088
|
*/
|
|
1050
1089
|
export function relTaskFile(
|
|
1051
1090
|
basePath: string, milestoneId: string, sliceId: string,
|
|
1052
|
-
taskId: string, suffix: string
|
|
1091
|
+
taskId: string, suffix: string, milestoneTitle?: string
|
|
1053
1092
|
): string {
|
|
1054
1093
|
const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
1055
1094
|
const phaseDir = resolveMilestonePath(basePath, milestoneId);
|
|
@@ -1062,6 +1101,6 @@ export function relTaskFile(
|
|
|
1062
1101
|
if (suffix === "PLAN") {
|
|
1063
1102
|
return relSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
1064
1103
|
}
|
|
1065
|
-
const relS = relSlicePath(basePath, milestoneId, sliceId);
|
|
1104
|
+
const relS = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
|
|
1066
1105
|
return `${relS}/${buildTaskFileName(taskId, suffix)}`;
|
|
1067
1106
|
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
HookStatusEntry,
|
|
12
12
|
PostUnitGateBlock,
|
|
13
13
|
} from "./types.js";
|
|
14
|
+
import type { SidecarItem } from "./auto/session.js";
|
|
14
15
|
import { getOrCreateRegistry, resolveHookArtifactPath } from "./rule-registry.js";
|
|
15
16
|
|
|
16
17
|
// Re-export resolveHookArtifactPath so existing importers still work.
|
|
@@ -75,6 +76,37 @@ export function restoreHookState(basePath: string): void {
|
|
|
75
76
|
getOrCreateRegistry().restoreState(basePath);
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Reconcile a restored `activeHook` against the session's sidecar queue.
|
|
81
|
+
*
|
|
82
|
+
* The registry persists `activeHook` to disk but the pending hook *dispatch*
|
|
83
|
+
* lives only on the non-persisted `s.sidecarQueue`. After a pause/resume or
|
|
84
|
+
* crash-recovery, `restoreHookState` re-hydrates `activeHook` while the dispatch
|
|
85
|
+
* is gone, so the registry believes a hook is in-flight but nothing will ever
|
|
86
|
+
* complete it — and the next unrelated unit's close-out is falsely charged
|
|
87
|
+
* against the stale hook and blocked. Re-enqueue the missing dispatch so the
|
|
88
|
+
* hook actually runs, restoring the invariant that a persisted `activeHook`
|
|
89
|
+
* always has a live dispatch (#1246).
|
|
90
|
+
*/
|
|
91
|
+
export function reconcileRestoredHookDispatch(
|
|
92
|
+
basePath: string,
|
|
93
|
+
sidecarQueue: SidecarItem[],
|
|
94
|
+
): void {
|
|
95
|
+
const dispatch = getOrCreateRegistry().getPendingHookDispatch(basePath);
|
|
96
|
+
if (!dispatch) return;
|
|
97
|
+
const alreadyQueued = sidecarQueue.some(
|
|
98
|
+
item => item.kind === "hook" && item.unitType === dispatch.unitType,
|
|
99
|
+
);
|
|
100
|
+
if (alreadyQueued) return;
|
|
101
|
+
sidecarQueue.push({
|
|
102
|
+
kind: "hook",
|
|
103
|
+
unitType: dispatch.unitType,
|
|
104
|
+
unitId: dispatch.unitId,
|
|
105
|
+
prompt: dispatch.prompt,
|
|
106
|
+
model: dispatch.model,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
78
110
|
export function clearPersistedHookState(basePath: string): void {
|
|
79
111
|
getOrCreateRegistry().clearPersistedState(basePath);
|
|
80
112
|
}
|