codecartographer-pi 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codecarto/GUIDE.md +1 -1
- package/.codecarto/broadside/SKILL.md +7 -3
- package/.codecarto/broadside/config.yaml +17 -9
- package/.codecarto/findings/contracts/SKILL.md +4 -1
- package/.codecarto/findings/defect-scan/SKILL.md +10 -0
- package/.codecarto/findings/defect-scan-mechanical/SKILL.md +6 -0
- package/.codecarto/findings/defect-scan-semantic/SKILL.md +8 -1
- package/.codecarto/findings/porting/SKILL.md +4 -0
- package/.codecarto/findings/protocols/SKILL.md +4 -0
- package/.codecarto/templates/mechanical-defects.md +15 -0
- package/.codecarto/templates/reimplementation-spec.md +5 -3
- package/.codecarto/templates/reverse-engineering-bundle.md +10 -1
- package/.codecarto/templates/semantic-defects.md +15 -0
- package/.codecarto/workflow/VALIDATE.md +1 -1
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +3 -3
- package/dist/core/amendment.js +9 -4
- package/dist/core/broadside.d.ts +49 -0
- package/dist/core/broadside.js +131 -30
- package/dist/core/completion.js +7 -1
- package/dist/core/library.js +5 -3
- package/dist/core/pipeline.d.ts +39 -3
- package/dist/core/pipeline.js +61 -7
- package/dist/core/prompts.js +10 -3
- package/dist/core/status.d.ts +8 -0
- package/dist/core/status.js +39 -17
- package/dist/core/utils.d.ts +7 -0
- package/dist/core/utils.js +7 -0
- package/dist/core/workspace.js +2 -2
- package/dist/core/yaml.js +8 -1
- package/dist/extensions/codecarto/auto-runner.d.ts +1 -1
- package/dist/extensions/codecarto/auto-runner.js +18 -3
- package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
- package/dist/extensions/codecarto/broadside-flags.js +12 -0
- package/dist/extensions/codecarto/index.js +95 -31
- package/dist/extensions/codecarto/phase-compaction.js +5 -1
- package/dist/mcp-server/server.d.ts +3 -1
- package/dist/mcp-server/server.js +99 -29
- package/package.json +1 -1
package/dist/core/utils.d.ts
CHANGED
|
@@ -77,3 +77,10 @@ export declare function formatMillis(ms: number): string;
|
|
|
77
77
|
* hand-edited markers) so callers can fall back to string equality.
|
|
78
78
|
*/
|
|
79
79
|
export declare function compareDottedVersions(a: string, b: string): number | null;
|
|
80
|
+
/**
|
|
81
|
+
* Every git subprocess gets this. A hung git — a credential helper waiting
|
|
82
|
+
* on a prompt, a slow filesystem — used to hang a publish or a Broad-Side
|
|
83
|
+
* submit indefinitely while every fetch carried a 30 s timeout (self-audit
|
|
84
|
+
* sem 3.12).
|
|
85
|
+
*/
|
|
86
|
+
export declare const GIT_TIMEOUT_MS = 30000;
|
package/dist/core/utils.js
CHANGED
|
@@ -206,3 +206,10 @@ export function compareDottedVersions(a, b) {
|
|
|
206
206
|
}
|
|
207
207
|
return 0;
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Every git subprocess gets this. A hung git — a credential helper waiting
|
|
211
|
+
* on a prompt, a slow filesystem — used to hang a publish or a Broad-Side
|
|
212
|
+
* submit indefinitely while every fetch carried a 30 s timeout (self-audit
|
|
213
|
+
* sem 3.12).
|
|
214
|
+
*/
|
|
215
|
+
export const GIT_TIMEOUT_MS = 30_000;
|
package/dist/core/workspace.js
CHANGED
|
@@ -7,7 +7,7 @@ import { appendFile, copyFile, cp, mkdir, readFile, readdir, rename } from "node
|
|
|
7
7
|
import { basename, dirname, join, relative } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { getPipelineLabel, recomputeCursor } from "./pipeline.js";
|
|
10
|
-
import { acquireLock, applyHandoff, autoAssignIds, createEmptyStatus, normalizeStatus, parseHandoff } from "./status.js";
|
|
10
|
+
import { acquireLock, applyHandoff, autoAssignIds, createEmptyStatus, normalizeStatus, parseHandoff, textOf } from "./status.js";
|
|
11
11
|
import { atomicWriteFile, compareDottedVersions, newlineIfUnterminated, pathExists } from "./utils.js";
|
|
12
12
|
import { loadYamlFile, stringifySimpleYaml } from "./yaml.js";
|
|
13
13
|
// Walk up from the current file to find the package root. Needed because the
|
|
@@ -68,7 +68,7 @@ export async function getWorkspaceState(cwd) {
|
|
|
68
68
|
if (!(await pathExists(statusPath)))
|
|
69
69
|
return null;
|
|
70
70
|
const rawStatus = await loadYamlFile(statusPath);
|
|
71
|
-
const pipelineRelativePath = rawStatus.pipeline
|
|
71
|
+
const pipelineRelativePath = (textOf(rawStatus.pipeline) ?? "").trim();
|
|
72
72
|
if (!pipelineRelativePath) {
|
|
73
73
|
throw new Error(`Missing pipeline in ${relative(cwd, statusPath) || statusPath}`);
|
|
74
74
|
}
|
package/dist/core/yaml.js
CHANGED
|
@@ -497,8 +497,15 @@ export function formatYamlScalar(value) {
|
|
|
497
497
|
const stringValue = String(value);
|
|
498
498
|
if (stringValue === "")
|
|
499
499
|
return '""';
|
|
500
|
-
|
|
500
|
+
// Bare only when the reader would hand the same string back. The charset
|
|
501
|
+
// keeps out spaces, quotes, `#` and `:`; the round-trip check keeps out the
|
|
502
|
+
// strings the reader coerces — "2048", "true", "null", "1.5" — which used
|
|
503
|
+
// to go out bare and come back as a number, a boolean, or nothing, until a
|
|
504
|
+
// repository named `2048` bricked its workspace on `project_name.trim`
|
|
505
|
+
// (#225). A lone `-` is a sequence marker inside a list, so it is quoted too.
|
|
506
|
+
if (/^[A-Za-z0-9_./-]+$/.test(stringValue) && stringValue !== "-" && parseYamlScalar(stringValue) === stringValue) {
|
|
501
507
|
return stringValue;
|
|
508
|
+
}
|
|
502
509
|
return JSON.stringify(stringValue);
|
|
503
510
|
}
|
|
504
511
|
export function stringifySimpleYaml(value, indent = 0) {
|
|
@@ -45,7 +45,7 @@ export interface AutoCompleteResult {
|
|
|
45
45
|
warnings: string[];
|
|
46
46
|
}
|
|
47
47
|
export declare function autoCompletePhase(cwd: string, validation: ValidationResult): Promise<AutoCompleteResult>;
|
|
48
|
-
export type AutoOutcome = "complete" | "stopped" | "aborted";
|
|
48
|
+
export type AutoOutcome = "complete" | "stopped" | "aborted" | "stuck";
|
|
49
49
|
export interface AutoRunOptions {
|
|
50
50
|
strict: boolean;
|
|
51
51
|
llmSteerOverride?: boolean;
|
|
@@ -17,7 +17,7 @@ import { clearPhase, finishPhase, getPhaseActivity, startPhase } from "./agent-s
|
|
|
17
17
|
import { buildSteeringMessage, rewritePhasePrompt } from "./agent-rewriter.js";
|
|
18
18
|
import { buildPhaseSummary } from "./agent-summary.js";
|
|
19
19
|
import { getAgentsWidget } from "./agent-widget.js";
|
|
20
|
-
import { appendUsageRun, buildPhasePrompt, buildValidationSummary, completeValidatedPhase, formatMillis, formatTokenCount,
|
|
20
|
+
import { appendUsageRun, buildPhasePrompt, buildValidationSummary, completeValidatedPhase, formatMillis, formatTokenCount, describeStuckPipeline, resolvePipelineOutcome, getWorkspaceState, describeConfigProblems, loadCodecartoConfig, PACKAGE_VERSION, PhasePreflightError, runPhasePreflight, validatePhaseOutput, writeDashboard, } from "../../core/index.js";
|
|
21
21
|
/**
|
|
22
22
|
* Run one phase end to end: optional LLM-steered rewrite, spawn the sub-agent,
|
|
23
23
|
* wait for it, then emit the side effects the historical /codecarto-next chain
|
|
@@ -203,13 +203,22 @@ export async function runAuto(ctx, pi, initialState, options) {
|
|
|
203
203
|
reason: "User aborted the auto run.",
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
206
|
+
const outcome = resolvePipelineOutcome(state);
|
|
207
|
+
if (outcome.kind === "stuck") {
|
|
208
|
+
// Not "complete": the loop ending because nothing can run is the
|
|
209
|
+
// case that used to read as success (#228).
|
|
210
|
+
return finish({
|
|
211
|
+
outcome: "stuck",
|
|
212
|
+
reason: describeStuckPipeline(outcome.blocked),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (outcome.kind === "complete") {
|
|
208
216
|
return finish({
|
|
209
217
|
outcome: "complete",
|
|
210
218
|
reason: "Pipeline complete.",
|
|
211
219
|
});
|
|
212
220
|
}
|
|
221
|
+
const phase = outcome.phase;
|
|
213
222
|
let preflight;
|
|
214
223
|
try {
|
|
215
224
|
preflight = await runPhasePreflight(state, phase);
|
|
@@ -308,6 +317,8 @@ export function buildAutoSummary(result, availableSkills = []) {
|
|
|
308
317
|
return `**Auto pipeline stopped at \`${result.stoppedAt?.phaseId ?? "?"}\`.**`;
|
|
309
318
|
case "aborted":
|
|
310
319
|
return `**Auto pipeline aborted${result.stoppedAt?.phaseId ? ` during \`${result.stoppedAt.phaseId}\`` : ""}.**`;
|
|
320
|
+
case "stuck":
|
|
321
|
+
return `**Auto pipeline stuck: no phase can run.**`;
|
|
311
322
|
}
|
|
312
323
|
})();
|
|
313
324
|
const statsLine = `_⟳ ${ranOf} · ${tokensStr} tokens · ${wallTime}_`;
|
|
@@ -321,6 +332,10 @@ export function buildAutoSummary(result, availableSkills = []) {
|
|
|
321
332
|
lines.push("", result.reason);
|
|
322
333
|
lines.push("", recoveryHint(result));
|
|
323
334
|
}
|
|
335
|
+
if (result.outcome === "stuck") {
|
|
336
|
+
// The reason is the stuck sentence, which already says what to fix.
|
|
337
|
+
lines.push("", result.reason);
|
|
338
|
+
}
|
|
324
339
|
if (result.outcome === "complete") {
|
|
325
340
|
lines.push("", "Dashboard: `.codecarto/dashboard.html`");
|
|
326
341
|
if (availableSkills.length > 0) {
|
|
@@ -16,11 +16,13 @@ export interface BroadsideFlags {
|
|
|
16
16
|
/** Undefined means "use the repository's config default". */
|
|
17
17
|
maxCost?: number;
|
|
18
18
|
waitSeconds?: number;
|
|
19
|
+
/** For collect: the run to collect instead of the most recent (#268). */
|
|
20
|
+
runId?: string;
|
|
19
21
|
benchmarks: boolean;
|
|
20
22
|
unknown: string[];
|
|
21
23
|
/** Set on an invalid combination. The caller surfaces it as an error. */
|
|
22
24
|
error?: string;
|
|
23
25
|
}
|
|
24
26
|
/** Every token the completer offers, in the order it offers them. */
|
|
25
|
-
export declare const KNOWN_BROADSIDE_TOKENS: readonly ["submit", "collect", "status", "models", "architecture", "api", "security", "defect", "conventions", "porting", "--incremental", "--no-incremental", "--max-cost=", "--wait=", "--no-synthesis", "--no-triage", "--no-retry-truncated", "--benchmarks"];
|
|
27
|
+
export declare const KNOWN_BROADSIDE_TOKENS: readonly ["submit", "collect", "status", "models", "architecture", "api", "security", "defect", "conventions", "porting", "--incremental", "--no-incremental", "--max-cost=", "--wait=", "--run=", "--no-synthesis", "--no-triage", "--no-retry-truncated", "--benchmarks"];
|
|
26
28
|
export declare function parseBroadsideFlags(args: string): BroadsideFlags;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// --no-incremental --no-triage
|
|
14
14
|
// --max-cost=N --no-retry-truncated
|
|
15
15
|
// --wait=SECONDS --benchmarks (models only)
|
|
16
|
+
// --run=ID (collect only: an older run, as listed by status)
|
|
16
17
|
//
|
|
17
18
|
// --incremental has a spelled-out negative because the value is tri-state:
|
|
18
19
|
// absent defers to config.yaml, so a repository that set `incremental: true`
|
|
@@ -34,6 +35,7 @@ export const KNOWN_BROADSIDE_TOKENS = [
|
|
|
34
35
|
"--no-incremental",
|
|
35
36
|
"--max-cost=",
|
|
36
37
|
"--wait=",
|
|
38
|
+
"--run=",
|
|
37
39
|
"--no-synthesis",
|
|
38
40
|
"--no-triage",
|
|
39
41
|
"--no-retry-truncated",
|
|
@@ -106,6 +108,13 @@ export function parseBroadsideFlags(args) {
|
|
|
106
108
|
result.waitSeconds = parseNumeric(token, "--wait", result);
|
|
107
109
|
continue;
|
|
108
110
|
}
|
|
111
|
+
if (token.startsWith("--run=")) {
|
|
112
|
+
const value = token.slice("--run=".length).trim();
|
|
113
|
+
if (!value)
|
|
114
|
+
result.error ??= "--run= needs a run id (see /codecarto-broadside status).";
|
|
115
|
+
result.runId = value || undefined;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
109
118
|
result.unknown.push(token);
|
|
110
119
|
}
|
|
111
120
|
// Flags that only mean something for one action are refused rather than
|
|
@@ -125,5 +134,8 @@ export function parseBroadsideFlags(args) {
|
|
|
125
134
|
if (result.action === "status" && result.waitSeconds !== undefined) {
|
|
126
135
|
result.error ??= "--wait is only meaningful for submit and collect; status reads recorded state.";
|
|
127
136
|
}
|
|
137
|
+
if (result.runId !== undefined && result.action !== "collect") {
|
|
138
|
+
result.error ??= `--run is only meaningful for collect (got action "${result.action}").`;
|
|
139
|
+
}
|
|
128
140
|
return result;
|
|
129
141
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
3
|
import { autoCompletePhase, buildAutoSummary, isPhaseRunning, runAuto, runSinglePhase } from "./auto-runner.js";
|
|
5
4
|
import { disposeAgentsWidget } from "./agent-widget.js";
|
|
@@ -10,7 +9,7 @@ import { parseNextFlags } from "./next-flags.js";
|
|
|
10
9
|
import { buildPiGuideMessage } from "./guide-framing.js";
|
|
11
10
|
import { isCtxLive, notifyCtx } from "./notify.js";
|
|
12
11
|
import { phaseCompactionExtension } from "./phase-compaction.js";
|
|
13
|
-
import { applyAmendment, buildPhasePrompt, buildSkillPrompt, buildValidationSummary, backupWorkspaceState, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, describeDanglingCarryForward, describeMissingCompletedOutputs,
|
|
12
|
+
import { applyAmendment, buildPhasePrompt, buildSkillPrompt, buildValidationSummary, backupWorkspaceState, canonicalPath, copyPackagedWorkspace, computePerPhaseTotals, computeTotals, ConfidentialityMismatchError, createEmptyStatus, DEFAULT_PIPELINE_PATH, describeScaffoldStaleness, deriveSlug, discoverLibrary, describeDanglingCarryForward, describeMissingCompletedOutputs, describeStuckPipeline, expandTilde, getPipelineLabel, getWorkspaceState, isWithinPath, resolveExistingPrefix, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, BroadsideConfigError, defaultBroadsideConfig, BroadsideCancelledError, broadsideDirFor, collectResultText, estimateSubmitText, getLens, listAmendmentNames, listBatchModels, listGuideTopics, listScaffoldRefreshFiles, listMissingCompletedOutputs, listSkillNames, resolvePipelineOutcome, resolveSkillName, loadAmendmentFile, loadBroadsideConfig, modelsText, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, statusText, describeConfigProblems, 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, writeDashboard, } from "../../core/index.js";
|
|
14
13
|
import { initLibrary } from "../../core/library.js";
|
|
15
14
|
import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
|
|
16
15
|
const STATUS_WIDGET_ID = "codecarto-widget";
|
|
@@ -56,9 +55,23 @@ function formatUsageDuration(ms) {
|
|
|
56
55
|
const seconds = Math.floor((ms % 60_000) / 1000);
|
|
57
56
|
return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
|
|
58
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The phase the status line and session name show: the eligible phase, the
|
|
60
|
+
* first blocked phase when the pipeline is stuck, or "complete" — never
|
|
61
|
+
* "complete" for a pipeline that cannot finish (#228).
|
|
62
|
+
*/
|
|
63
|
+
function currentPhaseLabel(state) {
|
|
64
|
+
const outcome = resolvePipelineOutcome(state);
|
|
65
|
+
if (outcome.kind === "eligible")
|
|
66
|
+
return outcome.phase.id;
|
|
67
|
+
if (outcome.kind === "stuck")
|
|
68
|
+
return outcome.blocked[0]?.phaseId ?? "stuck";
|
|
69
|
+
return "complete";
|
|
70
|
+
}
|
|
59
71
|
function buildStatusLines(state, extraLines = []) {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
72
|
+
const outcome = resolvePipelineOutcome(state);
|
|
73
|
+
const nextPhase = outcome.kind === "eligible" ? outcome.phase : null;
|
|
74
|
+
const currentPhase = currentPhaseLabel(state);
|
|
62
75
|
const pipelineLabel = getPipelineLabel(state.status.pipeline);
|
|
63
76
|
const completedCount = state.pipeline.phase_order.filter((phaseId) => state.status.phases[phaseId]?.status === "complete").length;
|
|
64
77
|
const terminalOpenQuestions = Object.values(state.status.phases).reduce((sum, phase) => sum + (phase.open_questions?.length ?? 0), 0);
|
|
@@ -68,7 +81,7 @@ function buildStatusLines(state, extraLines = []) {
|
|
|
68
81
|
const lines = [
|
|
69
82
|
"CodeCartographer",
|
|
70
83
|
`Phase: ${currentPhase}`,
|
|
71
|
-
`Pipeline state: ${
|
|
84
|
+
`Pipeline state: ${outcome.kind === "eligible" ? "in progress" : outcome.kind}`,
|
|
72
85
|
`Pipeline: ${pipelineLabel}`,
|
|
73
86
|
`Progress: ${completedCount}/${state.pipeline.phase_order.length} complete`,
|
|
74
87
|
`Open questions (terminal unresolved): ${terminalOpenQuestions}`,
|
|
@@ -76,6 +89,8 @@ function buildStatusLines(state, extraLines = []) {
|
|
|
76
89
|
`Post-pipeline work: ${postPipelinePending} pending`,
|
|
77
90
|
`Next: ${nextAction}`,
|
|
78
91
|
];
|
|
92
|
+
if (outcome.kind === "stuck")
|
|
93
|
+
lines.push(describeStuckPipeline(outcome.blocked));
|
|
79
94
|
const scaffoldNotice = describeScaffoldStaleness(state);
|
|
80
95
|
if (scaffoldNotice)
|
|
81
96
|
lines.push(`Scaffold: ${scaffoldNotice}`);
|
|
@@ -93,7 +108,7 @@ function setUiState(ctx, state, extraLines = []) {
|
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
95
110
|
const theme = ctx.ui.theme;
|
|
96
|
-
const currentPhase =
|
|
111
|
+
const currentPhase = currentPhaseLabel(state);
|
|
97
112
|
ctx.ui.setStatus(STATUS_LINE_ID, `${theme.fg("accent", "CC")} ${theme.fg("dim", currentPhase)}`);
|
|
98
113
|
ctx.ui.setWidget(STATUS_WIDGET_ID, buildStatusLines(state, extraLines));
|
|
99
114
|
}
|
|
@@ -291,7 +306,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
291
306
|
const state = await readWorkspaceState(ctx, false);
|
|
292
307
|
setUiState(ctx, state, extraLines ?? lastFeedbackLines);
|
|
293
308
|
if (state) {
|
|
294
|
-
const phaseId =
|
|
309
|
+
const phaseId = currentPhaseLabel(state);
|
|
295
310
|
if (phaseId)
|
|
296
311
|
pi.setSessionName(`CodeCartographer: ${phaseId}`);
|
|
297
312
|
}
|
|
@@ -498,13 +513,17 @@ export default function codeCartographerExtension(pi) {
|
|
|
498
513
|
const state = await ensureWorkspaceState(ctx);
|
|
499
514
|
if (!state)
|
|
500
515
|
return;
|
|
501
|
-
const
|
|
516
|
+
const outcome = resolvePipelineOutcome(state);
|
|
517
|
+
const nextPhase = currentPhaseLabel(state);
|
|
502
518
|
// Same check codecarto_status makes: a phase complete in status.yaml
|
|
503
519
|
// whose report is not on disk (#259).
|
|
504
520
|
const missingOutputs = describeMissingCompletedOutputs(await listMissingCompletedOutputs(state));
|
|
505
521
|
lastFeedbackLines = [`Current phase: ${nextPhase}`, `Pipeline: ${getPipelineLabel(state.status.pipeline)}`, ...missingOutputs];
|
|
506
522
|
setUiState(ctx, state, lastFeedbackLines);
|
|
507
|
-
if (
|
|
523
|
+
if (outcome.kind === "stuck") {
|
|
524
|
+
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}. ${describeStuckPipeline(outcome.blocked)}`, "error");
|
|
525
|
+
}
|
|
526
|
+
else if (missingOutputs.length > 0) {
|
|
508
527
|
notifyCtx(ctx, `CodeCartographer phase: ${nextPhase}. ${missingOutputs[0]}`, "warning");
|
|
509
528
|
}
|
|
510
529
|
else {
|
|
@@ -608,7 +627,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
608
627
|
// the readout tracks progress live instead of staying frozen
|
|
609
628
|
// at the initial phase until the whole auto run finishes.
|
|
610
629
|
setUiState(ctx, advancedState, [`Auto pipeline${flags.strict ? " (strict)" : ""} running…`]);
|
|
611
|
-
const phaseId =
|
|
630
|
+
const phaseId = currentPhaseLabel(advancedState);
|
|
612
631
|
if (phaseId)
|
|
613
632
|
pi.setSessionName(`CodeCartographer: ${phaseId}`);
|
|
614
633
|
},
|
|
@@ -624,13 +643,21 @@ export default function codeCartographerExtension(pi) {
|
|
|
624
643
|
notifyCtx(ctx, `Auto pipeline ${result.outcome}: ${result.phasesRun.length}/${result.totalPhases} phases.`, result.outcome === "complete" ? "info" : "warning");
|
|
625
644
|
return;
|
|
626
645
|
}
|
|
627
|
-
const
|
|
628
|
-
if (
|
|
646
|
+
const outcome = resolvePipelineOutcome(state);
|
|
647
|
+
if (outcome.kind === "stuck") {
|
|
648
|
+
const message = describeStuckPipeline(outcome.blocked);
|
|
649
|
+
lastFeedbackLines = [message];
|
|
650
|
+
setUiState(ctx, state, lastFeedbackLines);
|
|
651
|
+
notifyCtx(ctx, message, "error");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (outcome.kind === "complete") {
|
|
629
655
|
lastFeedbackLines = ["All phases complete."];
|
|
630
656
|
setUiState(ctx, state, lastFeedbackLines);
|
|
631
657
|
notifyCtx(ctx, "All CodeCartographer phases are complete.", "info");
|
|
632
658
|
return;
|
|
633
659
|
}
|
|
660
|
+
const phase = outcome.phase;
|
|
634
661
|
let preflight;
|
|
635
662
|
try {
|
|
636
663
|
preflight = await runPhasePreflight(state, phase);
|
|
@@ -868,9 +895,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
868
895
|
const state = await ensureWorkspaceState(ctx);
|
|
869
896
|
if (!state)
|
|
870
897
|
return;
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
873
|
-
notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${
|
|
898
|
+
const outcome = resolvePipelineOutcome(state);
|
|
899
|
+
if (outcome.kind === "eligible") {
|
|
900
|
+
notifyCtx(ctx, `Cannot run skill: pipeline is not complete (next phase: ${outcome.phase.id}). Finish the pipeline before running post-pipeline skills.`, "error");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
if (outcome.kind === "stuck") {
|
|
904
|
+
notifyCtx(ctx, `Cannot run skill: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`, "error");
|
|
874
905
|
return;
|
|
875
906
|
}
|
|
876
907
|
// Resolve against the installed list only: the name is never joined
|
|
@@ -908,11 +939,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
908
939
|
const lines = skills.length > 0
|
|
909
940
|
? [`Available skills (${skills.length}):`, ...skills.map((name) => ` - ${name}`)]
|
|
910
941
|
: ["No skills installed."];
|
|
911
|
-
const
|
|
942
|
+
const outcome = resolvePipelineOutcome(state);
|
|
912
943
|
if (skills.length > 0) {
|
|
913
|
-
lines.push(
|
|
914
|
-
? `Post-pipeline skills unlock when the pipeline completes (next phase: ${
|
|
915
|
-
:
|
|
944
|
+
lines.push(outcome.kind === "eligible"
|
|
945
|
+
? `Post-pipeline skills unlock when the pipeline completes (next phase: ${outcome.phase.id}).`
|
|
946
|
+
: outcome.kind === "stuck"
|
|
947
|
+
? `Post-pipeline skills unlock when the pipeline completes, and it cannot: ${describeStuckPipeline(outcome.blocked)}`
|
|
948
|
+
: "Run one with /codecarto-skill <name>.");
|
|
916
949
|
}
|
|
917
950
|
// Broad-Side is listed apart from the post-pipeline set because it
|
|
918
951
|
// answers to /codecarto-skill without the completion gate.
|
|
@@ -988,7 +1021,23 @@ export default function codeCartographerExtension(pi) {
|
|
|
988
1021
|
// Broad-Side runs on any git repository, with or without a workspace —
|
|
989
1022
|
// so this command never goes through ensureWorkspaceState.
|
|
990
1023
|
const broadsideDir = broadsideDirFor(ctx.cwd);
|
|
991
|
-
|
|
1024
|
+
// A config.yaml that exists but cannot be read refuses every action
|
|
1025
|
+
// that would act on it (#232); status only reads recorded runs, so it
|
|
1026
|
+
// answers and says the file is unreadable.
|
|
1027
|
+
let config;
|
|
1028
|
+
let configWarning = null;
|
|
1029
|
+
try {
|
|
1030
|
+
config = await loadBroadsideConfig(broadsideDir);
|
|
1031
|
+
}
|
|
1032
|
+
catch (error) {
|
|
1033
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1034
|
+
if (!(error instanceof BroadsideConfigError) || flags.action !== "status") {
|
|
1035
|
+
notifyCtx(ctx, `Broad-Side ${flags.action} refused: ${message}`, "error");
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
config = defaultBroadsideConfig();
|
|
1039
|
+
configWarning = message;
|
|
1040
|
+
}
|
|
992
1041
|
const finish = (lines, notice, level = "info") => {
|
|
993
1042
|
lastFeedbackLines = lines;
|
|
994
1043
|
if (codecartoModeActive) {
|
|
@@ -1005,9 +1054,18 @@ export default function codeCartographerExtension(pi) {
|
|
|
1005
1054
|
notifyCtx(ctx, notice, level);
|
|
1006
1055
|
};
|
|
1007
1056
|
if (flags.action === "status") {
|
|
1008
|
-
|
|
1057
|
+
let state;
|
|
1058
|
+
try {
|
|
1059
|
+
({ state } = await runBroadsideStatus(ctx.cwd));
|
|
1060
|
+
}
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
// A corrupt state.json: nothing trustworthy to report, and the
|
|
1063
|
+
// error names the preserved copy (#233).
|
|
1064
|
+
notifyCtx(ctx, `Broad-Side status failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1009
1067
|
const runs = state.runs.length;
|
|
1010
|
-
finish(statusText(state).split("\n"), runs > 0 ? `Broad-Side: ${runs} recorded run${runs === 1 ? "" : "s"}` : "Broad-Side: no runs recorded yet");
|
|
1068
|
+
finish([...statusText(state).split("\n"), ...(configWarning ? [`Warning: ${configWarning}`] : [])], runs > 0 ? `Broad-Side: ${runs} recorded run${runs === 1 ? "" : "s"}` : "Broad-Side: no runs recorded yet", configWarning ? "warning" : "info");
|
|
1011
1069
|
return;
|
|
1012
1070
|
}
|
|
1013
1071
|
const apiKey = resolveBroadsideKey(config.apiKey);
|
|
@@ -1045,8 +1103,10 @@ export default function codeCartographerExtension(pi) {
|
|
|
1045
1103
|
progress.set(lensId, `${status} (${counts.completed ?? 0}/${counts.total ?? "?"})`);
|
|
1046
1104
|
renderProgress("Polling batches…");
|
|
1047
1105
|
};
|
|
1106
|
+
// An explicit --wait=0 polls once and returns; it used to become
|
|
1107
|
+
// undefined and, in core, the 25-minute budget (#230).
|
|
1048
1108
|
const waitSeconds = flags.waitSeconds ?? config.waitSeconds;
|
|
1049
|
-
const waitMs = waitSeconds
|
|
1109
|
+
const waitMs = waitSeconds * 1000;
|
|
1050
1110
|
const includeSynthesis = flags.includeSynthesis ?? config.includeSynthesis;
|
|
1051
1111
|
const includeTriage = flags.includeTriage ?? config.includeTriage;
|
|
1052
1112
|
const retryTruncated = flags.retryTruncated ?? config.retryTruncated;
|
|
@@ -1113,6 +1173,7 @@ export default function codeCartographerExtension(pi) {
|
|
|
1113
1173
|
includeTriage,
|
|
1114
1174
|
retryTruncated,
|
|
1115
1175
|
onStatus,
|
|
1176
|
+
...(flags.runId && { runId: flags.runId }),
|
|
1116
1177
|
});
|
|
1117
1178
|
const lines = collectResultText(collect).split("\n");
|
|
1118
1179
|
const done = collect.status === "completed";
|
|
@@ -1255,12 +1316,11 @@ export default function codeCartographerExtension(pi) {
|
|
|
1255
1316
|
notifyCtx(ctx, "Usage: /codecarto-library-init <path> [--namespace <name>]", "warning");
|
|
1256
1317
|
return;
|
|
1257
1318
|
}
|
|
1258
|
-
|
|
1319
|
+
// The same expansion the config loader applies, so `~user/x` and a
|
|
1320
|
+
// bare `~` mean the same thing everywhere (self-audit mech 6.14).
|
|
1321
|
+
const libraryPath = resolve(expandTilde(pathArg));
|
|
1259
1322
|
try {
|
|
1260
|
-
const result = await initLibrary(libraryPath, {
|
|
1261
|
-
namespaced: !!namespace,
|
|
1262
|
-
...(namespace ? {} : {}),
|
|
1263
|
-
});
|
|
1323
|
+
const result = await initLibrary(libraryPath, { namespaced: !!namespace });
|
|
1264
1324
|
// Write library.path (and library.namespace when given) to the
|
|
1265
1325
|
// user-global config and nothing else — not publish_confirm, so
|
|
1266
1326
|
// initializing a library does not change how publish behaves on
|
|
@@ -1459,9 +1519,13 @@ export default function codeCartographerExtension(pi) {
|
|
|
1459
1519
|
notifyCtx(ctx, error instanceof Error ? error.message : String(error), "error");
|
|
1460
1520
|
return;
|
|
1461
1521
|
}
|
|
1462
|
-
const
|
|
1463
|
-
if (
|
|
1464
|
-
notifyCtx(ctx, `Cannot amend: the pipeline is not complete
|
|
1522
|
+
const outcome = resolvePipelineOutcome(state);
|
|
1523
|
+
if (outcome.kind === "stuck") {
|
|
1524
|
+
notifyCtx(ctx, `Cannot amend: the pipeline is not complete. ${describeStuckPipeline(outcome.blocked)}`, "error");
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
if (outcome.kind === "eligible") {
|
|
1528
|
+
notifyCtx(ctx, `Cannot amend: the pipeline is not complete (next phase: ${outcome.phase.id}). `
|
|
1465
1529
|
+ "Resolve open questions and routed items through that phase's handoff (open_question_closures / carry_forward_closures) instead.", "error");
|
|
1466
1530
|
return;
|
|
1467
1531
|
}
|
|
@@ -7,7 +7,11 @@ export function phaseIdFromSessionName(sessionName) {
|
|
|
7
7
|
if (!sessionName?.startsWith(PHASE_SESSION_PREFIX))
|
|
8
8
|
return null;
|
|
9
9
|
const phaseId = sessionName.slice(PHASE_SESSION_PREFIX.length).trim();
|
|
10
|
-
|
|
10
|
+
// The same alphabet assertSafePhaseId admits (core/status.ts). A narrower
|
|
11
|
+
// match here silently dropped the bash block, write confinement, and
|
|
12
|
+
// checkpointing for a custom pipeline whose ids used `.`, `_`, or a capital
|
|
13
|
+
// (self-audit mech 6.11).
|
|
14
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(phaseId) ? phaseId : null;
|
|
11
15
|
}
|
|
12
16
|
export function buildPhaseCompactionInstructions(phaseId, primaryOutput) {
|
|
13
17
|
const output = primaryOutput ? `.codecarto/${primaryOutput}` : "the phase's declared primary output";
|
|
@@ -37,6 +37,7 @@ export declare function handleSwitchPipeline(args: {
|
|
|
37
37
|
}>;
|
|
38
38
|
export declare function handleNext(args: {
|
|
39
39
|
cwd: string;
|
|
40
|
+
unattended?: boolean;
|
|
40
41
|
}): Promise<{
|
|
41
42
|
content: {
|
|
42
43
|
type: "text";
|
|
@@ -49,6 +50,7 @@ export declare function handleNext(args: {
|
|
|
49
50
|
export declare function handlePhase(args: {
|
|
50
51
|
cwd: string;
|
|
51
52
|
phase: string;
|
|
53
|
+
unattended?: boolean;
|
|
52
54
|
}): Promise<{
|
|
53
55
|
content: {
|
|
54
56
|
type: "text";
|
|
@@ -130,7 +132,6 @@ export declare function handleLibraryInit(args: {
|
|
|
130
132
|
library_path: string;
|
|
131
133
|
name?: string;
|
|
132
134
|
namespace?: string;
|
|
133
|
-
cwd?: string;
|
|
134
135
|
}): Promise<{
|
|
135
136
|
content: {
|
|
136
137
|
type: "text";
|
|
@@ -236,6 +237,7 @@ export declare function handleBroadside(args: {
|
|
|
236
237
|
lenses?: string[];
|
|
237
238
|
api_key?: string;
|
|
238
239
|
wait_seconds?: number;
|
|
240
|
+
run_id?: string;
|
|
239
241
|
include_synthesis?: boolean;
|
|
240
242
|
include_triage?: boolean;
|
|
241
243
|
retry_truncated?: boolean;
|