@yagni-app/code 1.0.0 → 1.0.1

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.
Files changed (55) hide show
  1. package/README.md +42 -0
  2. package/dist/cli.js +231 -6
  3. package/dist/crashReport.d.ts +8 -0
  4. package/dist/crashReport.js +13 -1
  5. package/dist/doctor.d.ts +7 -0
  6. package/dist/doctor.js +33 -0
  7. package/dist/extension/askAdvisorTool.d.ts +7 -0
  8. package/dist/extension/askAdvisorTool.js +11 -3
  9. package/dist/extension/askYagniTool.js +2 -0
  10. package/dist/extension/branding.d.ts +15 -0
  11. package/dist/extension/branding.js +76 -0
  12. package/dist/extension/chipEditor.d.ts +22 -1
  13. package/dist/extension/chipEditor.js +58 -5
  14. package/dist/extension/condensedTools.d.ts +93 -0
  15. package/dist/extension/condensedTools.js +392 -0
  16. package/dist/extension/diffStat.d.ts +62 -0
  17. package/dist/extension/diffStat.js +158 -0
  18. package/dist/extension/footer.d.ts +2 -0
  19. package/dist/extension/footer.js +21 -8
  20. package/dist/extension/index.d.ts +6 -0
  21. package/dist/extension/index.js +70 -2
  22. package/dist/extension/permission/execPolicy.js +47 -0
  23. package/dist/extension/pipeline/invocation.d.ts +7 -0
  24. package/dist/extension/pipeline/invocation.js +7 -0
  25. package/dist/extension/pipeline/personas.js +4 -4
  26. package/dist/extension/pipeline/runner.d.ts +1 -0
  27. package/dist/extension/pipeline/runner.js +15 -3
  28. package/dist/extension/pipeline/sessionWorktree.d.ts +64 -0
  29. package/dist/extension/pipeline/sessionWorktree.js +225 -0
  30. package/dist/extension/scratchpad.d.ts +66 -0
  31. package/dist/extension/scratchpad.js +93 -0
  32. package/dist/extension/subagents.d.ts +10 -0
  33. package/dist/extension/subagents.js +18 -4
  34. package/dist/extension/todos.d.ts +1 -0
  35. package/dist/extension/todos.js +15 -0
  36. package/dist/extension/toolRuns.d.ts +92 -0
  37. package/dist/extension/toolRuns.js +201 -0
  38. package/dist/extension/webFetchTool.js +2 -0
  39. package/dist/extension/workingLine.d.ts +49 -0
  40. package/dist/extension/workingLine.js +116 -0
  41. package/dist/feedback.d.ts +77 -0
  42. package/dist/feedback.js +500 -0
  43. package/dist/goHeadless.d.ts +3 -0
  44. package/dist/goHeadless.js +13 -0
  45. package/dist/launch.d.ts +8 -0
  46. package/dist/launch.js +6 -0
  47. package/dist/otel.d.ts +150 -0
  48. package/dist/otel.js +291 -0
  49. package/dist/outputFormat.d.ts +83 -0
  50. package/dist/outputFormat.js +207 -0
  51. package/dist/paths.d.ts +10 -0
  52. package/dist/paths.js +13 -0
  53. package/dist/worktreeArgs.d.ts +43 -0
  54. package/dist/worktreeArgs.js +96 -0
  55. package/package.json +3 -2
@@ -95,6 +95,66 @@ export const TICKET_IMAGE_RULE = "When you read or fetch a ticket from any track
95
95
  "ticket whose images you have not actually looked at.";
96
96
  /** Stable header that starts the ticket-image rule section (idempotency anchor). */
97
97
  const TICKET_IMAGE_RULE_HEADER = "## Rule: always read a ticket's images";
98
+ /**
99
+ * GitHub operations recipe. A standing directive that teaches the
100
+ * model the one correct way to reply to inline PR review comments and how to
101
+ * pass multi-line bodies to `gh`/`git` without shell-quoting failures. It is
102
+ * GitHub-mechanics knowledge every session needs — the two failures it fixes
103
+ * (hoisting several inline replies into one top-level comment; a shell-
104
+ * quoting dance that mangles multi-line bodies) are universal, not
105
+ * workflow-specific.
106
+ *
107
+ * Content constraints (parity with {@link TICKET_IMAGE_RULE}): must not contain
108
+ * the standalone word "pi", must not open with a `- ` bullet line, no emojis.
109
+ */
110
+ export const GITHUB_OPERATIONS = "## GitHub Operations\n" +
111
+ "\n" +
112
+ "Use `gh` via bash for GitHub reads and writes (pull requests, issues, " +
113
+ "reviews, checks, releases). Given a GitHub URL, run `gh` to get the info.\n" +
114
+ "\n" +
115
+ "## Reply to inline review comments in-thread\n" +
116
+ "\n" +
117
+ "When a PR review leaves inline comments and you have addressed them, reply " +
118
+ "to each inline comment in its own thread, describing how it was handled. " +
119
+ "Do not hoist replies to several inline threads into a single top-level " +
120
+ "comment — each inline comment earns its own reply.\n" +
121
+ "\n" +
122
+ "Top-level comments are for matters with no inline anchor: an overall " +
123
+ "summary, a question about the change as a whole, or a review that produced " +
124
+ "no inline comments (for example, the only failure was on a CI run).\n" +
125
+ "\n" +
126
+ "## How to reply to an inline comment\n" +
127
+ "\n" +
128
+ "List inline comments on a PR:\n" +
129
+ " gh api repos/{owner}/{repo}/pulls/{number}/comments\n" +
130
+ "\n" +
131
+ "Reply to a specific inline comment (write a multi-line reply to a file " +
132
+ "first, then pass the path — see Bodies below):\n" +
133
+ " gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies \\\n" +
134
+ " -F body=@<path>\n" +
135
+ "\n" +
136
+ "This is the one reply path. Do not use `in_reply_to` on the comment-creation " +
137
+ "endpoint — it works only on top-level comments and does not nest.\n" +
138
+ "\n" +
139
+ "## Bodies: write to a file, pass the path\n" +
140
+ "\n" +
141
+ "Pass multi-line body text (PR description, comment, review reply, commit " +
142
+ "message) via a file, never inline it in the shell command. Inline forms " +
143
+ "(`--body \"$(cat <<'EOF' … EOF)\"`, bare `-f body='…'`) break on some " +
144
+ "platforms and on apostrophes/backticks/`$` in the body; a file has no " +
145
+ "quoting surface. Use the session scratchpad for the file.\n" +
146
+ "\n" +
147
+ "Write the body with the `write` tool, then:\n" +
148
+ " gh pr create --title \"…\" --body-file <path>\n" +
149
+ " gh pr comment <n> --body-file <path>\n" +
150
+ " gh api repos/{owner}/{repo}/pulls/{number}/comments/{comment_id}/replies -F body=@<path>\n" +
151
+ " git commit -F <path>\n" +
152
+ "\n" +
153
+ "For a short single-line body with no apostrophe, `-f body='…'` and " +
154
+ "`-m '…'` are fine; the file form is the default for anything " +
155
+ "multi-paragraph.";
156
+ /** Stable header that starts the GitHub operations section (idempotency anchor). */
157
+ const GITHUB_OPERATIONS_HEADER = "## GitHub Operations";
98
158
  /**
99
159
  * The injected-reminder framing (YAG-574, Change A prerequisite). Claude Code
100
160
  * carries this exact sentence in every system prompt so its whole reminder
@@ -249,6 +309,13 @@ export function brandSystemPrompt(original, opts = {}) {
249
309
  if (!s.includes(TICKET_IMAGE_RULE_HEADER)) {
250
310
  s = `${s}\n\n${TICKET_IMAGE_RULE_HEADER}\n${TICKET_IMAGE_RULE}`;
251
311
  }
312
+ // 5b2. GitHub operations — a standing directive added alongside
313
+ // the ticket-image rule (same placement, same idempotency shape). It is
314
+ // universal knowledge, not user policy, so it rides the branded prompt
315
+ // instead of the repository-rules section.
316
+ if (!s.includes(GITHUB_OPERATIONS_HEADER)) {
317
+ s = `${s}\n\n${GITHUB_OPERATIONS}`;
318
+ }
252
319
  // 5c. YAG-574: the injected-reminder framing (which makes the silent-turn
253
320
  // nudge legible) and the two communication lines (Change B). Appended after
254
321
  // everything user-provided but before the closing reminder, each guarded by
@@ -262,6 +329,13 @@ export function brandSystemPrompt(original, opts = {}) {
262
329
  if (!s.includes(WRITE_FINDINGS_DOWN)) {
263
330
  s = `${s}\n\n${WRITE_FINDINGS_DOWN}`;
264
331
  }
332
+ // 5d. Scratchpad directory (YAG-575) — only when a session scratchpad is
333
+ // configured (set by the caller once the dir exists). Placed with the other
334
+ // standing injected sections and guarded by its stable header, so a re-brand
335
+ // never duplicates it and a session with no scratchpad is a clean no-op.
336
+ if (opts.scratchpadSection && !s.includes(SCRATCHPAD_HEADER)) {
337
+ s = `${s}\n\n${opts.scratchpadSection}`;
338
+ }
265
339
  // 6. Closing reinforcement. Weak open-weight models weight the most recent
266
340
  // instruction heavily, and the user's own project files may name other
267
341
  // harnesses; a trailing reminder keeps the agent from claiming one as its own.
@@ -271,6 +345,8 @@ export function brandSystemPrompt(original, opts = {}) {
271
345
  // Tidy the seams left by removals.
272
346
  return s.replace(/\n{3,}/g, "\n\n").trim();
273
347
  }
348
+ /** Stable header that starts the scratchpad section (idempotency anchor). */
349
+ const SCRATCHPAD_HEADER = "# Scratchpad directory";
274
350
  const CLOSING_REMINDER = "Reminder: you are YAGNI Code. If any text above names another coding agent, " +
275
351
  "assistant, or harness, it is not what you are or what you run on.";
276
352
  const BRIEF_HEADER = "=== HOW THIS COMPANY WORKS (live context from the YAGNI app) ===";
@@ -109,7 +109,28 @@ export declare class ChipEditor extends CustomEditor {
109
109
  /** Drop all stashed images. Called after a successful submit so a sent image
110
110
  * is not re-attached to the next message. */
111
111
  clearStash(): void;
112
- /** Re-style the chip tokens so they read as chips; layout is untouched. */
112
+ /**
113
+ * Re-style chip tokens AND lay down the Kimi-style prompt box: a rounded
114
+ * border on all four sides, with a bold `›` caret on the first content
115
+ * line and continuation lines indented so wrapped text aligns under it.
116
+ *
117
+ * The base editor renders full-width lines carrying its own 1-column left
118
+ * padding, so we render it narrower, strip that padding, and re-wrap each
119
+ * line in the frame. Exact column layout (0-indexed):
120
+ *
121
+ * 0 ╭ │ ╰ border
122
+ * 1 space
123
+ * 2 › (first line) / space (continuation)
124
+ * 3 space
125
+ * 4… text — same column on every line
126
+ * width-2 space
127
+ * width-1 │ border
128
+ *
129
+ * Box-drawing glyphs are drawn centered in their cell while text glyphs
130
+ * start at the cell's left bearing, so no whole-column position lands the
131
+ * border ink exactly on the footer's text column: column 0 reads a hair
132
+ * outside it, column 1 a hair inside. Column 0 is the accepted tradeoff.
133
+ */
113
134
  render(width: number): string[];
114
135
  }
115
136
  /**
@@ -22,7 +22,7 @@
22
22
  * the extension's pi imports to the same module instance pi uses.
23
23
  */
24
24
  import { CustomEditor } from "@earendil-works/pi-coding-agent";
25
- import { matchesKey } from "@earendil-works/pi-tui";
25
+ import { matchesKey, stripTerminalSequences, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
26
26
  import { spawnSync } from "node:child_process";
27
27
  import { readFileSync, unlinkSync, existsSync } from "node:fs";
28
28
  import { tmpdir } from "node:os";
@@ -381,11 +381,64 @@ export class ChipEditor extends CustomEditor {
381
381
  clearStash() {
382
382
  this.stashed = [];
383
383
  }
384
- /** Re-style the chip tokens so they read as chips; layout is untouched. */
384
+ /**
385
+ * Re-style chip tokens AND lay down the Kimi-style prompt box: a rounded
386
+ * border on all four sides, with a bold `›` caret on the first content
387
+ * line and continuation lines indented so wrapped text aligns under it.
388
+ *
389
+ * The base editor renders full-width lines carrying its own 1-column left
390
+ * padding, so we render it narrower, strip that padding, and re-wrap each
391
+ * line in the frame. Exact column layout (0-indexed):
392
+ *
393
+ * 0 ╭ │ ╰ border
394
+ * 1 space
395
+ * 2 › (first line) / space (continuation)
396
+ * 3 space
397
+ * 4… text — same column on every line
398
+ * width-2 space
399
+ * width-1 │ border
400
+ *
401
+ * Box-drawing glyphs are drawn centered in their cell while text glyphs
402
+ * start at the cell's left bearing, so no whole-column position lands the
403
+ * border ink exactly on the footer's text column: column 0 reads a hair
404
+ * outside it, column 1 a hair inside. Column 0 is the accepted tradeoff.
405
+ */
385
406
  render(width) {
386
- return super
387
- .render(width)
388
- .map((line) => line.replace(CHIP_RE, (m) => `${CHIP_ON}${m}${CHIP_OFF}`));
407
+ const styled = super.render(Math.max(1, width - 4)).map((line) => line.replace(CHIP_RE, (m) => `${CHIP_ON}${m}${CHIP_OFF}`));
408
+ const border = (s) => this.borderColor(s);
409
+ const CARET = "\u001b[1m›\u001b[22m";
410
+ const FIRST_PREFIX = `${border("│")} ${CARET} `;
411
+ const NEXT_PREFIX = `${border("│")} `;
412
+ const SUFFIX = ` ${border("│")}`;
413
+ const contentWidth = Math.max(1, width - 6);
414
+ const out = [];
415
+ let borderCount = 0;
416
+ let firstContentLine = true;
417
+ for (const line of styled) {
418
+ const stripped = stripTerminalSequences(line);
419
+ if (/^─+$/.test(stripped) || /^─*\s*[↑↓]/.test(stripped)) {
420
+ borderCount += 1;
421
+ const [left, right] = borderCount === 1 ? ["╭", "╮"] : ["╰", "╯"];
422
+ // One corner + one dash on each side lands the row at `width`.
423
+ out.push(`${border(`${left}─`)}${line}${border(`─${right}`)}`);
424
+ continue;
425
+ }
426
+ // Strip the base editor's own left padding (the frame replaces it),
427
+ // then fit the body to the content width exactly so no line can
428
+ // exceed the terminal width.
429
+ let body = line.startsWith(" ") ? line.slice(1) : line;
430
+ if (visibleWidth(body) > contentWidth)
431
+ body = truncateToWidth(body, contentWidth, "");
432
+ const pad = " ".repeat(Math.max(0, contentWidth - visibleWidth(body)));
433
+ if (borderCount !== 1) {
434
+ // Autocomplete rows (after the bottom border): align under the text.
435
+ out.push(`${" ".repeat(4)}${body}${pad}${" ".repeat(2)}`);
436
+ continue;
437
+ }
438
+ out.push(`${firstContentLine ? FIRST_PREFIX : NEXT_PREFIX}${body}${pad}${SUFFIX}`);
439
+ firstContentLine = false;
440
+ }
441
+ return out;
389
442
  }
390
443
  }
391
444
  /**
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Condensed, Claude Code-style rendering for pi's seven built-in tools.
3
+ *
4
+ * pi resolves renderers per slot (`toolDefinition.renderCall ?? builtIn.renderCall`),
5
+ * so re-registering a built-in by name with the SAME factory-made definition
6
+ * spread underneath swaps ONLY the presentation: name, description, parameters,
7
+ * prompt metadata, and execution semantics stay byte-identical with the
8
+ * built-in (execution delegates to the same `create*ToolDefinition` the session
9
+ * would have built, including the settings-driven bash shellPath/commandPrefix
10
+ * and read image auto-resize). pi 0.84.1 registers such overrides silently —
11
+ * there is no built-in-override startup warning in this version (verified
12
+ * against dist; the extensions.md claim is stale).
13
+ *
14
+ * Rendering contract per row (renderShell "self", so no tinted Box):
15
+ * - renderCall paints the TITLE slot: a live "● Tool(arg)" line while running,
16
+ * a "● Tool(arg)" line for visible rows, the run summary line for the tail
17
+ * of a completed quiet run, or nothing (a zero-line component removes the
18
+ * row entirely, including its spacer).
19
+ * - renderResult paints the BODY slot: condensed write/edit previews, bash
20
+ * error/partial output tails, or nothing. Expanded (ctrl+o) shows full
21
+ * output for every row.
22
+ *
23
+ * String assembly is pure over {@link RenderTheme} (subagentRender.ts's
24
+ * pattern) so tests run against plain text. Renderer exceptions are swallowed
25
+ * by pi (degrading to the built-in fallback), so everything stays total.
26
+ */
27
+ import { SettingsManager, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
28
+ import type { RenderTheme } from "./subagentRender.js";
29
+ import { ToolRunTracker } from "./toolRuns.js";
30
+ /** Lines of write content shown collapsed (mirrors Claude Code's preview). */
31
+ export declare const WRITE_PREVIEW_LINES = 8;
32
+ /** Visual lines of a diff shown collapsed. */
33
+ export declare const DIFF_PREVIEW_LINES = 12;
34
+ /** Output tail lines shown under a failed shell command. */
35
+ export declare const ERROR_TAIL_LINES = 5;
36
+ /** Output tail lines shown under a still-running shell command. */
37
+ export declare const PARTIAL_TAIL_LINES = 3;
38
+ declare const BUILTIN_NAMES: readonly ["read", "bash", "edit", "write", "grep", "find", "ls"];
39
+ type BuiltinName = (typeof BUILTIN_NAMES)[number];
40
+ export type RowStatus = "running" | "ok" | "error";
41
+ /** Home-collapse, and prefer cwd-relative for paths inside the project. */
42
+ export declare function displayPath(rawPath: string, cwd?: string): string;
43
+ /** Whether a tool-call path targets the session scratchpad. */
44
+ export declare function isScratchpadPath(rawPath: unknown, scratchpadDir: string | undefined, cwd?: string): boolean;
45
+ /** The one argument worth showing in a title: path, command, or pattern. */
46
+ export declare function primaryArg(name: BuiltinName, args: Record<string, unknown> | undefined, cwd?: string): string;
47
+ /** `● Tool(arg)` — the visible-row (and live-row) title line. */
48
+ export declare function formatRowTitle(name: BuiltinName, args: Record<string, unknown> | undefined, status: RowStatus, theme: RenderTheme, cwd?: string): string;
49
+ /** Extract "Command exited with code N" from a bash error body, if present. */
50
+ export declare function splitBashError(outputText: string): {
51
+ output: string;
52
+ exitLine: string | undefined;
53
+ };
54
+ /** `⎿ exit line` + a dim tail of output under a failed shell command. */
55
+ export declare function formatBashErrorBody(outputText: string, theme: RenderTheme): string[];
56
+ /** A dim tail of live output under a still-running shell command. */
57
+ export declare function formatBashPartialBody(outputText: string, theme: RenderTheme): string[];
58
+ /** Count added lines in a unified patch (edit summaries surface "+N"). */
59
+ export declare function countPatchAdditions(patch: string | undefined): number;
60
+ /**
61
+ * `⎿ Wrote N lines to path` + a numbered, syntax-highlighted preview.
62
+ * `highlight` is injectable so tests stay independent of pi's theme state.
63
+ */
64
+ export declare function formatWriteBody(rawPath: string, content: string, expanded: boolean, theme: RenderTheme, cwd?: string, highlight?: (code: string, filePath: string) => string[]): string[];
65
+ /**
66
+ * `⎿ Updated path (+A -R)` + a colored diff preview.
67
+ * `paintDiff` is injectable for the same reason as `highlight` above.
68
+ */
69
+ export declare function formatEditBody(rawPath: string, details: {
70
+ diff?: string;
71
+ patch?: string;
72
+ } | undefined, expanded: boolean, theme: RenderTheme, cwd?: string, paintDiff?: (diffText: string) => string): string[];
73
+ /** Expanded body: the raw output, dimmed and capped. */
74
+ export declare function formatExpandedOutput(outputText: string, theme: RenderTheme): string[];
75
+ export interface RegisterCondensedToolsDeps {
76
+ /** Session scratchpad dir; writes/edits under it aggregate instead of rendering. */
77
+ scratchpadDir?: string;
78
+ /** Registration-time cwd (defaults to process.cwd()). */
79
+ cwd?: string;
80
+ /**
81
+ * Settings loader seam. The default mirrors what pi's session does when it
82
+ * builds base tools: bash gets shellPath + shellCommandPrefix, read gets
83
+ * image autoResize. Injectable so tests never touch the config dir.
84
+ */
85
+ loadSettings?: (cwd: string) => SettingsManager;
86
+ }
87
+ /**
88
+ * Re-register the seven built-ins with condensed renderers. Returns the
89
+ * tracker so callers (and tests) can feed message boundaries into it.
90
+ */
91
+ export declare function registerCondensedTools(pi: ExtensionAPI, deps?: RegisterCondensedToolsDeps): ToolRunTracker;
92
+ export {};
93
+ //# sourceMappingURL=condensedTools.d.ts.map