@yagni-app/code-staging 0.0.0 → 0.1.0-staging.1002.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 (147) hide show
  1. package/LICENSE.md +55 -0
  2. package/README.md +166 -0
  3. package/dist/branding.d.ts +25 -0
  4. package/dist/branding.js +27 -0
  5. package/dist/claudeCompat.d.ts +78 -0
  6. package/dist/claudeCompat.js +198 -0
  7. package/dist/cli.d.ts +55 -0
  8. package/dist/cli.js +326 -0
  9. package/dist/config.d.ts +19 -0
  10. package/dist/config.js +28 -0
  11. package/dist/credentials.d.ts +41 -0
  12. package/dist/credentials.js +75 -0
  13. package/dist/distribution.d.ts +15 -0
  14. package/dist/distribution.js +45 -0
  15. package/dist/doctor.d.ts +94 -0
  16. package/dist/doctor.js +343 -0
  17. package/dist/extension/advisor.d.ts +84 -0
  18. package/dist/extension/advisor.js +101 -0
  19. package/dist/extension/askAdvisorTool.d.ts +83 -0
  20. package/dist/extension/askAdvisorTool.js +188 -0
  21. package/dist/extension/askYagniTool.d.ts +39 -0
  22. package/dist/extension/askYagniTool.js +63 -0
  23. package/dist/extension/bless.d.ts +45 -0
  24. package/dist/extension/bless.js +74 -0
  25. package/dist/extension/branding.d.ts +48 -0
  26. package/dist/extension/branding.js +119 -0
  27. package/dist/extension/chipEditor.d.ts +117 -0
  28. package/dist/extension/chipEditor.js +369 -0
  29. package/dist/extension/config.d.ts +112 -0
  30. package/dist/extension/config.js +108 -0
  31. package/dist/extension/costHud.d.ts +73 -0
  32. package/dist/extension/costHud.js +123 -0
  33. package/dist/extension/decisionCapture.d.ts +52 -0
  34. package/dist/extension/decisionCapture.js +66 -0
  35. package/dist/extension/decisions.d.ts +83 -0
  36. package/dist/extension/decisions.js +200 -0
  37. package/dist/extension/diagnostics.d.ts +41 -0
  38. package/dist/extension/diagnostics.js +110 -0
  39. package/dist/extension/index.d.ts +116 -0
  40. package/dist/extension/index.js +388 -0
  41. package/dist/extension/initDone.d.ts +28 -0
  42. package/dist/extension/initDone.js +66 -0
  43. package/dist/extension/initPass.d.ts +152 -0
  44. package/dist/extension/initPass.js +394 -0
  45. package/dist/extension/nextWorkTool.d.ts +51 -0
  46. package/dist/extension/nextWorkTool.js +80 -0
  47. package/dist/extension/permission.d.ts +91 -0
  48. package/dist/extension/permission.js +236 -0
  49. package/dist/extension/pipeline/activity.d.ts +37 -0
  50. package/dist/extension/pipeline/activity.js +151 -0
  51. package/dist/extension/pipeline/activityFeed.d.ts +59 -0
  52. package/dist/extension/pipeline/activityFeed.js +175 -0
  53. package/dist/extension/pipeline/budget.d.ts +48 -0
  54. package/dist/extension/pipeline/budget.js +68 -0
  55. package/dist/extension/pipeline/checkpoint.d.ts +31 -0
  56. package/dist/extension/pipeline/checkpoint.js +176 -0
  57. package/dist/extension/pipeline/eval.d.ts +168 -0
  58. package/dist/extension/pipeline/eval.js +182 -0
  59. package/dist/extension/pipeline/events.d.ts +56 -0
  60. package/dist/extension/pipeline/events.js +147 -0
  61. package/dist/extension/pipeline/findings.d.ts +42 -0
  62. package/dist/extension/pipeline/findings.js +144 -0
  63. package/dist/extension/pipeline/finish.d.ts +128 -0
  64. package/dist/extension/pipeline/finish.js +307 -0
  65. package/dist/extension/pipeline/goCommand.d.ts +128 -0
  66. package/dist/extension/pipeline/goCommand.js +972 -0
  67. package/dist/extension/pipeline/goCompareCommand.d.ts +48 -0
  68. package/dist/extension/pipeline/goCompareCommand.js +184 -0
  69. package/dist/extension/pipeline/goFlags.d.ts +40 -0
  70. package/dist/extension/pipeline/goFlags.js +46 -0
  71. package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
  72. package/dist/extension/pipeline/goStatusCommands.js +188 -0
  73. package/dist/extension/pipeline/invocation.d.ts +45 -0
  74. package/dist/extension/pipeline/invocation.js +64 -0
  75. package/dist/extension/pipeline/orchestrator.d.ts +131 -0
  76. package/dist/extension/pipeline/orchestrator.js +636 -0
  77. package/dist/extension/pipeline/personas.d.ts +44 -0
  78. package/dist/extension/pipeline/personas.js +248 -0
  79. package/dist/extension/pipeline/resilience.d.ts +85 -0
  80. package/dist/extension/pipeline/resilience.js +166 -0
  81. package/dist/extension/pipeline/resume.d.ts +18 -0
  82. package/dist/extension/pipeline/resume.js +106 -0
  83. package/dist/extension/pipeline/runRegistry.d.ts +112 -0
  84. package/dist/extension/pipeline/runRegistry.js +202 -0
  85. package/dist/extension/pipeline/runSession.d.ts +152 -0
  86. package/dist/extension/pipeline/runSession.js +167 -0
  87. package/dist/extension/pipeline/runState.d.ts +158 -0
  88. package/dist/extension/pipeline/runState.js +264 -0
  89. package/dist/extension/pipeline/runner.d.ts +79 -0
  90. package/dist/extension/pipeline/runner.js +298 -0
  91. package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
  92. package/dist/extension/pipeline/scrubSecrets.js +32 -0
  93. package/dist/extension/pipeline/stages.d.ts +51 -0
  94. package/dist/extension/pipeline/stages.js +113 -0
  95. package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
  96. package/dist/extension/pipeline/ticketResolution.js +75 -0
  97. package/dist/extension/pipeline/types.d.ts +436 -0
  98. package/dist/extension/pipeline/types.js +48 -0
  99. package/dist/extension/pipeline/verify.d.ts +277 -0
  100. package/dist/extension/pipeline/verify.js +758 -0
  101. package/dist/extension/pipeline/workspace.d.ts +32 -0
  102. package/dist/extension/pipeline/workspace.js +73 -0
  103. package/dist/extension/pipeline/worktree.d.ts +107 -0
  104. package/dist/extension/pipeline/worktree.js +200 -0
  105. package/dist/extension/provider.d.ts +11 -0
  106. package/dist/extension/provider.js +26 -0
  107. package/dist/extension/recall.d.ts +93 -0
  108. package/dist/extension/recall.js +187 -0
  109. package/dist/extension/recordContextTool.d.ts +38 -0
  110. package/dist/extension/recordContextTool.js +85 -0
  111. package/dist/extension/recordDecisionTool.d.ts +52 -0
  112. package/dist/extension/recordDecisionTool.js +102 -0
  113. package/dist/extension/repoDocs.d.ts +81 -0
  114. package/dist/extension/repoDocs.js +260 -0
  115. package/dist/extension/resilientFetch.d.ts +60 -0
  116. package/dist/extension/resilientFetch.js +133 -0
  117. package/dist/extension/reviewTool.d.ts +34 -0
  118. package/dist/extension/reviewTool.js +81 -0
  119. package/dist/extension/spool.d.ts +92 -0
  120. package/dist/extension/spool.js +266 -0
  121. package/dist/extension/stateHome.d.ts +2 -0
  122. package/dist/extension/stateHome.js +6 -0
  123. package/dist/extension/subagents.d.ts +135 -0
  124. package/dist/extension/subagents.js +281 -0
  125. package/dist/extension/surface.d.ts +10 -0
  126. package/dist/extension/surface.js +12 -0
  127. package/dist/extension/todos.d.ts +110 -0
  128. package/dist/extension/todos.js +217 -0
  129. package/dist/extension/tokenProvider.d.ts +93 -0
  130. package/dist/extension/tokenProvider.js +234 -0
  131. package/dist/launch.d.ts +76 -0
  132. package/dist/launch.js +111 -0
  133. package/dist/login.d.ts +45 -0
  134. package/dist/login.js +142 -0
  135. package/dist/logout.d.ts +14 -0
  136. package/dist/logout.js +34 -0
  137. package/dist/paths.d.ts +31 -0
  138. package/dist/paths.js +87 -0
  139. package/dist/piPackage.d.ts +33 -0
  140. package/dist/piPackage.js +71 -0
  141. package/dist/profiles.d.ts +80 -0
  142. package/dist/profiles.js +222 -0
  143. package/dist/refresh.d.ts +70 -0
  144. package/dist/refresh.js +117 -0
  145. package/dist/upgrade.d.ts +97 -0
  146. package/dist/upgrade.js +284 -0
  147. package/package.json +39 -3
@@ -0,0 +1,200 @@
1
+ /**
2
+ * The client-side decisions surface (spec §4, W4): the `/decide` and
3
+ * `/decisions` commands plus the shared write helper the bless-capture flow
4
+ * reuses.
5
+ *
6
+ * - `bankDecision` POSTs a `source: 'cli_correction'` decision with a generated
7
+ * idempotencyKey through the durable write-spool (part A), so a transport
8
+ * failure or 5xx spools it for replay instead of losing it, and a replay can
9
+ * never double-bank (server dedups on the key).
10
+ * - `fetchDecisions` / `supersedeDecision` reuse the shared authed-fetch seam,
11
+ * so a GET/PATCH inherits the one 401-refresh-then-retry-once behaviour.
12
+ * - `/decide <question> => <decision>` records ONE decision directly (the user
13
+ * typed it — explicit intent, no extra confirm or debounce).
14
+ * - `/decisions` lists recent decisions (id-prefixed, compact); `/decisions
15
+ * supersede <id>` flips one to superseded.
16
+ *
17
+ * Formatters and the arg parser are pure; the network calls are injectable via
18
+ * the shared `SpoolClientOpts` so tests never touch disk or the network.
19
+ */
20
+ import { randomUUID } from "node:crypto";
21
+ import { friendlyFetchError, METERED_POST_FETCH_POLICY, resilientFetch } from "./resilientFetch.js";
22
+ import { sendOrSpool } from "./spool.js";
23
+ /**
24
+ * Bank a `cli_correction` decision durably. Every logical write carries a
25
+ * generated idempotencyKey; the backend dedups on it, so a spool replay or a
26
+ * 401 retry can never bank the same decision twice. Transport failures and 5xx
27
+ * are spooled for replay instead of lost (R4 write half). Never throws.
28
+ */
29
+ export function bankDecision(opts, params, signal) {
30
+ const idempotencyKey = (opts.makeIdempotencyKey ?? randomUUID)();
31
+ return sendOrSpool(opts, "record_decision", "/api/yagni-code/decisions", {
32
+ question: params.question,
33
+ decision: params.decision,
34
+ rationale: params.rationale,
35
+ repo: params.repo,
36
+ source: "cli_correction",
37
+ }, idempotencyKey, signal);
38
+ }
39
+ export const DEFAULT_LIST_LIMIT = 20;
40
+ /** GET recent decisions (any read scope). Throws a friendly error on non-2xx. */
41
+ export async function fetchDecisions(opts, list = {}, signal) {
42
+ const limit = list.limit ?? DEFAULT_LIST_LIMIT;
43
+ const query = new URLSearchParams({ limit: String(limit) });
44
+ if (list.repo)
45
+ query.set("repo", list.repo);
46
+ const res = await resilientFetch(`${opts.baseUrl}/api/yagni-code/decisions?${query.toString()}`, { method: "GET", headers: { authorization: `Bearer ${opts.getToken() ?? ""}` } }, { fetchImpl: opts.fetchImpl, signal });
47
+ if (!res.ok)
48
+ throw new Error(await friendlyFetchError("list decisions", res));
49
+ const data = (await res.json());
50
+ return Array.isArray(data.decisions) ? data.decisions : [];
51
+ }
52
+ /**
53
+ * PATCH one decision to `superseded` (scope yagni_code:write). Idempotent flip,
54
+ * so a single attempt is enough — no client retry (invariant §0.8). Throws a
55
+ * friendly error on non-2xx.
56
+ */
57
+ export async function supersedeDecision(opts, id, supersededBy, signal) {
58
+ const res = await resilientFetch(`${opts.baseUrl}/api/yagni-code/decisions/${encodeURIComponent(id)}`, {
59
+ method: "PATCH",
60
+ headers: {
61
+ "content-type": "application/json",
62
+ authorization: `Bearer ${opts.getToken() ?? ""}`,
63
+ },
64
+ body: JSON.stringify({ status: "superseded", ...(supersededBy ? { supersededBy } : {}) }),
65
+ }, { fetchImpl: opts.fetchImpl, signal, policy: METERED_POST_FETCH_POLICY });
66
+ if (!res.ok)
67
+ throw new Error(await friendlyFetchError("supersede decision", res));
68
+ }
69
+ /** Pure: parse `<question> => <decision>`; null when either half is missing. */
70
+ export function parseDecideArgs(raw) {
71
+ const idx = raw.indexOf("=>");
72
+ if (idx < 0)
73
+ return null;
74
+ const question = raw.slice(0, idx).trim();
75
+ const decision = raw.slice(idx + 2).trim();
76
+ if (!question || !decision)
77
+ return null;
78
+ return { question, decision };
79
+ }
80
+ /** Pure: a short id prefix for compact display. */
81
+ export function shortId(id) {
82
+ return id.slice(0, 8);
83
+ }
84
+ /** Pure: render the compact, id-prefixed decisions list (no em-dashes). */
85
+ export function formatDecisionsList(items) {
86
+ if (items.length === 0) {
87
+ return "No decisions recorded yet. Use /decide <question> => <decision> to bank one.";
88
+ }
89
+ const header = `Recorded decisions (${items.length}):`;
90
+ const lines = items.map((d) => {
91
+ const flag = d.status === "active" ? "" : ` [${d.status}]`;
92
+ const cited = d.citedCount > 0 ? ` (cited ${d.citedCount})` : "";
93
+ return `${shortId(d.id)}${flag} ${d.question} => ${d.decision}${cited}`;
94
+ });
95
+ return [header, ...lines].join("\n");
96
+ }
97
+ /**
98
+ * Resolve a displayed short-id prefix (or a full id) to the full decision id by
99
+ * matching against the current list. Returns the full id, the raw arg when no
100
+ * match is found (let the backend answer an honest 404), or "ambiguous" when
101
+ * more than one decision shares the prefix. Fail-soft: a list error falls back
102
+ * to the raw arg.
103
+ */
104
+ async function resolveDecisionId(opts, arg, signal) {
105
+ let items = [];
106
+ try {
107
+ items = await fetchDecisions(opts, {}, signal);
108
+ }
109
+ catch {
110
+ return arg; // cannot resolve → let the PATCH itself surface the outcome
111
+ }
112
+ const matches = items.filter((d) => d.id === arg || d.id.startsWith(arg));
113
+ if (matches.length === 1)
114
+ return matches[0].id;
115
+ if (matches.length > 1)
116
+ return "ambiguous";
117
+ return arg;
118
+ }
119
+ /** A friendly one-liner for a bank outcome, or null when nothing to say. */
120
+ function bankOutcomeNotice(outcome) {
121
+ if (outcome.kind === "ok")
122
+ return { message: "Recorded the decision in YAGNI.", type: "info" };
123
+ if (outcome.kind === "spooled") {
124
+ return {
125
+ message: `Could not reach YAGNI (${outcome.reason}). Saved the decision locally; it will sync automatically.`,
126
+ type: "warning",
127
+ };
128
+ }
129
+ return { message: outcome.message, type: "error" };
130
+ }
131
+ /**
132
+ * Wire the `/decide` and `/decisions` commands. Both are fail-soft: any
133
+ * transport error surfaces as an honest notice, never a thrown command.
134
+ */
135
+ export function registerDecisionCommands(pi, opts) {
136
+ pi.registerCommand("decide", {
137
+ description: "Record a product-intent decision directly: /decide <question> => <decision>. YAGNI answers the same question next time.",
138
+ handler: async (args, ctx) => {
139
+ const notify = (m, t) => {
140
+ if (ctx.hasUI)
141
+ ctx.ui.notify(m, t);
142
+ };
143
+ const parsed = parseDecideArgs(args);
144
+ if (!parsed) {
145
+ notify("Usage: /decide <question> => <decision>", "warning");
146
+ return;
147
+ }
148
+ try {
149
+ const outcome = await bankDecision(opts, { question: parsed.question, decision: parsed.decision }, ctx.signal);
150
+ const notice = bankOutcomeNotice(outcome);
151
+ notify(notice.message, notice.type);
152
+ }
153
+ catch (err) {
154
+ notify(`Could not record the decision: ${err instanceof Error ? err.message : String(err)}`, "error");
155
+ }
156
+ },
157
+ });
158
+ pi.registerCommand("decisions", {
159
+ description: "List recent recorded decisions. /decisions supersede <id> marks one superseded.",
160
+ handler: async (args, ctx) => {
161
+ const notify = (m, t) => {
162
+ if (ctx.hasUI)
163
+ ctx.ui.notify(m, t);
164
+ };
165
+ const trimmed = args.trim();
166
+ if (trimmed.toLowerCase().startsWith("supersede")) {
167
+ const arg = trimmed.slice("supersede".length).trim();
168
+ if (!arg) {
169
+ notify("Usage: /decisions supersede <id>", "warning");
170
+ return;
171
+ }
172
+ try {
173
+ // The list shows short id prefixes, so resolve the argument against
174
+ // the current list to the full UUID the backend PATCH requires. A
175
+ // failed lookup falls through with the raw arg (the backend then
176
+ // answers an honest 404).
177
+ const id = await resolveDecisionId(opts, arg, ctx.signal);
178
+ if (id === "ambiguous") {
179
+ notify(`Ambiguous decision id "${arg}"; use more characters.`, "warning");
180
+ return;
181
+ }
182
+ await supersedeDecision(opts, id, undefined, ctx.signal);
183
+ notify(`Superseded decision ${shortId(id)}.`, "info");
184
+ }
185
+ catch (err) {
186
+ notify(`Could not supersede decision: ${err instanceof Error ? err.message : String(err)}`, "error");
187
+ }
188
+ return;
189
+ }
190
+ try {
191
+ const items = await fetchDecisions(opts, {}, ctx.signal);
192
+ await pi.sendUserMessage(formatDecisionsList(items));
193
+ }
194
+ catch (err) {
195
+ notify(`Could not list decisions: ${err instanceof Error ? err.message : String(err)}`, "error");
196
+ }
197
+ },
198
+ });
199
+ }
200
+ //# sourceMappingURL=decisions.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * CLI diagnostics for the image-paste feature — layer C of the
3
+ * observability decision (A=debug flag, B=backend events, C=local log+report).
4
+ *
5
+ * The CLI is a terminal app, so it cannot log to stdout without corrupting the
6
+ * TUI. Instead it appends sanitized, single-line JSON events to a small
7
+ * rotating log under `~/.yagni-code/logs/`. "Sanitized" means event names,
8
+ * counts, mime types, byte sizes, and boolean outcomes — NEVER image bytes,
9
+ * message content, tokens, or file contents. Paths are recorded by basename
10
+ * only (the tmp `clipboard-*.png` name), never the full directory.
11
+ *
12
+ * A support flow can tail this file and POST it with the user's consent;
13
+ * nothing is uploaded automatically.
14
+ */
15
+ export declare function _setDiagnosticsHomeForTest(dir: string | null): void;
16
+ export declare function diagnosticsLogPath(): string;
17
+ /** Layer A: verbose mode. `YAGNI_DEBUG=1` (or "true") turns on extra detail. */
18
+ export declare function isDebug(env?: NodeJS.ProcessEnv): boolean;
19
+ export interface ImagePasteEvent {
20
+ /** Stable event name: "paste_path" | "chip_drop" | "transform" | "backend_status". */
21
+ event: string;
22
+ /** Outcome: "ok" | "passthrough" | "error" | a status code as string. */
23
+ outcome?: string;
24
+ mimeType?: string;
25
+ bytes?: number;
26
+ imageCount?: number;
27
+ /** basename of the tmp image file only (never a full path). */
28
+ file?: string;
29
+ /** Extra debug-only detail; written only when YAGNI_DEBUG is on. */
30
+ detail?: string;
31
+ }
32
+ /**
33
+ * Append one sanitized event. Fail-soft: a logging failure must never break the
34
+ * prompt. `detail` is included only when YAGNI_DEBUG is on.
35
+ */
36
+ export declare function logImagePaste(ev: ImagePasteEvent): void;
37
+ /** Read the most recent log content (for a user-triggered report). */
38
+ export declare function readRecentDiagnostics(maxBytes?: number): string;
39
+ /** List existing diagnostic log files (active + rotations), for a report. */
40
+ export declare function listDiagnosticFiles(): string[];
41
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CLI diagnostics for the image-paste feature — layer C of the
3
+ * observability decision (A=debug flag, B=backend events, C=local log+report).
4
+ *
5
+ * The CLI is a terminal app, so it cannot log to stdout without corrupting the
6
+ * TUI. Instead it appends sanitized, single-line JSON events to a small
7
+ * rotating log under `~/.yagni-code/logs/`. "Sanitized" means event names,
8
+ * counts, mime types, byte sizes, and boolean outcomes — NEVER image bytes,
9
+ * message content, tokens, or file contents. Paths are recorded by basename
10
+ * only (the tmp `clipboard-*.png` name), never the full directory.
11
+ *
12
+ * A support flow can tail this file and POST it with the user's consent;
13
+ * nothing is uploaded automatically.
14
+ */
15
+ import { appendFileSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, } from "node:fs";
16
+ import { dirname, join, basename } from "node:path";
17
+ import { codeStateHome } from "./stateHome.js";
18
+ /** Test seam (mirrors `_setYagniCodeHomeForTest`): point the log at a tmpdir. */
19
+ let homeOverride = null;
20
+ export function _setDiagnosticsHomeForTest(dir) {
21
+ homeOverride = dir;
22
+ }
23
+ function yagniCodeHome() {
24
+ return codeStateHome(homeOverride);
25
+ }
26
+ export function diagnosticsLogPath() {
27
+ return join(yagniCodeHome(), "logs", "image-paste.log");
28
+ }
29
+ const MAX_LOG_BYTES = 256 * 1024; // rotate the active file past this
30
+ const KEEP_ROTATIONS = 2; // keep image-paste.log.1 and .2 alongside the active file
31
+ /** Layer A: verbose mode. `YAGNI_DEBUG=1` (or "true") turns on extra detail. */
32
+ export function isDebug(env = process.env) {
33
+ const v = env.YAGNI_DEBUG;
34
+ return v === "1" || v === "true";
35
+ }
36
+ /**
37
+ * Append one sanitized event. Fail-soft: a logging failure must never break the
38
+ * prompt. `detail` is included only when YAGNI_DEBUG is on.
39
+ */
40
+ export function logImagePaste(ev) {
41
+ try {
42
+ // Hermetic under `node --test`: never touch the real home dir from a test
43
+ // unless the test explicitly stubbed it. Writing to the runner's $HOME made
44
+ // the suite's exit depend on the CI filesystem (a read-only or slow $HOME
45
+ // could stall the write), which is the leading suspect for the CI hang.
46
+ if (process.env.NODE_TEST_CONTEXT && homeOverride === null)
47
+ return;
48
+ const line = {
49
+ ts: new Date().toISOString(),
50
+ event: ev.event,
51
+ ...(ev.outcome !== undefined ? { outcome: ev.outcome } : {}),
52
+ ...(ev.mimeType !== undefined ? { mimeType: ev.mimeType } : {}),
53
+ ...(ev.bytes !== undefined ? { bytes: ev.bytes } : {}),
54
+ ...(ev.imageCount !== undefined ? { imageCount: ev.imageCount } : {}),
55
+ ...(ev.file !== undefined ? { file: basename(ev.file) } : {}),
56
+ ...(ev.detail !== undefined && isDebug() ? { detail: ev.detail } : {}),
57
+ };
58
+ const path = diagnosticsLogPath();
59
+ mkdirSync(dirname(path), { recursive: true });
60
+ rotateIfNeeded(path);
61
+ appendFileSync(path, JSON.stringify(line) + "\n", "utf8");
62
+ }
63
+ catch {
64
+ /* logging must never throw into the editor */
65
+ }
66
+ }
67
+ /** Shift image-paste.log -> .1 -> .2 once the active file passes the size cap. */
68
+ function rotateIfNeeded(path) {
69
+ try {
70
+ if (statSync(path).size < MAX_LOG_BYTES)
71
+ return;
72
+ for (let i = KEEP_ROTATIONS; i >= 1; i--) {
73
+ const from = i === 1 ? path : `${path}.${i - 1}`;
74
+ const to = `${path}.${i}`;
75
+ try {
76
+ renameSync(from, to);
77
+ }
78
+ catch {
79
+ /* absent source — fine */
80
+ }
81
+ }
82
+ }
83
+ catch {
84
+ /* rotation is best-effort */
85
+ }
86
+ }
87
+ /** Read the most recent log content (for a user-triggered report). */
88
+ export function readRecentDiagnostics(maxBytes = 64 * 1024) {
89
+ try {
90
+ const data = readFileSync(diagnosticsLogPath(), "utf8");
91
+ return data.length > maxBytes ? data.slice(data.length - maxBytes) : data;
92
+ }
93
+ catch {
94
+ return "";
95
+ }
96
+ }
97
+ /** List existing diagnostic log files (active + rotations), for a report. */
98
+ export function listDiagnosticFiles() {
99
+ try {
100
+ const dir = dirname(diagnosticsLogPath());
101
+ return readdirSync(dir)
102
+ .filter((f) => f.startsWith("image-paste.log"))
103
+ .sort()
104
+ .map((f) => join(dir, f));
105
+ }
106
+ catch {
107
+ return [];
108
+ }
109
+ }
110
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1,116 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { runInitPass as defaultRunInitPass } from "./initPass.js";
3
+ import { type FlushOutcome, type SpoolClientOpts } from "./spool.js";
4
+ import { type TokenProvider } from "./tokenProvider.js";
5
+ import { type ContextBrief, type ModelEntry } from "./config.js";
6
+ /**
7
+ * YAGNI Code extension entry point.
8
+ *
9
+ * pi awaits async factory functions before startup, so fetching the model
10
+ * catalog here is safe. The factory:
11
+ * 1. registers the `yagni` model provider (catalog fetched from the backend),
12
+ * 2. registers the `ask_yagni` tool,
13
+ * 3. rebrands the system prompt as YAGNI Code on every turn (no "pi" leaks), and
14
+ * 4. sets the terminal title + a footer brand mark.
15
+ *
16
+ * If the catalog fetch fails, the error propagates (fail loud) carrying an
17
+ * actionable `yagni login` message.
18
+ */
19
+ /** Injectable seams so the factory is unit-testable without pi or a network. */
20
+ export interface RegisterYagniDeps {
21
+ baseUrl?: string;
22
+ getToken?: () => string | undefined;
23
+ /** The token's ISO expiry, forwarded by the launcher (for the expiry notice). */
24
+ getTokenExpiresAt?: () => string | undefined;
25
+ /** Clock seam for the expiry notice (defaults to Date.now). */
26
+ now?: () => number;
27
+ fetchImpl?: typeof fetch;
28
+ fetchCatalog?: (opts: {
29
+ baseUrl: string;
30
+ getToken: () => string | undefined;
31
+ fetchImpl?: typeof fetch;
32
+ }) => Promise<ModelEntry[]>;
33
+ fetchContextBrief?: (opts: {
34
+ baseUrl: string;
35
+ getToken: () => string | undefined;
36
+ fetchImpl?: typeof fetch;
37
+ }) => Promise<ContextBrief | null>;
38
+ /**
39
+ * The CLI init pass (Onramp Door B). Injectable so tests assert it fires on a
40
+ * fresh-workspace first-run and is skipped otherwise, without a network or disk.
41
+ */
42
+ runInitPass?: typeof defaultRunInitPass;
43
+ /** The workspace id the token is bound to (keys the one-time init marker). */
44
+ getWorkspaceId?: () => string | undefined;
45
+ /** Has the init pass already run once for this workspace? Injectable (no disk in tests). */
46
+ isInitDone?: (workspaceId: string) => boolean;
47
+ /** Record that the init pass ran once for this workspace. Injectable (no disk in tests). */
48
+ markInitDone?: (workspaceId: string) => void;
49
+ /**
50
+ * The refreshing token provider (spec §4). Injectable so tests control the
51
+ * rotation surface; the default provider seeds from `getToken`/
52
+ * `getTokenExpiresAt` (falling back to the env) and owns the proactive
53
+ * refresh timer + the 401 refresh-and-retry-once seam.
54
+ */
55
+ tokenProvider?: TokenProvider;
56
+ /** The spool flush (R4 write half), injectable so tests never touch disk. */
57
+ flushSpool?: (opts: SpoolClientOpts) => Promise<FlushOutcome>;
58
+ env?: NodeJS.ProcessEnv;
59
+ }
60
+ export declare function registerYagni(pi: ExtensionAPI, deps?: RegisterYagniDeps): Promise<void>;
61
+ export default function (pi: ExtensionAPI): Promise<void>;
62
+ export { makeAskYagniTool } from "./askYagniTool.js";
63
+ export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
64
+ export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
65
+ export type { Citation, MakeAskYagniToolOptions } from "./askYagniTool.js";
66
+ export { makeReviewBusinessMatchTool } from "./reviewTool.js";
67
+ export type { MakeReviewToolOptions } from "./reviewTool.js";
68
+ export { makeRecordEngineeringContextTool } from "./recordContextTool.js";
69
+ export type { MakeRecordContextToolOptions } from "./recordContextTool.js";
70
+ export { makeRecordDecisionTool } from "./recordDecisionTool.js";
71
+ export type { MakeRecordDecisionToolOptions } from "./recordDecisionTool.js";
72
+ export { makeSuggestNextWorkTool, defaultNextAction } from "./nextWorkTool.js";
73
+ export type { MakeNextWorkToolOptions, DefaultNextAction, NextActionOption, } from "./nextWorkTool.js";
74
+ export { recordDecision } from "./recordDecisionTool.js";
75
+ export type { RecordDecisionParams } from "./recordDecisionTool.js";
76
+ export { runInitPass, isFreshWorkspace, readRepoIntake, draftEngineering, summarizeDraft, FRESH_BRIEF_MIN_CHARS, } from "./initPass.js";
77
+ export type { RunInitPassDeps, InitPassOutcome, RepoIntake, RepoIntakeFs, RepoAdr, EngineeringDraft, DraftTeam, } from "./initPass.js";
78
+ export { isInitDone, markInitDone, initDoneMarkerFile, _setInitDoneHomeForTest } from "./initDone.js";
79
+ export { brandSystemPrompt, YAGNI_IDENTITY, BRAND_NAME } from "./branding.js";
80
+ export { fetchCatalog, getToken, getWorkspaceId, resolveBaseUrl } from "./config.js";
81
+ export type { FetchCatalogOptions, ModelEntry } from "./config.js";
82
+ export { buildYagniProvider } from "./provider.js";
83
+ export { registerGoCommand } from "./pipeline/goCommand.js";
84
+ export type { RegisterGoDeps } from "./pipeline/goCommand.js";
85
+ export { runPipeline } from "./pipeline/orchestrator.js";
86
+ export type { RunPipelineDeps } from "./pipeline/orchestrator.js";
87
+ export { withResilience, classifyTransient, composeAbortSignal } from "./pipeline/resilience.js";
88
+ export type { ResilienceAttemptRecord, ResilienceSeams, RunStageFn } from "./pipeline/resilience.js";
89
+ export { DEFAULT_RESILIENCE_POLICY } from "./pipeline/types.js";
90
+ export type { ResiliencePolicy } from "./pipeline/types.js";
91
+ export { buildVerifyEnv, detectVerifyCommand, makeRunVerify, parseVerifyFailures } from "./pipeline/verify.js";
92
+ export type { VerifyCommand, VerifyOutcome } from "./pipeline/verify.js";
93
+ export { blindStages, reportOnlyStages, makeGroundedVsBlindEval, formatComparisonReport } from "./pipeline/eval.js";
94
+ export type { ComparisonReport, LaneFit, LaneOutcome } from "./pipeline/eval.js";
95
+ export { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
96
+ export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkdown, buildSubagentStage, formatAgentList, mapModelTier, SUBAGENT_TOOL_NAME, GENERAL_AGENT_NAME, MAX_PARALLEL_SUBAGENTS, DEFAULT_SUBAGENT_TOOLS, } from "./subagents.js";
97
+ export type { SubagentDef, SubagentSource } from "./subagents.js";
98
+ export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, todoSummary, TODO_TOOL_NAME, MAX_TODOS, } from "./todos.js";
99
+ export type { TodoItem, TodoStatus, TodoTheme } from "./todos.js";
100
+ export { decideGate, registerPermissionGate, filterStalePlanContext, DEFAULT_PERMISSION_POLICY, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
101
+ export type { PermissionMode, PermissionPolicy, GateDecision, RegisterPermissionDeps, BlessRememberInfo, } from "./permission.js";
102
+ export { makeBlessStore, blessPath } from "./bless.js";
103
+ export type { BlessStore, BlessRule } from "./bless.js";
104
+ export { bankDecision, fetchDecisions, supersedeDecision, parseDecideArgs, formatDecisionsList, shortId, registerDecisionCommands, DEFAULT_LIST_LIMIT, } from "./decisions.js";
105
+ export type { DecisionClientOpts, DecisionListItem, BankDecisionParams, DecideArgs } from "./decisions.js";
106
+ export { makeDecisionCapture, CAPTURE_DEBOUNCE_MS } from "./decisionCapture.js";
107
+ export type { DecisionCapture, DecisionCaptureDeps, BlessCaptureInfo, CaptureCtx } from "./decisionCapture.js";
108
+ export { registerAmbientRecall, fetchRecall, formatRecallBlock, toRepoRelativePath, normalizeRecall, RECALL_TIMEOUT_MS, RECALL_MIN_DECISIONS, } from "./recall.js";
109
+ export type { RecallResult, RecallClientOpts, RegisterRecallDeps } from "./recall.js";
110
+ export { makeCostAccumulator, formatCostLine, registerCostCommand } from "./costHud.js";
111
+ export type { CostSnapshot, Headroom } from "./costHud.js";
112
+ export { makeTokenProvider, makeAuthedFetch, persistRotationToProfile, PROACTIVE_REFRESH_WINDOW_MS, REFRESH_RETRY_MS, } from "./tokenProvider.js";
113
+ export type { TokenProvider, TokenProviderDeps, ScheduleFn } from "./tokenProvider.js";
114
+ export { appendToSpool, loadSpool, flushSpool, sendOrSpool, spoolFile, MAX_SPOOL_AGE_MS, _setSpoolHomeForTest, } from "./spool.js";
115
+ export type { SpoolEntry, SpoolClientOpts, FlushOutcome, JudgmentWriteOutcome } from "./spool.js";
116
+ //# sourceMappingURL=index.d.ts.map