@opengsd/gsd-pi 1.9.0-dev.5c1a3ff2 → 1.9.0-dev.d2070876
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.js +2 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto/unit-phase.js +37 -5
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +12 -4
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -5
- package/dist/resources/extensions/gsd/auto-start.js +29 -9
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +40 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +10 -9
- package/dist/resources/extensions/gsd/compat/compat-marker.js +80 -5
- package/dist/resources/extensions/gsd/db/engine.js +5 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +21 -2
- package/dist/resources/extensions/gsd/error-classifier.js +12 -2
- package/dist/resources/extensions/gsd/exec-sandbox.js +1 -0
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +3 -9
- package/dist/resources/extensions/gsd/paths.js +17 -7
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +98 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +4 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +5 -2
- package/dist/resources/extensions/gsd/stop-notice.js +9 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/shared/interview-ui.js +43 -6
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +9 -3
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +121 -15
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +272 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +17 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +17 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +103 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto/unit-phase.ts +41 -5
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -4
- package/src/resources/extensions/gsd/auto-runtime-state.ts +12 -7
- package/src/resources/extensions/gsd/auto-start.ts +31 -8
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +56 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +20 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -2
- package/src/resources/extensions/gsd/commands-verdict.ts +16 -9
- package/src/resources/extensions/gsd/compat/compat-marker.ts +99 -5
- package/src/resources/extensions/gsd/db/engine.ts +5 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +27 -2
- package/src/resources/extensions/gsd/error-classifier.ts +13 -2
- package/src/resources/extensions/gsd/exec-sandbox.ts +1 -0
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +3 -9
- package/src/resources/extensions/gsd/paths.ts +17 -7
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +132 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +4 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +4 -2
- package/src/resources/extensions/gsd/stop-notice.ts +11 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/db-engine-cjs-compat.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/external-markdown-edit.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/shared/interview-ui.ts +47 -8
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +29 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → sggX-h41OjePo-FjdArP5}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → sggX-h41OjePo-FjdArP5}/_ssgManifest.js +0 -0
|
@@ -8,9 +8,81 @@
|
|
|
8
8
|
// renderAllFromDb pass re-projects; the write-time invalidation hook then
|
|
9
9
|
// refreshes the marker entry, closing the loop.
|
|
10
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
11
|
+
import { basename, join } from "node:path";
|
|
12
12
|
import { computeProjectionSha, readCompatMarker, writeCompatMarker, } from "../../compat/compat-marker.js";
|
|
13
|
+
import { _getAdapter } from "../../gsd-db.js";
|
|
13
14
|
import { logWarning } from "../../workflow-logger.js";
|
|
15
|
+
function deepestProjectionScope(entities) {
|
|
16
|
+
let best = null;
|
|
17
|
+
for (const entity of entities) {
|
|
18
|
+
const parts = entity.split("/").filter(Boolean);
|
|
19
|
+
if (parts.length === 0)
|
|
20
|
+
continue;
|
|
21
|
+
if (!best || parts.length > best.length)
|
|
22
|
+
best = parts;
|
|
23
|
+
}
|
|
24
|
+
if (!best)
|
|
25
|
+
return null;
|
|
26
|
+
return {
|
|
27
|
+
milestoneId: best[0],
|
|
28
|
+
sliceId: best[1] ?? null,
|
|
29
|
+
taskId: best[2] ?? null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function artifactTypeFromProjectionPath(projectionPath) {
|
|
33
|
+
const name = basename(projectionPath);
|
|
34
|
+
const match = name.match(/^(?:[A-Za-z]\d+|\d+(?:-\d+)?)-(.+)\.md$/);
|
|
35
|
+
return match?.[1]?.toUpperCase() ?? null;
|
|
36
|
+
}
|
|
37
|
+
function dbProjectionMatches(projectionPath, entry, actualSha) {
|
|
38
|
+
const adapter = _getAdapter();
|
|
39
|
+
if (!adapter)
|
|
40
|
+
return false;
|
|
41
|
+
try {
|
|
42
|
+
const rows = adapter
|
|
43
|
+
.prepare("SELECT full_content FROM artifacts WHERE path = :path")
|
|
44
|
+
.all({ ":path": projectionPath });
|
|
45
|
+
const scope = deepestProjectionScope(entry.entities);
|
|
46
|
+
const artifactType = artifactTypeFromProjectionPath(projectionPath);
|
|
47
|
+
if (scope?.taskId) {
|
|
48
|
+
rows.push(...adapter
|
|
49
|
+
.prepare(`SELECT full_content
|
|
50
|
+
FROM artifacts
|
|
51
|
+
WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid
|
|
52
|
+
AND (:type IS NULL OR artifact_type = :type)`)
|
|
53
|
+
.all({
|
|
54
|
+
":mid": scope.milestoneId,
|
|
55
|
+
":sid": scope.sliceId,
|
|
56
|
+
":tid": scope.taskId,
|
|
57
|
+
":type": artifactType,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
else if (scope?.sliceId) {
|
|
61
|
+
rows.push(...adapter
|
|
62
|
+
.prepare(`SELECT full_content
|
|
63
|
+
FROM artifacts
|
|
64
|
+
WHERE milestone_id = :mid AND slice_id = :sid AND task_id IS NULL
|
|
65
|
+
AND (:type IS NULL OR artifact_type = :type)`)
|
|
66
|
+
.all({
|
|
67
|
+
":mid": scope.milestoneId,
|
|
68
|
+
":sid": scope.sliceId,
|
|
69
|
+
":type": artifactType,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
else if (scope) {
|
|
73
|
+
rows.push(...adapter
|
|
74
|
+
.prepare(`SELECT full_content
|
|
75
|
+
FROM artifacts
|
|
76
|
+
WHERE milestone_id = :mid AND slice_id IS NULL AND task_id IS NULL
|
|
77
|
+
AND (:type IS NULL OR artifact_type = :type)`)
|
|
78
|
+
.all({ ":mid": scope.milestoneId, ":type": artifactType }));
|
|
79
|
+
}
|
|
80
|
+
return rows.some((row) => computeProjectionSha(row.full_content) === actualSha);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
14
86
|
/**
|
|
15
87
|
* Detect sha drift between the marker baseline and current file contents.
|
|
16
88
|
*
|
|
@@ -21,11 +93,15 @@ import { logWarning } from "../../workflow-logger.js";
|
|
|
21
93
|
* - Sha mismatch → one record per drifted file, scoped to its recorded entities.
|
|
22
94
|
*/
|
|
23
95
|
function detectExternalMarkdownEdit(_state, ctx) {
|
|
24
|
-
const marker = readCompatMarker(ctx.basePath
|
|
96
|
+
const marker = readCompatMarker(ctx.basePath, {
|
|
97
|
+
healInvalidKeys: !ctx.dryRun,
|
|
98
|
+
quarantineInvalid: !ctx.dryRun,
|
|
99
|
+
});
|
|
25
100
|
const entries = Object.entries(marker.projections);
|
|
26
101
|
if (entries.length === 0)
|
|
27
102
|
return [];
|
|
28
103
|
const records = [];
|
|
104
|
+
let markerChanged = false;
|
|
29
105
|
for (const [projectionPath, entry] of entries) {
|
|
30
106
|
const abs = join(ctx.basePath, ".gsd", projectionPath);
|
|
31
107
|
if (!existsSync(abs))
|
|
@@ -33,6 +109,21 @@ function detectExternalMarkdownEdit(_state, ctx) {
|
|
|
33
109
|
const actual = computeProjectionSha(readFileSync(abs, "utf-8"));
|
|
34
110
|
if (actual === entry.sha)
|
|
35
111
|
continue;
|
|
112
|
+
if (dbProjectionMatches(projectionPath, entry, actual)) {
|
|
113
|
+
if (!ctx.dryRun) {
|
|
114
|
+
marker.projections[projectionPath] = {
|
|
115
|
+
sha: actual,
|
|
116
|
+
entities: entry.entities,
|
|
117
|
+
};
|
|
118
|
+
markerChanged = true;
|
|
119
|
+
logWarning("reconcile", `external-markdown-edit: refreshed stale compat marker for ${projectionPath} (expectedSha=${entry.sha}, actualSha=${actual})`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
logWarning("reconcile", `external-markdown-edit: stale compat marker for ${projectionPath} (expectedSha=${entry.sha}, actualSha=${actual})`);
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
logWarning("reconcile", `external-markdown-edit drift for ${projectionPath} (expectedSha=${entry.sha}, actualSha=${actual})`);
|
|
36
127
|
records.push({
|
|
37
128
|
kind: "external-markdown-edit",
|
|
38
129
|
projectionPath,
|
|
@@ -41,6 +132,11 @@ function detectExternalMarkdownEdit(_state, ctx) {
|
|
|
41
132
|
entities: entry.entities,
|
|
42
133
|
});
|
|
43
134
|
}
|
|
135
|
+
if (markerChanged) {
|
|
136
|
+
marker.lastWriter = "gsd-pi";
|
|
137
|
+
marker.lastProjectedAt = new Date().toISOString();
|
|
138
|
+
writeCompatMarker(ctx.basePath, marker);
|
|
139
|
+
}
|
|
44
140
|
return records;
|
|
45
141
|
}
|
|
46
142
|
/**
|
|
@@ -55,7 +55,10 @@ function detectUnseededPlanningFiles(ctx, projections, passthrough) {
|
|
|
55
55
|
return records;
|
|
56
56
|
}
|
|
57
57
|
async function detectExternalPlanningEdit(_state, ctx) {
|
|
58
|
-
const marker = readCompatMarker(ctx.basePath
|
|
58
|
+
const marker = readCompatMarker(ctx.basePath, {
|
|
59
|
+
healInvalidKeys: !ctx.dryRun,
|
|
60
|
+
quarantineInvalid: !ctx.dryRun,
|
|
61
|
+
});
|
|
59
62
|
if (!marker.planning?.active) {
|
|
60
63
|
// Not yet activated. Activation (layout parse + DB import) is owned by
|
|
61
64
|
// capturePlanningCompatIfNeeded, called from reconcileBeforeDispatch when
|
|
@@ -9,7 +9,7 @@ import { renderRoadmapFromDb } from "../../markdown-renderer.js";
|
|
|
9
9
|
import { findMilestoneIds } from "../../milestone-ids.js";
|
|
10
10
|
import { parseRoadmap } from "../../parsers-legacy.js";
|
|
11
11
|
import { resolveMilestoneFile } from "../../paths.js";
|
|
12
|
-
import { isClosedStatus } from "../../status-guards.js";
|
|
12
|
+
import { isClosedStatus, isSkippedForDispatch } from "../../status-guards.js";
|
|
13
13
|
function arraysEqual(a, b) {
|
|
14
14
|
if (a.length !== b.length)
|
|
15
15
|
return false;
|
|
@@ -76,7 +76,10 @@ export function detectRoadmapDivergenceDrift(_state, ctx) {
|
|
|
76
76
|
for (const milestoneId of findMilestoneIds(ctx.basePath)) {
|
|
77
77
|
// Skip milestones that don't yet have a DB row — that's the
|
|
78
78
|
// unregistered-milestone drift handler's responsibility.
|
|
79
|
-
|
|
79
|
+
const milestone = getMilestone(milestoneId);
|
|
80
|
+
if (!milestone)
|
|
81
|
+
continue;
|
|
82
|
+
if (isSkippedForDispatch(milestone.status))
|
|
80
83
|
continue;
|
|
81
84
|
if (milestoneHasDivergence(ctx.basePath, milestoneId)) {
|
|
82
85
|
drifts.push({ kind: "roadmap-divergence", milestoneId });
|
|
@@ -22,6 +22,12 @@ export function formatStopNoticePrefix(reason) {
|
|
|
22
22
|
const prefix = stopNoticeKind(reason) === "blocked" ? "Auto-mode blocked" : "Auto-mode stopped";
|
|
23
23
|
return displayReason ? `${prefix} — ${displayReason}` : prefix;
|
|
24
24
|
}
|
|
25
|
+
export function formatVerdictRecordedNotice(message) {
|
|
26
|
+
return `Verdict recorded: ${message}`;
|
|
27
|
+
}
|
|
28
|
+
export function formatVerdictRejectedNotice(message) {
|
|
29
|
+
return `Verdict rejected: ${message}`;
|
|
30
|
+
}
|
|
25
31
|
// ─── Classification (headless host side) ────────────────────────────────
|
|
26
32
|
// The canonical lowercase prefixes the headless event loop recognizes in
|
|
27
33
|
// notify messages. Emitters above and ad-hoc emitters elsewhere must start
|
|
@@ -33,6 +39,8 @@ export const TERMINAL_NOTICE_PREFIXES = [
|
|
|
33
39
|
"auto-mode complete",
|
|
34
40
|
"no active milestone",
|
|
35
41
|
"auto-mode idle",
|
|
42
|
+
"verdict recorded",
|
|
43
|
+
"verdict rejected",
|
|
36
44
|
];
|
|
37
45
|
/** Manual-resolution notices emitted before auto-mode can formally pause/stop. */
|
|
38
46
|
export function isManualResolutionNotice(message) {
|
|
@@ -65,6 +73,7 @@ export function isInteractiveMenuUnavailableNotice(message) {
|
|
|
65
73
|
}
|
|
66
74
|
export function isBlockedNoticeMessage(message) {
|
|
67
75
|
return (message.includes("blocked:") ||
|
|
76
|
+
message.startsWith("verdict rejected") ||
|
|
68
77
|
(isPauseNotice(message) && !isNonBlockingPauseNotice(message)) ||
|
|
69
78
|
isManualResolutionNotice(message) ||
|
|
70
79
|
isInteractiveMenuUnavailableNotice(message));
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync, } from "node:fs";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { reconcileWorktreeDb } from "./gsd-db.js";
|
|
24
|
-
import { resolveGsdPathContract } from "./paths.js";
|
|
24
|
+
import { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
|
|
25
25
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
26
26
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
27
27
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
@@ -129,6 +129,8 @@ function syncOtherMilestoneArtifacts(srcMilestonesDir, dstMilestonesDir, current
|
|
|
129
129
|
if (milestoneEntry.name === currentMilestoneId)
|
|
130
130
|
continue;
|
|
131
131
|
const srcMilestoneDir = join(srcMilestonesDir, milestoneEntry.name);
|
|
132
|
+
if (!dirIsContentBearingLegacyMilestone(srcMilestoneDir))
|
|
133
|
+
continue;
|
|
132
134
|
const dstMilestoneDir = join(dstMilestonesDir, milestoneEntry.name);
|
|
133
135
|
// Additively project the entire milestone subtree (force:false), not just
|
|
134
136
|
// top-level files. Prior completed milestones keep their per-slice and
|
|
@@ -219,25 +221,25 @@ export function _projectRootToWorktreeImpl(projectRoot, worktreePath_, milestone
|
|
|
219
221
|
// Without this, worktree-local files (e.g. VALIDATION.md written
|
|
220
222
|
// by validate-milestone) get clobbered by stale project root copies,
|
|
221
223
|
// causing an infinite re-validation loop (#1886).
|
|
222
|
-
safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId), { force: false });
|
|
223
|
-
// Additively project the full subtree of every OTHER milestone so
|
|
224
|
-
// worktree-bound units can read prior-milestone context artifacts and so
|
|
225
|
-
// completed milestones retain their per-slice/per-task SUMMARY.md and
|
|
226
|
-
// UAT.md on the worktree filesystem (otherwise the stale-render detector
|
|
227
|
-
// flags them as "complete in DB but missing on disk").
|
|
228
|
-
syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
|
|
229
|
-
// Force-sync ASSESSMENT files that have a verdict from project root (#2821).
|
|
230
|
-
// The additive-only copy above preserves worktree-local files, but
|
|
231
|
-
// ASSESSMENT files are special: after run-uat writes a verdict and post-unit
|
|
232
|
-
// syncs it to the project root, the worktree may retain a stale copy (e.g.
|
|
233
|
-
// verdict:fail while the project root has verdict:pass from a retry). On
|
|
234
|
-
// session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
|
|
235
|
-
// persists, checkNeedsRunUat finds no passing verdict → re-dispatches
|
|
236
|
-
// run-uat indefinitely (stuck-loop ×9).
|
|
237
224
|
const prMilestoneDir = join(prGsd, "milestones", milestoneId);
|
|
238
225
|
const wtMilestoneDir = join(wtGsd, "milestones", milestoneId);
|
|
239
|
-
|
|
240
|
-
|
|
226
|
+
if (dirIsContentBearingLegacyMilestone(prMilestoneDir)) {
|
|
227
|
+
safeCopyRecursive(prMilestoneDir, wtMilestoneDir, { force: false });
|
|
228
|
+
// Force-sync ASSESSMENT files that have a verdict from project root (#2821).
|
|
229
|
+
// The additive-only copy above preserves worktree-local files, but
|
|
230
|
+
// ASSESSMENT files are special: after run-uat writes a verdict and post-unit
|
|
231
|
+
// syncs it to the project root, the worktree may retain a stale copy (e.g.
|
|
232
|
+
// verdict:fail while the project root has verdict:pass from a retry). On
|
|
233
|
+
// session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
|
|
234
|
+
// persists, checkNeedsRunUat finds no passing verdict → re-dispatches
|
|
235
|
+
// run-uat indefinitely (stuck-loop ×9).
|
|
236
|
+
forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
|
|
237
|
+
forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
|
|
238
|
+
}
|
|
239
|
+
// Additively project the full subtree of every OTHER content-bearing legacy
|
|
240
|
+
// milestone so worktree-bound units can read prior-milestone context artifacts
|
|
241
|
+
// without recreating empty/meta-only milestones/ scaffolds in flat-phase projects.
|
|
242
|
+
syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
|
|
241
243
|
// Forward-sync completed-units.json from project root to worktree.
|
|
242
244
|
// Project root is authoritative for completion state after crash recovery;
|
|
243
245
|
// without this, the worktree re-dispatches already-completed units (#1886).
|
|
@@ -144,6 +144,10 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
144
144
|
let previewViewport = 0;
|
|
145
145
|
let previewTotal = 0;
|
|
146
146
|
function resetPreviewScroll() { previewScroll = 0; }
|
|
147
|
+
let optionsScroll = 0;
|
|
148
|
+
let optionsViewport = 0;
|
|
149
|
+
let optionsTotal = 0;
|
|
150
|
+
function resetOptionsScroll() { optionsScroll = 0; }
|
|
147
151
|
function finish(result) {
|
|
148
152
|
if (completed)
|
|
149
153
|
return;
|
|
@@ -203,6 +207,7 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
203
207
|
saveEditorToState();
|
|
204
208
|
currentIdx = newIdx;
|
|
205
209
|
resetPreviewScroll();
|
|
210
|
+
resetOptionsScroll();
|
|
206
211
|
loadStateToEditor();
|
|
207
212
|
focusNotes = states[currentIdx].notesVisible && states[currentIdx].notes.length > 0;
|
|
208
213
|
refresh();
|
|
@@ -376,32 +381,49 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
376
381
|
return;
|
|
377
382
|
}
|
|
378
383
|
}
|
|
379
|
-
// ──
|
|
384
|
+
// ── Side-by-side panel scrolling ─────────────────────────────
|
|
380
385
|
if (matchesKey(data, Key.pageUp)) {
|
|
386
|
+
let didScroll = false;
|
|
387
|
+
if (optionsScroll > 0) {
|
|
388
|
+
optionsScroll = Math.max(0, optionsScroll - optionsViewport);
|
|
389
|
+
didScroll = true;
|
|
390
|
+
}
|
|
381
391
|
if (previewScroll > 0) {
|
|
382
392
|
previewScroll = Math.max(0, previewScroll - previewViewport);
|
|
383
|
-
|
|
393
|
+
didScroll = true;
|
|
384
394
|
}
|
|
395
|
+
if (didScroll)
|
|
396
|
+
refresh();
|
|
385
397
|
return;
|
|
386
398
|
}
|
|
387
399
|
if (matchesKey(data, Key.pageDown)) {
|
|
400
|
+
let didScroll = false;
|
|
401
|
+
const maxOptionsScroll = Math.max(0, optionsTotal - optionsViewport);
|
|
402
|
+
if (optionsScroll < maxOptionsScroll) {
|
|
403
|
+
optionsScroll = Math.min(maxOptionsScroll, optionsScroll + optionsViewport);
|
|
404
|
+
didScroll = true;
|
|
405
|
+
}
|
|
388
406
|
const maxScroll = Math.max(0, previewTotal - previewViewport);
|
|
389
407
|
if (previewScroll < maxScroll) {
|
|
390
408
|
previewScroll = Math.min(maxScroll, previewScroll + previewViewport);
|
|
391
|
-
|
|
409
|
+
didScroll = true;
|
|
392
410
|
}
|
|
411
|
+
if (didScroll)
|
|
412
|
+
refresh();
|
|
393
413
|
return;
|
|
394
414
|
}
|
|
395
415
|
// ── Cursor navigation ────────────────────────────────────────
|
|
396
416
|
if (matchesKey(data, Key.up)) {
|
|
397
417
|
st.cursorIndex = (st.cursorIndex - 1 + optCount) % optCount;
|
|
398
418
|
resetPreviewScroll();
|
|
419
|
+
resetOptionsScroll();
|
|
399
420
|
refresh();
|
|
400
421
|
return;
|
|
401
422
|
}
|
|
402
423
|
if (matchesKey(data, Key.down)) {
|
|
403
424
|
st.cursorIndex = (st.cursorIndex + 1) % optCount;
|
|
404
425
|
resetPreviewScroll();
|
|
426
|
+
resetOptionsScroll();
|
|
405
427
|
refresh();
|
|
406
428
|
return;
|
|
407
429
|
}
|
|
@@ -637,6 +659,8 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
637
659
|
// No scrollable preview unless the side-by-side path sets these below.
|
|
638
660
|
previewTotal = 0;
|
|
639
661
|
previewViewport = 0;
|
|
662
|
+
optionsTotal = 0;
|
|
663
|
+
optionsViewport = 0;
|
|
640
664
|
if (useSideBySide) {
|
|
641
665
|
// ── Preview path ──────────────────────────────────────
|
|
642
666
|
const ui = makeUI(theme, contentWidth);
|
|
@@ -672,10 +696,21 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
672
696
|
const previewWidth = Math.max(MIN_PREVIEW_WIDTH, Math.min(contentWidth - MIN_OPTIONS_WIDTH - DIVIDER_WIDTH, Math.floor(contentWidth * PREVIEW_RATIO)));
|
|
673
697
|
const leftWidth = Math.max(MIN_OPTIONS_WIDTH, contentWidth - previewWidth - DIVIDER_WIDTH);
|
|
674
698
|
const fullLeft = renderOptionsColumn(leftWidth);
|
|
675
|
-
|
|
699
|
+
optionsTotal = fullLeft.length;
|
|
700
|
+
optionsViewport = maxBody;
|
|
701
|
+
const maxOptionsScroll = Math.max(0, fullLeft.length - maxBody);
|
|
702
|
+
if (optionsScroll > maxOptionsScroll)
|
|
703
|
+
optionsScroll = maxOptionsScroll;
|
|
704
|
+
const leftLines = fullLeft.slice(optionsScroll, optionsScroll + maxBody);
|
|
676
705
|
if (fullLeft.length > maxBody) {
|
|
677
|
-
const
|
|
678
|
-
|
|
706
|
+
const hiddenAbove = optionsScroll;
|
|
707
|
+
const hiddenBelow = fullLeft.length - (optionsScroll + maxBody);
|
|
708
|
+
if (hiddenAbove > 0) {
|
|
709
|
+
leftLines[0] = scrollIndicator(`▲ ${hiddenAbove} more`, leftWidth);
|
|
710
|
+
}
|
|
711
|
+
if (hiddenBelow > 0) {
|
|
712
|
+
leftLines[leftLines.length - 1] = scrollIndicator(`▼ ${hiddenBelow} more · PgUp/PgDn`, leftWidth);
|
|
713
|
+
}
|
|
679
714
|
}
|
|
680
715
|
const preview = getCurrentPreview();
|
|
681
716
|
const fullRight = preview ? renderPreviewColumn(preview, previewWidth) : [];
|
|
@@ -734,6 +769,8 @@ export async function showInterviewRound(questions, opts, ctx) {
|
|
|
734
769
|
}
|
|
735
770
|
if (previewTotal > previewViewport)
|
|
736
771
|
hints.push("pgup/pgdn scroll preview");
|
|
772
|
+
if (optionsTotal > optionsViewport)
|
|
773
|
+
hints.push("pgup/pgdn scroll options");
|
|
737
774
|
hints.push("esc to exit");
|
|
738
775
|
const footer = ui.hints(hints)[0] ?? "";
|
|
739
776
|
cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
|