codecartographer-pi 0.19.2 → 0.19.3

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.
@@ -39,7 +39,8 @@ decisions: []
39
39
  # evidence: <optional: where the pattern showed up>
40
40
  proposed_conventions: []
41
41
  closeout_summary: ""
42
- # Optional full closeout Markdown. YAML literal blocks are supported.
42
+ # Optional full closeout Markdown. Block scalars are supported — literal (|, |-,
43
+ # |+) to keep line breaks, folded (>, >-, >+) to join wrapped prose into one line.
43
44
  closeout_content: |-
44
45
  # Closeout — <phase-id>
45
46
 
@@ -3,4 +3,4 @@
3
3
  # workspace's framework-owned files (GUIDE.md, templates/, workflow/ pipelines
4
4
  # and VALIDATE.md) predate the running release. Written at release time and
5
5
  # copied verbatim by init — never edit by hand.
6
- scaffold_version: 0.19.2
6
+ scaffold_version: 0.19.3
package/dist/core/yaml.js CHANGED
@@ -105,6 +105,61 @@ export function parseYamlScalar(rawValue) {
105
105
  }
106
106
  return trimmed;
107
107
  }
108
+ function parseBlockScalarHeader(rawValue) {
109
+ const match = /^([|>])([-+]?)$/.exec(rawValue);
110
+ if (!match)
111
+ return null;
112
+ return {
113
+ literal: match[1] === "|",
114
+ chomp: match[2] === "-" ? "strip" : match[2] === "+" ? "keep" : "clip",
115
+ };
116
+ }
117
+ /**
118
+ * Fold a block scalar's lines per YAML's folding rules: a single line break
119
+ * between two content lines becomes a space, and a run of k blank lines becomes
120
+ * k newlines. Lines indented deeper than the block's own content indent are
121
+ * "more indented" and keep their breaks literally, which is what lets a folded
122
+ * block hold an indented snippet without it being flattened onto one line.
123
+ */
124
+ function foldBlockLines(blockLines) {
125
+ let result = "";
126
+ let pendingBreaks = 0;
127
+ let started = false;
128
+ let previousMoreIndented = false;
129
+ for (const line of blockLines) {
130
+ if (line.trim() === "") {
131
+ pendingBreaks++;
132
+ continue;
133
+ }
134
+ const moreIndented = /^[ \t]/.test(line);
135
+ if (!started) {
136
+ result = line;
137
+ started = true;
138
+ previousMoreIndented = moreIndented;
139
+ continue;
140
+ }
141
+ if (pendingBreaks > 0) {
142
+ result += "\n".repeat(pendingBreaks);
143
+ pendingBreaks = 0;
144
+ }
145
+ else if (moreIndented || previousMoreIndented) {
146
+ result += "\n";
147
+ }
148
+ else {
149
+ result += " ";
150
+ }
151
+ result += line;
152
+ previousMoreIndented = moreIndented;
153
+ }
154
+ return started ? result + "\n".repeat(pendingBreaks) : "";
155
+ }
156
+ function applyBlockScalar(blockLines, header) {
157
+ const content = header.literal ? blockLines.join("\n") : foldBlockLines(blockLines);
158
+ if (header.chomp === "keep")
159
+ return content;
160
+ const stripped = content.replace(/\n+$/, "");
161
+ return header.chomp === "strip" ? stripped : `${stripped}\n`;
162
+ }
108
163
  export function parseSimpleYaml(raw) {
109
164
  const lines = raw.split(/\r?\n/);
110
165
  let index = 0;
@@ -164,7 +219,8 @@ export function parseSimpleYaml(raw) {
164
219
  throw new Error(`Duplicate YAML key: ${key} near line: ${line.trim()}`);
165
220
  }
166
221
  seen.add(key);
167
- if (rawValue === "|" || rawValue === "|-") {
222
+ const blockHeader = parseBlockScalarHeader(rawValue);
223
+ if (blockHeader) {
168
224
  const blockLines = [];
169
225
  let contentIndent = null;
170
226
  while (index < lines.length) {
@@ -181,8 +237,7 @@ export function parseSimpleYaml(raw) {
181
237
  blockLines.push(blockLine.slice(Math.min(contentIndent, blockIndent)));
182
238
  index++;
183
239
  }
184
- const content = blockLines.join("\n").replace(/\n+$/, "");
185
- assign(key, rawValue === "|" ? `${content}\n` : content);
240
+ assign(key, applyBlockScalar(blockLines, blockHeader));
186
241
  continue;
187
242
  }
188
243
  if (rawValue !== "") {
@@ -0,0 +1,11 @@
1
+ /** Tool names in the guide that have no Pi slash command. */
2
+ export declare const MCP_ONLY_TOOLS: readonly ["codecarto_library_list", "codecarto_library_reindex"];
3
+ export declare const GUIDE_PREAMBLE: string;
4
+ export declare const PI_SURFACE_ADDENDUM: string;
5
+ /**
6
+ * Assemble the message /codecarto-guide queues. The guide document is embedded
7
+ * whole and unmodified between the framing and the addendum — `guide.test.mjs`
8
+ * pins that documents are served entire rather than summarized, and that holds
9
+ * on this surface too.
10
+ */
11
+ export declare function buildPiGuideMessage(documentContent: string, otherTopics: readonly string[]): string;
@@ -0,0 +1,55 @@
1
+ // Surface framing for the packaged agent guide when it is read into a Pi
2
+ // session.
3
+ //
4
+ // Two problems this solves, both invisible on the MCP surface:
5
+ //
6
+ // 1. The guide arrives as a user message. /codecarto-guide queues the document
7
+ // through pi.sendUserMessage, so ~200 lines of imperative instructions land
8
+ // as if the user had typed them, with no task attached. A model handed
9
+ // instructions and no task either starts driving immediately or stalls
10
+ // asking what to do; both are wrong for what is a reference lookup.
11
+ //
12
+ // 2. The guide is written for the MCP surface. It tells the agent to call
13
+ // codecarto_* tools — but the Pi extension registers no tools at all, only
14
+ // slash commands the *user* invokes. A model that tries to follow it finds
15
+ // nothing to call and reasonably concludes the server is missing. The drive
16
+ // loop differs too: on Pi, /codecarto-next runs the phase as an isolated
17
+ // sub-agent and then auto-validates and auto-completes it, so the guide's
18
+ // hand-written execute → handoff → validate → complete loop does not
19
+ // describe a Pi session.
20
+ //
21
+ // The guide text itself stays untouched — agent-skill/ is the single source and
22
+ // core/guide.ts serves it verbatim to every surface. Per-surface adaptation
23
+ // belongs in the wrapper, which is here.
24
+ /** Tool names in the guide that have no Pi slash command. */
25
+ export const MCP_ONLY_TOOLS = ["codecarto_library_list", "codecarto_library_reindex"];
26
+ export const GUIDE_PREAMBLE = [
27
+ "**CodeCartographer guide — reference material, not a task.**",
28
+ "",
29
+ "The user ran `/codecarto-guide`, which queues the guide below into this session so it is available when needed. Nothing is being asked of you yet.",
30
+ "",
31
+ "Do not start a workflow, do not begin a phase, and do not ask which repository or pipeline to use. Acknowledge in a sentence that you have read it, then wait. Answer from it when the user asks.",
32
+ ].join("\n");
33
+ export const PI_SURFACE_ADDENDUM = [
34
+ "---",
35
+ "",
36
+ "## Reading this guide in a Pi session",
37
+ "",
38
+ "The guide above is written for the MCP surface, where an agent drives the workflow by calling `codecarto_*` tools. **This session is the Pi extension, which registers no tools.** There is nothing named `codecarto_*` for you to call, and their absence does not mean a server is missing or misconfigured.",
39
+ "",
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
+ "- **Ignore \"every tool takes an absolute `cwd`\".** Slash commands act on the session's own directory; there is no `cwd` argument to pass.",
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
+ ].join("\n");
44
+ /**
45
+ * Assemble the message /codecarto-guide queues. The guide document is embedded
46
+ * whole and unmodified between the framing and the addendum — `guide.test.mjs`
47
+ * pins that documents are served entire rather than summarized, and that holds
48
+ * on this surface too.
49
+ */
50
+ export function buildPiGuideMessage(documentContent, otherTopics) {
51
+ const footer = otherTopics.length > 0
52
+ ? `\n\n---\nOther guide topics: ${otherTopics.join(", ")} (run /codecarto-guide <topic>).`
53
+ : "";
54
+ return `${GUIDE_PREAMBLE}\n\n---\n\n${documentContent}\n\n${PI_SURFACE_ADDENDUM}${footer}`;
55
+ }
@@ -8,6 +8,7 @@ import { narrateDashboard } from "./dashboard-narrator.js";
8
8
  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
+ import { buildPiGuideMessage } from "./guide-framing.js";
11
12
  import { phaseCompactionExtension } from "./phase-compaction.js";
12
13
  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";
13
14
  import { initLibrary } from "../../core/library.js";
@@ -937,10 +938,10 @@ export default function codeCartographerExtension(pi) {
937
938
  return;
938
939
  }
939
940
  const other = topics.filter((name) => name !== document.topic);
940
- const footer = other.length > 0
941
- ? `\n\n---\nOther guide topics: ${other.join(", ")} (run /codecarto-guide <topic>).`
942
- : "";
943
- const message = `${document.content}${footer}`;
941
+ // Framed, not bare: the guide is MCP-centric text arriving as a user
942
+ // message, so it needs both a "this is reference, not a task" header
943
+ // and a Pi-surface addendum. See guide-framing.ts.
944
+ const message = buildPiGuideMessage(document.content, other);
944
945
  if (ctx.isIdle()) {
945
946
  pi.sendUserMessage(message);
946
947
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.19.2",
3
+ "version": "0.19.3",
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",