gsd-pi 2.82.0-dev.725028083 → 2.82.0-dev.ed17d078d
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/gsd/auto/orchestrator.js +113 -6
- package/dist/resources/extensions/gsd/auto.js +121 -30
- package/dist/resources/extensions/gsd/md-importer.js +1 -1
- package/dist/resources/extensions/gsd/migrate/command.js +5 -0
- package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
- package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
- package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
- 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 +17 -17
- 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 +17 -17
- 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 +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +5 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +6 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
- package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
- package/src/resources/extensions/gsd/auto.ts +129 -31
- package/src/resources/extensions/gsd/md-importer.ts +1 -1
- package/src/resources/extensions/gsd/migrate/command.ts +5 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
- package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
- /package/dist/web/standalone/.next/static/{KDRTXR-22LPCsa80X9dey → YEvjuT-fsFfYQhDSWtueS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KDRTXR-22LPCsa80X9dey → YEvjuT-fsFfYQhDSWtueS}/_ssgManifest.js +0 -0
|
@@ -240,12 +240,14 @@ import { runAutoLoopWithUok } from "./uok/kernel.js";
|
|
|
240
240
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
241
241
|
import { validateDirectory } from "./validate-directory.js";
|
|
242
242
|
import { createAutoOrchestrator } from "./auto/orchestrator.js";
|
|
243
|
-
import type { AutoOrchestrationModule, AutoOrchestratorDeps } from "./auto/contracts.js";
|
|
243
|
+
import type { AutoOrchestrationModule, AutoOrchestratorDeps, DispatchAdapter } from "./auto/contracts.js";
|
|
244
244
|
import { reconcileBeforeDispatch } from "./state-reconciliation.js";
|
|
245
245
|
import { compileUnitToolContract } from "./tool-contract.js";
|
|
246
246
|
import { createWorktreeSafetyModule } from "./worktree-safety.js";
|
|
247
247
|
import { resolveManifest } from "./unit-context-manifest.js";
|
|
248
248
|
import { classifyFailure } from "./recovery-classification.js";
|
|
249
|
+
import { supportsStructuredQuestions } from "./workflow-mcp.js";
|
|
250
|
+
import type { MinimalModelRegistry } from "./context-budget.js";
|
|
249
251
|
// Slice-level parallelism (#2340)
|
|
250
252
|
import { getEligibleSlices } from "./slice-parallel-eligibility.js";
|
|
251
253
|
import { startSliceParallel } from "./slice-parallel-orchestrator.js";
|
|
@@ -1792,6 +1794,75 @@ function buildLifecycle(): WorktreeLifecycle {
|
|
|
1792
1794
|
return new WorktreeLifecycle(s, buildWorktreeLifecycleDeps());
|
|
1793
1795
|
}
|
|
1794
1796
|
|
|
1797
|
+
/**
|
|
1798
|
+
* Build the production `DispatchAdapter` used by `createWiredAutoOrchestrationModule`.
|
|
1799
|
+
*
|
|
1800
|
+
* Exported so tests can verify parity with `runDispatch`'s `resolveDispatch` call —
|
|
1801
|
+
* the wired adapter must derive `structuredQuestionsAvailable`, `sessionContextWindow`,
|
|
1802
|
+
* `sessionProvider`, and `modelRegistry` the same way phases.ts:runDispatch does.
|
|
1803
|
+
*/
|
|
1804
|
+
export function createWiredDispatchAdapter(
|
|
1805
|
+
ctx: ExtensionContext,
|
|
1806
|
+
pi: ExtensionAPI,
|
|
1807
|
+
dispatchBasePath: string,
|
|
1808
|
+
): DispatchAdapter {
|
|
1809
|
+
return {
|
|
1810
|
+
async decideNextUnit(input) {
|
|
1811
|
+
const state = input.stateSnapshot;
|
|
1812
|
+
const active = state.activeMilestone;
|
|
1813
|
+
if (!active) return null;
|
|
1814
|
+
|
|
1815
|
+
const prefs = loadEffectiveGSDPreferences(dispatchBasePath)?.preferences;
|
|
1816
|
+
|
|
1817
|
+
// Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
|
|
1818
|
+
// (#5789). Prefer caller-supplied values when present so test harnesses and
|
|
1819
|
+
// alternative wirings can inject deterministic snapshots; otherwise pull from
|
|
1820
|
+
// the captured pi/ctx references.
|
|
1821
|
+
const sessionProvider = input.sessionProvider ?? ctx.model?.provider;
|
|
1822
|
+
const sessionContextWindow = input.sessionContextWindow ?? ctx.model?.contextWindow;
|
|
1823
|
+
const modelRegistry = input.modelRegistry ?? (ctx.modelRegistry as MinimalModelRegistry | undefined);
|
|
1824
|
+
const authMode =
|
|
1825
|
+
sessionProvider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
|
|
1826
|
+
? ctx.modelRegistry.getProviderAuthMode(sessionProvider)
|
|
1827
|
+
: undefined;
|
|
1828
|
+
const activeTools = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
|
|
1829
|
+
// Mirrors runDispatch: deep-planning keeps approval gates in plain chat
|
|
1830
|
+
// because structured questions can be cancelled outside the chat turn on
|
|
1831
|
+
// some transports.
|
|
1832
|
+
const structuredQuestionsAvailable =
|
|
1833
|
+
input.structuredQuestionsAvailable ??
|
|
1834
|
+
(prefs?.planning_depth === "deep"
|
|
1835
|
+
? "false"
|
|
1836
|
+
: supportsStructuredQuestions(activeTools, {
|
|
1837
|
+
authMode,
|
|
1838
|
+
baseUrl: ctx.model?.baseUrl,
|
|
1839
|
+
})
|
|
1840
|
+
? "true"
|
|
1841
|
+
: "false");
|
|
1842
|
+
|
|
1843
|
+
const action = await resolveDispatch({
|
|
1844
|
+
basePath: dispatchBasePath,
|
|
1845
|
+
mid: active.id,
|
|
1846
|
+
midTitle: active.title,
|
|
1847
|
+
state,
|
|
1848
|
+
prefs,
|
|
1849
|
+
structuredQuestionsAvailable,
|
|
1850
|
+
sessionContextWindow,
|
|
1851
|
+
sessionProvider,
|
|
1852
|
+
modelRegistry,
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
if (action.action !== "dispatch") return null;
|
|
1856
|
+
return {
|
|
1857
|
+
unitType: action.unitType,
|
|
1858
|
+
unitId: action.unitId,
|
|
1859
|
+
reason: action.matchedRule ?? "dispatch",
|
|
1860
|
+
preconditions: [],
|
|
1861
|
+
};
|
|
1862
|
+
},
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1795
1866
|
/**
|
|
1796
1867
|
* Thin entry glue for the new Auto Orchestration module.
|
|
1797
1868
|
*
|
|
@@ -1801,7 +1872,7 @@ function buildLifecycle(): WorktreeLifecycle {
|
|
|
1801
1872
|
*/
|
|
1802
1873
|
export function createWiredAutoOrchestrationModule(
|
|
1803
1874
|
ctx: ExtensionContext,
|
|
1804
|
-
|
|
1875
|
+
pi: ExtensionAPI,
|
|
1805
1876
|
dispatchBasePath: string,
|
|
1806
1877
|
runtimeBasePath = resolveProjectRoot(dispatchBasePath),
|
|
1807
1878
|
): AutoOrchestrationModule {
|
|
@@ -1830,30 +1901,7 @@ export function createWiredAutoOrchestrationModule(
|
|
|
1830
1901
|
};
|
|
1831
1902
|
},
|
|
1832
1903
|
},
|
|
1833
|
-
dispatch:
|
|
1834
|
-
async decideNextUnit(input) {
|
|
1835
|
-
const state = input.stateSnapshot;
|
|
1836
|
-
const active = state.activeMilestone;
|
|
1837
|
-
if (!active) return null;
|
|
1838
|
-
|
|
1839
|
-
const prefs = loadEffectiveGSDPreferences(dispatchBasePath)?.preferences;
|
|
1840
|
-
const action = await resolveDispatch({
|
|
1841
|
-
basePath: dispatchBasePath,
|
|
1842
|
-
mid: active.id,
|
|
1843
|
-
midTitle: active.title,
|
|
1844
|
-
state,
|
|
1845
|
-
prefs,
|
|
1846
|
-
});
|
|
1847
|
-
|
|
1848
|
-
if (action.action !== "dispatch") return null;
|
|
1849
|
-
return {
|
|
1850
|
-
unitType: action.unitType,
|
|
1851
|
-
unitId: action.unitId,
|
|
1852
|
-
reason: action.matchedRule ?? "dispatch",
|
|
1853
|
-
preconditions: [],
|
|
1854
|
-
};
|
|
1855
|
-
},
|
|
1856
|
-
},
|
|
1904
|
+
dispatch: createWiredDispatchAdapter(ctx, pi, dispatchBasePath),
|
|
1857
1905
|
recovery: {
|
|
1858
1906
|
async classifyAndRecover(input) {
|
|
1859
1907
|
const recovery = classifyFailure(input);
|
|
@@ -1902,12 +1950,25 @@ export function createWiredAutoOrchestrationModule(
|
|
|
1902
1950
|
async cleanupOnStop() {},
|
|
1903
1951
|
},
|
|
1904
1952
|
health: {
|
|
1953
|
+
checkResourcesStale() {
|
|
1954
|
+
return checkResourcesStale(s.resourceVersionOnStart);
|
|
1955
|
+
},
|
|
1905
1956
|
async preAdvanceGate() {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1957
|
+
try {
|
|
1958
|
+
const gate = await preDispatchHealthGate(dispatchBasePath);
|
|
1959
|
+
if (gate.proceed) {
|
|
1960
|
+
return {
|
|
1961
|
+
kind: "pass",
|
|
1962
|
+
fixesApplied: gate.fixesApplied,
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
return {
|
|
1966
|
+
kind: "fail",
|
|
1967
|
+
reason: gate.reason ?? "Pre-dispatch health check failed — run /gsd doctor for details.",
|
|
1968
|
+
};
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
return { kind: "threw", error };
|
|
1971
|
+
}
|
|
1911
1972
|
},
|
|
1912
1973
|
async postAdvanceRecord(result) {
|
|
1913
1974
|
if (result.kind === "error") {
|
|
@@ -1975,6 +2036,43 @@ export function createWiredAutoOrchestrationModule(
|
|
|
1975
2036
|
}
|
|
1976
2037
|
},
|
|
1977
2038
|
},
|
|
2039
|
+
uokGate: {
|
|
2040
|
+
async emit(input) {
|
|
2041
|
+
const prefs = loadEffectiveGSDPreferences(dispatchBasePath)?.preferences;
|
|
2042
|
+
const uokFlags = resolveUokFlags(prefs);
|
|
2043
|
+
if (!uokFlags.gates) return;
|
|
2044
|
+
const milestoneId = input.milestoneId ?? s.currentMilestoneId ?? undefined;
|
|
2045
|
+
try {
|
|
2046
|
+
const { UokGateRunner } = await import("./uok/gate-runner.js");
|
|
2047
|
+
const runner = new UokGateRunner();
|
|
2048
|
+
runner.register({
|
|
2049
|
+
id: input.gateId,
|
|
2050
|
+
type: input.gateType,
|
|
2051
|
+
execute: async () => ({
|
|
2052
|
+
outcome: input.outcome,
|
|
2053
|
+
failureClass: input.failureClass,
|
|
2054
|
+
rationale: input.rationale,
|
|
2055
|
+
findings: input.findings ?? "",
|
|
2056
|
+
}),
|
|
2057
|
+
});
|
|
2058
|
+
await runner.run(input.gateId, {
|
|
2059
|
+
basePath: dispatchBasePath,
|
|
2060
|
+
traceId: `pre-dispatch:${flowId}`,
|
|
2061
|
+
turnId: `orch-${seq}`,
|
|
2062
|
+
milestoneId,
|
|
2063
|
+
unitType: "pre-dispatch",
|
|
2064
|
+
unitId: `orch-${seq}`,
|
|
2065
|
+
});
|
|
2066
|
+
} catch (err) {
|
|
2067
|
+
logWarning("engine", `uok gate emit failed: ${getErrorMessage(err)}`, {
|
|
2068
|
+
file: "auto.ts",
|
|
2069
|
+
gateId: input.gateId,
|
|
2070
|
+
gateType: input.gateType,
|
|
2071
|
+
...(milestoneId ? { milestoneId } : {}),
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
},
|
|
2075
|
+
},
|
|
1978
2076
|
};
|
|
1979
2077
|
|
|
1980
2078
|
return createAutoOrchestrator(deps);
|
|
@@ -306,7 +306,7 @@ function importRequirements(gsdDir: string): number {
|
|
|
306
306
|
// ─── Hierarchy Artifact Walker ─────────────────────────────────────────────
|
|
307
307
|
|
|
308
308
|
/** Artifact suffixes to look for at each hierarchy level */
|
|
309
|
-
const MILESTONE_SUFFIXES = ['ROADMAP', 'CONTEXT', 'RESEARCH', 'ASSESSMENT'];
|
|
309
|
+
const MILESTONE_SUFFIXES = ['ROADMAP', 'CONTEXT', 'RESEARCH', 'ASSESSMENT', 'SUMMARY', 'VALIDATION'];
|
|
310
310
|
const SLICE_SUFFIXES = ['PLAN', 'SUMMARY', 'RESEARCH', 'CONTEXT', 'ASSESSMENT', 'UAT'];
|
|
311
311
|
const TASK_SUFFIXES = ['PLAN', 'SUMMARY', 'CONTINUE', 'CONTEXT', 'RESEARCH'];
|
|
312
312
|
|
|
@@ -34,6 +34,9 @@ export type MigrationImportCounts = ReturnType<typeof migrateFromMarkdown>;
|
|
|
34
34
|
|
|
35
35
|
function assertMigrationImportMatchesPreview(imported: MigrationImportCounts, preview: MigrationPreview): void {
|
|
36
36
|
const mismatches: string[] = [];
|
|
37
|
+
if (imported.decisions !== preview.decisions.total) {
|
|
38
|
+
mismatches.push(`decisions ${imported.decisions}/${preview.decisions.total}`);
|
|
39
|
+
}
|
|
37
40
|
if (imported.hierarchy.milestones !== preview.milestoneCount) {
|
|
38
41
|
mismatches.push(`milestones ${imported.hierarchy.milestones}/${preview.milestoneCount}`);
|
|
39
42
|
}
|
|
@@ -73,6 +76,7 @@ export async function importWrittenMigrationToDb(
|
|
|
73
76
|
/** Format preview stats for embedding in the review prompt. */
|
|
74
77
|
function formatPreviewStats(preview: MigrationPreview): string {
|
|
75
78
|
const lines = [
|
|
79
|
+
`- Decisions: ${preview.decisions.total}`,
|
|
76
80
|
`- Milestones: ${preview.milestoneCount}`,
|
|
77
81
|
`- Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
|
|
78
82
|
`- Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
|
|
@@ -179,6 +183,7 @@ export async function handleMigrate(
|
|
|
179
183
|
|
|
180
184
|
// ── Build preview text ─────────────────────────────────────────────────────
|
|
181
185
|
const lines: string[] = [
|
|
186
|
+
`Decisions: ${preview.decisions.total}`,
|
|
182
187
|
`Milestones: ${preview.milestoneCount}`,
|
|
183
188
|
`Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
|
|
184
189
|
`Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
import type { GSDProject } from './types.js';
|
|
5
5
|
import type { MigrationPreview } from './writer.js';
|
|
6
6
|
|
|
7
|
+
function countCanonicalDecisionRows(content: string): number {
|
|
8
|
+
return content
|
|
9
|
+
.split('\n')
|
|
10
|
+
.filter((line) => /^\|\s*D\d+\s*\|/.test(line.trim()))
|
|
11
|
+
.length;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* Compute pre-write statistics from a GSDProject without performing I/O.
|
|
9
16
|
* Used to show the user what a migration will produce before writing anything.
|
|
@@ -36,6 +43,9 @@ export function generatePreview(project: GSDProject): MigrationPreview {
|
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
return {
|
|
46
|
+
decisions: {
|
|
47
|
+
total: countCanonicalDecisionRows(project.decisionsContent),
|
|
48
|
+
},
|
|
39
49
|
milestoneCount: project.milestones.length,
|
|
40
50
|
totalSlices,
|
|
41
51
|
totalTasks,
|
|
@@ -238,16 +238,53 @@ function normalizeStatus(status: string): 'active' | 'validated' | 'deferred' {
|
|
|
238
238
|
return 'active';
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
function normalizeRequirementId(id: string): string | null {
|
|
242
|
+
const match = id.trim().match(/^R(\d+)$/i);
|
|
243
|
+
if (!match) return null;
|
|
244
|
+
return `R${match[1].padStart(3, '0')}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
241
247
|
function mapRequirements(reqs: PlanningRequirement[]): GSDRequirement[] {
|
|
242
248
|
let autoId = 0;
|
|
249
|
+
const reservedIds = new Set(
|
|
250
|
+
reqs
|
|
251
|
+
.map((req) => normalizeRequirementId(req.id))
|
|
252
|
+
.filter((id): id is string => id !== null),
|
|
253
|
+
);
|
|
254
|
+
const usedIds = new Set<string>();
|
|
255
|
+
|
|
256
|
+
function nextRequirementId(): string {
|
|
257
|
+
let id = '';
|
|
258
|
+
do {
|
|
259
|
+
autoId++;
|
|
260
|
+
id = padId('R', autoId, 3);
|
|
261
|
+
} while (usedIds.has(id) || reservedIds.has(id));
|
|
262
|
+
usedIds.add(id);
|
|
263
|
+
return id;
|
|
264
|
+
}
|
|
265
|
+
|
|
243
266
|
return reqs.map((req) => {
|
|
244
|
-
|
|
267
|
+
const originalId = req.id.trim();
|
|
268
|
+
const canonicalId = normalizeRequirementId(originalId);
|
|
269
|
+
let id: string;
|
|
270
|
+
let description = req.description;
|
|
271
|
+
|
|
272
|
+
if (canonicalId && !usedIds.has(canonicalId)) {
|
|
273
|
+
id = canonicalId;
|
|
274
|
+
usedIds.add(id);
|
|
275
|
+
} else {
|
|
276
|
+
id = nextRequirementId();
|
|
277
|
+
if (originalId) {
|
|
278
|
+
description = `Legacy ID: ${originalId}\n\n${description}`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
245
282
|
return {
|
|
246
|
-
id
|
|
283
|
+
id,
|
|
247
284
|
title: req.title,
|
|
248
285
|
class: 'core-capability',
|
|
249
286
|
status: normalizeStatus(req.status),
|
|
250
|
-
description
|
|
287
|
+
description,
|
|
251
288
|
source: 'inferred',
|
|
252
289
|
primarySlice: 'none yet',
|
|
253
290
|
};
|
|
@@ -286,7 +323,24 @@ function deriveDecisions(parsed: PlanningProject): string {
|
|
|
286
323
|
}
|
|
287
324
|
}
|
|
288
325
|
if (decisions.length === 0) return '';
|
|
289
|
-
|
|
326
|
+
const lines = [
|
|
327
|
+
'# Decisions Register',
|
|
328
|
+
'',
|
|
329
|
+
'<!-- Append-only. Never edit or remove existing rows.',
|
|
330
|
+
' To reverse a decision, add a new row that supersedes it.',
|
|
331
|
+
' Read this file at the start of any planning or research phase. -->',
|
|
332
|
+
'',
|
|
333
|
+
'| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |',
|
|
334
|
+
'|---|------|-------|----------|--------|-----------|------------|---------|',
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
decisions.forEach((decision, index) => {
|
|
338
|
+
const id = padId('D', index + 1, 3);
|
|
339
|
+
const escaped = decision.replace(/\|/g, '\\|');
|
|
340
|
+
lines.push(`| ${id} | migration | migrated-summary | ${escaped} | ${escaped} | Migrated from legacy summary key-decisions | Yes | agent |`);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
return lines.join('\n') + '\n';
|
|
290
344
|
}
|
|
291
345
|
|
|
292
346
|
// ─── Main Entry Point ──────────────────────────────────────────────────────
|
|
@@ -37,6 +37,9 @@ export interface WrittenFiles {
|
|
|
37
37
|
|
|
38
38
|
/** Pre-write statistics computed from a GSDProject without I/O. */
|
|
39
39
|
export interface MigrationPreview {
|
|
40
|
+
decisions: {
|
|
41
|
+
total: number;
|
|
42
|
+
};
|
|
40
43
|
milestoneCount: number;
|
|
41
44
|
totalSlices: number;
|
|
42
45
|
totalTasks: number;
|
|
@@ -374,7 +377,17 @@ export function formatProject(content: string): string {
|
|
|
374
377
|
*/
|
|
375
378
|
export function formatDecisions(content: string): string {
|
|
376
379
|
if (!content || !content.trim()) {
|
|
377
|
-
return
|
|
380
|
+
return [
|
|
381
|
+
'# Decisions Register',
|
|
382
|
+
'',
|
|
383
|
+
'<!-- Append-only. Never edit or remove existing rows.',
|
|
384
|
+
' To reverse a decision, add a new row that supersedes it.',
|
|
385
|
+
' Read this file at the start of any planning or research phase. -->',
|
|
386
|
+
'',
|
|
387
|
+
'| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |',
|
|
388
|
+
'|---|------|-------|----------|--------|-----------|------------|---------|',
|
|
389
|
+
'',
|
|
390
|
+
].join('\n');
|
|
378
391
|
}
|
|
379
392
|
return content.endsWith('\n') ? content : content + '\n';
|
|
380
393
|
}
|