codecartographer-pi 0.19.6 → 0.21.0
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/.codecarto/GUIDE.md +2 -2
- package/.codecarto/broadside/SKILL.md +21 -3
- package/.codecarto/broadside/config.yaml +35 -9
- package/.codecarto/findings/contracts/SKILL.md +4 -1
- package/.codecarto/findings/defect-scan/SKILL.md +10 -0
- package/.codecarto/findings/defect-scan-mechanical/SKILL.md +6 -0
- package/.codecarto/findings/defect-scan-semantic/SKILL.md +8 -1
- package/.codecarto/findings/porting/SKILL.md +4 -0
- package/.codecarto/findings/protocols/SKILL.md +4 -0
- package/.codecarto/templates/mechanical-defects.md +15 -0
- package/.codecarto/templates/reimplementation-spec.md +5 -3
- package/.codecarto/templates/reverse-engineering-bundle.md +10 -1
- package/.codecarto/templates/semantic-defects.md +15 -0
- package/.codecarto/workflow/VALIDATE.md +3 -2
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +13 -9
- package/dist/core/amendment.js +9 -4
- package/dist/core/broadside.d.ts +121 -2
- package/dist/core/broadside.js +478 -92
- package/dist/core/completion.js +88 -23
- package/dist/core/dashboard-writer.d.ts +8 -0
- package/dist/core/dashboard-writer.js +159 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/library.js +9 -4
- package/dist/core/orchestrator-config.d.ts +32 -7
- package/dist/core/orchestrator-config.js +124 -44
- package/dist/core/pipeline.d.ts +73 -0
- package/dist/core/pipeline.js +134 -10
- package/dist/core/prompts.d.ts +20 -0
- package/dist/core/prompts.js +53 -13
- package/dist/core/secrets.d.ts +16 -0
- package/dist/core/secrets.js +98 -0
- package/dist/core/status.d.ts +16 -0
- package/dist/core/status.js +47 -20
- package/dist/core/synthesis.js +5 -2
- package/dist/core/utils.d.ts +7 -0
- package/dist/core/utils.js +7 -0
- package/dist/core/workspace.d.ts +55 -8
- package/dist/core/workspace.js +116 -8
- package/dist/core/yaml.js +181 -15
- package/dist/extensions/codecarto/agent-rewriter.js +21 -14
- package/dist/extensions/codecarto/agent-runner.d.ts +6 -2
- package/dist/extensions/codecarto/agent-runner.js +27 -9
- package/dist/extensions/codecarto/agent-state.d.ts +0 -2
- package/dist/extensions/codecarto/auto-runner.d.ts +1 -1
- package/dist/extensions/codecarto/auto-runner.js +27 -8
- package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
- package/dist/extensions/codecarto/broadside-flags.js +12 -0
- package/dist/extensions/codecarto/dashboard-narrator.js +9 -2
- package/dist/extensions/codecarto/dashboard-writer.d.ts +1 -8
- package/dist/extensions/codecarto/dashboard-writer.js +5 -154
- package/dist/extensions/codecarto/index.js +158 -47
- package/dist/extensions/codecarto/phase-compaction.js +5 -1
- package/dist/mcp-server/server.d.ts +3 -1
- package/dist/mcp-server/server.js +205 -77
- package/package.json +3 -2
|
@@ -17,8 +17,7 @@ import { clearPhase, finishPhase, getPhaseActivity, startPhase } from "./agent-s
|
|
|
17
17
|
import { buildSteeringMessage, rewritePhasePrompt } from "./agent-rewriter.js";
|
|
18
18
|
import { buildPhaseSummary } from "./agent-summary.js";
|
|
19
19
|
import { getAgentsWidget } from "./agent-widget.js";
|
|
20
|
-
import { writeDashboard } from "
|
|
21
|
-
import { appendUsageRun, buildPhasePrompt, buildValidationSummary, completeValidatedPhase, formatMillis, formatTokenCount, getNextEligiblePhase, getWorkspaceState, loadCodecartoConfig, PACKAGE_VERSION, PhasePreflightError, runPhasePreflight, validatePhaseOutput, } from "../../core/index.js";
|
|
20
|
+
import { appendUsageRun, buildPhasePrompt, buildValidationSummary, completeValidatedPhase, formatMillis, formatTokenCount, describeStuckPipeline, resolvePipelineOutcome, getWorkspaceState, describeConfigProblems, loadCodecartoConfig, PACKAGE_VERSION, PhasePreflightError, runPhasePreflight, validatePhaseOutput, writeDashboard, } from "../../core/index.js";
|
|
22
21
|
/**
|
|
23
22
|
* Run one phase end to end: optional LLM-steered rewrite, spawn the sub-agent,
|
|
24
23
|
* wait for it, then emit the side effects the historical /codecarto-next chain
|
|
@@ -60,7 +59,6 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
60
59
|
getAgentsWidget().attach(ctx.ui);
|
|
61
60
|
try {
|
|
62
61
|
const result = await runPhase(ctx, prompt, {
|
|
63
|
-
onSessionCreated: (session) => { activity.session = session; },
|
|
64
62
|
onToolStart: (id, name) => { activity.activeTools.set(id, name); activity.toolUses++; },
|
|
65
63
|
onToolEnd: (id) => { activity.activeTools.delete(id); },
|
|
66
64
|
onTextDelta: (_delta, fullText) => { activity.responseText = fullText; },
|
|
@@ -101,7 +99,9 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
101
99
|
display: true,
|
|
102
100
|
});
|
|
103
101
|
void recordUsage(state.workspaceDir, phase.id, status, activity, result.sessionFile);
|
|
104
|
-
|
|
102
|
+
// The phase's sub-agent replaced the session, so reading ctx.cwd here
|
|
103
|
+
// would throw (#201); the state captured before the run has the root.
|
|
104
|
+
void writeDashboard(state.cwd, PACKAGE_VERSION);
|
|
105
105
|
return {
|
|
106
106
|
status: result.aborted ? "aborted" : "completed",
|
|
107
107
|
activity,
|
|
@@ -128,7 +128,7 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
128
128
|
display: true,
|
|
129
129
|
});
|
|
130
130
|
void recordUsage(state.workspaceDir, phase.id, "error", activity);
|
|
131
|
-
void writeDashboard(
|
|
131
|
+
void writeDashboard(state.cwd, PACKAGE_VERSION);
|
|
132
132
|
return { status: "error", activity, error: message };
|
|
133
133
|
}
|
|
134
134
|
finally {
|
|
@@ -190,6 +190,10 @@ export async function runAuto(ctx, pi, initialState, options) {
|
|
|
190
190
|
const totalTokens = { input: 0, output: 0, cacheWrite: 0 };
|
|
191
191
|
const totalPhases = initialState.pipeline.phase_order.length;
|
|
192
192
|
const config = await loadCodecartoConfig(initialState.workspaceDir);
|
|
193
|
+
// Once, before the loop: a dropped config file changes what this run does
|
|
194
|
+
// (the steer toggle lives there) and nothing else in the loop reads it.
|
|
195
|
+
if (config.problems.length > 0)
|
|
196
|
+
notifyCtx(ctx, describeConfigProblems(config).join("\n"), "warning");
|
|
193
197
|
const llmSteerEnabled = options.llmSteerOverride ?? config.orchestrator.llm_steer_next_phase;
|
|
194
198
|
let state = initialState;
|
|
195
199
|
while (true) {
|
|
@@ -199,13 +203,22 @@ export async function runAuto(ctx, pi, initialState, options) {
|
|
|
199
203
|
reason: "User aborted the auto run.",
|
|
200
204
|
});
|
|
201
205
|
}
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
206
|
+
const outcome = resolvePipelineOutcome(state);
|
|
207
|
+
if (outcome.kind === "stuck") {
|
|
208
|
+
// Not "complete": the loop ending because nothing can run is the
|
|
209
|
+
// case that used to read as success (#228).
|
|
210
|
+
return finish({
|
|
211
|
+
outcome: "stuck",
|
|
212
|
+
reason: describeStuckPipeline(outcome.blocked),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (outcome.kind === "complete") {
|
|
204
216
|
return finish({
|
|
205
217
|
outcome: "complete",
|
|
206
218
|
reason: "Pipeline complete.",
|
|
207
219
|
});
|
|
208
220
|
}
|
|
221
|
+
const phase = outcome.phase;
|
|
209
222
|
let preflight;
|
|
210
223
|
try {
|
|
211
224
|
preflight = await runPhasePreflight(state, phase);
|
|
@@ -241,7 +254,7 @@ export async function runAuto(ctx, pi, initialState, options) {
|
|
|
241
254
|
if (phaseResult.status === "completed") {
|
|
242
255
|
// State must be refreshed because the sub-agent may have written
|
|
243
256
|
// findings to disk that the validator reads.
|
|
244
|
-
const stateForValidation = (await getWorkspaceState(
|
|
257
|
+
const stateForValidation = (await getWorkspaceState(autoCwd)) ?? state;
|
|
245
258
|
validation = await validatePhaseOutput(stateForValidation, phase.id);
|
|
246
259
|
}
|
|
247
260
|
const decision = decideAfterPhase(phaseResult.status, phaseResult.error, validation, options.strict);
|
|
@@ -304,6 +317,8 @@ export function buildAutoSummary(result, availableSkills = []) {
|
|
|
304
317
|
return `**Auto pipeline stopped at \`${result.stoppedAt?.phaseId ?? "?"}\`.**`;
|
|
305
318
|
case "aborted":
|
|
306
319
|
return `**Auto pipeline aborted${result.stoppedAt?.phaseId ? ` during \`${result.stoppedAt.phaseId}\`` : ""}.**`;
|
|
320
|
+
case "stuck":
|
|
321
|
+
return `**Auto pipeline stuck: no phase can run.**`;
|
|
307
322
|
}
|
|
308
323
|
})();
|
|
309
324
|
const statsLine = `_⟳ ${ranOf} · ${tokensStr} tokens · ${wallTime}_`;
|
|
@@ -317,6 +332,10 @@ export function buildAutoSummary(result, availableSkills = []) {
|
|
|
317
332
|
lines.push("", result.reason);
|
|
318
333
|
lines.push("", recoveryHint(result));
|
|
319
334
|
}
|
|
335
|
+
if (result.outcome === "stuck") {
|
|
336
|
+
// The reason is the stuck sentence, which already says what to fix.
|
|
337
|
+
lines.push("", result.reason);
|
|
338
|
+
}
|
|
320
339
|
if (result.outcome === "complete") {
|
|
321
340
|
lines.push("", "Dashboard: `.codecarto/dashboard.html`");
|
|
322
341
|
if (availableSkills.length > 0) {
|
|
@@ -16,11 +16,13 @@ export interface BroadsideFlags {
|
|
|
16
16
|
/** Undefined means "use the repository's config default". */
|
|
17
17
|
maxCost?: number;
|
|
18
18
|
waitSeconds?: number;
|
|
19
|
+
/** For collect: the run to collect instead of the most recent (#268). */
|
|
20
|
+
runId?: string;
|
|
19
21
|
benchmarks: boolean;
|
|
20
22
|
unknown: string[];
|
|
21
23
|
/** Set on an invalid combination. The caller surfaces it as an error. */
|
|
22
24
|
error?: string;
|
|
23
25
|
}
|
|
24
26
|
/** Every token the completer offers, in the order it offers them. */
|
|
25
|
-
export declare const KNOWN_BROADSIDE_TOKENS: readonly ["submit", "collect", "status", "models", "architecture", "api", "security", "defect", "conventions", "porting", "--incremental", "--no-incremental", "--max-cost=", "--wait=", "--no-synthesis", "--no-triage", "--no-retry-truncated", "--benchmarks"];
|
|
27
|
+
export declare const KNOWN_BROADSIDE_TOKENS: readonly ["submit", "collect", "status", "models", "architecture", "api", "security", "defect", "conventions", "porting", "--incremental", "--no-incremental", "--max-cost=", "--wait=", "--run=", "--no-synthesis", "--no-triage", "--no-retry-truncated", "--benchmarks"];
|
|
26
28
|
export declare function parseBroadsideFlags(args: string): BroadsideFlags;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// --no-incremental --no-triage
|
|
14
14
|
// --max-cost=N --no-retry-truncated
|
|
15
15
|
// --wait=SECONDS --benchmarks (models only)
|
|
16
|
+
// --run=ID (collect only: an older run, as listed by status)
|
|
16
17
|
//
|
|
17
18
|
// --incremental has a spelled-out negative because the value is tri-state:
|
|
18
19
|
// absent defers to config.yaml, so a repository that set `incremental: true`
|
|
@@ -34,6 +35,7 @@ export const KNOWN_BROADSIDE_TOKENS = [
|
|
|
34
35
|
"--no-incremental",
|
|
35
36
|
"--max-cost=",
|
|
36
37
|
"--wait=",
|
|
38
|
+
"--run=",
|
|
37
39
|
"--no-synthesis",
|
|
38
40
|
"--no-triage",
|
|
39
41
|
"--no-retry-truncated",
|
|
@@ -106,6 +108,13 @@ export function parseBroadsideFlags(args) {
|
|
|
106
108
|
result.waitSeconds = parseNumeric(token, "--wait", result);
|
|
107
109
|
continue;
|
|
108
110
|
}
|
|
111
|
+
if (token.startsWith("--run=")) {
|
|
112
|
+
const value = token.slice("--run=".length).trim();
|
|
113
|
+
if (!value)
|
|
114
|
+
result.error ??= "--run= needs a run id (see /codecarto-broadside status).";
|
|
115
|
+
result.runId = value || undefined;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
109
118
|
result.unknown.push(token);
|
|
110
119
|
}
|
|
111
120
|
// Flags that only mean something for one action are refused rather than
|
|
@@ -125,5 +134,8 @@ export function parseBroadsideFlags(args) {
|
|
|
125
134
|
if (result.action === "status" && result.waitSeconds !== undefined) {
|
|
126
135
|
result.error ??= "--wait is only meaningful for submit and collect; status reads recorded state.";
|
|
127
136
|
}
|
|
137
|
+
if (result.runId !== undefined && result.action !== "collect") {
|
|
138
|
+
result.error ??= `--run is only meaningful for collect (got action "${result.action}").`;
|
|
139
|
+
}
|
|
128
140
|
return result;
|
|
129
141
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { readFile, readdir } from "node:fs/promises";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { disposeChildSession } from "./agent-runner.js";
|
|
16
17
|
import { atomicWriteFile, computeTotals, NARRATION_CACHE_RELATIVE_PATH, loadUsage, pathExists, stringifySimpleYaml, } from "../../core/index.js";
|
|
17
18
|
import { createChildModelRuntime } from "./child-model-runtime.js";
|
|
18
19
|
// Per-closeout byte budget when stuffing the narrator's input. Three
|
|
@@ -158,8 +159,14 @@ async function runNarratorOnce(ctx, prompt) {
|
|
|
158
159
|
tools: [],
|
|
159
160
|
resourceLoader: loader,
|
|
160
161
|
});
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
try {
|
|
163
|
+
await session.prompt(prompt);
|
|
164
|
+
return getLastAssistantText(session);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
// One prompt, one answer; the child has nothing left to do (#256).
|
|
168
|
+
disposeChildSession(session);
|
|
169
|
+
}
|
|
163
170
|
}
|
|
164
171
|
function getLastAssistantText(session) {
|
|
165
172
|
for (let i = session.messages.length - 1; i >= 0; i--) {
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Render and atomically replace `.codecarto/dashboard.html`.
|
|
3
|
-
* @returns true when a fresh dashboard landed on disk; false when the
|
|
4
|
-
* workspace is missing or any gather/render/write step failed (swallowed —
|
|
5
|
-
* lifecycle callers must never fail on a dashboard problem, but they may
|
|
6
|
-
* report truthfully whether a refresh happened).
|
|
7
|
-
*/
|
|
8
|
-
export declare function writeDashboard(cwd: string, packageVersion: string): Promise<boolean>;
|
|
1
|
+
export { writeDashboard } from "../../core/dashboard-writer.ts";
|
|
@@ -1,154 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { atomicWriteFile, DASHBOARD_RELATIVE_PATH, getWorkspaceState, loadUsage, NARRATION_CACHE_RELATIVE_PATH, parseSimpleYaml, pathExists, renderDashboard, } from "../../core/index.js";
|
|
8
|
-
const CLOSEOUT_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/;
|
|
9
|
-
/**
|
|
10
|
-
* Render and atomically replace `.codecarto/dashboard.html`.
|
|
11
|
-
* @returns true when a fresh dashboard landed on disk; false when the
|
|
12
|
-
* workspace is missing or any gather/render/write step failed (swallowed —
|
|
13
|
-
* lifecycle callers must never fail on a dashboard problem, but they may
|
|
14
|
-
* report truthfully whether a refresh happened).
|
|
15
|
-
*/
|
|
16
|
-
export async function writeDashboard(cwd, packageVersion) {
|
|
17
|
-
try {
|
|
18
|
-
const state = await getWorkspaceState(cwd);
|
|
19
|
-
if (!state)
|
|
20
|
-
return false;
|
|
21
|
-
const workspaceDir = state.workspaceDir;
|
|
22
|
-
const [usage, closeouts, outputsPresent, narration] = await Promise.all([
|
|
23
|
-
loadUsage(workspaceDir),
|
|
24
|
-
listCloseouts(workspaceDir),
|
|
25
|
-
buildOutputsPresent(state.workspaceDir, state.pipeline),
|
|
26
|
-
loadNarration(workspaceDir),
|
|
27
|
-
]);
|
|
28
|
-
const inputs = {
|
|
29
|
-
status: state.status,
|
|
30
|
-
pipeline: state.pipeline,
|
|
31
|
-
usage,
|
|
32
|
-
closeouts,
|
|
33
|
-
outputsPresent,
|
|
34
|
-
packageVersion,
|
|
35
|
-
generatedAt: new Date().toISOString(),
|
|
36
|
-
narration,
|
|
37
|
-
};
|
|
38
|
-
const html = renderDashboard(inputs);
|
|
39
|
-
await atomicWriteFile(join(workspaceDir, DASHBOARD_RELATIVE_PATH), html);
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Best-effort: a failed dashboard write must not surface as a phase
|
|
44
|
-
// error. The user's pipeline state is unaffected; the next state
|
|
45
|
-
// change will trigger another render attempt.
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
async function listCloseouts(workspaceDir) {
|
|
50
|
-
const dir = join(workspaceDir, "closeouts");
|
|
51
|
-
if (!(await pathExists(dir)))
|
|
52
|
-
return [];
|
|
53
|
-
let entries;
|
|
54
|
-
try {
|
|
55
|
-
entries = await readdir(dir);
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
const out = [];
|
|
61
|
-
for (const name of entries) {
|
|
62
|
-
const m = CLOSEOUT_FILENAME_RE.exec(name);
|
|
63
|
-
if (!m)
|
|
64
|
-
continue;
|
|
65
|
-
out.push({ date: m[1], phaseOrModule: m[2], fileName: name, summary: await readCloseoutSummary(join(dir, name)) });
|
|
66
|
-
}
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
async function readCloseoutSummary(path) {
|
|
70
|
-
try {
|
|
71
|
-
const raw = await readFile(path, "utf8");
|
|
72
|
-
const lines = raw.split(/\r?\n/);
|
|
73
|
-
const summaryStart = lines.findIndex((line) => /^##\s+Summary\s*$/i.test(line.trim()));
|
|
74
|
-
if (summaryStart === -1)
|
|
75
|
-
return undefined;
|
|
76
|
-
const body = [];
|
|
77
|
-
for (const line of lines.slice(summaryStart + 1)) {
|
|
78
|
-
if (/^##\s+/.test(line.trim()))
|
|
79
|
-
break;
|
|
80
|
-
const trimmed = line.trim();
|
|
81
|
-
if (!trimmed || trimmed === "-")
|
|
82
|
-
continue;
|
|
83
|
-
body.push(trimmed.replace(/^[-*]\s+/, ""));
|
|
84
|
-
if (body.join(" ").length > 280)
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
const summary = body.join(" ").trim();
|
|
88
|
-
return summary ? `${summary.slice(0, 280)}${summary.length > 280 ? "…" : ""}` : undefined;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function buildOutputsPresent(workspaceDir, pipeline) {
|
|
95
|
-
const out = new Map();
|
|
96
|
-
for (const phaseId of pipeline.phase_order) {
|
|
97
|
-
const phaseDef = pipeline.phases.find((p) => p.id === phaseId);
|
|
98
|
-
if (!phaseDef)
|
|
99
|
-
continue;
|
|
100
|
-
const entry = { secondary: [] };
|
|
101
|
-
if (phaseDef.primary_output) {
|
|
102
|
-
entry.primary = {
|
|
103
|
-
path: phaseDef.primary_output,
|
|
104
|
-
exists: await pathExists(join(workspaceDir, phaseDef.primary_output)),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
for (const sec of phaseDef.secondary_outputs ?? []) {
|
|
108
|
-
entry.secondary.push({
|
|
109
|
-
path: sec.path,
|
|
110
|
-
exists: await pathExists(join(workspaceDir, sec.path)),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
out.set(phaseId, entry);
|
|
114
|
-
}
|
|
115
|
-
return out;
|
|
116
|
-
}
|
|
117
|
-
async function loadNarration(workspaceDir) {
|
|
118
|
-
const path = join(workspaceDir, NARRATION_CACHE_RELATIVE_PATH);
|
|
119
|
-
if (!(await pathExists(path)))
|
|
120
|
-
return undefined;
|
|
121
|
-
try {
|
|
122
|
-
const raw = await readFile(path, "utf8");
|
|
123
|
-
const { frontmatter, body } = splitFrontmatter(raw);
|
|
124
|
-
if (!frontmatter)
|
|
125
|
-
return undefined;
|
|
126
|
-
const generatedAt = typeof frontmatter.generatedAt === "string" ? frontmatter.generatedAt : "";
|
|
127
|
-
const phaseCountAtGeneration = typeof frontmatter.phaseCountAtGeneration === "number" ? frontmatter.phaseCountAtGeneration : 0;
|
|
128
|
-
if (!generatedAt)
|
|
129
|
-
return undefined;
|
|
130
|
-
return { content: body.trim(), generatedAt, phaseCountAtGeneration };
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function splitFrontmatter(raw) {
|
|
137
|
-
if (!raw.startsWith("---\n"))
|
|
138
|
-
return { frontmatter: null, body: raw };
|
|
139
|
-
const end = raw.indexOf("\n---\n", 4);
|
|
140
|
-
if (end === -1)
|
|
141
|
-
return { frontmatter: null, body: raw };
|
|
142
|
-
const yamlText = raw.slice(4, end);
|
|
143
|
-
const body = raw.slice(end + 5);
|
|
144
|
-
try {
|
|
145
|
-
const parsed = parseSimpleYaml(yamlText);
|
|
146
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
147
|
-
return { frontmatter: parsed, body };
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// fall through
|
|
152
|
-
}
|
|
153
|
-
return { frontmatter: null, body };
|
|
154
|
-
}
|
|
1
|
+
// Moved to core/dashboard-writer.ts (#254): both surfaces render the dashboard
|
|
2
|
+
// at the same lifecycle points, so it is a shared primitive. This re-export
|
|
3
|
+
// keeps the old import path — scripts/build-demo-dashboard.mjs among them —
|
|
4
|
+
// working.
|
|
5
|
+
export { writeDashboard } from "../../core/dashboard-writer.js";
|