@opengsd/gsd-pi 1.4.0-dev.5fc22c6f → 1.4.0-dev.c18009cd
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/claude-code-cli/stream-adapter.js +58 -4
- package/dist/resources/extensions/gsd/artifact-verification.js +17 -5
- package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
- package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
- package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
- package/dist/resources/extensions/gsd/auto.js +10 -3
- package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +174 -16
- package/dist/resources/extensions/gsd/gsd-db.js +52 -0
- package/dist/resources/extensions/gsd/migrate-external.js +13 -4
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
- package/dist/resources/extensions/gsd/repo-identity.js +16 -0
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
- package/dist/resources/extensions/gsd/unit-registry.js +27 -2
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
- 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 +6 -6
- 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 +6 -6
- 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/integrations/hermes/CONTEXT.md +47 -0
- package/integrations/hermes/README.md +21 -0
- package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
- package/integrations/hermes/docs/setup.md +4 -0
- package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
- package/integrations/hermes/open_gsd_hermes/config.py +2 -0
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
- package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
- package/integrations/hermes/plugin.yaml +4 -0
- package/integrations/hermes/tests/test_config.py +6 -0
- package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
- package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
- package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
- package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
- package/integrations/hermes/tests/test_milestone_client.py +447 -0
- package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
- package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
- 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/session-manager.js +1 -1
- package/packages/daemon/dist/session-manager.js.map +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +18 -5
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +46 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.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/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
- package/packages/pi-coding-agent/dist/core/session-manager.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/claude-code-cli/stream-adapter.ts +64 -3
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +212 -39
- package/src/resources/extensions/gsd/artifact-verification.ts +16 -4
- package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
- package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
- package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
- package/src/resources/extensions/gsd/auto.ts +9 -3
- package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +230 -21
- package/src/resources/extensions/gsd/gsd-db.ts +72 -0
- package/src/resources/extensions/gsd/migrate-external.ts +14 -4
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
- package/src/resources/extensions/gsd/repo-identity.ts +17 -0
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +335 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
- package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/unit-registry.ts +34 -2
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
- /package/dist/web/standalone/.next/static/{kIEFsCryyGy6RzifULbJl → L2UMqXELiDH3oz-7yguXp}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kIEFsCryyGy6RzifULbJl → L2UMqXELiDH3oz-7yguXp}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
-
import { getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, _getAdapter, } from "./gsd-db.js";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
|
|
4
4
|
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
5
5
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
6
6
|
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
@@ -11,7 +11,11 @@ import { readEvents } from "./workflow-events.js";
|
|
|
11
11
|
import { flushWorkflowProjections } from "./projection-flush.js";
|
|
12
12
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
13
13
|
import { parsePlan } from "./parsers-legacy.js";
|
|
14
|
+
import { parseSummary } from "./files.js";
|
|
14
15
|
const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
|
|
16
|
+
function escapeRegExp(value) {
|
|
17
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
|
+
}
|
|
15
19
|
function relativeFile(basePath, filePath) {
|
|
16
20
|
return relative(basePath, filePath).split("\\").join("/");
|
|
17
21
|
}
|
|
@@ -28,6 +32,17 @@ function userContentMissingMessage(path, artifactType) {
|
|
|
28
32
|
const type = normalizedArtifactType(artifactType) || "UNKNOWN";
|
|
29
33
|
return `Artifact \`${path}\` is a user-authored ${type} file recorded in the database but missing from disk. Re-run \`${userContentRecoveryCommand(type)}\` in this milestone to regenerate it.`;
|
|
30
34
|
}
|
|
35
|
+
function artifactPathRelativeToGsd(artifactPath) {
|
|
36
|
+
const parts = artifactPath.split(/[\\/]+/);
|
|
37
|
+
const gsdIndex = parts.lastIndexOf(".gsd");
|
|
38
|
+
if (gsdIndex < 0 || gsdIndex === parts.length - 1)
|
|
39
|
+
return artifactPath;
|
|
40
|
+
return parts.slice(gsdIndex + 1).join("/");
|
|
41
|
+
}
|
|
42
|
+
function isPathInside(basePath, candidatePath) {
|
|
43
|
+
const rel = relative(basePath, candidatePath);
|
|
44
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
45
|
+
}
|
|
31
46
|
function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, unitId, status, checkboxDone) {
|
|
32
47
|
const dbDone = isClosedStatus(status);
|
|
33
48
|
if (checkboxDone === dbDone)
|
|
@@ -99,12 +114,97 @@ function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMil
|
|
|
99
114
|
return issue.file === relativeFile(basePath, roadmapPath);
|
|
100
115
|
}
|
|
101
116
|
function artifactExistsOnDisk(basePath, artifactPath) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
return resolveArtifactDiskPath(basePath, artifactPath) !== null;
|
|
118
|
+
}
|
|
119
|
+
function resolveArtifactDiskPath(basePath, artifactPath) {
|
|
120
|
+
const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
|
|
121
|
+
if (isAbsolute(relativeArtifactPath)) {
|
|
122
|
+
return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
|
|
123
|
+
}
|
|
124
|
+
for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
|
|
125
|
+
const candidate = join(root, relativeArtifactPath);
|
|
126
|
+
if (isPathInside(root, candidate) && existsSync(candidate))
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function taskExistsInPlan(basePath, milestoneId, sliceId, taskId) {
|
|
132
|
+
const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
133
|
+
if (!planPath || !existsSync(planPath))
|
|
134
|
+
return false;
|
|
135
|
+
try {
|
|
136
|
+
return parsePlan(readFileSync(planPath, "utf-8")).tasks.some((task) => task.id === taskId);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function isPassingVerificationResult(value) {
|
|
143
|
+
const normalized = value.trim().toLowerCase();
|
|
144
|
+
if (!normalized)
|
|
145
|
+
return false;
|
|
146
|
+
if (/\b(fail(?:ed|ing)?|error|blocked|mixed|untested)\b/.test(normalized))
|
|
147
|
+
return false;
|
|
148
|
+
if (/\b(?:not|never|no|didn't|did\s+not|cannot|can't|won't|couldn't)\s+(?:\w+\s+){0,3}?(?:pass(?:ed|ing)?|success(?:ful)?|succeeded)\b/.test(normalized)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return /\b(pass(?:ed|ing)?|success(?:ful)?|succeeded)\b/.test(normalized) || normalized === "all-pass";
|
|
152
|
+
}
|
|
153
|
+
function readTaskCompletionEvidenceFromSummary(basePath, row) {
|
|
154
|
+
if (!row.slice_id || !row.task_id)
|
|
155
|
+
return null;
|
|
156
|
+
if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const diskPath = resolveArtifactDiskPath(basePath, row.path);
|
|
160
|
+
if (!diskPath)
|
|
161
|
+
return null;
|
|
162
|
+
try {
|
|
163
|
+
const fullSummaryMd = readFileSync(diskPath, "utf-8");
|
|
164
|
+
const summary = parseSummary(fullSummaryMd);
|
|
165
|
+
const fm = summary.frontmatter;
|
|
166
|
+
if (fm.id !== row.task_id || fm.parent !== row.slice_id || fm.milestone !== row.milestone_id)
|
|
167
|
+
return null;
|
|
168
|
+
if (fm.blocker_discovered)
|
|
169
|
+
return null;
|
|
170
|
+
if (!isPassingVerificationResult(fm.verification_result))
|
|
171
|
+
return null;
|
|
172
|
+
const completedAt = fm.completed_at.trim();
|
|
173
|
+
if (!completedAt || !Number.isFinite(Date.parse(completedAt)))
|
|
174
|
+
return null;
|
|
175
|
+
return {
|
|
176
|
+
completedAt,
|
|
177
|
+
verificationResult: fm.verification_result.trim(),
|
|
178
|
+
title: summary.title.replace(new RegExp(`^${escapeRegExp(row.task_id)}:\\s*`), "").trim(),
|
|
179
|
+
oneLiner: summary.oneLiner,
|
|
180
|
+
narrative: summary.whatHappened,
|
|
181
|
+
duration: fm.duration,
|
|
182
|
+
blockerDiscovered: fm.blocker_discovered,
|
|
183
|
+
deviations: summary.deviations,
|
|
184
|
+
knownIssues: summary.knownLimitations,
|
|
185
|
+
keyFiles: fm.key_files.filter((file) => file !== "(none)"),
|
|
186
|
+
keyDecisions: fm.key_decisions.filter((decision) => decision !== "(none)"),
|
|
187
|
+
fullSummaryMd,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function repairTaskArtifactDbStatusDivergence(basePath, row) {
|
|
195
|
+
const evidence = readTaskCompletionEvidenceFromSummary(basePath, row);
|
|
196
|
+
if (!evidence || !row.slice_id || !row.task_id)
|
|
197
|
+
return false;
|
|
198
|
+
repairTaskCompletionFromSummary({
|
|
199
|
+
milestoneId: row.milestone_id,
|
|
200
|
+
sliceId: row.slice_id,
|
|
201
|
+
taskId: row.task_id,
|
|
202
|
+
...evidence,
|
|
203
|
+
});
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
function artifactDbStatusDivergenceFixable(basePath, row) {
|
|
207
|
+
return readTaskCompletionEvidenceFromSummary(basePath, row) !== null;
|
|
108
208
|
}
|
|
109
209
|
function artifactUnitId(row) {
|
|
110
210
|
if (!row.milestone_id)
|
|
@@ -124,6 +224,38 @@ function artifactScope(row) {
|
|
|
124
224
|
return "milestone";
|
|
125
225
|
return "project";
|
|
126
226
|
}
|
|
227
|
+
function sameArtifactIdentity(left, right) {
|
|
228
|
+
return left.artifact_type === right.artifact_type &&
|
|
229
|
+
left.milestone_id === right.milestone_id &&
|
|
230
|
+
left.slice_id === right.slice_id &&
|
|
231
|
+
left.task_id === right.task_id;
|
|
232
|
+
}
|
|
233
|
+
function isMilestonesArtifactPath(artifactPath) {
|
|
234
|
+
return artifactPathRelativeToGsd(artifactPath).startsWith("milestones/");
|
|
235
|
+
}
|
|
236
|
+
function expectedMilestonesArtifactPath(row) {
|
|
237
|
+
if (!row.milestone_id)
|
|
238
|
+
return null;
|
|
239
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
240
|
+
if (!artifactType)
|
|
241
|
+
return null;
|
|
242
|
+
if (row.slice_id && row.task_id) {
|
|
243
|
+
return `milestones/${row.milestone_id}/slices/${row.slice_id}/tasks/${row.task_id}-${artifactType}.md`;
|
|
244
|
+
}
|
|
245
|
+
if (row.slice_id) {
|
|
246
|
+
return `milestones/${row.milestone_id}/slices/${row.slice_id}/${row.slice_id}-${artifactType}.md`;
|
|
247
|
+
}
|
|
248
|
+
return `milestones/${row.milestone_id}/${row.milestone_id}-${artifactType}.md`;
|
|
249
|
+
}
|
|
250
|
+
function hasPresentMilestonesReplacement(basePath, row, artifactRows) {
|
|
251
|
+
const expectedPath = expectedMilestonesArtifactPath(row);
|
|
252
|
+
if (expectedPath && artifactExistsOnDisk(basePath, expectedPath))
|
|
253
|
+
return true;
|
|
254
|
+
return artifactRows.some((other) => other.path !== row.path &&
|
|
255
|
+
isMilestonesArtifactPath(other.path) &&
|
|
256
|
+
sameArtifactIdentity(row, other) &&
|
|
257
|
+
artifactExistsOnDisk(basePath, other.path));
|
|
258
|
+
}
|
|
127
259
|
export async function checkEngineHealth(basePath, issues, fixesApplied, options) {
|
|
128
260
|
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
129
261
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
@@ -327,16 +459,25 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
327
459
|
if (artifactExistsOnDisk(basePath, row.path))
|
|
328
460
|
continue;
|
|
329
461
|
const unitId = artifactUnitId(row);
|
|
462
|
+
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
463
|
+
if (options?.repair && issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows)) {
|
|
464
|
+
// Route the write through the Single Writer owner (gsd-db.ts) instead
|
|
465
|
+
// of issuing raw DELETE SQL here — doctor is a read-only consumer and
|
|
466
|
+
// the single-writer invariant forbids write SQL outside the allowlist.
|
|
467
|
+
deleteArtifactByPath(row.path);
|
|
468
|
+
fixesApplied.push(`pruned stale flat-phase artifact row ${row.path}`);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
330
471
|
if (isUserAuthoredArtifactType(row.artifact_type)) {
|
|
331
472
|
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
332
|
-
missingUserContentArtifacts.push({ path:
|
|
473
|
+
missingUserContentArtifacts.push({ path: issuePath, artifactType });
|
|
333
474
|
issues.push({
|
|
334
475
|
severity: "warning",
|
|
335
476
|
code: "artifact_user_content_missing",
|
|
336
477
|
scope: artifactScope(row),
|
|
337
478
|
unitId,
|
|
338
|
-
message: userContentMissingMessage(
|
|
339
|
-
file:
|
|
479
|
+
message: userContentMissingMessage(issuePath, artifactType),
|
|
480
|
+
file: issuePath,
|
|
340
481
|
fixable: false,
|
|
341
482
|
});
|
|
342
483
|
continue;
|
|
@@ -346,9 +487,9 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
346
487
|
code: "artifact_file_missing",
|
|
347
488
|
scope: artifactScope(row),
|
|
348
489
|
unitId,
|
|
349
|
-
message: `Artifact ${
|
|
350
|
-
file:
|
|
351
|
-
fixable:
|
|
490
|
+
message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
|
|
491
|
+
file: issuePath,
|
|
492
|
+
fixable: issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows),
|
|
352
493
|
});
|
|
353
494
|
}
|
|
354
495
|
}
|
|
@@ -395,13 +536,30 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
395
536
|
if (seen.has(unitId))
|
|
396
537
|
continue;
|
|
397
538
|
seen.add(unitId);
|
|
539
|
+
const fixable = artifactDbStatusDivergenceFixable(basePath, row);
|
|
540
|
+
let repairFailed = false;
|
|
541
|
+
if (options?.repair && fixable) {
|
|
542
|
+
try {
|
|
543
|
+
if (repairTaskArtifactDbStatusDivergence(basePath, row)) {
|
|
544
|
+
fixesApplied.push(`repaired task completion from SUMMARY artifact for ${unitId}`);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
repairFailed = true;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
398
552
|
issues.push({
|
|
399
553
|
severity: "error",
|
|
400
554
|
code: "artifact_db_status_divergence",
|
|
401
555
|
scope: row.task_id ? "task" : row.slice_id ? "slice" : "milestone",
|
|
402
556
|
unitId,
|
|
403
|
-
message:
|
|
404
|
-
|
|
557
|
+
message: repairFailed
|
|
558
|
+
? `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Doctor found valid SUMMARY completion evidence but could not repair the database state.`
|
|
559
|
+
: fixable
|
|
560
|
+
? `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Doctor can repair this from the SUMMARY completion evidence.`
|
|
561
|
+
: `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Runtime will not import it silently; run explicit recovery/repair after review.`,
|
|
562
|
+
fixable,
|
|
405
563
|
});
|
|
406
564
|
}
|
|
407
565
|
}
|
|
@@ -410,6 +410,58 @@ export function insertTask(t) {
|
|
|
410
410
|
export function updateTaskStatus(milestoneId, sliceId, taskId, status, completedAt) {
|
|
411
411
|
applyStatusTransition({ entity: "task", milestoneId, sliceId, taskId, status, completedAt });
|
|
412
412
|
}
|
|
413
|
+
export function repairTaskCompletionFromSummary(t) {
|
|
414
|
+
const db = getDbOrNull();
|
|
415
|
+
if (!db)
|
|
416
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
417
|
+
transaction(() => {
|
|
418
|
+
const seq = db.prepare(`SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
|
|
419
|
+
FROM tasks
|
|
420
|
+
WHERE milestone_id = :mid AND slice_id = :sid`).get({ ":mid": t.milestoneId, ":sid": t.sliceId });
|
|
421
|
+
db.prepare(`INSERT INTO tasks (
|
|
422
|
+
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
423
|
+
verification_result, duration, completed_at, blocker_discovered,
|
|
424
|
+
deviations, known_issues, key_files, key_decisions, full_summary_md,
|
|
425
|
+
sequence
|
|
426
|
+
) VALUES (
|
|
427
|
+
:milestone_id, :slice_id, :id, :title, 'complete', :one_liner, :narrative,
|
|
428
|
+
:verification_result, :duration, :completed_at, :blocker_discovered,
|
|
429
|
+
:deviations, :known_issues, :key_files, :key_decisions, :full_summary_md,
|
|
430
|
+
:sequence
|
|
431
|
+
)
|
|
432
|
+
ON CONFLICT(milestone_id, slice_id, id) DO UPDATE SET
|
|
433
|
+
title = CASE WHEN NULLIF(:title, '') IS NOT NULL THEN :title ELSE tasks.title END,
|
|
434
|
+
status = 'complete',
|
|
435
|
+
one_liner = CASE WHEN NULLIF(:one_liner, '') IS NOT NULL THEN :one_liner ELSE tasks.one_liner END,
|
|
436
|
+
narrative = CASE WHEN NULLIF(:narrative, '') IS NOT NULL THEN :narrative ELSE tasks.narrative END,
|
|
437
|
+
verification_result = :verification_result,
|
|
438
|
+
duration = CASE WHEN NULLIF(:duration, '') IS NOT NULL THEN :duration ELSE tasks.duration END,
|
|
439
|
+
completed_at = :completed_at,
|
|
440
|
+
blocker_discovered = :blocker_discovered,
|
|
441
|
+
deviations = CASE WHEN NULLIF(:deviations, '') IS NOT NULL THEN :deviations ELSE tasks.deviations END,
|
|
442
|
+
known_issues = CASE WHEN NULLIF(:known_issues, '') IS NOT NULL THEN :known_issues ELSE tasks.known_issues END,
|
|
443
|
+
key_files = :key_files,
|
|
444
|
+
key_decisions = :key_decisions,
|
|
445
|
+
full_summary_md = :full_summary_md`).run({
|
|
446
|
+
":milestone_id": t.milestoneId,
|
|
447
|
+
":slice_id": t.sliceId,
|
|
448
|
+
":id": t.taskId,
|
|
449
|
+
":title": t.title ?? "",
|
|
450
|
+
":one_liner": t.oneLiner ?? "",
|
|
451
|
+
":narrative": t.narrative ?? "",
|
|
452
|
+
":verification_result": t.verificationResult,
|
|
453
|
+
":duration": t.duration ?? "",
|
|
454
|
+
":completed_at": t.completedAt,
|
|
455
|
+
":blocker_discovered": t.blockerDiscovered ? 1 : 0,
|
|
456
|
+
":deviations": t.deviations ?? "",
|
|
457
|
+
":known_issues": t.knownIssues ?? "",
|
|
458
|
+
":key_files": JSON.stringify(t.keyFiles ?? []),
|
|
459
|
+
":key_decisions": JSON.stringify(t.keyDecisions ?? []),
|
|
460
|
+
":full_summary_md": t.fullSummaryMd,
|
|
461
|
+
":sequence": seq?.next_sequence ?? 1,
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
}
|
|
413
465
|
export function setTaskBlockerDiscovered(milestoneId, sliceId, taskId, discovered) {
|
|
414
466
|
if (!getDbOrNull())
|
|
415
467
|
return;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
9
9
|
import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, statSync, symlinkSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import { externalGsdRoot, isInsideWorktree } from "./repo-identity.js";
|
|
11
|
+
import { externalGsdRoot, externalStateAlreadyExistsForProject, isInsideWorktree } from "./repo-identity.js";
|
|
12
12
|
import { getErrorMessage } from "./error-utils.js";
|
|
13
13
|
import { hasGitTrackedGsdFiles } from "./gitignore.js";
|
|
14
14
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
@@ -74,6 +74,15 @@ export function migrateToExternalState(basePath) {
|
|
|
74
74
|
return { migrated: false };
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
// If external state already contains project data, this is not a legacy
|
|
78
|
+
// migration source. It is likely an already-migrated project whose symlink
|
|
79
|
+
// was replaced by a real directory, so copying would risk data loss.
|
|
80
|
+
if (externalStateAlreadyExistsForProject(basePath)) {
|
|
81
|
+
return {
|
|
82
|
+
migrated: false,
|
|
83
|
+
error: "External state already exists for this project; leaving local .gsd directory untouched to avoid overwriting authoritative state",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
77
86
|
const externalPath = externalGsdRoot(basePath);
|
|
78
87
|
const migratingPath = join(basePath, ".gsd.migrating");
|
|
79
88
|
try {
|
|
@@ -81,13 +90,13 @@ export function migrateToExternalState(basePath) {
|
|
|
81
90
|
mkdirSync(externalPath, { recursive: true });
|
|
82
91
|
// Rename .gsd -> .gsd.migrating (atomic lock).
|
|
83
92
|
// On Windows, NTFS may reject rename with EPERM if file descriptors are
|
|
84
|
-
// open (VS Code watchers, antivirus on-access scan).
|
|
85
|
-
// copy+delete (#1292).
|
|
93
|
+
// open (VS Code watchers, antivirus on-access scan). WSL/DrvFs can report
|
|
94
|
+
// the same transient lock as EACCES. Fall back to copy+delete (#1292).
|
|
86
95
|
try {
|
|
87
96
|
renameSync(localGsd, migratingPath);
|
|
88
97
|
}
|
|
89
98
|
catch (renameErr) {
|
|
90
|
-
if (renameErr?.code === "EPERM" || renameErr?.code === "EBUSY") {
|
|
99
|
+
if (renameErr?.code === "EPERM" || renameErr?.code === "EBUSY" || renameErr?.code === "EACCES") {
|
|
91
100
|
try {
|
|
92
101
|
cpSync(localGsd, migratingPath, { recursive: true, force: true });
|
|
93
102
|
rmSync(localGsd, { recursive: true, force: true });
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Process-level transaction wrapper for milestone merge closeout.
|
|
3
3
|
export function runMilestoneMergeTransaction(deps, input) {
|
|
4
|
-
return deps.
|
|
4
|
+
return deps.mergeMilestone(input.basePath, input.milestoneId, input.roadmapContent);
|
|
5
5
|
}
|
|
6
|
-
export function createMilestoneMergeTransaction(
|
|
6
|
+
export function createMilestoneMergeTransaction(mergeMilestone) {
|
|
7
7
|
return function mergeMilestoneTransaction(basePath, milestoneId, roadmapContent) {
|
|
8
|
-
return runMilestoneMergeTransaction({
|
|
8
|
+
return runMilestoneMergeTransaction({ mergeMilestone }, { basePath, milestoneId, roadmapContent });
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -328,6 +328,22 @@ export function externalProjectsRoot() {
|
|
|
328
328
|
const base = process.env.GSD_STATE_DIR || gsdHome();
|
|
329
329
|
return join(base, "projects");
|
|
330
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Check whether this project already has authoritative external state.
|
|
333
|
+
*
|
|
334
|
+
* Used by legacy `.gsd/` migration to avoid re-copying a re-materialized local
|
|
335
|
+
* directory over newer state when an already-migrated symlink was replaced.
|
|
336
|
+
*/
|
|
337
|
+
export function externalStateAlreadyExistsForProject(basePath) {
|
|
338
|
+
const base = process.env.GSD_STATE_DIR || gsdHome();
|
|
339
|
+
const computedPath = join(base, "projects", repoIdentity(basePath));
|
|
340
|
+
if (hasProjectState(computedPath))
|
|
341
|
+
return true;
|
|
342
|
+
const markerId = readGsdIdMarker(resolveGitRoot(basePath));
|
|
343
|
+
if (!markerId)
|
|
344
|
+
return false;
|
|
345
|
+
return hasProjectState(join(base, "projects", markerId));
|
|
346
|
+
}
|
|
331
347
|
// ─── Numbered Variant Cleanup ────────────────────────────────────────────────
|
|
332
348
|
/**
|
|
333
349
|
* macOS collision pattern: `.gsd 2`, `.gsd 3`, `.gsd 4`, etc.
|
|
@@ -42,12 +42,14 @@ export function captureRootDirtySnapshot(rootPath) {
|
|
|
42
42
|
if (!line.trim())
|
|
43
43
|
continue;
|
|
44
44
|
const code = line.slice(0, 2);
|
|
45
|
+
if (code !== "??")
|
|
46
|
+
continue;
|
|
45
47
|
const path = line.slice(3).replace(/^"|"$/g, "");
|
|
46
48
|
if (!path || isRootRuntimePath(path))
|
|
47
49
|
continue;
|
|
48
50
|
snapshot.set(path, {
|
|
49
51
|
path,
|
|
50
|
-
status: code
|
|
52
|
+
status: code,
|
|
51
53
|
fingerprint: fileFingerprint(rootPath, path),
|
|
52
54
|
});
|
|
53
55
|
}
|
|
@@ -58,7 +60,7 @@ export function detectRootWriteLeak(input) {
|
|
|
58
60
|
const leaked = [];
|
|
59
61
|
for (const entry of after.values()) {
|
|
60
62
|
const prior = input.before?.get(entry.path);
|
|
61
|
-
if (!prior || prior.
|
|
63
|
+
if (!prior || prior.fingerprint !== entry.fingerprint) {
|
|
62
64
|
leaked.push(entry);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
//
|
|
21
21
|
// Not yet declared here (remaining ADR-033 steps): the manifest data
|
|
22
22
|
// (`UNIT_MANIFESTS` stays in unit-context-manifest.ts, already type-enforced
|
|
23
|
-
// against the registry's `UnitType`)
|
|
24
|
-
//
|
|
23
|
+
// against the registry's `UnitType`). Prompt-template association is declared
|
|
24
|
+
// for direct one-template units; conditional/composite prompt selection stays
|
|
25
|
+
// with the prompt builders until it has a richer registry shape.
|
|
25
26
|
import { BROWSER_CONTRACT_TOOL_NAMES } from "../shared/browser-contract.js";
|
|
26
27
|
// ─── Shared tool-name constants (used by registry rows) ──────────────────
|
|
27
28
|
export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
|
|
@@ -50,6 +51,7 @@ export const UNIT_REGISTRY = {
|
|
|
50
51
|
kind: "primary",
|
|
51
52
|
scopeClass: "standard",
|
|
52
53
|
phaseChain: ["research"],
|
|
54
|
+
promptTemplate: "research-milestone",
|
|
53
55
|
toolContract: {
|
|
54
56
|
allowedGsdTools: [
|
|
55
57
|
"gsd_summary_save",
|
|
@@ -65,6 +67,7 @@ export const UNIT_REGISTRY = {
|
|
|
65
67
|
kind: "primary",
|
|
66
68
|
scopeClass: "standard",
|
|
67
69
|
phaseChain: ["planning"],
|
|
70
|
+
promptTemplate: "plan-milestone",
|
|
68
71
|
toolContract: {
|
|
69
72
|
allowedGsdTools: [
|
|
70
73
|
"gsd_milestone_status",
|
|
@@ -113,6 +116,7 @@ export const UNIT_REGISTRY = {
|
|
|
113
116
|
kind: "primary",
|
|
114
117
|
scopeClass: "section-close",
|
|
115
118
|
phaseChain: ["validation", "planning"],
|
|
119
|
+
promptTemplate: "validate-milestone",
|
|
116
120
|
toolContract: {
|
|
117
121
|
allowedGsdTools: [
|
|
118
122
|
"gsd_milestone_status",
|
|
@@ -130,6 +134,7 @@ export const UNIT_REGISTRY = {
|
|
|
130
134
|
kind: "primary",
|
|
131
135
|
scopeClass: "standard",
|
|
132
136
|
phaseChain: ["completion"],
|
|
137
|
+
promptTemplate: "complete-milestone",
|
|
133
138
|
toolContract: {
|
|
134
139
|
allowedGsdTools: [
|
|
135
140
|
"gsd_milestone_status",
|
|
@@ -153,6 +158,7 @@ export const UNIT_REGISTRY = {
|
|
|
153
158
|
kind: "primary",
|
|
154
159
|
scopeClass: "standard",
|
|
155
160
|
phaseChain: ["research"],
|
|
161
|
+
promptTemplate: "research-slice",
|
|
156
162
|
toolContract: {
|
|
157
163
|
allowedGsdTools: [
|
|
158
164
|
"gsd_milestone_status",
|
|
@@ -169,6 +175,7 @@ export const UNIT_REGISTRY = {
|
|
|
169
175
|
kind: "primary",
|
|
170
176
|
scopeClass: "standard",
|
|
171
177
|
phaseChain: ["planning"],
|
|
178
|
+
promptTemplate: "plan-slice",
|
|
172
179
|
toolContract: {
|
|
173
180
|
allowedGsdTools: [
|
|
174
181
|
"gsd_milestone_status",
|
|
@@ -187,6 +194,7 @@ export const UNIT_REGISTRY = {
|
|
|
187
194
|
kind: "primary",
|
|
188
195
|
scopeClass: "standard",
|
|
189
196
|
phaseChain: ["planning"],
|
|
197
|
+
promptTemplate: "refine-slice",
|
|
190
198
|
toolContract: {
|
|
191
199
|
allowedGsdTools: [
|
|
192
200
|
"gsd_milestone_status",
|
|
@@ -203,6 +211,7 @@ export const UNIT_REGISTRY = {
|
|
|
203
211
|
kind: "primary",
|
|
204
212
|
scopeClass: "standard",
|
|
205
213
|
phaseChain: ["planning"],
|
|
214
|
+
promptTemplate: "replan-slice",
|
|
206
215
|
toolContract: {
|
|
207
216
|
allowedGsdTools: ["gsd_replan_slice", "gsd_decision_save"],
|
|
208
217
|
requiredWorkflowTools: ["gsd_replan_slice"],
|
|
@@ -212,6 +221,7 @@ export const UNIT_REGISTRY = {
|
|
|
212
221
|
kind: "primary",
|
|
213
222
|
scopeClass: "section-close",
|
|
214
223
|
phaseChain: ["completion"],
|
|
224
|
+
promptTemplate: "complete-slice",
|
|
215
225
|
toolContract: {
|
|
216
226
|
allowedGsdTools: [
|
|
217
227
|
"gsd_milestone_status",
|
|
@@ -246,6 +256,7 @@ export const UNIT_REGISTRY = {
|
|
|
246
256
|
kind: "primary",
|
|
247
257
|
scopeClass: "standard",
|
|
248
258
|
phaseChain: ["validation", "planning"],
|
|
259
|
+
promptTemplate: "reassess-roadmap",
|
|
249
260
|
toolContract: {
|
|
250
261
|
allowedGsdTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
|
|
251
262
|
requiredWorkflowTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
|
|
@@ -255,6 +266,7 @@ export const UNIT_REGISTRY = {
|
|
|
255
266
|
kind: "primary",
|
|
256
267
|
scopeClass: "execute-task",
|
|
257
268
|
phaseChain: ["execution"],
|
|
269
|
+
promptTemplate: "execute-task",
|
|
258
270
|
toolContract: {
|
|
259
271
|
allowedGsdTools: [
|
|
260
272
|
"gsd_task_complete",
|
|
@@ -299,6 +311,7 @@ export const UNIT_REGISTRY = {
|
|
|
299
311
|
kind: "primary",
|
|
300
312
|
scopeClass: "execute-task",
|
|
301
313
|
phaseChain: ["execution"],
|
|
314
|
+
promptTemplate: "reactive-execute",
|
|
302
315
|
toolContract: {
|
|
303
316
|
allowedGsdTools: [
|
|
304
317
|
"gsd_milestone_status",
|
|
@@ -314,6 +327,7 @@ export const UNIT_REGISTRY = {
|
|
|
314
327
|
kind: "primary",
|
|
315
328
|
scopeClass: "standard",
|
|
316
329
|
phaseChain: ["uat", "completion"],
|
|
330
|
+
promptTemplate: "run-uat",
|
|
317
331
|
toolContract: {
|
|
318
332
|
allowedGsdTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent"],
|
|
319
333
|
requiredWorkflowTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES],
|
|
@@ -328,6 +342,7 @@ export const UNIT_REGISTRY = {
|
|
|
328
342
|
kind: "primary",
|
|
329
343
|
scopeClass: "standard",
|
|
330
344
|
phaseChain: ["validation", "planning"],
|
|
345
|
+
promptTemplate: "gate-evaluate",
|
|
331
346
|
toolContract: {
|
|
332
347
|
allowedGsdTools: ["gsd_save_gate_result"],
|
|
333
348
|
requiredWorkflowTools: ["gsd_save_gate_result"],
|
|
@@ -337,6 +352,7 @@ export const UNIT_REGISTRY = {
|
|
|
337
352
|
kind: "primary",
|
|
338
353
|
scopeClass: "standard",
|
|
339
354
|
phaseChain: ["validation", "planning"],
|
|
355
|
+
promptTemplate: "rewrite-docs",
|
|
340
356
|
toolContract: {
|
|
341
357
|
allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
|
|
342
358
|
requiredWorkflowTools: [],
|
|
@@ -361,6 +377,7 @@ export const UNIT_REGISTRY = {
|
|
|
361
377
|
kind: "primary",
|
|
362
378
|
scopeClass: "standard",
|
|
363
379
|
phaseChain: ["discuss", "planning"],
|
|
380
|
+
promptTemplate: "guided-workflow-preferences",
|
|
364
381
|
toolContract: {
|
|
365
382
|
allowedGsdTools: ["gsd_summary_save"],
|
|
366
383
|
requiredWorkflowTools: [],
|
|
@@ -370,6 +387,7 @@ export const UNIT_REGISTRY = {
|
|
|
370
387
|
kind: "primary",
|
|
371
388
|
scopeClass: "standard",
|
|
372
389
|
phaseChain: ["discuss", "planning"],
|
|
390
|
+
promptTemplate: "guided-discuss-project",
|
|
373
391
|
toolContract: {
|
|
374
392
|
allowedGsdTools: ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
|
|
375
393
|
requiredWorkflowTools: ["ask_user_questions", "gsd_summary_save"],
|
|
@@ -379,6 +397,7 @@ export const UNIT_REGISTRY = {
|
|
|
379
397
|
kind: "primary",
|
|
380
398
|
scopeClass: "standard",
|
|
381
399
|
phaseChain: ["discuss", "planning"],
|
|
400
|
+
promptTemplate: "guided-discuss-requirements",
|
|
382
401
|
toolContract: {
|
|
383
402
|
allowedGsdTools: ["gsd_requirement_save", "gsd_summary_save"],
|
|
384
403
|
requiredWorkflowTools: ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"],
|
|
@@ -388,6 +407,7 @@ export const UNIT_REGISTRY = {
|
|
|
388
407
|
kind: "primary",
|
|
389
408
|
scopeClass: "standard",
|
|
390
409
|
phaseChain: ["discuss", "planning"],
|
|
410
|
+
promptTemplate: "guided-research-decision",
|
|
391
411
|
toolContract: {
|
|
392
412
|
allowedGsdTools: ["gsd_summary_save"],
|
|
393
413
|
requiredWorkflowTools: ["ask_user_questions"],
|
|
@@ -400,6 +420,7 @@ export const UNIT_REGISTRY = {
|
|
|
400
420
|
kind: "primary",
|
|
401
421
|
scopeClass: "standard",
|
|
402
422
|
phaseChain: ["research"],
|
|
423
|
+
promptTemplate: "guided-research-project",
|
|
403
424
|
toolContract: {
|
|
404
425
|
allowedGsdTools: [],
|
|
405
426
|
requiredWorkflowTools: [],
|
|
@@ -419,3 +440,7 @@ export function getUnitDescriptor(unitType) {
|
|
|
419
440
|
export function getUnitPhaseChain(unitType) {
|
|
420
441
|
return getUnitDescriptor(unitType)?.phaseChain ?? null;
|
|
421
442
|
}
|
|
443
|
+
/** Prompt template id for units with a direct one-template builder. */
|
|
444
|
+
export function getUnitPromptTemplate(unitType) {
|
|
445
|
+
return getUnitDescriptor(unitType)?.promptTemplate;
|
|
446
|
+
}
|