codecartographer-pi 0.19.3 → 0.19.5
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/workflow/scaffold-version.yaml +1 -1
- package/dist/core/yaml.js +31 -17
- package/dist/extensions/codecarto/auto-runner.js +11 -17
- package/dist/extensions/codecarto/guide-framing.js +21 -0
- package/dist/extensions/codecarto/index.js +115 -136
- package/dist/extensions/codecarto/notify.d.ts +16 -0
- package/dist/extensions/codecarto/notify.js +53 -0
- package/package.json +1 -1
package/dist/core/yaml.js
CHANGED
|
@@ -167,6 +167,31 @@ export function parseSimpleYaml(raw) {
|
|
|
167
167
|
while (index < lines.length && isBlankOrComment(lines[index] ?? ""))
|
|
168
168
|
index++;
|
|
169
169
|
};
|
|
170
|
+
/**
|
|
171
|
+
* Read the body of a block scalar that opened on the line just consumed.
|
|
172
|
+
* Shared by mapping values (`key: >-`) and sequence items (`- >-`): when only
|
|
173
|
+
* the mapping path had it, a handoff whose `decisions:` list used `- >-`
|
|
174
|
+
* still failed with the indentation error that #211 was supposed to end.
|
|
175
|
+
*/
|
|
176
|
+
const collectBlockScalarLines = (baseIndent) => {
|
|
177
|
+
const blockLines = [];
|
|
178
|
+
let contentIndent = null;
|
|
179
|
+
while (index < lines.length) {
|
|
180
|
+
const blockLine = lines[index] ?? "";
|
|
181
|
+
if (blockLine.trim() === "") {
|
|
182
|
+
blockLines.push("");
|
|
183
|
+
index++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const blockIndent = countIndent(blockLine);
|
|
187
|
+
if (blockIndent <= baseIndent)
|
|
188
|
+
break;
|
|
189
|
+
contentIndent ??= blockIndent;
|
|
190
|
+
blockLines.push(blockLine.slice(Math.min(contentIndent, blockIndent)));
|
|
191
|
+
index++;
|
|
192
|
+
}
|
|
193
|
+
return blockLines;
|
|
194
|
+
};
|
|
170
195
|
const parseBlock = (indent) => {
|
|
171
196
|
skipBlank();
|
|
172
197
|
if (index >= lines.length)
|
|
@@ -221,23 +246,7 @@ export function parseSimpleYaml(raw) {
|
|
|
221
246
|
seen.add(key);
|
|
222
247
|
const blockHeader = parseBlockScalarHeader(rawValue);
|
|
223
248
|
if (blockHeader) {
|
|
224
|
-
|
|
225
|
-
let contentIndent = null;
|
|
226
|
-
while (index < lines.length) {
|
|
227
|
-
const blockLine = lines[index] ?? "";
|
|
228
|
-
if (blockLine.trim() === "") {
|
|
229
|
-
blockLines.push("");
|
|
230
|
-
index++;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const blockIndent = countIndent(blockLine);
|
|
234
|
-
if (blockIndent <= indent)
|
|
235
|
-
break;
|
|
236
|
-
contentIndent ??= blockIndent;
|
|
237
|
-
blockLines.push(blockLine.slice(Math.min(contentIndent, blockIndent)));
|
|
238
|
-
index++;
|
|
239
|
-
}
|
|
240
|
-
assign(key, applyBlockScalar(blockLines, blockHeader));
|
|
249
|
+
assign(key, applyBlockScalar(collectBlockScalarLines(indent), blockHeader));
|
|
241
250
|
continue;
|
|
242
251
|
}
|
|
243
252
|
if (rawValue !== "") {
|
|
@@ -285,6 +294,11 @@ export function parseSimpleYaml(raw) {
|
|
|
285
294
|
}
|
|
286
295
|
continue;
|
|
287
296
|
}
|
|
297
|
+
const itemBlockHeader = parseBlockScalarHeader(rawItem);
|
|
298
|
+
if (itemBlockHeader) {
|
|
299
|
+
result.push(applyBlockScalar(collectBlockScalarLines(indent), itemBlockHeader));
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
288
302
|
const separator = findKeySeparator(rawItem);
|
|
289
303
|
if (separator !== -1) {
|
|
290
304
|
const key = rawItem.slice(0, separator).trim();
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// write, closeout-stub creation, dashboard regen. UI notifications stay in
|
|
13
13
|
// the calling handler.
|
|
14
14
|
import { runPhase } from "./agent-runner.js";
|
|
15
|
+
import { notifyCtx } from "./notify.js";
|
|
15
16
|
import { clearPhase, finishPhase, getPhaseActivity, startPhase } from "./agent-state.js";
|
|
16
17
|
import { buildSteeringMessage, rewritePhasePrompt } from "./agent-rewriter.js";
|
|
17
18
|
import { buildPhaseSummary } from "./agent-summary.js";
|
|
@@ -34,13 +35,11 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
34
35
|
preflight: options.preflight,
|
|
35
36
|
});
|
|
36
37
|
if (options.llmSteerEnabled) {
|
|
37
|
-
|
|
38
|
-
ctx.ui.notify(`Customizing ${phase.id} prompt via LLM rewriter…`, "info");
|
|
38
|
+
notifyCtx(ctx, `Customizing ${phase.id} prompt via LLM rewriter…`, "info");
|
|
39
39
|
const rewrite = await rewritePhasePrompt({ ctx, state, originalPrompt: prompt, nextPhaseId: phase.id });
|
|
40
40
|
if (rewrite.used) {
|
|
41
41
|
prompt = rewrite.prompt;
|
|
42
|
-
|
|
43
|
-
ctx.ui.notify(`LLM rewriter customized ${phase.id} seed prompt.`, "info");
|
|
42
|
+
notifyCtx(ctx, `LLM rewriter customized ${phase.id} seed prompt.`, "info");
|
|
44
43
|
pi.sendMessage({
|
|
45
44
|
customType: "codecarto-steering",
|
|
46
45
|
content: buildSteeringMessage({
|
|
@@ -51,15 +50,14 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
51
50
|
display: true,
|
|
52
51
|
});
|
|
53
52
|
}
|
|
54
|
-
else
|
|
55
|
-
ctx
|
|
53
|
+
else {
|
|
54
|
+
notifyCtx(ctx, `LLM rewriter skipped (${rewrite.skipReason}); using stock prompt.`, "warning");
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
const activity = startPhase(phase.id);
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
notifyCtx(ctx, `CodeCartographer phase: ${phase.id} (sub-agent running)`, "info");
|
|
59
|
+
if (ctx.hasUI)
|
|
61
60
|
getAgentsWidget().attach(ctx.ui);
|
|
62
|
-
}
|
|
63
61
|
try {
|
|
64
62
|
const result = await runPhase(ctx, prompt, {
|
|
65
63
|
onSessionCreated: (session) => { activity.session = session; },
|
|
@@ -84,11 +82,9 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
84
82
|
}, { sessionName: `CodeCartographer phase: ${phase.id}`, primaryOutput: phase.primary_output }, options.signal);
|
|
85
83
|
const status = result.aborted ? "aborted" : "completed";
|
|
86
84
|
finishPhase(phase.id, { status });
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
: `Phase ${phase.id} sub-agent finished (${result.toolUses} tool uses, ${result.turnCount} turns).`, result.aborted ? "warning" : "info");
|
|
91
|
-
}
|
|
85
|
+
notifyCtx(ctx, result.aborted
|
|
86
|
+
? `Phase ${phase.id} aborted.`
|
|
87
|
+
: `Phase ${phase.id} sub-agent finished (${result.toolUses} tool uses, ${result.turnCount} turns).`, result.aborted ? "warning" : "info");
|
|
92
88
|
pi.sendMessage({
|
|
93
89
|
customType: "codecarto-phase-summary",
|
|
94
90
|
content: buildPhaseSummary({
|
|
@@ -115,9 +111,7 @@ export async function runSinglePhase(ctx, pi, state, phase, options) {
|
|
|
115
111
|
catch (err) {
|
|
116
112
|
const message = err instanceof Error ? err.message : String(err);
|
|
117
113
|
finishPhase(phase.id, { status: "error", error: message });
|
|
118
|
-
|
|
119
|
-
ctx.ui.notify(`Phase ${phase.id} sub-agent failed: ${message}`, "error");
|
|
120
|
-
}
|
|
114
|
+
notifyCtx(ctx, `Phase ${phase.id} sub-agent failed: ${message}`, "error");
|
|
121
115
|
pi.sendMessage({
|
|
122
116
|
customType: "codecarto-phase-summary",
|
|
123
117
|
content: buildPhaseSummary({
|
|
@@ -40,6 +40,27 @@ export const PI_SURFACE_ADDENDUM = [
|
|
|
40
40
|
`- **Every tool name maps to a slash command the user runs**, mechanically: \`codecarto_status\` → \`/codecarto-status\`, \`codecarto_next\` → \`/codecarto-next\`, and so on. Two have no Pi equivalent: ${MCP_ONLY_TOOLS.map((name) => `\`${name}\``).join(" and ")}.`,
|
|
41
41
|
"- **Ignore \"every tool takes an absolute `cwd`\".** Slash commands act on the session's own directory; there is no `cwd` argument to pass.",
|
|
42
42
|
"- **The drive loop is different.** `/codecarto-next` executes the phase itself, as an isolated sub-agent, and then auto-validates and auto-completes it. The guide's hand-written loop — take the prompt, execute it, write the handoff, then validate and complete yourself — describes the MCP surface. On Pi the user drives and the extension executes; your job is to explain what the framework is doing and answer questions about it, not to reproduce that loop by hand.",
|
|
43
|
+
"",
|
|
44
|
+
"### How to drive a run",
|
|
45
|
+
"",
|
|
46
|
+
"`/codecarto-next` takes flags that change how much runs and how each phase is seeded. They are independent: `--auto` decides *how many phases run*, `--llm-steer` decides *what prompt each one gets*.",
|
|
47
|
+
"",
|
|
48
|
+
"| Invocation | What it does |",
|
|
49
|
+
"| --- | --- |",
|
|
50
|
+
"| `/codecarto-next` | Runs the next eligible phase, once. Good for watching a single phase or retrying one that stopped. |",
|
|
51
|
+
"| `/codecarto-next --auto` | Runs every remaining phase back to back, validating and completing each before starting the next. Stops on a validation failure or a sub-agent error. |",
|
|
52
|
+
"| `/codecarto-next --auto --llm-steer` | The same, with each phase's prompt rewritten from the previous phase's closeout. **This is the usual choice for a full run** — it is what makes phase N+1 aware of what phase N found. |",
|
|
53
|
+
"| `/codecarto-next --auto --strict --llm-steer` | The same, but also stops on `PASS WITH GAPS` instead of advancing through it. Use when gaps should be reviewed rather than carried forward. |",
|
|
54
|
+
"",
|
|
55
|
+
"Notes worth passing on when the user asks:",
|
|
56
|
+
"",
|
|
57
|
+
"- **The first phase is never steered** — there is no previous closeout to steer from, so it reports `LLM rewriter skipped (no previous phase to steer from)` and uses the stock prompt. That message is normal, not a failure.",
|
|
58
|
+
"- **Steering costs an extra model call per phase**, on top of the phase sub-agent itself.",
|
|
59
|
+
"- `--strict` is only valid with `--auto`; on its own it is an error.",
|
|
60
|
+
"- `--no-llm-steer` forces steering off for one invocation when the workspace config has it on (`orchestrator.llm_steer_next_phase`, default off).",
|
|
61
|
+
"- **An auto run that stops says why in its summary block.** If a phase produced its artifact but the pipeline still shows it incomplete, read the `Auto pipeline stopped at …` message rather than assuming the phase failed — the phase usually succeeded and something after it did not.",
|
|
62
|
+
"",
|
|
63
|
+
"If the user has just initialized a workspace and has not said what they want, tell them the run command rather than waiting to be asked: `/codecarto-next --auto --llm-steer` for a full pass, or plain `/codecarto-next` to watch one phase first.",
|
|
43
64
|
].join("\n");
|
|
44
65
|
/**
|
|
45
66
|
* Assemble the message /codecarto-guide queues. The guide document is embedded
|
|
@@ -9,6 +9,7 @@ import { writeDashboard } from "./dashboard-writer.js";
|
|
|
9
9
|
import { parseBroadsideFlags, KNOWN_BROADSIDE_TOKENS } from "./broadside-flags.js";
|
|
10
10
|
import { parseNextFlags } from "./next-flags.js";
|
|
11
11
|
import { buildPiGuideMessage } from "./guide-framing.js";
|
|
12
|
+
import { isCtxLive, notifyCtx } from "./notify.js";
|
|
12
13
|
import { phaseCompactionExtension } from "./phase-compaction.js";
|
|
13
14
|
import { applyAmendment, buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, BroadsideCancelledError, broadsideDirFor, collectResultText, estimateSubmitText, getLens, listAmendmentNames, listBatchModels, listGuideTopics, listScaffoldRefreshFiles, listSkillNames, loadAmendmentFile, loadBroadsideConfig, modelsText, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, statusText, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, readBroadsideSkill, readGuide, refreshScaffold, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, resolvePublishSourceRepo, SourceRepoMismatchError, runPhasePreflight, SCAFFOLD_REFRESH_PROTECTED, seedOrchestratorFiles, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
|
|
14
15
|
import { initLibrary } from "../../core/library.js";
|
|
@@ -84,40 +85,6 @@ function buildStatusLines(state, extraLines = []) {
|
|
|
84
85
|
}
|
|
85
86
|
return lines;
|
|
86
87
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Whether `ctx` still belongs to the live session.
|
|
89
|
-
*
|
|
90
|
-
* Pi invalidates an extension ctx when the session is replaced, and from then
|
|
91
|
-
* on *every* property access on it throws — `ctx.cwd` and `ctx.hasUI` included.
|
|
92
|
-
* A phase runs as a sub-agent, so by the time post-phase work fires, the ctx
|
|
93
|
-
* captured when the command started may already be dead. That is an ordinary
|
|
94
|
-
* outcome rather than an error: the UI it would have refreshed is gone with the
|
|
95
|
-
* session. Callers skip their UI work instead of throwing into a `void` call
|
|
96
|
-
* that nothing is waiting on.
|
|
97
|
-
*/
|
|
98
|
-
function isCtxLive(ctx) {
|
|
99
|
-
try {
|
|
100
|
-
return typeof ctx.cwd === "string";
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Notify through `ctx`, dropping the message if the session it belonged to is
|
|
108
|
-
* gone.
|
|
109
|
-
*
|
|
110
|
-
* `ctx.hasUI` throws on a stale ctx rather than returning false, so the usual
|
|
111
|
-
* `if (ctx.hasUI) ctx.ui.notify(...)` guard was itself a throw site. Inside a
|
|
112
|
-
* promise chain that was worse than a lost message: the `.catch` handler threw
|
|
113
|
-
* while reporting the original failure, and that second rejection had nothing
|
|
114
|
-
* left to catch it.
|
|
115
|
-
*/
|
|
116
|
-
function notifyCtx(ctx, message, level) {
|
|
117
|
-
if (!isCtxLive(ctx) || !ctx.hasUI)
|
|
118
|
-
return;
|
|
119
|
-
ctx.ui.notify(message, level);
|
|
120
|
-
}
|
|
121
88
|
function setUiState(ctx, state, extraLines = []) {
|
|
122
89
|
if (!isCtxLive(ctx) || !ctx.hasUI)
|
|
123
90
|
return;
|
|
@@ -310,8 +277,8 @@ export default function codeCartographerExtension(pi) {
|
|
|
310
277
|
setUiState(ctx, null);
|
|
311
278
|
// The ctx can die between the read above and here, so the error
|
|
312
279
|
// path must not assume it is still usable either.
|
|
313
|
-
if (notifyOnError
|
|
314
|
-
ctx
|
|
280
|
+
if (notifyOnError)
|
|
281
|
+
notifyCtx(ctx, message, "error");
|
|
315
282
|
return null;
|
|
316
283
|
}
|
|
317
284
|
};
|
|
@@ -370,8 +337,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
370
337
|
if (!(await pathExists(workspaceDir)))
|
|
371
338
|
return undefined;
|
|
372
339
|
if (event.toolName === "bash") {
|
|
373
|
-
|
|
374
|
-
ctx.ui.notify("Blocked bash in CodeCartographer mode", "warning");
|
|
340
|
+
notifyCtx(ctx, "Blocked bash in CodeCartographer mode", "warning");
|
|
375
341
|
return { block: true, reason: "CodeCartographer mode disables bash to keep source analysis read-only." };
|
|
376
342
|
}
|
|
377
343
|
if (event.toolName === "edit" || event.toolName === "write") {
|
|
@@ -385,9 +351,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
385
351
|
}
|
|
386
352
|
const withinAllowed = await Promise.all(allowedRoots.map((allowedRoot) => isWithinPathResolved(targetPath, allowedRoot)));
|
|
387
353
|
if (!withinAllowed.some((result) => result)) {
|
|
388
|
-
|
|
389
|
-
ctx.ui.notify(`Blocked ${event.toolName} outside .codecarto/ or configured library: ${inputPath}`, "warning");
|
|
390
|
-
}
|
|
354
|
+
notifyCtx(ctx, `Blocked ${event.toolName} outside .codecarto/ or configured library: ${inputPath}`, "warning");
|
|
391
355
|
return { block: true, reason: `CodeCartographer mode only allows ${event.toolName} within .codecarto/ or the configured CodeCartographer library.` };
|
|
392
356
|
}
|
|
393
357
|
}
|
|
@@ -398,7 +362,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
398
362
|
handler: async (_args, ctx) => {
|
|
399
363
|
const workspaceDir = join(ctx.cwd, ".codecarto");
|
|
400
364
|
if (!(await pathExists(join(workspaceDir, "workflow", "status.yaml")))) {
|
|
401
|
-
ctx
|
|
365
|
+
notifyCtx(ctx, "No existing CodeCartographer workspace found. Run /codecarto-init first.", "warning");
|
|
402
366
|
return;
|
|
403
367
|
}
|
|
404
368
|
try {
|
|
@@ -407,11 +371,11 @@ export default function codeCartographerExtension(pi) {
|
|
|
407
371
|
lastFeedbackLines = [`Opened existing workspace: ${getPipelineLabel(state.status.pipeline)}`];
|
|
408
372
|
pi.setActiveTools(SAFE_TOOL_NAMES);
|
|
409
373
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
410
|
-
ctx
|
|
374
|
+
notifyCtx(ctx, "Opened existing CodeCartographer workspace without resetting state.", "info");
|
|
411
375
|
}
|
|
412
376
|
catch (error) {
|
|
413
377
|
const message = error instanceof Error ? error.message : String(error);
|
|
414
|
-
ctx
|
|
378
|
+
notifyCtx(ctx, `Unable to open CodeCartographer workspace: ${message}`, "error");
|
|
415
379
|
}
|
|
416
380
|
},
|
|
417
381
|
});
|
|
@@ -420,7 +384,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
420
384
|
handler: async (_args, ctx) => {
|
|
421
385
|
const interviewPath = join(ctx.cwd, ".codecarto", "findings", "vision-capture", "INTERVIEW.md");
|
|
422
386
|
if (!(await pathExists(interviewPath))) {
|
|
423
|
-
ctx
|
|
387
|
+
notifyCtx(ctx, "Vision interview skill not found. Run /codecarto-init synthesis first.", "warning");
|
|
424
388
|
return;
|
|
425
389
|
}
|
|
426
390
|
const interviewSkill = await readFile(interviewPath, "utf8");
|
|
@@ -442,7 +406,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
442
406
|
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
443
407
|
}
|
|
444
408
|
lastFeedbackLines = ["Vision interview started — answer the questions in chat."];
|
|
445
|
-
ctx
|
|
409
|
+
notifyCtx(ctx, "Vision interview queued — answer the questions in the chat.", "info");
|
|
446
410
|
},
|
|
447
411
|
});
|
|
448
412
|
pi.registerCommand("codecarto-init", {
|
|
@@ -457,13 +421,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
457
421
|
const trimmedArgs = args.trim();
|
|
458
422
|
const pipelineChoice = resolvePipelineChoice(trimmedArgs);
|
|
459
423
|
if (trimmedArgs && !pipelineChoice) {
|
|
460
|
-
ctx
|
|
424
|
+
notifyCtx(ctx, `Unknown pipeline: ${trimmedArgs}`, "error");
|
|
461
425
|
return;
|
|
462
426
|
}
|
|
463
427
|
const targetWorkspaceDir = join(ctx.cwd, ".codecarto");
|
|
464
428
|
const sourceWorkspaceDir = packagedWorkspaceDir;
|
|
465
429
|
if (!(await pathExists(sourceWorkspaceDir))) {
|
|
466
|
-
ctx
|
|
430
|
+
notifyCtx(ctx, "Packaged .codecarto assets are missing.", "error");
|
|
467
431
|
return;
|
|
468
432
|
}
|
|
469
433
|
const targetExists = await pathExists(targetWorkspaceDir);
|
|
@@ -475,8 +439,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
475
439
|
return;
|
|
476
440
|
const backupDir = join(ctx.cwd, `.codecarto-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
477
441
|
await rename(targetWorkspaceDir, backupDir);
|
|
478
|
-
|
|
479
|
-
ctx.ui.notify(`Backed up existing workspace to ${basename(backupDir)}/`, "info");
|
|
442
|
+
notifyCtx(ctx, `Backed up existing workspace to ${basename(backupDir)}/`, "info");
|
|
480
443
|
}
|
|
481
444
|
}
|
|
482
445
|
if (!(await pathExists(targetWorkspaceDir))) {
|
|
@@ -488,7 +451,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
488
451
|
const selectedPipelinePath = pipelineChoice ?? rawStatus.pipeline?.trim() ?? DEFAULT_PIPELINE_PATH;
|
|
489
452
|
const resolvedPipelinePath = join(targetWorkspaceDir, selectedPipelinePath);
|
|
490
453
|
if (!(await pathExists(resolvedPipelinePath))) {
|
|
491
|
-
ctx
|
|
454
|
+
notifyCtx(ctx, `Pipeline not found: ${selectedPipelinePath}`, "error");
|
|
492
455
|
return;
|
|
493
456
|
}
|
|
494
457
|
const pipeline = await loadYamlFile(resolvedPipelinePath);
|
|
@@ -499,8 +462,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
499
462
|
// duties maintain so they exist from the first phase.
|
|
500
463
|
await seedOrchestratorFiles(targetWorkspaceDir);
|
|
501
464
|
codecartoModeActive = true;
|
|
502
|
-
|
|
503
|
-
|
|
465
|
+
// Name the run command here: init is the moment someone needs it, and
|
|
466
|
+
// the flags that make a full run useful are not guessable from the
|
|
467
|
+
// command name alone.
|
|
468
|
+
lastFeedbackLines = [
|
|
469
|
+
`Initialized workspace with pipeline: ${getPipelineLabel(selectedPipelinePath)}`,
|
|
470
|
+
"Full run: `/codecarto-next --auto --llm-steer` — or `/codecarto-next` to watch one phase first.",
|
|
471
|
+
];
|
|
472
|
+
notifyCtx(ctx, `Initialized CodeCartographer (${getPipelineLabel(selectedPipelinePath)}). Full run: /codecarto-next --auto --llm-steer`, "info");
|
|
504
473
|
// Render the initial dashboard (empty usage, all phases pending) so
|
|
505
474
|
// the user sees the file exist immediately after /codecarto-init.
|
|
506
475
|
void writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
@@ -518,7 +487,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
518
487
|
const nextPhase = getNextEligiblePhase(state)?.id ?? "complete";
|
|
519
488
|
lastFeedbackLines = [`Current phase: ${nextPhase}`, `Pipeline: ${getPipelineLabel(state.status.pipeline)}`];
|
|
520
489
|
setUiState(ctx, state, lastFeedbackLines);
|
|
521
|
-
ctx
|
|
490
|
+
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}`, "info");
|
|
522
491
|
},
|
|
523
492
|
});
|
|
524
493
|
pi.registerCommand("codecarto-switch-pipeline", {
|
|
@@ -532,12 +501,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
532
501
|
handler: async (args, ctx) => {
|
|
533
502
|
const trimmedArgs = args.trim();
|
|
534
503
|
if (!trimmedArgs) {
|
|
535
|
-
ctx
|
|
504
|
+
notifyCtx(ctx, "Usage: /codecarto-switch-pipeline <variant> (e.g. lite, full, synthesis)", "warning");
|
|
536
505
|
return;
|
|
537
506
|
}
|
|
538
507
|
const pipelineChoice = resolvePipelineChoice(trimmedArgs);
|
|
539
508
|
if (!pipelineChoice) {
|
|
540
|
-
ctx
|
|
509
|
+
notifyCtx(ctx, `Unknown pipeline: ${trimmedArgs}`, "error");
|
|
541
510
|
return;
|
|
542
511
|
}
|
|
543
512
|
const state = await ensureWorkspaceState(ctx);
|
|
@@ -545,7 +514,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
545
514
|
return;
|
|
546
515
|
const currentPipeline = state.status.pipeline;
|
|
547
516
|
if (currentPipeline === pipelineChoice) {
|
|
548
|
-
ctx
|
|
517
|
+
notifyCtx(ctx, `Already on pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
549
518
|
return;
|
|
550
519
|
}
|
|
551
520
|
try {
|
|
@@ -561,40 +530,52 @@ export default function codeCartographerExtension(pi) {
|
|
|
561
530
|
lines.push(`Phases not in new pipeline: ${result.dropped.join(", ")} (findings remain on disk)`);
|
|
562
531
|
lastFeedbackLines = lines;
|
|
563
532
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
564
|
-
ctx
|
|
533
|
+
notifyCtx(ctx, `Switched to pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
565
534
|
void writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
566
535
|
}
|
|
567
536
|
catch (error) {
|
|
568
537
|
const message = error instanceof Error ? error.message : String(error);
|
|
569
538
|
lastFeedbackLines = [message];
|
|
570
539
|
setUiState(ctx, state, lastFeedbackLines);
|
|
571
|
-
ctx
|
|
540
|
+
notifyCtx(ctx, message, "error");
|
|
572
541
|
}
|
|
573
542
|
},
|
|
574
543
|
});
|
|
575
544
|
pi.registerCommand("codecarto-next", {
|
|
576
|
-
description: "Run the next
|
|
545
|
+
description: "Run the next phase as a sub-agent. Full run: --auto --llm-steer. Add --strict to stop on PASS WITH GAPS.",
|
|
577
546
|
getArgumentCompletions: (prefix) => {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
547
|
+
// Descriptions, not bare flag names: the completion list is the only
|
|
548
|
+
// place most users will ever see what these do, and the useful
|
|
549
|
+
// combination (--auto --llm-steer) is not guessable from the names.
|
|
550
|
+
// --strict is offered only once --auto is present, because on its own
|
|
551
|
+
// it is rejected — suggesting it standalone invites the one error the
|
|
552
|
+
// parser has.
|
|
553
|
+
const autoAlreadyTyped = prefix.includes("--auto");
|
|
554
|
+
const items = [
|
|
555
|
+
{ value: "--auto", label: "--auto", description: "run every remaining phase back to back (recommended with --llm-steer)" },
|
|
556
|
+
{ value: "--llm-steer", label: "--llm-steer", description: "seed each phase from the previous phase's closeout; no effect on the first phase" },
|
|
557
|
+
{ value: "--no-llm-steer", label: "--no-llm-steer", description: "force steering off when the workspace config turns it on" },
|
|
558
|
+
...(autoAlreadyTyped
|
|
559
|
+
? [{ value: "--strict", label: "--strict", description: "with --auto: stop on PASS WITH GAPS instead of advancing" }]
|
|
560
|
+
: []),
|
|
561
|
+
].filter((item) => item.value.startsWith(prefix.split(/\s+/).pop() ?? prefix));
|
|
581
562
|
return items.length > 0 ? items : null;
|
|
582
563
|
},
|
|
583
564
|
handler: async (args, ctx) => {
|
|
584
565
|
const flags = parseNextFlags(args);
|
|
585
566
|
if (flags.error) {
|
|
586
|
-
ctx
|
|
567
|
+
notifyCtx(ctx, flags.error, "error");
|
|
587
568
|
return;
|
|
588
569
|
}
|
|
589
570
|
if (flags.unknown.length > 0) {
|
|
590
|
-
ctx
|
|
571
|
+
notifyCtx(ctx, `Unknown /codecarto-next flag: ${flags.unknown.join(" ")}`, "error");
|
|
591
572
|
return;
|
|
592
573
|
}
|
|
593
574
|
const state = await ensureWorkspaceState(ctx);
|
|
594
575
|
if (!state)
|
|
595
576
|
return;
|
|
596
577
|
if (flags.auto) {
|
|
597
|
-
ctx
|
|
578
|
+
notifyCtx(ctx, `Auto pipeline${flags.strict ? " (strict)" : ""} running…`, "info");
|
|
598
579
|
const result = await runAuto(ctx, pi, state, {
|
|
599
580
|
strict: flags.strict,
|
|
600
581
|
llmSteerOverride: flags.llmSteerOverride,
|
|
@@ -617,14 +598,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
617
598
|
});
|
|
618
599
|
lastFeedbackLines = [`Auto pipeline ${result.outcome}: ${result.reason}`];
|
|
619
600
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
620
|
-
ctx
|
|
601
|
+
notifyCtx(ctx, `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`, result.outcome === "complete" ? "info" : "warning");
|
|
621
602
|
return;
|
|
622
603
|
}
|
|
623
604
|
const phase = getNextEligiblePhase(state);
|
|
624
605
|
if (!phase) {
|
|
625
606
|
lastFeedbackLines = ["All phases complete."];
|
|
626
607
|
setUiState(ctx, state, lastFeedbackLines);
|
|
627
|
-
ctx
|
|
608
|
+
notifyCtx(ctx, "All CodeCartographer phases are complete.", "info");
|
|
628
609
|
return;
|
|
629
610
|
}
|
|
630
611
|
let preflight;
|
|
@@ -635,13 +616,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
635
616
|
const message = error instanceof Error ? error.message : String(error);
|
|
636
617
|
lastFeedbackLines = [message];
|
|
637
618
|
setUiState(ctx, state, lastFeedbackLines);
|
|
638
|
-
ctx
|
|
619
|
+
notifyCtx(ctx, message, error instanceof PhasePreflightError ? "warning" : "error");
|
|
639
620
|
return;
|
|
640
621
|
}
|
|
641
622
|
// Reject re-entry: don't spawn a duplicate runner for a phase that's
|
|
642
623
|
// already in flight from a previous /codecarto-next invocation.
|
|
643
624
|
if (isPhaseRunning(phase.id)) {
|
|
644
|
-
ctx
|
|
625
|
+
notifyCtx(ctx, `Phase ${phase.id} is already running.`, "warning");
|
|
645
626
|
return;
|
|
646
627
|
}
|
|
647
628
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
@@ -717,7 +698,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
717
698
|
handler: async (args, ctx) => {
|
|
718
699
|
const phaseId = args.trim();
|
|
719
700
|
if (!phaseId) {
|
|
720
|
-
ctx
|
|
701
|
+
notifyCtx(ctx, "Usage: /codecarto-phase <phase>", "warning");
|
|
721
702
|
return;
|
|
722
703
|
}
|
|
723
704
|
const state = await ensureWorkspaceState(ctx);
|
|
@@ -725,7 +706,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
725
706
|
return;
|
|
726
707
|
const phase = resolvePhase(state, phaseId);
|
|
727
708
|
if (!phase) {
|
|
728
|
-
ctx
|
|
709
|
+
notifyCtx(ctx, `Unknown phase: ${phaseId}`, "error");
|
|
729
710
|
return;
|
|
730
711
|
}
|
|
731
712
|
let prompt;
|
|
@@ -734,7 +715,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
734
715
|
}
|
|
735
716
|
catch (error) {
|
|
736
717
|
const message = error instanceof Error ? error.message : String(error);
|
|
737
|
-
ctx
|
|
718
|
+
notifyCtx(ctx, message, error instanceof PhasePreflightError ? "warning" : "error");
|
|
738
719
|
return;
|
|
739
720
|
}
|
|
740
721
|
if (ctx.isIdle()) {
|
|
@@ -745,7 +726,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
745
726
|
}
|
|
746
727
|
lastFeedbackLines = [`Queued explicit phase prompt for ${phase.id}`];
|
|
747
728
|
setUiState(ctx, state, lastFeedbackLines);
|
|
748
|
-
ctx
|
|
729
|
+
notifyCtx(ctx, `Queued CodeCartographer phase: ${phase.id}`, "info");
|
|
749
730
|
},
|
|
750
731
|
});
|
|
751
732
|
pi.registerCommand("codecarto-validate", {
|
|
@@ -758,13 +739,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
758
739
|
if (validation instanceof Error) {
|
|
759
740
|
lastFeedbackLines = [validation.message];
|
|
760
741
|
setUiState(ctx, state, lastFeedbackLines);
|
|
761
|
-
ctx
|
|
742
|
+
notifyCtx(ctx, validation.message, "error");
|
|
762
743
|
return;
|
|
763
744
|
}
|
|
764
745
|
lastFeedbackLines = buildValidationSummary(validation);
|
|
765
746
|
setUiState(ctx, state, lastFeedbackLines);
|
|
766
747
|
const level = validation.overall === "FAIL" || validation.overall === "MISSING" ? "error" : validation.overall === "PASS WITH GAPS" ? "warning" : "info";
|
|
767
|
-
ctx
|
|
748
|
+
notifyCtx(ctx, `Validation ${validation.phaseId}: ${validation.overall}`, level);
|
|
768
749
|
},
|
|
769
750
|
});
|
|
770
751
|
pi.registerCommand("codecarto-complete", {
|
|
@@ -777,13 +758,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
777
758
|
if (validation instanceof Error) {
|
|
778
759
|
lastFeedbackLines = [validation.message];
|
|
779
760
|
setUiState(ctx, currentState, lastFeedbackLines);
|
|
780
|
-
ctx
|
|
761
|
+
notifyCtx(ctx, validation.message, "error");
|
|
781
762
|
return;
|
|
782
763
|
}
|
|
783
764
|
if (validation.overall === "FAIL" || validation.overall === "MISSING") {
|
|
784
765
|
lastFeedbackLines = buildValidationSummary(validation);
|
|
785
766
|
setUiState(ctx, currentState, lastFeedbackLines);
|
|
786
|
-
ctx
|
|
767
|
+
notifyCtx(ctx, `Cannot complete ${validation.phaseId}: ${validation.overall}`, "error");
|
|
787
768
|
return;
|
|
788
769
|
}
|
|
789
770
|
// Completion refuses for reasons the framework words carefully — a
|
|
@@ -801,7 +782,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
801
782
|
const message = error instanceof Error ? error.message : String(error);
|
|
802
783
|
lastFeedbackLines = [`Completion refused: ${message}`];
|
|
803
784
|
setUiState(ctx, currentState, lastFeedbackLines);
|
|
804
|
-
ctx
|
|
785
|
+
notifyCtx(ctx, message, "error");
|
|
805
786
|
return;
|
|
806
787
|
}
|
|
807
788
|
const { updatedState, closeoutNotice, warnings } = completion;
|
|
@@ -816,11 +797,11 @@ export default function codeCartographerExtension(pi) {
|
|
|
816
797
|
for (const note of notes)
|
|
817
798
|
lastFeedbackLines.push(`NOTE: ${note} Non-gating.`);
|
|
818
799
|
setUiState(ctx, updatedState, lastFeedbackLines);
|
|
819
|
-
ctx
|
|
800
|
+
notifyCtx(ctx, `Marked ${validation.phaseId} complete`, validation.overall === "PASS WITH GAPS" || notes.length > 0 ? "warning" : "info");
|
|
820
801
|
if (closeoutNotice)
|
|
821
|
-
ctx
|
|
802
|
+
notifyCtx(ctx, closeoutNotice, "info");
|
|
822
803
|
for (const note of notes)
|
|
823
|
-
ctx
|
|
804
|
+
notifyCtx(ctx, note, "warning");
|
|
824
805
|
},
|
|
825
806
|
});
|
|
826
807
|
pi.registerCommand("codecarto-skill", {
|
|
@@ -830,7 +811,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
830
811
|
if (!skillName) {
|
|
831
812
|
const available = await listSkillNames(join(ctx.cwd, ".codecarto"));
|
|
832
813
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : "";
|
|
833
|
-
ctx
|
|
814
|
+
notifyCtx(ctx, `Usage: /codecarto-skill <name>${hint}`, "warning");
|
|
834
815
|
return;
|
|
835
816
|
}
|
|
836
817
|
// Broad-Side is a reading guide for batch reconnaissance output, not a
|
|
@@ -840,7 +821,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
840
821
|
if (skillName === BROADSIDE_SKILL_NAME) {
|
|
841
822
|
const skill = await readBroadsideSkill(ctx.cwd).catch(() => null);
|
|
842
823
|
if (!skill) {
|
|
843
|
-
ctx
|
|
824
|
+
notifyCtx(ctx, "Broad-Side reading guide not found. Reinstall codecartographer-pi.", "error");
|
|
844
825
|
return;
|
|
845
826
|
}
|
|
846
827
|
const message = [
|
|
@@ -854,7 +835,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
854
835
|
else {
|
|
855
836
|
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
856
837
|
}
|
|
857
|
-
ctx
|
|
838
|
+
notifyCtx(ctx, "Queued the Broad-Side reading guide", "info");
|
|
858
839
|
return;
|
|
859
840
|
}
|
|
860
841
|
const state = await ensureWorkspaceState(ctx);
|
|
@@ -862,14 +843,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
862
843
|
return;
|
|
863
844
|
const nextPhase = getNextEligiblePhase(state);
|
|
864
845
|
if (nextPhase) {
|
|
865
|
-
ctx
|
|
846
|
+
notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${nextPhase.id}). Finish the pipeline before running post-pipeline skills.`, "error");
|
|
866
847
|
return;
|
|
867
848
|
}
|
|
868
849
|
const skillFile = join(state.workspaceDir, "skills", skillName, "SKILL.md");
|
|
869
850
|
if (!(await pathExists(skillFile))) {
|
|
870
851
|
const available = await listSkillNames(state.workspaceDir);
|
|
871
852
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : " (no skills installed)";
|
|
872
|
-
ctx
|
|
853
|
+
notifyCtx(ctx, `Unknown skill: ${skillName}${hint}. The Broad-Side reading guide is served as \`${BROADSIDE_SKILL_NAME}\` and is not pipeline-gated.`, "error");
|
|
873
854
|
return;
|
|
874
855
|
}
|
|
875
856
|
const prompt = await buildSkillPrompt(state, skillName);
|
|
@@ -881,7 +862,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
881
862
|
}
|
|
882
863
|
lastFeedbackLines = [`Queued post-pipeline skill: ${skillName}`];
|
|
883
864
|
setUiState(ctx, state, lastFeedbackLines);
|
|
884
|
-
ctx
|
|
865
|
+
notifyCtx(ctx, `Queued CodeCartographer skill: ${skillName}`, "info");
|
|
885
866
|
},
|
|
886
867
|
});
|
|
887
868
|
pi.registerCommand("codecarto-list-skills", {
|
|
@@ -910,7 +891,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
910
891
|
}
|
|
911
892
|
lastFeedbackLines = lines;
|
|
912
893
|
setUiState(ctx, state, lastFeedbackLines);
|
|
913
|
-
ctx
|
|
894
|
+
notifyCtx(ctx, skills.length > 0
|
|
914
895
|
? `${skills.length} post-pipeline skill${skills.length === 1 ? "" : "s"}: ${skills.join(", ")}`
|
|
915
896
|
: "No post-pipeline skills installed.", "info");
|
|
916
897
|
},
|
|
@@ -934,7 +915,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
934
915
|
document = await readGuide(args.trim() || undefined);
|
|
935
916
|
}
|
|
936
917
|
catch (error) {
|
|
937
|
-
ctx
|
|
918
|
+
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
938
919
|
return;
|
|
939
920
|
}
|
|
940
921
|
const other = topics.filter((name) => name !== document.topic);
|
|
@@ -951,7 +932,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
951
932
|
lastFeedbackLines = [`Queued the CodeCartographer guide: ${document.topic}`];
|
|
952
933
|
if (codecartoModeActive)
|
|
953
934
|
void refreshWorkspaceUi(ctx, lastFeedbackLines).catch(() => undefined);
|
|
954
|
-
ctx
|
|
935
|
+
notifyCtx(ctx, `Queued the CodeCartographer guide (${document.topic})`, "info");
|
|
955
936
|
},
|
|
956
937
|
});
|
|
957
938
|
pi.registerCommand("codecarto-broadside", {
|
|
@@ -965,12 +946,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
965
946
|
handler: async (args, ctx) => {
|
|
966
947
|
const flags = parseBroadsideFlags(args);
|
|
967
948
|
if (flags.unknown.length > 0) {
|
|
968
|
-
ctx
|
|
949
|
+
notifyCtx(ctx, `Unknown /codecarto-broadside argument: ${flags.unknown.join(" ")}. ` +
|
|
969
950
|
`Actions: submit, collect, status, models. Lenses: ${BROADSIDE_LENS_IDS.join(", ")}.`, "error");
|
|
970
951
|
return;
|
|
971
952
|
}
|
|
972
953
|
if (flags.error) {
|
|
973
|
-
ctx
|
|
954
|
+
notifyCtx(ctx, flags.error, "error");
|
|
974
955
|
return;
|
|
975
956
|
}
|
|
976
957
|
// Broad-Side runs on any git repository, with or without a workspace —
|
|
@@ -990,7 +971,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
990
971
|
// the result can live, so it holds it instead of being cleared.
|
|
991
972
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, ["Broad-Side", ...lines]);
|
|
992
973
|
}
|
|
993
|
-
ctx
|
|
974
|
+
notifyCtx(ctx, notice, level);
|
|
994
975
|
};
|
|
995
976
|
if (flags.action === "status") {
|
|
996
977
|
const { state } = await runBroadsideStatus(ctx.cwd);
|
|
@@ -1000,12 +981,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1000
981
|
}
|
|
1001
982
|
const apiKey = resolveBroadsideKey(config.apiKey);
|
|
1002
983
|
if (!apiKey) {
|
|
1003
|
-
ctx
|
|
984
|
+
notifyCtx(ctx, "No OpenRouter API key. Set OPENROUTER_API_KEY in the environment, or add api_key to " +
|
|
1004
985
|
".codecarto/broadside/config.yaml. (A slash command takes no key: it would land in the transcript.)", "error");
|
|
1005
986
|
return;
|
|
1006
987
|
}
|
|
1007
988
|
if (flags.action === "models") {
|
|
1008
|
-
ctx
|
|
989
|
+
notifyCtx(ctx, "Fetching the OpenRouter batch-model catalog…", "info");
|
|
1009
990
|
try {
|
|
1010
991
|
const { entries, benchmarks } = await listBatchModels(broadsideDir, config, apiKey, {
|
|
1011
992
|
includeBenchmarks: flags.benchmarks,
|
|
@@ -1013,7 +994,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1013
994
|
finish(modelsText(entries, { benchmarks, defaultModel: config.model }).split("\n"), `Broad-Side: ${entries.length} batch model${entries.length === 1 ? "" : "s"} listed`);
|
|
1014
995
|
}
|
|
1015
996
|
catch (error) {
|
|
1016
|
-
ctx
|
|
997
|
+
notifyCtx(ctx, `Model catalog lookup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1017
998
|
}
|
|
1018
999
|
return;
|
|
1019
1000
|
}
|
|
@@ -1059,10 +1040,10 @@ export default function codeCartographerExtension(pi) {
|
|
|
1059
1040
|
if (ctx.hasUI)
|
|
1060
1041
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
1061
1042
|
if (error instanceof BroadsideCancelledError) {
|
|
1062
|
-
ctx
|
|
1043
|
+
notifyCtx(ctx, "Broad-Side cancelled. Nothing was submitted.", "info");
|
|
1063
1044
|
return;
|
|
1064
1045
|
}
|
|
1065
|
-
ctx
|
|
1046
|
+
notifyCtx(ctx, `Broad-Side submit failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1066
1047
|
return;
|
|
1067
1048
|
}
|
|
1068
1049
|
const lines = estimateSubmitText(submit, lenses.map(getLens)).split("\n");
|
|
@@ -1071,7 +1052,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1071
1052
|
finish(lines, `Broad-Side submitted: run ${submit.runId} (~$${submit.estimatedTotalCost.toFixed(4)})`);
|
|
1072
1053
|
return;
|
|
1073
1054
|
}
|
|
1074
|
-
ctx
|
|
1055
|
+
notifyCtx(ctx, `Broad-Side submitted run ${submit.runId}; polling for up to ${waitSeconds}s…`, "info");
|
|
1075
1056
|
try {
|
|
1076
1057
|
const collect = await runBroadsideCollect(ctx.cwd, apiKey, {
|
|
1077
1058
|
waitMs,
|
|
@@ -1087,7 +1068,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1087
1068
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
1088
1069
|
// The batches are submitted and paid for either way — say so, so
|
|
1089
1070
|
// nobody re-submits a run that is already in flight.
|
|
1090
|
-
ctx
|
|
1071
|
+
notifyCtx(ctx, `Broad-Side submitted run ${submit.runId}, but collect failed: ` +
|
|
1091
1072
|
`${error instanceof Error ? error.message : String(error)}. Retry with /codecarto-broadside collect.`, "error");
|
|
1092
1073
|
}
|
|
1093
1074
|
return;
|
|
@@ -1111,7 +1092,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1111
1092
|
catch (error) {
|
|
1112
1093
|
if (ctx.hasUI)
|
|
1113
1094
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
1114
|
-
ctx
|
|
1095
|
+
notifyCtx(ctx, `Broad-Side collect failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1115
1096
|
}
|
|
1116
1097
|
},
|
|
1117
1098
|
});
|
|
@@ -1123,22 +1104,22 @@ export default function codeCartographerExtension(pi) {
|
|
|
1123
1104
|
return;
|
|
1124
1105
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
1125
1106
|
if (!config.library.path) {
|
|
1126
|
-
ctx
|
|
1107
|
+
notifyCtx(ctx, "No library.path is configured. Create a library directory with a .codecarto-library marker, then set library.path in ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml.", "error");
|
|
1127
1108
|
return;
|
|
1128
1109
|
}
|
|
1129
1110
|
const marker = await discoverLibrary(config.library.path);
|
|
1130
1111
|
if (!marker) {
|
|
1131
|
-
ctx
|
|
1112
|
+
notifyCtx(ctx, `No CodeCartographer library at ${config.library.path} (missing .codecarto-library). Create a .codecarto-library marker file in that directory.`, "error");
|
|
1132
1113
|
return;
|
|
1133
1114
|
}
|
|
1134
1115
|
const phase = resolvePhase(state, "reimplementation-spec");
|
|
1135
1116
|
if (!phase?.primary_output) {
|
|
1136
|
-
ctx
|
|
1117
|
+
notifyCtx(ctx, "The active pipeline does not produce a reimplementation spec to publish.", "error");
|
|
1137
1118
|
return;
|
|
1138
1119
|
}
|
|
1139
1120
|
const specPath = join(state.workspaceDir, phase.primary_output);
|
|
1140
1121
|
if (!(await pathExists(specPath))) {
|
|
1141
|
-
ctx
|
|
1122
|
+
notifyCtx(ctx, `Reimplementation spec is missing: .codecarto/${phase.primary_output}`, "error");
|
|
1142
1123
|
return;
|
|
1143
1124
|
}
|
|
1144
1125
|
const spec = await readFile(specPath, "utf8");
|
|
@@ -1149,7 +1130,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1149
1130
|
const headline = derivePublishHeadline(spec, ctx.cwd);
|
|
1150
1131
|
const namespace = marker.namespaced ? config.library.namespace ?? undefined : undefined;
|
|
1151
1132
|
if (marker.namespaced && !namespace) {
|
|
1152
|
-
ctx
|
|
1133
|
+
notifyCtx(ctx, "The configured library is namespaced; set library.namespace before publishing.", "error");
|
|
1153
1134
|
return;
|
|
1154
1135
|
}
|
|
1155
1136
|
const label = `${namespace ? `${namespace}/` : ""}${slug}`;
|
|
@@ -1194,7 +1175,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1194
1175
|
// recorded the directory to one that records the git remote.
|
|
1195
1176
|
const moved = await ctx.ui.confirm("Source repository changed — did it move?", `Library entry ${label} records source_repo "${error.recorded}", but this publish carries "${error.incoming}". If the repository genuinely moved (rename, org transfer, host change — or this is the first publish since CodeCartographer began recording the git remote instead of the local directory), answer yes and this spec is appended as the entry's next version. If these are two different projects that share a directory name, answer no: nothing is written, and the second project needs a distinct slug (codecarto_publish on MCP accepts one). Did the repository move?`);
|
|
1196
1177
|
if (!moved) {
|
|
1197
|
-
ctx
|
|
1178
|
+
notifyCtx(ctx, "Publish cancelled. Nothing was written.", "info");
|
|
1198
1179
|
return;
|
|
1199
1180
|
}
|
|
1200
1181
|
options.allowSourceRepoChange = true;
|
|
@@ -1204,7 +1185,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1204
1185
|
// default; whether it may go into a wider library is the user's call.
|
|
1205
1186
|
const publishAnyway = await ctx.ui.confirm("Confidentiality mismatch — publish anyway?", `This spec's confidentiality is "${error.entryConfidentiality}" (CodeCartographer's default; /codecarto-publish declares none), but the library "${marker.name}" has visibility "${error.libraryVisibility}". Publishing would expose it to everyone that library reaches. Publish anyway?`);
|
|
1206
1187
|
if (!publishAnyway) {
|
|
1207
|
-
ctx
|
|
1188
|
+
notifyCtx(ctx, "Publish cancelled. Nothing was written.", "info");
|
|
1208
1189
|
return;
|
|
1209
1190
|
}
|
|
1210
1191
|
options.allowConfidentialityMismatch = true;
|
|
@@ -1217,11 +1198,11 @@ export default function codeCartographerExtension(pi) {
|
|
|
1217
1198
|
lastFeedbackLines = [`Published ${result.namespace ? `${result.namespace}/` : ""}${result.slug} v${result.version}`, result.isNewVersion ? "New content version." : "Metadata-only update (content unchanged)."];
|
|
1218
1199
|
await writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
1219
1200
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
1220
|
-
ctx
|
|
1201
|
+
notifyCtx(ctx, `Published ${result.namespace ? `${result.namespace}/` : ""}${result.slug} v${result.version}.`, "info");
|
|
1221
1202
|
}
|
|
1222
1203
|
catch (error) {
|
|
1223
1204
|
const message = error instanceof Error ? error.message : String(error);
|
|
1224
|
-
ctx
|
|
1205
|
+
notifyCtx(ctx, `Unable to publish: ${message}`, "error");
|
|
1225
1206
|
}
|
|
1226
1207
|
},
|
|
1227
1208
|
});
|
|
@@ -1233,7 +1214,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1233
1214
|
const namespaceIdx = parts.indexOf("--namespace");
|
|
1234
1215
|
const namespace = namespaceIdx >= 0 ? parts[namespaceIdx + 1] : null;
|
|
1235
1216
|
if (!pathArg) {
|
|
1236
|
-
ctx
|
|
1217
|
+
notifyCtx(ctx, "Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
|
|
1237
1218
|
return;
|
|
1238
1219
|
}
|
|
1239
1220
|
const libraryPath = pathArg.startsWith("~") ? join(homedir(), pathArg.slice(1)) : resolve(pathArg);
|
|
@@ -1249,14 +1230,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1249
1230
|
? `Library already exists at ${libraryPath} (marker preserved). Config updated.`
|
|
1250
1231
|
: `Created library at ${libraryPath} with marker "${result.marker.name}".`;
|
|
1251
1232
|
lastFeedbackLines = [msg, `Config written to ${configPath}`];
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
ctx.ui.notify(`Config written to ${configPath}`, "info");
|
|
1255
|
-
}
|
|
1233
|
+
notifyCtx(ctx, msg, "info");
|
|
1234
|
+
notifyCtx(ctx, `Config written to ${configPath}`, "info");
|
|
1256
1235
|
}
|
|
1257
1236
|
catch (error) {
|
|
1258
1237
|
const message = error instanceof Error ? error.message : String(error);
|
|
1259
|
-
ctx
|
|
1238
|
+
notifyCtx(ctx, `Library init failed: ${message}`, "error");
|
|
1260
1239
|
}
|
|
1261
1240
|
},
|
|
1262
1241
|
});
|
|
@@ -1282,7 +1261,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1282
1261
|
lastFeedbackLines = lines;
|
|
1283
1262
|
if (state)
|
|
1284
1263
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1285
|
-
ctx
|
|
1264
|
+
notifyCtx(ctx, "Configuration shown in status widget.", "info");
|
|
1286
1265
|
},
|
|
1287
1266
|
});
|
|
1288
1267
|
pi.registerCommand("codecarto-usage", {
|
|
@@ -1295,7 +1274,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1295
1274
|
if (usage.runs.length === 0) {
|
|
1296
1275
|
lastFeedbackLines = ["No phase runs recorded yet."];
|
|
1297
1276
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1298
|
-
ctx
|
|
1277
|
+
notifyCtx(ctx, "No phase runs recorded yet.", "info");
|
|
1299
1278
|
return;
|
|
1300
1279
|
}
|
|
1301
1280
|
const totals = computeTotals(usage);
|
|
@@ -1315,7 +1294,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1315
1294
|
}
|
|
1316
1295
|
lastFeedbackLines = lines;
|
|
1317
1296
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1318
|
-
ctx
|
|
1297
|
+
notifyCtx(ctx, `CodeCartographer usage: ${totals.runs} run${totals.runs === 1 ? "" : "s"}, ${formatUsageTokens(totals.tokens.input + totals.tokens.output)} tokens total`, "info");
|
|
1319
1298
|
},
|
|
1320
1299
|
});
|
|
1321
1300
|
pi.registerCommand("codecarto-dashboard", {
|
|
@@ -1329,26 +1308,26 @@ export default function codeCartographerExtension(pi) {
|
|
|
1329
1308
|
handler: async (args, ctx) => {
|
|
1330
1309
|
const flags = parseDashboardFlags(args);
|
|
1331
1310
|
if (flags.unknown.length > 0) {
|
|
1332
|
-
ctx
|
|
1311
|
+
notifyCtx(ctx, `Unknown /codecarto-dashboard flag: ${flags.unknown.join(" ")}`, "error");
|
|
1333
1312
|
return;
|
|
1334
1313
|
}
|
|
1335
1314
|
const state = await ensureWorkspaceState(ctx);
|
|
1336
1315
|
if (!state)
|
|
1337
1316
|
return;
|
|
1338
1317
|
if (flags.narrate) {
|
|
1339
|
-
ctx
|
|
1318
|
+
notifyCtx(ctx, `Narrating dashboard via LLM…`, "info");
|
|
1340
1319
|
const result = await narrateDashboard(ctx, state);
|
|
1341
1320
|
if (result.used) {
|
|
1342
|
-
ctx
|
|
1321
|
+
notifyCtx(ctx, "Narration written to .codecarto/.dashboard-narration.local.md", "info");
|
|
1343
1322
|
}
|
|
1344
1323
|
else {
|
|
1345
|
-
ctx
|
|
1324
|
+
notifyCtx(ctx, `LLM narration skipped (${result.skipReason}); rendering deterministic dashboard.`, "warning");
|
|
1346
1325
|
}
|
|
1347
1326
|
}
|
|
1348
1327
|
await writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
1349
1328
|
lastFeedbackLines = ["Dashboard regenerated: .codecarto/dashboard.html"];
|
|
1350
1329
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1351
|
-
ctx
|
|
1330
|
+
notifyCtx(ctx, "Dashboard regenerated: .codecarto/dashboard.html", "info");
|
|
1352
1331
|
},
|
|
1353
1332
|
});
|
|
1354
1333
|
pi.registerCommand("codecarto-refresh-scaffold", {
|
|
@@ -1364,12 +1343,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1364
1343
|
files = await listScaffoldRefreshFiles();
|
|
1365
1344
|
}
|
|
1366
1345
|
catch (error) {
|
|
1367
|
-
ctx
|
|
1346
|
+
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
1368
1347
|
return;
|
|
1369
1348
|
}
|
|
1370
1349
|
const approved = await ctx.ui.confirm("Refresh the .codecarto/ scaffold from the packaged template?", describeScaffoldRefreshPreview(files, state.scaffoldVersion));
|
|
1371
1350
|
if (!approved) {
|
|
1372
|
-
ctx
|
|
1351
|
+
notifyCtx(ctx, "Scaffold refresh cancelled. Nothing was written.", "info");
|
|
1373
1352
|
return;
|
|
1374
1353
|
}
|
|
1375
1354
|
try {
|
|
@@ -1382,13 +1361,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
1382
1361
|
];
|
|
1383
1362
|
// Re-read state so the widget's staleness line clears with the marker.
|
|
1384
1363
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
1385
|
-
ctx
|
|
1364
|
+
notifyCtx(ctx, `Refreshed ${result.written.length} framework-owned file(s) (${transition}).`, "info");
|
|
1386
1365
|
}
|
|
1387
1366
|
catch (error) {
|
|
1388
1367
|
const message = error instanceof Error ? error.message : String(error);
|
|
1389
1368
|
lastFeedbackLines = [message];
|
|
1390
1369
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1391
|
-
ctx
|
|
1370
|
+
notifyCtx(ctx, `Scaffold refresh failed: ${message}`, "error");
|
|
1392
1371
|
}
|
|
1393
1372
|
},
|
|
1394
1373
|
});
|
|
@@ -1410,12 +1389,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1410
1389
|
const hint = staged.length > 0
|
|
1411
1390
|
? ` (staged: ${staged.join(", ")})`
|
|
1412
1391
|
: " — write .codecarto/scratch/amendments/<name>.yaml first (see templates/amendment.yaml)";
|
|
1413
|
-
ctx
|
|
1392
|
+
notifyCtx(ctx, `Usage: /codecarto-amend <name>${hint}`, "warning");
|
|
1414
1393
|
return;
|
|
1415
1394
|
}
|
|
1416
1395
|
const name = resolveAmendmentName(args, ctx.cwd);
|
|
1417
1396
|
if (!name) {
|
|
1418
|
-
ctx
|
|
1397
|
+
notifyCtx(ctx, `Amendments are read from .codecarto/scratch/amendments/ only; pass the amendment name or a path inside that directory, not ${args.trim()}.`, "error");
|
|
1419
1398
|
return;
|
|
1420
1399
|
}
|
|
1421
1400
|
// The same refusals codecarto_amend surfaces, raised before the
|
|
@@ -1425,12 +1404,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1425
1404
|
amendment = await loadAmendmentFile(name, state.workspaceDir);
|
|
1426
1405
|
}
|
|
1427
1406
|
catch (error) {
|
|
1428
|
-
ctx
|
|
1407
|
+
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
1429
1408
|
return;
|
|
1430
1409
|
}
|
|
1431
1410
|
const nextPhase = getNextEligiblePhase(state);
|
|
1432
1411
|
if (nextPhase) {
|
|
1433
|
-
ctx
|
|
1412
|
+
notifyCtx(ctx, `Cannot amend: the pipeline is not complete (next phase: ${nextPhase.id}). `
|
|
1434
1413
|
+ "Resolve open questions and routed items through that phase's handoff (open_question_closures / carry_forward_closures) instead.", "error");
|
|
1435
1414
|
return;
|
|
1436
1415
|
}
|
|
@@ -1438,7 +1417,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1438
1417
|
// before anything is written — MCP's codecarto_amend applies on call.
|
|
1439
1418
|
const approved = await ctx.ui.confirm(`Apply amendment "${amendment.slug}"?`, describeAmendmentPreview(amendment, state));
|
|
1440
1419
|
if (!approved) {
|
|
1441
|
-
ctx
|
|
1420
|
+
notifyCtx(ctx, `Amendment ${amendment.slug} cancelled. Nothing was written.`, "info");
|
|
1442
1421
|
return;
|
|
1443
1422
|
}
|
|
1444
1423
|
try {
|
|
@@ -1459,14 +1438,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
1459
1438
|
lastFeedbackLines = lines;
|
|
1460
1439
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
1461
1440
|
const closed = applied.openQuestionsClosed.length + applied.postPipelineClosed.length;
|
|
1462
|
-
ctx
|
|
1441
|
+
notifyCtx(ctx, `Amendment ${amendment.slug} applied: ${applied.openQuestionsClosed.length} open question(s) and ${applied.postPipelineClosed.length} post-pipeline item(s) closed`
|
|
1463
1442
|
+ `${applied.unknownIds.length > 0 ? `; ${applied.unknownIds.length} id(s) matched nothing` : ""}.`, closed === 0 || applied.unknownIds.length > 0 ? "warning" : "info");
|
|
1464
1443
|
}
|
|
1465
1444
|
catch (error) {
|
|
1466
1445
|
const message = error instanceof Error ? error.message : String(error);
|
|
1467
1446
|
lastFeedbackLines = [message];
|
|
1468
1447
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1469
|
-
ctx
|
|
1448
|
+
notifyCtx(ctx, `Amendment failed: ${message}`, "error");
|
|
1470
1449
|
}
|
|
1471
1450
|
},
|
|
1472
1451
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export type NotifyLevel = "info" | "warning" | "error";
|
|
3
|
+
/** Whether `ctx` still belongs to the live session. See the module comment. */
|
|
4
|
+
export declare function isCtxLive(ctx: ExtensionContext | ExtensionCommandContext): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Format a notification for a stream. Exported so tests pin the shape a script
|
|
7
|
+
* would parse: one line, prefixed, level visible.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatStreamNotification(message: string, level: NotifyLevel): string;
|
|
10
|
+
/**
|
|
11
|
+
* Notify through `ctx`: the TUI when there is one, stderr when there is not,
|
|
12
|
+
* nothing when the ctx is stale.
|
|
13
|
+
*/
|
|
14
|
+
export declare function notifyCtx(ctx: ExtensionContext | ExtensionCommandContext, message: string, level: NotifyLevel, stream?: {
|
|
15
|
+
write(chunk: string): unknown;
|
|
16
|
+
}): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// User-facing notifications from the orchestrator side of the Pi extension.
|
|
2
|
+
//
|
|
3
|
+
// Two things go wrong with a bare `ctx.ui.notify(...)`, and this module is the
|
|
4
|
+
// one place that handles both.
|
|
5
|
+
//
|
|
6
|
+
// A stale ctx throws. Pi invalidates an extension ctx when the session is
|
|
7
|
+
// replaced, and from then on *every* property access on it throws — `ctx.cwd`
|
|
8
|
+
// and `ctx.hasUI` included. A phase runs as a sub-agent, so by the time
|
|
9
|
+
// post-phase work fires, the ctx captured when the command started may already
|
|
10
|
+
// be dead. That is an ordinary outcome, not an error: the UI it would have
|
|
11
|
+
// refreshed is gone with the session, so the message is dropped.
|
|
12
|
+
//
|
|
13
|
+
// A non-interactive session shows nothing. Under `pi -p`, `ctx.hasUI` is false
|
|
14
|
+
// and `ctx.ui.notify` is a silent no-op. Commands whose only output is a notify
|
|
15
|
+
// — /codecarto-status, /codecarto-usage, /codecarto-list-skills — then exit 0
|
|
16
|
+
// having printed nothing, which is byte-for-byte what a silent refusal looks
|
|
17
|
+
// like (#219). So when there is no UI, the message goes to stderr instead:
|
|
18
|
+
// stderr rather than stdout because `--mode json` owns stdout for its event
|
|
19
|
+
// stream and prose there would corrupt it.
|
|
20
|
+
//
|
|
21
|
+
// This is for the orchestrator's ctx only. Code that runs inside the phase
|
|
22
|
+
// sub-agent (phase-compaction.ts) also sees `hasUI === false`, but there the
|
|
23
|
+
// parent TUI may be on screen, and writing to stderr would put text into a live
|
|
24
|
+
// terminal. That code keeps its own `if (ctx.hasUI)` guards.
|
|
25
|
+
/** Whether `ctx` still belongs to the live session. See the module comment. */
|
|
26
|
+
export function isCtxLive(ctx) {
|
|
27
|
+
try {
|
|
28
|
+
return typeof ctx.cwd === "string";
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Format a notification for a stream. Exported so tests pin the shape a script
|
|
36
|
+
* would parse: one line, prefixed, level visible.
|
|
37
|
+
*/
|
|
38
|
+
export function formatStreamNotification(message, level) {
|
|
39
|
+
return `[codecarto] ${level}: ${message}\n`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Notify through `ctx`: the TUI when there is one, stderr when there is not,
|
|
43
|
+
* nothing when the ctx is stale.
|
|
44
|
+
*/
|
|
45
|
+
export function notifyCtx(ctx, message, level, stream = process.stderr) {
|
|
46
|
+
if (!isCtxLive(ctx))
|
|
47
|
+
return;
|
|
48
|
+
if (ctx.hasUI) {
|
|
49
|
+
ctx.ui.notify(message, level);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
stream.write(formatStreamNotification(message, level));
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecartographer-pi",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.5",
|
|
4
4
|
"mcpName": "io.github.HuginnIndustries/codecartographer",
|
|
5
5
|
"description": "Turn an unfamiliar codebase into a validated reimplementation spec, then synthesize confirmed specs and a product vision into a traceable plan.",
|
|
6
6
|
"type": "module",
|