@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
|
@@ -13,13 +13,12 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
13
13
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
14
|
import { dirname, join, relative } from "node:path";
|
|
15
15
|
import { getAllMilestones, getMilestone, getMilestoneScopedArtifacts, getSliceScopedArtifacts, getMilestoneSlices, getSliceTasks, getTasksBySliceIds, getTask, getSlice, insertArtifact, deleteArtifactByPath, getGateResults, isDbAvailable, } from "./gsd-db.js";
|
|
16
|
-
import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, gsdProjectionRoot, gsdRoot,
|
|
16
|
+
import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, targetMilestoneFile, targetSliceFile, gsdProjectionRoot, gsdRoot, buildTaskFileName, } from "./paths.js";
|
|
17
17
|
import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
|
|
18
18
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
19
19
|
import { invalidateStateCache } from "./state.js";
|
|
20
20
|
import { clearPathCache, milestonesDir, legacyMilestonesDir, isLegacyMilestonesLayout, resolveMilestonePath, relSliceFile, canonicalPhaseDirName } from "./paths.js";
|
|
21
21
|
import { readCompatMarker, writeCompatMarker, computeProjectionSha } from "./compat/compat-marker.js";
|
|
22
|
-
import { planFileName, milestoneIdToPhaseNum, sliceIdToPlanNum, } from "./layout-policy.js";
|
|
23
22
|
// ─── Compat marker invalidation ───────────────────────────────────────────
|
|
24
23
|
// Every successful projection write pushes its (basePath, projectionPath,
|
|
25
24
|
// entities) here; invalidateCaches() drains it and refreshes .gsd/.compat.json
|
|
@@ -134,19 +133,6 @@ function sanitizeInlineRoadmapText(value) {
|
|
|
134
133
|
.replace(/\s+/g, " ")
|
|
135
134
|
.trim();
|
|
136
135
|
}
|
|
137
|
-
function resolveRoadmapProjectionPath(basePath, milestoneId) {
|
|
138
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
139
|
-
const existing = resolveMilestonePath(basePath, milestoneId);
|
|
140
|
-
const legacyBase = legacyMilestonesDir(basePath);
|
|
141
|
-
const isLegacyLayout = existing
|
|
142
|
-
? existing.startsWith(legacyBase + "/") || existing.startsWith(legacyBase + "\\")
|
|
143
|
-
: isLegacyMilestonesLayout(basePath);
|
|
144
|
-
const phaseDir = existing ?? join(isLegacyLayout ? legacyBase : milestonesDir(basePath), isLegacyLayout ? milestoneId : canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
|
|
145
|
-
const roadmapFileName = isLegacyLayout
|
|
146
|
-
? `${milestoneId}-ROADMAP.md`
|
|
147
|
-
: `${String(phaseNum).padStart(2, "0")}-ROADMAP.md`;
|
|
148
|
-
return join(phaseDir, roadmapFileName);
|
|
149
|
-
}
|
|
150
136
|
function isMilestoneFlatPhaseLayout(basePath, milestoneId) {
|
|
151
137
|
const existing = resolveMilestonePath(basePath, milestoneId);
|
|
152
138
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
@@ -492,7 +478,7 @@ export async function renderRoadmapFromDb(basePath, milestoneId) {
|
|
|
492
478
|
// clear "write the ROADMAP" failure) instead of a misleading stub.
|
|
493
479
|
if (slices.length === 0 && !milestone.vision.trim()) {
|
|
494
480
|
logWarning("projection", `renderRoadmapFromDb skipped unplanned milestone ${milestoneId} (zero slices, empty vision) — refusing to write a stub ROADMAP`);
|
|
495
|
-
const absPath =
|
|
481
|
+
const absPath = targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestone.title);
|
|
496
482
|
if (existsSync(absPath)) {
|
|
497
483
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
498
484
|
unlinkSync(absPath);
|
|
@@ -506,7 +492,7 @@ export async function renderRoadmapFromDb(basePath, milestoneId) {
|
|
|
506
492
|
}
|
|
507
493
|
return { skipped: "unplanned-milestone" };
|
|
508
494
|
}
|
|
509
|
-
const absPath =
|
|
495
|
+
const absPath = targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestone.title);
|
|
510
496
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
511
497
|
const content = renderRoadmapMarkdown(milestone, slices);
|
|
512
498
|
await writeAndStore(absPath, artifactPath, content, {
|
|
@@ -536,21 +522,15 @@ export async function renderMilestoneArtifactsFromDb(basePath, milestoneId) {
|
|
|
536
522
|
const artifacts = getMilestoneScopedArtifacts(milestoneId);
|
|
537
523
|
if (artifacts.length === 0)
|
|
538
524
|
return false;
|
|
539
|
-
const
|
|
540
|
-
join(milestonesDir(basePath), canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
|
|
541
|
-
mkdirSync(phaseDir, { recursive: true });
|
|
542
|
-
const legacyBase = legacyMilestonesDir(basePath);
|
|
543
|
-
const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
|
|
525
|
+
const milestone = getMilestone(milestoneId);
|
|
544
526
|
let wrote = false;
|
|
545
527
|
for (const artifact of artifacts) {
|
|
546
528
|
if (artifact.artifact_type === "ROADMAP")
|
|
547
529
|
continue;
|
|
548
530
|
if (!artifact.full_content.trim())
|
|
549
531
|
continue;
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
: buildMilestoneFileName(milestoneId, artifact.artifact_type);
|
|
553
|
-
const absPath = join(phaseDir, fileName);
|
|
532
|
+
const absPath = targetMilestoneFile(basePath, milestoneId, artifact.artifact_type, milestone?.title);
|
|
533
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
554
534
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
555
535
|
await writeAndStore(absPath, artifactPath, artifact.full_content, {
|
|
556
536
|
artifact_type: artifact.artifact_type,
|
|
@@ -664,20 +644,11 @@ export async function renderSliceSummary(basePath, milestoneId, sliceId) {
|
|
|
664
644
|
if (!slice) {
|
|
665
645
|
return false; // No slice data — skip silently
|
|
666
646
|
}
|
|
667
|
-
|
|
668
|
-
// finds existing dirs. In flat-phase the slice "path" is the phase dir.
|
|
669
|
-
let slicePath = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
670
|
-
if (!slicePath) {
|
|
671
|
-
const mDir = milestonesDir(basePath);
|
|
672
|
-
const dirName = canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title);
|
|
673
|
-
slicePath = join(mDir, dirName);
|
|
674
|
-
mkdirSync(slicePath, { recursive: true });
|
|
675
|
-
}
|
|
647
|
+
const milestoneTitle = getMilestone(milestoneId)?.title;
|
|
676
648
|
let wrote = false;
|
|
677
|
-
// Write SUMMARY — flat-phase: NN-MM-SUMMARY.md inside phase dir
|
|
678
649
|
if (slice.full_summary_md) {
|
|
679
|
-
const
|
|
680
|
-
|
|
650
|
+
const summaryAbs = targetSliceFile(basePath, milestoneId, sliceId, "SUMMARY", milestoneTitle);
|
|
651
|
+
mkdirSync(dirname(summaryAbs), { recursive: true });
|
|
681
652
|
const summaryArtifact = toArtifactPath(summaryAbs, basePath);
|
|
682
653
|
await writeAndStore(summaryAbs, summaryArtifact, slice.full_summary_md, {
|
|
683
654
|
artifact_type: "SUMMARY",
|
|
@@ -686,10 +657,9 @@ export async function renderSliceSummary(basePath, milestoneId, sliceId) {
|
|
|
686
657
|
}, basePath);
|
|
687
658
|
wrote = true;
|
|
688
659
|
}
|
|
689
|
-
// Write UAT — flat-phase: NN-MM-UAT.md inside phase dir
|
|
690
660
|
if (slice.full_uat_md) {
|
|
691
|
-
const
|
|
692
|
-
|
|
661
|
+
const uatAbs = targetSliceFile(basePath, milestoneId, sliceId, "UAT", milestoneTitle);
|
|
662
|
+
mkdirSync(dirname(uatAbs), { recursive: true });
|
|
693
663
|
const uatArtifact = toArtifactPath(uatAbs, basePath);
|
|
694
664
|
await writeAndStore(uatAbs, uatArtifact, slice.full_uat_md, {
|
|
695
665
|
artifact_type: "UAT",
|
|
@@ -874,7 +844,7 @@ function detectStaleRendersImpl(basePath) {
|
|
|
874
844
|
// TODO(flat-phase): roadmap checkbox parsing may not match flat-phase
|
|
875
845
|
// roadmap format, causing false-positive drift loops. Skip during transition.
|
|
876
846
|
/*
|
|
877
|
-
const roadmapPath =
|
|
847
|
+
const roadmapPath = targetMilestoneFile(basePath, milestone.id, "ROADMAP", milestone.title);
|
|
878
848
|
if (existsSync(roadmapPath)) {
|
|
879
849
|
try {
|
|
880
850
|
const parsed = parseProjectionByIdentity(roadmapPath, parseRoadmap) as ReturnType<typeof parseRoadmap>;
|
|
@@ -961,34 +931,27 @@ function detectStaleRendersImpl(basePath) {
|
|
|
961
931
|
}
|
|
962
932
|
}
|
|
963
933
|
}
|
|
964
|
-
// Check missing slice summary/UAT files
|
|
934
|
+
// Check missing slice summary/UAT files. Use the same target helper as
|
|
935
|
+
// renderSliceSummary so detection and repair agree on flat-phase vs legacy
|
|
936
|
+
// output locations.
|
|
965
937
|
const sliceRow = getSlice(milestone.id, slice.id);
|
|
966
938
|
if (sliceRow && sliceRow.status === "complete") {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const summaryName = planFileName(phaseNum, planNum, "SUMMARY");
|
|
975
|
-
const summaryAbsPath = join(slicePath, summaryName);
|
|
976
|
-
if (!existsSync(summaryAbsPath)) {
|
|
977
|
-
stale.push({
|
|
978
|
-
path: summaryAbsPath,
|
|
979
|
-
reason: `${slice.id} is complete with summary in DB but SUMMARY.md missing on disk`,
|
|
980
|
-
});
|
|
981
|
-
}
|
|
939
|
+
if (sliceRow.full_summary_md) {
|
|
940
|
+
const summaryAbsPath = targetSliceFile(basePath, milestone.id, slice.id, "SUMMARY", milestone.title);
|
|
941
|
+
if (!existsSync(summaryAbsPath)) {
|
|
942
|
+
stale.push({
|
|
943
|
+
path: summaryAbsPath,
|
|
944
|
+
reason: `${slice.id} is complete with summary in DB but SUMMARY.md missing on disk`,
|
|
945
|
+
});
|
|
982
946
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
947
|
+
}
|
|
948
|
+
if (sliceRow.full_uat_md) {
|
|
949
|
+
const uatAbsPath = targetSliceFile(basePath, milestone.id, slice.id, "UAT", milestone.title);
|
|
950
|
+
if (!existsSync(uatAbsPath)) {
|
|
951
|
+
stale.push({
|
|
952
|
+
path: uatAbsPath,
|
|
953
|
+
reason: `${slice.id} is complete with UAT in DB but UAT.md missing on disk`,
|
|
954
|
+
});
|
|
992
955
|
}
|
|
993
956
|
}
|
|
994
957
|
}
|
|
@@ -1013,12 +976,8 @@ export function roadmapRenderMarksSliceDone(roadmapContent, sliceId) {
|
|
|
1013
976
|
return parseRoadmap(roadmapContent).slices.some((slice) => slice.id === sliceId && slice.done);
|
|
1014
977
|
}
|
|
1015
978
|
export async function renderReplanFromDb(basePath, milestoneId, sliceId, replanData) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
?? join(milestonesDir(basePath), canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
|
|
1019
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
1020
|
-
const planNum = sliceIdToPlanNum(sliceId);
|
|
1021
|
-
const absPath = join(slicePath, planFileName(phaseNum, planNum, "REPLAN"));
|
|
979
|
+
const absPath = targetSliceFile(basePath, milestoneId, sliceId, "REPLAN", getMilestone(milestoneId)?.title);
|
|
980
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
1022
981
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
1023
982
|
const lines = [];
|
|
1024
983
|
lines.push(`# ${sliceId} Replan`);
|
|
@@ -1045,24 +1004,8 @@ export async function renderReplanFromDb(basePath, milestoneId, sliceId, replanD
|
|
|
1045
1004
|
return { replanPath: absPath, content };
|
|
1046
1005
|
}
|
|
1047
1006
|
export async function renderAssessmentFromDb(basePath, milestoneId, sliceId, assessmentData) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
// stray slices/SID/ created by an earlier planning step would send this write
|
|
1051
|
-
// to a hybrid path (wrong dir, flat filename) that verification cannot find.
|
|
1052
|
-
// Guard with the same legacy-base check relSlicePath() uses so flat-phase
|
|
1053
|
-
// milestones always target the phase dir.
|
|
1054
|
-
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
1055
|
-
const legacyBase = legacyMilestonesDir(basePath);
|
|
1056
|
-
const isLegacyLayout = mDir
|
|
1057
|
-
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
1058
|
-
: false;
|
|
1059
|
-
const fallbackDir = join(milestonesDir(basePath), canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
|
|
1060
|
-
const slicePath = isLegacyLayout
|
|
1061
|
-
? (resolveSlicePath(basePath, milestoneId, sliceId) ?? fallbackDir)
|
|
1062
|
-
: (mDir ?? fallbackDir);
|
|
1063
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
1064
|
-
const planNum = sliceIdToPlanNum(sliceId);
|
|
1065
|
-
const absPath = join(slicePath, planFileName(phaseNum, planNum, "ASSESSMENT"));
|
|
1007
|
+
const absPath = targetSliceFile(basePath, milestoneId, sliceId, "ASSESSMENT", getMilestone(milestoneId)?.title);
|
|
1008
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
1066
1009
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
1067
1010
|
const lines = [];
|
|
1068
1011
|
lines.push(`# ${sliceId} Assessment`);
|
|
@@ -172,7 +172,7 @@ export function snapshotUnitMetrics(ctx, unitType, unitId, startedAt, model, opt
|
|
|
172
172
|
ledger.units.push(unit);
|
|
173
173
|
}
|
|
174
174
|
saveLedger(basePath, ledger);
|
|
175
|
-
if (isUnifiedAuditEnabled()) {
|
|
175
|
+
if (isUnifiedAuditEnabled(basePath)) {
|
|
176
176
|
emitUokAuditEvent(basePath, buildAuditEnvelope({
|
|
177
177
|
traceId: opts?.traceId ?? `metrics:${unitType}:${unitId}`,
|
|
178
178
|
turnId: opts?.turnId,
|
|
@@ -324,7 +324,7 @@ export function snapshotUnitMetricsByScope(scope, ctx, unitType, unitId, started
|
|
|
324
324
|
scopedLedger.units.push(unit);
|
|
325
325
|
}
|
|
326
326
|
saveLedger(base, scopedLedger);
|
|
327
|
-
if (isUnifiedAuditEnabled()) {
|
|
327
|
+
if (isUnifiedAuditEnabled(base)) {
|
|
328
328
|
emitUokAuditEvent(base, buildAuditEnvelope({
|
|
329
329
|
traceId: opts?.traceId ?? `metrics:${unitType}:${unitId}`,
|
|
330
330
|
turnId: opts?.turnId,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* via prefix matching, so existing projects work without migration.
|
|
11
11
|
*/
|
|
12
12
|
import { readdirSync, existsSync, realpathSync, statSync, Dirent } from "node:fs";
|
|
13
|
-
import { join, dirname, normalize, resolve } from "node:path";
|
|
13
|
+
import { join, dirname, normalize, relative, resolve } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { spawnSync } from "node:child_process";
|
|
16
16
|
import { nativeScanGsdTree } from "./native-parser-bridge.js";
|
|
@@ -331,21 +331,26 @@ export function resolveGsdPathContract(workRoot, originalProjectRoot) {
|
|
|
331
331
|
const resolvedWorkRoot = resolve(workRoot || process.cwd());
|
|
332
332
|
const isWorktree = isGsdWorktreePath(resolvedWorkRoot);
|
|
333
333
|
if (isWorktree && !originalProjectRoot?.trim()) {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
334
|
+
const rawProjectGsd = resolveExternalStateProjectGsdFromWorktreePath(resolvedWorkRoot);
|
|
335
|
+
if (rawProjectGsd) {
|
|
336
|
+
// Canonicalize the `.gsd` root so the DB path matches what every other
|
|
337
|
+
// accessor (gsdRoot/gsdProjectionRoot) returns — otherwise an unresolved
|
|
338
|
+
// symlink (e.g. `/mnt/c/.../.gsd` → native ext4 on WSL) selects the wrong
|
|
339
|
+
// journal mode and yields a fragile, move-prone handle. See issue #1239.
|
|
340
|
+
const projectGsd = normalizeRealPath(rawProjectGsd);
|
|
336
341
|
return {
|
|
337
342
|
projectRoot: dirname(dirname(projectGsd)),
|
|
338
343
|
workRoot: resolvedWorkRoot,
|
|
339
344
|
projectGsd,
|
|
340
|
-
worktreeGsd: join(resolvedWorkRoot, ".gsd"),
|
|
345
|
+
worktreeGsd: normalizeRealPath(join(resolvedWorkRoot, ".gsd")),
|
|
341
346
|
projectDb: join(projectGsd, "gsd.db"),
|
|
342
347
|
isWorktree,
|
|
343
348
|
};
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
351
|
const projectRoot = resolve(resolveWorktreeProjectRoot(resolvedWorkRoot, originalProjectRoot));
|
|
347
|
-
const projectGsd = join(projectRoot, ".gsd");
|
|
348
|
-
const worktreeGsd = isWorktree ? join(resolvedWorkRoot, ".gsd") : null;
|
|
352
|
+
const projectGsd = normalizeRealPath(join(projectRoot, ".gsd"));
|
|
353
|
+
const worktreeGsd = isWorktree ? normalizeRealPath(join(resolvedWorkRoot, ".gsd")) : null;
|
|
349
354
|
return {
|
|
350
355
|
projectRoot,
|
|
351
356
|
workRoot: resolvedWorkRoot,
|
|
@@ -879,11 +884,22 @@ export function resolveTasksDir(basePath, milestoneId, sliceId) {
|
|
|
879
884
|
* Resolve a specific task file.
|
|
880
885
|
*/
|
|
881
886
|
export function resolveTaskFile(basePath, milestoneId, sliceId, taskId, suffix) {
|
|
882
|
-
const
|
|
883
|
-
if (!
|
|
887
|
+
const phaseDir = resolveMilestonePath(basePath, milestoneId);
|
|
888
|
+
if (!phaseDir)
|
|
884
889
|
return null;
|
|
885
|
-
const
|
|
886
|
-
|
|
890
|
+
const legacyBase = legacyMilestonesDir(basePath);
|
|
891
|
+
const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
|
|
892
|
+
if (suffix !== "PLAN" && !isLegacy) {
|
|
893
|
+
const flatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
|
|
894
|
+
return existsSync(flatPath) ? flatPath : null;
|
|
895
|
+
}
|
|
896
|
+
const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
897
|
+
if (tDir) {
|
|
898
|
+
const file = resolveFile(tDir, taskId, suffix);
|
|
899
|
+
if (file)
|
|
900
|
+
return join(tDir, file);
|
|
901
|
+
}
|
|
902
|
+
return null;
|
|
887
903
|
}
|
|
888
904
|
// ─── Relative Path Builders (for prompts — .gsd/milestones/...) ────────────
|
|
889
905
|
/**
|
|
@@ -915,29 +931,30 @@ export function relMilestonePath(basePath, milestoneId, title) {
|
|
|
915
931
|
return `.gsd/${LAYOUT_SEGMENTS.level1}/${canonicalPhaseDirName(milestoneId, title)}`;
|
|
916
932
|
}
|
|
917
933
|
/**
|
|
918
|
-
* Build
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
export function relMilestoneFile(basePath, milestoneId, suffix) {
|
|
924
|
-
const mRel = relMilestonePath(basePath, milestoneId);
|
|
925
|
-
// resolveMilestoneFile checks NN-SUFFIX.md (flat-phase) and MID-SUFFIX.md (legacy).
|
|
926
|
-
const absFile = resolveMilestoneFile(basePath, milestoneId, suffix);
|
|
927
|
-
if (absFile) {
|
|
928
|
-
const fileName = absFile.split(/[/\\]/).pop();
|
|
929
|
-
return `${mRel}/${fileName}`;
|
|
930
|
-
}
|
|
931
|
-
// File doesn't exist yet — use layout-aware fallback filename.
|
|
934
|
+
* Build the canonical absolute write target for a milestone file.
|
|
935
|
+
* Existing compatibility filenames are intentionally ignored; readers that need
|
|
936
|
+
* to preserve an existing file should call resolveMilestoneFile first.
|
|
937
|
+
*/
|
|
938
|
+
export function targetMilestoneFile(basePath, milestoneId, suffix, title) {
|
|
932
939
|
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
933
940
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
934
941
|
const isLegacy = mDir
|
|
935
942
|
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
936
|
-
:
|
|
943
|
+
: isLegacyMilestonesLayout(basePath);
|
|
944
|
+
const dir = mDir ?? join(isLegacy ? legacyBase : milestonesDir(basePath), isLegacy ? milestoneId : canonicalPhaseDirName(milestoneId, title));
|
|
937
945
|
const fileName = isLegacy
|
|
938
946
|
? `${milestoneId}-${suffix}.md`
|
|
939
947
|
: `${String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0")}-${suffix}.md`;
|
|
940
|
-
return
|
|
948
|
+
return join(dir, fileName);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Build relative .gsd/ path to a milestone file.
|
|
952
|
+
* Preserves an existing compatibility filename when present; otherwise falls
|
|
953
|
+
* back to the canonical write target.
|
|
954
|
+
*/
|
|
955
|
+
export function relMilestoneFile(basePath, milestoneId, suffix, title) {
|
|
956
|
+
const rel = relative(gsdProjectionRoot(basePath), resolveMilestoneFile(basePath, milestoneId, suffix) ?? targetMilestoneFile(basePath, milestoneId, suffix, title)).replace(/\\/g, "/");
|
|
957
|
+
return `.gsd/${rel}`;
|
|
941
958
|
}
|
|
942
959
|
/**
|
|
943
960
|
* Build relative .gsd/ path to a slice directory.
|
|
@@ -965,32 +982,36 @@ export function relSlicePath(basePath, milestoneId, sliceId, milestoneTitle) {
|
|
|
965
982
|
return relMilestonePath(basePath, milestoneId, milestoneTitle);
|
|
966
983
|
}
|
|
967
984
|
/**
|
|
968
|
-
* Build
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
* @param milestoneTitle - Optional milestone title forwarded to relSlicePath /
|
|
972
|
-
* relMilestonePath for title-aware flat-phase fallback dir naming. Only used
|
|
973
|
-
* when the phase directory does not yet exist on disk.
|
|
985
|
+
* Build the canonical absolute write target for a slice file.
|
|
986
|
+
* Existing compatibility filenames are intentionally ignored; readers that need
|
|
987
|
+
* to preserve an existing file should call resolveSliceFile first.
|
|
974
988
|
*/
|
|
975
|
-
export function
|
|
976
|
-
const sRel = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
|
|
977
|
-
const absPath = resolveSliceFile(basePath, milestoneId, sliceId, suffix);
|
|
978
|
-
if (absPath) {
|
|
979
|
-
const fileName = absPath.split(/[/\\]/).pop();
|
|
980
|
-
return `${sRel}/${fileName}`;
|
|
981
|
-
}
|
|
982
|
-
// Fallback when file doesn't exist yet — use layout-aware filename.
|
|
989
|
+
export function targetSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle) {
|
|
983
990
|
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
984
991
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
985
992
|
const isLegacy = mDir
|
|
986
993
|
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
987
|
-
:
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
+
: isLegacyMilestonesLayout(basePath);
|
|
995
|
+
const milestoneDir = mDir
|
|
996
|
+
?? dirname(targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestoneTitle));
|
|
997
|
+
const slicesDir = join(milestoneDir, "slices");
|
|
998
|
+
const dir = isLegacy
|
|
999
|
+
? join(slicesDir, resolveDir(slicesDir, sliceId) ?? sliceId)
|
|
1000
|
+
: milestoneDir;
|
|
1001
|
+
const fileName = isLegacy
|
|
1002
|
+
? `${sliceId}-${suffix}.md`
|
|
1003
|
+
: planFileName(milestoneIdToPhaseNum(milestoneId), sliceIdToPlanNum(sliceId), suffix);
|
|
1004
|
+
return join(dir, fileName);
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Build relative .gsd/ path to a slice file.
|
|
1008
|
+
* Preserves an existing compatibility filename when present; otherwise falls
|
|
1009
|
+
* back to the canonical write target.
|
|
1010
|
+
*/
|
|
1011
|
+
export function relSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle) {
|
|
1012
|
+
const rel = relative(gsdProjectionRoot(basePath), resolveSliceFile(basePath, milestoneId, sliceId, suffix)
|
|
1013
|
+
?? targetSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle)).replace(/\\/g, "/");
|
|
1014
|
+
return `.gsd/${rel}`;
|
|
994
1015
|
}
|
|
995
1016
|
/**
|
|
996
1017
|
* Build relative .gsd/ path to a task file.
|
|
@@ -999,7 +1020,7 @@ export function relSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTi
|
|
|
999
1020
|
* Flat-phase: PLAN → slice plan path (tasks as checkboxes); other suffixes
|
|
1000
1021
|
* (e.g. SUMMARY) → phase dir / TID-SUFFIX.md
|
|
1001
1022
|
*/
|
|
1002
|
-
export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix) {
|
|
1023
|
+
export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix, milestoneTitle) {
|
|
1003
1024
|
const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
1004
1025
|
const phaseDir = resolveMilestonePath(basePath, milestoneId);
|
|
1005
1026
|
// Legacy: slice path is a slices/SID/ subdir inside the milestone dir
|
|
@@ -1011,6 +1032,6 @@ export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix) {
|
|
|
1011
1032
|
if (suffix === "PLAN") {
|
|
1012
1033
|
return relSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
1013
1034
|
}
|
|
1014
|
-
const relS = relSlicePath(basePath, milestoneId, sliceId);
|
|
1035
|
+
const relS = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
|
|
1015
1036
|
return `${relS}/${buildTaskFileName(taskId, suffix)}`;
|
|
1016
1037
|
}
|
|
@@ -42,6 +42,33 @@ export function persistHookState(basePath) {
|
|
|
42
42
|
export function restoreHookState(basePath) {
|
|
43
43
|
getOrCreateRegistry().restoreState(basePath);
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Reconcile a restored `activeHook` against the session's sidecar queue.
|
|
47
|
+
*
|
|
48
|
+
* The registry persists `activeHook` to disk but the pending hook *dispatch*
|
|
49
|
+
* lives only on the non-persisted `s.sidecarQueue`. After a pause/resume or
|
|
50
|
+
* crash-recovery, `restoreHookState` re-hydrates `activeHook` while the dispatch
|
|
51
|
+
* is gone, so the registry believes a hook is in-flight but nothing will ever
|
|
52
|
+
* complete it — and the next unrelated unit's close-out is falsely charged
|
|
53
|
+
* against the stale hook and blocked. Re-enqueue the missing dispatch so the
|
|
54
|
+
* hook actually runs, restoring the invariant that a persisted `activeHook`
|
|
55
|
+
* always has a live dispatch (#1246).
|
|
56
|
+
*/
|
|
57
|
+
export function reconcileRestoredHookDispatch(basePath, sidecarQueue) {
|
|
58
|
+
const dispatch = getOrCreateRegistry().getPendingHookDispatch(basePath);
|
|
59
|
+
if (!dispatch)
|
|
60
|
+
return;
|
|
61
|
+
const alreadyQueued = sidecarQueue.some(item => item.kind === "hook" && item.unitType === dispatch.unitType);
|
|
62
|
+
if (alreadyQueued)
|
|
63
|
+
return;
|
|
64
|
+
sidecarQueue.push({
|
|
65
|
+
kind: "hook",
|
|
66
|
+
unitType: dispatch.unitType,
|
|
67
|
+
unitId: dispatch.unitId,
|
|
68
|
+
prompt: dispatch.prompt,
|
|
69
|
+
model: dispatch.model,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
45
72
|
export function clearPersistedHookState(basePath) {
|
|
46
73
|
getOrCreateRegistry().clearPersistedState(basePath);
|
|
47
74
|
}
|
|
@@ -66,6 +66,39 @@ function resolveWinningPhase(models, chain) {
|
|
|
66
66
|
}
|
|
67
67
|
return undefined;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Normalize a single model field into a {@link ResolvedModelConfig}.
|
|
71
|
+
*
|
|
72
|
+
* Accepts either the legacy bare-string form or the extended object form
|
|
73
|
+
* (`{ model, provider?, fallbacks? }`). This is the shared normalization used
|
|
74
|
+
* for phase buckets so sibling single-model fields — `post_unit_hooks[].model`,
|
|
75
|
+
* `auto_supervisor.model`, and `reactive_execution.subagent_model` — honor
|
|
76
|
+
* `fallbacks[]` identically rather than accepting a bare string only (#1229).
|
|
77
|
+
*
|
|
78
|
+
* Returns undefined for an unset, blank, or model-less field.
|
|
79
|
+
*/
|
|
80
|
+
export function normalizeModelFieldConfig(field) {
|
|
81
|
+
if (!field)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (typeof field === "string") {
|
|
84
|
+
return field.trim() ? { primary: field, fallbacks: [] } : undefined;
|
|
85
|
+
}
|
|
86
|
+
if (!field.model)
|
|
87
|
+
return undefined;
|
|
88
|
+
// When provider is explicitly set, prepend it to the model ID so the
|
|
89
|
+
// resolution code in auto.ts can do an explicit provider match.
|
|
90
|
+
const primary = field.provider && !field.model.includes("/")
|
|
91
|
+
? `${field.provider}/${field.model}`
|
|
92
|
+
: field.model;
|
|
93
|
+
// Carry the per-field `thinking` sub-field through so the dispatch site can
|
|
94
|
+
// prefer it over the session/floor level, mirroring phase-inline thinking
|
|
95
|
+
// (#1269). `undefined` is preserved as absent so behavior is unchanged.
|
|
96
|
+
return {
|
|
97
|
+
primary,
|
|
98
|
+
fallbacks: field.fallbacks ?? [],
|
|
99
|
+
...(field.thinking ? { thinking: field.thinking } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
69
102
|
/**
|
|
70
103
|
* Resolve model and fallbacks for a given auto-mode unit type.
|
|
71
104
|
* Returns the primary model and ordered fallbacks, or undefined if not configured.
|
|
@@ -73,6 +106,15 @@ function resolveWinningPhase(models, chain) {
|
|
|
73
106
|
* Supports both legacy string format and extended object format:
|
|
74
107
|
* - Legacy: `planning: claude-opus-4-6`
|
|
75
108
|
* - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
|
|
109
|
+
*
|
|
110
|
+
* Hook unit types (`hook/<name>`) resolve against the matching
|
|
111
|
+
* `post_unit_hooks[]` entry's `model` field so hook sessions honor the same
|
|
112
|
+
* `fallbacks[]` chain as phase buckets — the recovery path can then switch to
|
|
113
|
+
* a fallback provider instead of hard-failing (and potentially pausing) the
|
|
114
|
+
* run on a transient provider trip (#1229).
|
|
115
|
+
*
|
|
116
|
+
* The synthetic `supervisor` unit type resolves against `auto_supervisor.model`
|
|
117
|
+
* for supervisor interventions (wrap-up warnings, timeout recovery).
|
|
76
118
|
*/
|
|
77
119
|
export function resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds, preferredModelId, skipProfileDefaults = false) {
|
|
78
120
|
const loadOpts = {
|
|
@@ -81,26 +123,25 @@ export function resolveModelWithFallbacksForUnit(unitType, basePath, availableMo
|
|
|
81
123
|
...(skipProfileDefaults ? { skipProfileDefaults: true } : {}),
|
|
82
124
|
};
|
|
83
125
|
const prefs = loadEffectiveGSDPreferences(basePath, loadOpts);
|
|
126
|
+
// Supervisor interventions resolve against `auto_supervisor.model` (#1229).
|
|
127
|
+
if (unitType === "supervisor") {
|
|
128
|
+
return normalizeModelFieldConfig(prefs?.preferences?.auto_supervisor?.model);
|
|
129
|
+
}
|
|
130
|
+
// Hook sessions resolve against their own `post_unit_hooks[].model` field,
|
|
131
|
+
// which now shares the phase-bucket object form (#1229).
|
|
132
|
+
if (unitType.startsWith("hook/")) {
|
|
133
|
+
const hookName = unitType.slice("hook/".length);
|
|
134
|
+
const hooks = prefs?.preferences?.post_unit_hooks;
|
|
135
|
+
const hook = Array.isArray(hooks) ? hooks.find((h) => h.name === hookName) : undefined;
|
|
136
|
+
return normalizeModelFieldConfig(hook?.model);
|
|
137
|
+
}
|
|
84
138
|
const chain = phaseChainForUnit(unitType);
|
|
85
139
|
if (!chain)
|
|
86
140
|
return undefined;
|
|
87
141
|
const winner = resolveWinningPhase(prefs?.preferences?.models, chain);
|
|
88
142
|
if (!winner)
|
|
89
143
|
return undefined;
|
|
90
|
-
|
|
91
|
-
// Normalize: string -> { model, fallbacks: [] }
|
|
92
|
-
if (typeof phaseConfig === "string") {
|
|
93
|
-
return { primary: phaseConfig, fallbacks: [] };
|
|
94
|
-
}
|
|
95
|
-
// When provider is explicitly set, prepend it to the model ID so the
|
|
96
|
-
// resolution code in auto.ts can do an explicit provider match.
|
|
97
|
-
const primary = phaseConfig.provider && !phaseConfig.model.includes("/")
|
|
98
|
-
? `${phaseConfig.provider}/${phaseConfig.model}`
|
|
99
|
-
: phaseConfig.model;
|
|
100
|
-
return {
|
|
101
|
-
primary,
|
|
102
|
-
fallbacks: phaseConfig.fallbacks ?? [],
|
|
103
|
-
};
|
|
144
|
+
return normalizeModelFieldConfig(winner.config);
|
|
104
145
|
}
|
|
105
146
|
/**
|
|
106
147
|
* Resolve the explicitly configured reasoning effort for a unit type (ADR-026).
|
|
@@ -125,6 +166,13 @@ export function resolveModelWithFallbacksForUnit(unitType, basePath, availableMo
|
|
|
125
166
|
* applied here.
|
|
126
167
|
*/
|
|
127
168
|
export function resolveThinkingLevelForUnit(unitType, basePath, availableModelIds) {
|
|
169
|
+
// Single-field configs — `auto_supervisor.model` and `post_unit_hooks[].model`
|
|
170
|
+
// — carry their own inline `thinking` on the object model form and have no
|
|
171
|
+
// phase-bucket chain, so the walk below never covers them. Resolve their
|
|
172
|
+
// per-field level directly (#1269).
|
|
173
|
+
if (unitType === "supervisor" || unitType.startsWith("hook/")) {
|
|
174
|
+
return resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds)?.thinking;
|
|
175
|
+
}
|
|
128
176
|
const loadOpts = availableModelIds !== undefined ? { availableModelIds } : undefined;
|
|
129
177
|
const prefs = loadEffectiveGSDPreferences(basePath, loadOpts)?.preferences;
|
|
130
178
|
if (!prefs)
|
|
@@ -383,12 +431,15 @@ export function resolveDynamicRoutingConfig() {
|
|
|
383
431
|
export function resolveAutoSupervisorConfig() {
|
|
384
432
|
const prefs = loadEffectiveGSDPreferences();
|
|
385
433
|
const configured = prefs?.preferences.auto_supervisor ?? {};
|
|
434
|
+
const modelConfig = normalizeModelFieldConfig(configured.model);
|
|
386
435
|
return {
|
|
387
436
|
soft_timeout_minutes: configured.soft_timeout_minutes ?? 20,
|
|
388
437
|
idle_timeout_minutes: configured.idle_timeout_minutes ?? 10,
|
|
389
438
|
hard_timeout_minutes: configured.hard_timeout_minutes ?? 30,
|
|
390
439
|
stalled_tool_timeout_minutes: configured.stalled_tool_timeout_minutes ?? 5,
|
|
391
|
-
...(
|
|
440
|
+
...(modelConfig
|
|
441
|
+
? { model: modelConfig.primary, modelFallbacks: modelConfig.fallbacks }
|
|
442
|
+
: {}),
|
|
392
443
|
};
|
|
393
444
|
}
|
|
394
445
|
// ─── Token Profile Resolution ─────────────────────────────────────────────
|