codecartographer-pi 0.19.4 → 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.
|
@@ -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({
|
|
@@ -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);
|
|
@@ -506,7 +469,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
506
469
|
`Initialized workspace with pipeline: ${getPipelineLabel(selectedPipelinePath)}`,
|
|
507
470
|
"Full run: `/codecarto-next --auto --llm-steer` — or `/codecarto-next` to watch one phase first.",
|
|
508
471
|
];
|
|
509
|
-
ctx
|
|
472
|
+
notifyCtx(ctx, `Initialized CodeCartographer (${getPipelineLabel(selectedPipelinePath)}). Full run: /codecarto-next --auto --llm-steer`, "info");
|
|
510
473
|
// Render the initial dashboard (empty usage, all phases pending) so
|
|
511
474
|
// the user sees the file exist immediately after /codecarto-init.
|
|
512
475
|
void writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
@@ -524,7 +487,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
524
487
|
const nextPhase = getNextEligiblePhase(state)?.id ?? "complete";
|
|
525
488
|
lastFeedbackLines = [`Current phase: ${nextPhase}`, `Pipeline: ${getPipelineLabel(state.status.pipeline)}`];
|
|
526
489
|
setUiState(ctx, state, lastFeedbackLines);
|
|
527
|
-
ctx
|
|
490
|
+
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}`, "info");
|
|
528
491
|
},
|
|
529
492
|
});
|
|
530
493
|
pi.registerCommand("codecarto-switch-pipeline", {
|
|
@@ -538,12 +501,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
538
501
|
handler: async (args, ctx) => {
|
|
539
502
|
const trimmedArgs = args.trim();
|
|
540
503
|
if (!trimmedArgs) {
|
|
541
|
-
ctx
|
|
504
|
+
notifyCtx(ctx, "Usage: /codecarto-switch-pipeline <variant> (e.g. lite, full, synthesis)", "warning");
|
|
542
505
|
return;
|
|
543
506
|
}
|
|
544
507
|
const pipelineChoice = resolvePipelineChoice(trimmedArgs);
|
|
545
508
|
if (!pipelineChoice) {
|
|
546
|
-
ctx
|
|
509
|
+
notifyCtx(ctx, `Unknown pipeline: ${trimmedArgs}`, "error");
|
|
547
510
|
return;
|
|
548
511
|
}
|
|
549
512
|
const state = await ensureWorkspaceState(ctx);
|
|
@@ -551,7 +514,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
551
514
|
return;
|
|
552
515
|
const currentPipeline = state.status.pipeline;
|
|
553
516
|
if (currentPipeline === pipelineChoice) {
|
|
554
|
-
ctx
|
|
517
|
+
notifyCtx(ctx, `Already on pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
555
518
|
return;
|
|
556
519
|
}
|
|
557
520
|
try {
|
|
@@ -567,14 +530,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
567
530
|
lines.push(`Phases not in new pipeline: ${result.dropped.join(", ")} (findings remain on disk)`);
|
|
568
531
|
lastFeedbackLines = lines;
|
|
569
532
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
570
|
-
ctx
|
|
533
|
+
notifyCtx(ctx, `Switched to pipeline: ${getPipelineLabel(pipelineChoice)}`, "info");
|
|
571
534
|
void writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
572
535
|
}
|
|
573
536
|
catch (error) {
|
|
574
537
|
const message = error instanceof Error ? error.message : String(error);
|
|
575
538
|
lastFeedbackLines = [message];
|
|
576
539
|
setUiState(ctx, state, lastFeedbackLines);
|
|
577
|
-
ctx
|
|
540
|
+
notifyCtx(ctx, message, "error");
|
|
578
541
|
}
|
|
579
542
|
},
|
|
580
543
|
});
|
|
@@ -601,18 +564,18 @@ export default function codeCartographerExtension(pi) {
|
|
|
601
564
|
handler: async (args, ctx) => {
|
|
602
565
|
const flags = parseNextFlags(args);
|
|
603
566
|
if (flags.error) {
|
|
604
|
-
ctx
|
|
567
|
+
notifyCtx(ctx, flags.error, "error");
|
|
605
568
|
return;
|
|
606
569
|
}
|
|
607
570
|
if (flags.unknown.length > 0) {
|
|
608
|
-
ctx
|
|
571
|
+
notifyCtx(ctx, `Unknown /codecarto-next flag: ${flags.unknown.join(" ")}`, "error");
|
|
609
572
|
return;
|
|
610
573
|
}
|
|
611
574
|
const state = await ensureWorkspaceState(ctx);
|
|
612
575
|
if (!state)
|
|
613
576
|
return;
|
|
614
577
|
if (flags.auto) {
|
|
615
|
-
ctx
|
|
578
|
+
notifyCtx(ctx, `Auto pipeline${flags.strict ? " (strict)" : ""} running…`, "info");
|
|
616
579
|
const result = await runAuto(ctx, pi, state, {
|
|
617
580
|
strict: flags.strict,
|
|
618
581
|
llmSteerOverride: flags.llmSteerOverride,
|
|
@@ -635,14 +598,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
635
598
|
});
|
|
636
599
|
lastFeedbackLines = [`Auto pipeline ${result.outcome}: ${result.reason}`];
|
|
637
600
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
638
|
-
ctx
|
|
601
|
+
notifyCtx(ctx, `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`, result.outcome === "complete" ? "info" : "warning");
|
|
639
602
|
return;
|
|
640
603
|
}
|
|
641
604
|
const phase = getNextEligiblePhase(state);
|
|
642
605
|
if (!phase) {
|
|
643
606
|
lastFeedbackLines = ["All phases complete."];
|
|
644
607
|
setUiState(ctx, state, lastFeedbackLines);
|
|
645
|
-
ctx
|
|
608
|
+
notifyCtx(ctx, "All CodeCartographer phases are complete.", "info");
|
|
646
609
|
return;
|
|
647
610
|
}
|
|
648
611
|
let preflight;
|
|
@@ -653,13 +616,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
653
616
|
const message = error instanceof Error ? error.message : String(error);
|
|
654
617
|
lastFeedbackLines = [message];
|
|
655
618
|
setUiState(ctx, state, lastFeedbackLines);
|
|
656
|
-
ctx
|
|
619
|
+
notifyCtx(ctx, message, error instanceof PhasePreflightError ? "warning" : "error");
|
|
657
620
|
return;
|
|
658
621
|
}
|
|
659
622
|
// Reject re-entry: don't spawn a duplicate runner for a phase that's
|
|
660
623
|
// already in flight from a previous /codecarto-next invocation.
|
|
661
624
|
if (isPhaseRunning(phase.id)) {
|
|
662
|
-
ctx
|
|
625
|
+
notifyCtx(ctx, `Phase ${phase.id} is already running.`, "warning");
|
|
663
626
|
return;
|
|
664
627
|
}
|
|
665
628
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
@@ -735,7 +698,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
735
698
|
handler: async (args, ctx) => {
|
|
736
699
|
const phaseId = args.trim();
|
|
737
700
|
if (!phaseId) {
|
|
738
|
-
ctx
|
|
701
|
+
notifyCtx(ctx, "Usage: /codecarto-phase <phase>", "warning");
|
|
739
702
|
return;
|
|
740
703
|
}
|
|
741
704
|
const state = await ensureWorkspaceState(ctx);
|
|
@@ -743,7 +706,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
743
706
|
return;
|
|
744
707
|
const phase = resolvePhase(state, phaseId);
|
|
745
708
|
if (!phase) {
|
|
746
|
-
ctx
|
|
709
|
+
notifyCtx(ctx, `Unknown phase: ${phaseId}`, "error");
|
|
747
710
|
return;
|
|
748
711
|
}
|
|
749
712
|
let prompt;
|
|
@@ -752,7 +715,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
752
715
|
}
|
|
753
716
|
catch (error) {
|
|
754
717
|
const message = error instanceof Error ? error.message : String(error);
|
|
755
|
-
ctx
|
|
718
|
+
notifyCtx(ctx, message, error instanceof PhasePreflightError ? "warning" : "error");
|
|
756
719
|
return;
|
|
757
720
|
}
|
|
758
721
|
if (ctx.isIdle()) {
|
|
@@ -763,7 +726,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
763
726
|
}
|
|
764
727
|
lastFeedbackLines = [`Queued explicit phase prompt for ${phase.id}`];
|
|
765
728
|
setUiState(ctx, state, lastFeedbackLines);
|
|
766
|
-
ctx
|
|
729
|
+
notifyCtx(ctx, `Queued CodeCartographer phase: ${phase.id}`, "info");
|
|
767
730
|
},
|
|
768
731
|
});
|
|
769
732
|
pi.registerCommand("codecarto-validate", {
|
|
@@ -776,13 +739,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
776
739
|
if (validation instanceof Error) {
|
|
777
740
|
lastFeedbackLines = [validation.message];
|
|
778
741
|
setUiState(ctx, state, lastFeedbackLines);
|
|
779
|
-
ctx
|
|
742
|
+
notifyCtx(ctx, validation.message, "error");
|
|
780
743
|
return;
|
|
781
744
|
}
|
|
782
745
|
lastFeedbackLines = buildValidationSummary(validation);
|
|
783
746
|
setUiState(ctx, state, lastFeedbackLines);
|
|
784
747
|
const level = validation.overall === "FAIL" || validation.overall === "MISSING" ? "error" : validation.overall === "PASS WITH GAPS" ? "warning" : "info";
|
|
785
|
-
ctx
|
|
748
|
+
notifyCtx(ctx, `Validation ${validation.phaseId}: ${validation.overall}`, level);
|
|
786
749
|
},
|
|
787
750
|
});
|
|
788
751
|
pi.registerCommand("codecarto-complete", {
|
|
@@ -795,13 +758,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
795
758
|
if (validation instanceof Error) {
|
|
796
759
|
lastFeedbackLines = [validation.message];
|
|
797
760
|
setUiState(ctx, currentState, lastFeedbackLines);
|
|
798
|
-
ctx
|
|
761
|
+
notifyCtx(ctx, validation.message, "error");
|
|
799
762
|
return;
|
|
800
763
|
}
|
|
801
764
|
if (validation.overall === "FAIL" || validation.overall === "MISSING") {
|
|
802
765
|
lastFeedbackLines = buildValidationSummary(validation);
|
|
803
766
|
setUiState(ctx, currentState, lastFeedbackLines);
|
|
804
|
-
ctx
|
|
767
|
+
notifyCtx(ctx, `Cannot complete ${validation.phaseId}: ${validation.overall}`, "error");
|
|
805
768
|
return;
|
|
806
769
|
}
|
|
807
770
|
// Completion refuses for reasons the framework words carefully — a
|
|
@@ -819,7 +782,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
819
782
|
const message = error instanceof Error ? error.message : String(error);
|
|
820
783
|
lastFeedbackLines = [`Completion refused: ${message}`];
|
|
821
784
|
setUiState(ctx, currentState, lastFeedbackLines);
|
|
822
|
-
ctx
|
|
785
|
+
notifyCtx(ctx, message, "error");
|
|
823
786
|
return;
|
|
824
787
|
}
|
|
825
788
|
const { updatedState, closeoutNotice, warnings } = completion;
|
|
@@ -834,11 +797,11 @@ export default function codeCartographerExtension(pi) {
|
|
|
834
797
|
for (const note of notes)
|
|
835
798
|
lastFeedbackLines.push(`NOTE: ${note} Non-gating.`);
|
|
836
799
|
setUiState(ctx, updatedState, lastFeedbackLines);
|
|
837
|
-
ctx
|
|
800
|
+
notifyCtx(ctx, `Marked ${validation.phaseId} complete`, validation.overall === "PASS WITH GAPS" || notes.length > 0 ? "warning" : "info");
|
|
838
801
|
if (closeoutNotice)
|
|
839
|
-
ctx
|
|
802
|
+
notifyCtx(ctx, closeoutNotice, "info");
|
|
840
803
|
for (const note of notes)
|
|
841
|
-
ctx
|
|
804
|
+
notifyCtx(ctx, note, "warning");
|
|
842
805
|
},
|
|
843
806
|
});
|
|
844
807
|
pi.registerCommand("codecarto-skill", {
|
|
@@ -848,7 +811,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
848
811
|
if (!skillName) {
|
|
849
812
|
const available = await listSkillNames(join(ctx.cwd, ".codecarto"));
|
|
850
813
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : "";
|
|
851
|
-
ctx
|
|
814
|
+
notifyCtx(ctx, `Usage: /codecarto-skill <name>${hint}`, "warning");
|
|
852
815
|
return;
|
|
853
816
|
}
|
|
854
817
|
// Broad-Side is a reading guide for batch reconnaissance output, not a
|
|
@@ -858,7 +821,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
858
821
|
if (skillName === BROADSIDE_SKILL_NAME) {
|
|
859
822
|
const skill = await readBroadsideSkill(ctx.cwd).catch(() => null);
|
|
860
823
|
if (!skill) {
|
|
861
|
-
ctx
|
|
824
|
+
notifyCtx(ctx, "Broad-Side reading guide not found. Reinstall codecartographer-pi.", "error");
|
|
862
825
|
return;
|
|
863
826
|
}
|
|
864
827
|
const message = [
|
|
@@ -872,7 +835,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
872
835
|
else {
|
|
873
836
|
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
874
837
|
}
|
|
875
|
-
ctx
|
|
838
|
+
notifyCtx(ctx, "Queued the Broad-Side reading guide", "info");
|
|
876
839
|
return;
|
|
877
840
|
}
|
|
878
841
|
const state = await ensureWorkspaceState(ctx);
|
|
@@ -880,14 +843,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
880
843
|
return;
|
|
881
844
|
const nextPhase = getNextEligiblePhase(state);
|
|
882
845
|
if (nextPhase) {
|
|
883
|
-
ctx
|
|
846
|
+
notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${nextPhase.id}). Finish the pipeline before running post-pipeline skills.`, "error");
|
|
884
847
|
return;
|
|
885
848
|
}
|
|
886
849
|
const skillFile = join(state.workspaceDir, "skills", skillName, "SKILL.md");
|
|
887
850
|
if (!(await pathExists(skillFile))) {
|
|
888
851
|
const available = await listSkillNames(state.workspaceDir);
|
|
889
852
|
const hint = available.length > 0 ? ` (available: ${available.join(", ")})` : " (no skills installed)";
|
|
890
|
-
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");
|
|
891
854
|
return;
|
|
892
855
|
}
|
|
893
856
|
const prompt = await buildSkillPrompt(state, skillName);
|
|
@@ -899,7 +862,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
899
862
|
}
|
|
900
863
|
lastFeedbackLines = [`Queued post-pipeline skill: ${skillName}`];
|
|
901
864
|
setUiState(ctx, state, lastFeedbackLines);
|
|
902
|
-
ctx
|
|
865
|
+
notifyCtx(ctx, `Queued CodeCartographer skill: ${skillName}`, "info");
|
|
903
866
|
},
|
|
904
867
|
});
|
|
905
868
|
pi.registerCommand("codecarto-list-skills", {
|
|
@@ -928,7 +891,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
928
891
|
}
|
|
929
892
|
lastFeedbackLines = lines;
|
|
930
893
|
setUiState(ctx, state, lastFeedbackLines);
|
|
931
|
-
ctx
|
|
894
|
+
notifyCtx(ctx, skills.length > 0
|
|
932
895
|
? `${skills.length} post-pipeline skill${skills.length === 1 ? "" : "s"}: ${skills.join(", ")}`
|
|
933
896
|
: "No post-pipeline skills installed.", "info");
|
|
934
897
|
},
|
|
@@ -952,7 +915,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
952
915
|
document = await readGuide(args.trim() || undefined);
|
|
953
916
|
}
|
|
954
917
|
catch (error) {
|
|
955
|
-
ctx
|
|
918
|
+
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
956
919
|
return;
|
|
957
920
|
}
|
|
958
921
|
const other = topics.filter((name) => name !== document.topic);
|
|
@@ -969,7 +932,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
969
932
|
lastFeedbackLines = [`Queued the CodeCartographer guide: ${document.topic}`];
|
|
970
933
|
if (codecartoModeActive)
|
|
971
934
|
void refreshWorkspaceUi(ctx, lastFeedbackLines).catch(() => undefined);
|
|
972
|
-
ctx
|
|
935
|
+
notifyCtx(ctx, `Queued the CodeCartographer guide (${document.topic})`, "info");
|
|
973
936
|
},
|
|
974
937
|
});
|
|
975
938
|
pi.registerCommand("codecarto-broadside", {
|
|
@@ -983,12 +946,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
983
946
|
handler: async (args, ctx) => {
|
|
984
947
|
const flags = parseBroadsideFlags(args);
|
|
985
948
|
if (flags.unknown.length > 0) {
|
|
986
|
-
ctx
|
|
949
|
+
notifyCtx(ctx, `Unknown /codecarto-broadside argument: ${flags.unknown.join(" ")}. ` +
|
|
987
950
|
`Actions: submit, collect, status, models. Lenses: ${BROADSIDE_LENS_IDS.join(", ")}.`, "error");
|
|
988
951
|
return;
|
|
989
952
|
}
|
|
990
953
|
if (flags.error) {
|
|
991
|
-
ctx
|
|
954
|
+
notifyCtx(ctx, flags.error, "error");
|
|
992
955
|
return;
|
|
993
956
|
}
|
|
994
957
|
// Broad-Side runs on any git repository, with or without a workspace —
|
|
@@ -1008,7 +971,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1008
971
|
// the result can live, so it holds it instead of being cleared.
|
|
1009
972
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, ["Broad-Side", ...lines]);
|
|
1010
973
|
}
|
|
1011
|
-
ctx
|
|
974
|
+
notifyCtx(ctx, notice, level);
|
|
1012
975
|
};
|
|
1013
976
|
if (flags.action === "status") {
|
|
1014
977
|
const { state } = await runBroadsideStatus(ctx.cwd);
|
|
@@ -1018,12 +981,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1018
981
|
}
|
|
1019
982
|
const apiKey = resolveBroadsideKey(config.apiKey);
|
|
1020
983
|
if (!apiKey) {
|
|
1021
|
-
ctx
|
|
984
|
+
notifyCtx(ctx, "No OpenRouter API key. Set OPENROUTER_API_KEY in the environment, or add api_key to " +
|
|
1022
985
|
".codecarto/broadside/config.yaml. (A slash command takes no key: it would land in the transcript.)", "error");
|
|
1023
986
|
return;
|
|
1024
987
|
}
|
|
1025
988
|
if (flags.action === "models") {
|
|
1026
|
-
ctx
|
|
989
|
+
notifyCtx(ctx, "Fetching the OpenRouter batch-model catalog…", "info");
|
|
1027
990
|
try {
|
|
1028
991
|
const { entries, benchmarks } = await listBatchModels(broadsideDir, config, apiKey, {
|
|
1029
992
|
includeBenchmarks: flags.benchmarks,
|
|
@@ -1031,7 +994,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1031
994
|
finish(modelsText(entries, { benchmarks, defaultModel: config.model }).split("\n"), `Broad-Side: ${entries.length} batch model${entries.length === 1 ? "" : "s"} listed`);
|
|
1032
995
|
}
|
|
1033
996
|
catch (error) {
|
|
1034
|
-
ctx
|
|
997
|
+
notifyCtx(ctx, `Model catalog lookup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1035
998
|
}
|
|
1036
999
|
return;
|
|
1037
1000
|
}
|
|
@@ -1077,10 +1040,10 @@ export default function codeCartographerExtension(pi) {
|
|
|
1077
1040
|
if (ctx.hasUI)
|
|
1078
1041
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
1079
1042
|
if (error instanceof BroadsideCancelledError) {
|
|
1080
|
-
ctx
|
|
1043
|
+
notifyCtx(ctx, "Broad-Side cancelled. Nothing was submitted.", "info");
|
|
1081
1044
|
return;
|
|
1082
1045
|
}
|
|
1083
|
-
ctx
|
|
1046
|
+
notifyCtx(ctx, `Broad-Side submit failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1084
1047
|
return;
|
|
1085
1048
|
}
|
|
1086
1049
|
const lines = estimateSubmitText(submit, lenses.map(getLens)).split("\n");
|
|
@@ -1089,7 +1052,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1089
1052
|
finish(lines, `Broad-Side submitted: run ${submit.runId} (~$${submit.estimatedTotalCost.toFixed(4)})`);
|
|
1090
1053
|
return;
|
|
1091
1054
|
}
|
|
1092
|
-
ctx
|
|
1055
|
+
notifyCtx(ctx, `Broad-Side submitted run ${submit.runId}; polling for up to ${waitSeconds}s…`, "info");
|
|
1093
1056
|
try {
|
|
1094
1057
|
const collect = await runBroadsideCollect(ctx.cwd, apiKey, {
|
|
1095
1058
|
waitMs,
|
|
@@ -1105,7 +1068,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1105
1068
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
1106
1069
|
// The batches are submitted and paid for either way — say so, so
|
|
1107
1070
|
// nobody re-submits a run that is already in flight.
|
|
1108
|
-
ctx
|
|
1071
|
+
notifyCtx(ctx, `Broad-Side submitted run ${submit.runId}, but collect failed: ` +
|
|
1109
1072
|
`${error instanceof Error ? error.message : String(error)}. Retry with /codecarto-broadside collect.`, "error");
|
|
1110
1073
|
}
|
|
1111
1074
|
return;
|
|
@@ -1129,7 +1092,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1129
1092
|
catch (error) {
|
|
1130
1093
|
if (ctx.hasUI)
|
|
1131
1094
|
ctx.ui.setWidget(BROADSIDE_WIDGET_ID, undefined);
|
|
1132
|
-
ctx
|
|
1095
|
+
notifyCtx(ctx, `Broad-Side collect failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1133
1096
|
}
|
|
1134
1097
|
},
|
|
1135
1098
|
});
|
|
@@ -1141,22 +1104,22 @@ export default function codeCartographerExtension(pi) {
|
|
|
1141
1104
|
return;
|
|
1142
1105
|
const config = await loadCodecartoConfig(state.workspaceDir);
|
|
1143
1106
|
if (!config.library.path) {
|
|
1144
|
-
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");
|
|
1145
1108
|
return;
|
|
1146
1109
|
}
|
|
1147
1110
|
const marker = await discoverLibrary(config.library.path);
|
|
1148
1111
|
if (!marker) {
|
|
1149
|
-
ctx
|
|
1112
|
+
notifyCtx(ctx, `No CodeCartographer library at ${config.library.path} (missing .codecarto-library). Create a .codecarto-library marker file in that directory.`, "error");
|
|
1150
1113
|
return;
|
|
1151
1114
|
}
|
|
1152
1115
|
const phase = resolvePhase(state, "reimplementation-spec");
|
|
1153
1116
|
if (!phase?.primary_output) {
|
|
1154
|
-
ctx
|
|
1117
|
+
notifyCtx(ctx, "The active pipeline does not produce a reimplementation spec to publish.", "error");
|
|
1155
1118
|
return;
|
|
1156
1119
|
}
|
|
1157
1120
|
const specPath = join(state.workspaceDir, phase.primary_output);
|
|
1158
1121
|
if (!(await pathExists(specPath))) {
|
|
1159
|
-
ctx
|
|
1122
|
+
notifyCtx(ctx, `Reimplementation spec is missing: .codecarto/${phase.primary_output}`, "error");
|
|
1160
1123
|
return;
|
|
1161
1124
|
}
|
|
1162
1125
|
const spec = await readFile(specPath, "utf8");
|
|
@@ -1167,7 +1130,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1167
1130
|
const headline = derivePublishHeadline(spec, ctx.cwd);
|
|
1168
1131
|
const namespace = marker.namespaced ? config.library.namespace ?? undefined : undefined;
|
|
1169
1132
|
if (marker.namespaced && !namespace) {
|
|
1170
|
-
ctx
|
|
1133
|
+
notifyCtx(ctx, "The configured library is namespaced; set library.namespace before publishing.", "error");
|
|
1171
1134
|
return;
|
|
1172
1135
|
}
|
|
1173
1136
|
const label = `${namespace ? `${namespace}/` : ""}${slug}`;
|
|
@@ -1212,7 +1175,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1212
1175
|
// recorded the directory to one that records the git remote.
|
|
1213
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?`);
|
|
1214
1177
|
if (!moved) {
|
|
1215
|
-
ctx
|
|
1178
|
+
notifyCtx(ctx, "Publish cancelled. Nothing was written.", "info");
|
|
1216
1179
|
return;
|
|
1217
1180
|
}
|
|
1218
1181
|
options.allowSourceRepoChange = true;
|
|
@@ -1222,7 +1185,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1222
1185
|
// default; whether it may go into a wider library is the user's call.
|
|
1223
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?`);
|
|
1224
1187
|
if (!publishAnyway) {
|
|
1225
|
-
ctx
|
|
1188
|
+
notifyCtx(ctx, "Publish cancelled. Nothing was written.", "info");
|
|
1226
1189
|
return;
|
|
1227
1190
|
}
|
|
1228
1191
|
options.allowConfidentialityMismatch = true;
|
|
@@ -1235,11 +1198,11 @@ export default function codeCartographerExtension(pi) {
|
|
|
1235
1198
|
lastFeedbackLines = [`Published ${result.namespace ? `${result.namespace}/` : ""}${result.slug} v${result.version}`, result.isNewVersion ? "New content version." : "Metadata-only update (content unchanged)."];
|
|
1236
1199
|
await writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
1237
1200
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
1238
|
-
ctx
|
|
1201
|
+
notifyCtx(ctx, `Published ${result.namespace ? `${result.namespace}/` : ""}${result.slug} v${result.version}.`, "info");
|
|
1239
1202
|
}
|
|
1240
1203
|
catch (error) {
|
|
1241
1204
|
const message = error instanceof Error ? error.message : String(error);
|
|
1242
|
-
ctx
|
|
1205
|
+
notifyCtx(ctx, `Unable to publish: ${message}`, "error");
|
|
1243
1206
|
}
|
|
1244
1207
|
},
|
|
1245
1208
|
});
|
|
@@ -1251,7 +1214,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1251
1214
|
const namespaceIdx = parts.indexOf("--namespace");
|
|
1252
1215
|
const namespace = namespaceIdx >= 0 ? parts[namespaceIdx + 1] : null;
|
|
1253
1216
|
if (!pathArg) {
|
|
1254
|
-
ctx
|
|
1217
|
+
notifyCtx(ctx, "Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
|
|
1255
1218
|
return;
|
|
1256
1219
|
}
|
|
1257
1220
|
const libraryPath = pathArg.startsWith("~") ? join(homedir(), pathArg.slice(1)) : resolve(pathArg);
|
|
@@ -1267,14 +1230,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1267
1230
|
? `Library already exists at ${libraryPath} (marker preserved). Config updated.`
|
|
1268
1231
|
: `Created library at ${libraryPath} with marker "${result.marker.name}".`;
|
|
1269
1232
|
lastFeedbackLines = [msg, `Config written to ${configPath}`];
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
ctx.ui.notify(`Config written to ${configPath}`, "info");
|
|
1273
|
-
}
|
|
1233
|
+
notifyCtx(ctx, msg, "info");
|
|
1234
|
+
notifyCtx(ctx, `Config written to ${configPath}`, "info");
|
|
1274
1235
|
}
|
|
1275
1236
|
catch (error) {
|
|
1276
1237
|
const message = error instanceof Error ? error.message : String(error);
|
|
1277
|
-
ctx
|
|
1238
|
+
notifyCtx(ctx, `Library init failed: ${message}`, "error");
|
|
1278
1239
|
}
|
|
1279
1240
|
},
|
|
1280
1241
|
});
|
|
@@ -1300,7 +1261,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1300
1261
|
lastFeedbackLines = lines;
|
|
1301
1262
|
if (state)
|
|
1302
1263
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1303
|
-
ctx
|
|
1264
|
+
notifyCtx(ctx, "Configuration shown in status widget.", "info");
|
|
1304
1265
|
},
|
|
1305
1266
|
});
|
|
1306
1267
|
pi.registerCommand("codecarto-usage", {
|
|
@@ -1313,7 +1274,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1313
1274
|
if (usage.runs.length === 0) {
|
|
1314
1275
|
lastFeedbackLines = ["No phase runs recorded yet."];
|
|
1315
1276
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1316
|
-
ctx
|
|
1277
|
+
notifyCtx(ctx, "No phase runs recorded yet.", "info");
|
|
1317
1278
|
return;
|
|
1318
1279
|
}
|
|
1319
1280
|
const totals = computeTotals(usage);
|
|
@@ -1333,7 +1294,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1333
1294
|
}
|
|
1334
1295
|
lastFeedbackLines = lines;
|
|
1335
1296
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1336
|
-
ctx
|
|
1297
|
+
notifyCtx(ctx, `CodeCartographer usage: ${totals.runs} run${totals.runs === 1 ? "" : "s"}, ${formatUsageTokens(totals.tokens.input + totals.tokens.output)} tokens total`, "info");
|
|
1337
1298
|
},
|
|
1338
1299
|
});
|
|
1339
1300
|
pi.registerCommand("codecarto-dashboard", {
|
|
@@ -1347,26 +1308,26 @@ export default function codeCartographerExtension(pi) {
|
|
|
1347
1308
|
handler: async (args, ctx) => {
|
|
1348
1309
|
const flags = parseDashboardFlags(args);
|
|
1349
1310
|
if (flags.unknown.length > 0) {
|
|
1350
|
-
ctx
|
|
1311
|
+
notifyCtx(ctx, `Unknown /codecarto-dashboard flag: ${flags.unknown.join(" ")}`, "error");
|
|
1351
1312
|
return;
|
|
1352
1313
|
}
|
|
1353
1314
|
const state = await ensureWorkspaceState(ctx);
|
|
1354
1315
|
if (!state)
|
|
1355
1316
|
return;
|
|
1356
1317
|
if (flags.narrate) {
|
|
1357
|
-
ctx
|
|
1318
|
+
notifyCtx(ctx, `Narrating dashboard via LLM…`, "info");
|
|
1358
1319
|
const result = await narrateDashboard(ctx, state);
|
|
1359
1320
|
if (result.used) {
|
|
1360
|
-
ctx
|
|
1321
|
+
notifyCtx(ctx, "Narration written to .codecarto/.dashboard-narration.local.md", "info");
|
|
1361
1322
|
}
|
|
1362
1323
|
else {
|
|
1363
|
-
ctx
|
|
1324
|
+
notifyCtx(ctx, `LLM narration skipped (${result.skipReason}); rendering deterministic dashboard.`, "warning");
|
|
1364
1325
|
}
|
|
1365
1326
|
}
|
|
1366
1327
|
await writeDashboard(ctx.cwd, PACKAGE_VERSION);
|
|
1367
1328
|
lastFeedbackLines = ["Dashboard regenerated: .codecarto/dashboard.html"];
|
|
1368
1329
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1369
|
-
ctx
|
|
1330
|
+
notifyCtx(ctx, "Dashboard regenerated: .codecarto/dashboard.html", "info");
|
|
1370
1331
|
},
|
|
1371
1332
|
});
|
|
1372
1333
|
pi.registerCommand("codecarto-refresh-scaffold", {
|
|
@@ -1382,12 +1343,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1382
1343
|
files = await listScaffoldRefreshFiles();
|
|
1383
1344
|
}
|
|
1384
1345
|
catch (error) {
|
|
1385
|
-
ctx
|
|
1346
|
+
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
1386
1347
|
return;
|
|
1387
1348
|
}
|
|
1388
1349
|
const approved = await ctx.ui.confirm("Refresh the .codecarto/ scaffold from the packaged template?", describeScaffoldRefreshPreview(files, state.scaffoldVersion));
|
|
1389
1350
|
if (!approved) {
|
|
1390
|
-
ctx
|
|
1351
|
+
notifyCtx(ctx, "Scaffold refresh cancelled. Nothing was written.", "info");
|
|
1391
1352
|
return;
|
|
1392
1353
|
}
|
|
1393
1354
|
try {
|
|
@@ -1400,13 +1361,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
1400
1361
|
];
|
|
1401
1362
|
// Re-read state so the widget's staleness line clears with the marker.
|
|
1402
1363
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
1403
|
-
ctx
|
|
1364
|
+
notifyCtx(ctx, `Refreshed ${result.written.length} framework-owned file(s) (${transition}).`, "info");
|
|
1404
1365
|
}
|
|
1405
1366
|
catch (error) {
|
|
1406
1367
|
const message = error instanceof Error ? error.message : String(error);
|
|
1407
1368
|
lastFeedbackLines = [message];
|
|
1408
1369
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1409
|
-
ctx
|
|
1370
|
+
notifyCtx(ctx, `Scaffold refresh failed: ${message}`, "error");
|
|
1410
1371
|
}
|
|
1411
1372
|
},
|
|
1412
1373
|
});
|
|
@@ -1428,12 +1389,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1428
1389
|
const hint = staged.length > 0
|
|
1429
1390
|
? ` (staged: ${staged.join(", ")})`
|
|
1430
1391
|
: " — write .codecarto/scratch/amendments/<name>.yaml first (see templates/amendment.yaml)";
|
|
1431
|
-
ctx
|
|
1392
|
+
notifyCtx(ctx, `Usage: /codecarto-amend <name>${hint}`, "warning");
|
|
1432
1393
|
return;
|
|
1433
1394
|
}
|
|
1434
1395
|
const name = resolveAmendmentName(args, ctx.cwd);
|
|
1435
1396
|
if (!name) {
|
|
1436
|
-
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");
|
|
1437
1398
|
return;
|
|
1438
1399
|
}
|
|
1439
1400
|
// The same refusals codecarto_amend surfaces, raised before the
|
|
@@ -1443,12 +1404,12 @@ export default function codeCartographerExtension(pi) {
|
|
|
1443
1404
|
amendment = await loadAmendmentFile(name, state.workspaceDir);
|
|
1444
1405
|
}
|
|
1445
1406
|
catch (error) {
|
|
1446
|
-
ctx
|
|
1407
|
+
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
1447
1408
|
return;
|
|
1448
1409
|
}
|
|
1449
1410
|
const nextPhase = getNextEligiblePhase(state);
|
|
1450
1411
|
if (nextPhase) {
|
|
1451
|
-
ctx
|
|
1412
|
+
notifyCtx(ctx, `Cannot amend: the pipeline is not complete (next phase: ${nextPhase.id}). `
|
|
1452
1413
|
+ "Resolve open questions and routed items through that phase's handoff (open_question_closures / carry_forward_closures) instead.", "error");
|
|
1453
1414
|
return;
|
|
1454
1415
|
}
|
|
@@ -1456,7 +1417,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1456
1417
|
// before anything is written — MCP's codecarto_amend applies on call.
|
|
1457
1418
|
const approved = await ctx.ui.confirm(`Apply amendment "${amendment.slug}"?`, describeAmendmentPreview(amendment, state));
|
|
1458
1419
|
if (!approved) {
|
|
1459
|
-
ctx
|
|
1420
|
+
notifyCtx(ctx, `Amendment ${amendment.slug} cancelled. Nothing was written.`, "info");
|
|
1460
1421
|
return;
|
|
1461
1422
|
}
|
|
1462
1423
|
try {
|
|
@@ -1477,14 +1438,14 @@ export default function codeCartographerExtension(pi) {
|
|
|
1477
1438
|
lastFeedbackLines = lines;
|
|
1478
1439
|
await refreshWorkspaceUi(ctx, lastFeedbackLines);
|
|
1479
1440
|
const closed = applied.openQuestionsClosed.length + applied.postPipelineClosed.length;
|
|
1480
|
-
ctx
|
|
1441
|
+
notifyCtx(ctx, `Amendment ${amendment.slug} applied: ${applied.openQuestionsClosed.length} open question(s) and ${applied.postPipelineClosed.length} post-pipeline item(s) closed`
|
|
1481
1442
|
+ `${applied.unknownIds.length > 0 ? `; ${applied.unknownIds.length} id(s) matched nothing` : ""}.`, closed === 0 || applied.unknownIds.length > 0 ? "warning" : "info");
|
|
1482
1443
|
}
|
|
1483
1444
|
catch (error) {
|
|
1484
1445
|
const message = error instanceof Error ? error.message : String(error);
|
|
1485
1446
|
lastFeedbackLines = [message];
|
|
1486
1447
|
setUiState(ctx, state, lastFeedbackLines);
|
|
1487
|
-
ctx
|
|
1448
|
+
notifyCtx(ctx, `Amendment failed: ${message}`, "error");
|
|
1488
1449
|
}
|
|
1489
1450
|
},
|
|
1490
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",
|