@slowcook-ai/cli 0.19.0-alpha.11 → 0.19.0-alpha.16

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 (42) hide show
  1. package/AGENTS.md +239 -0
  2. package/REPORTING.md +193 -0
  3. package/dist/cli.js +22 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/chef/drift-fix.d.ts +1 -1
  6. package/dist/commands/chef/drift-fix.d.ts.map +1 -1
  7. package/dist/commands/chef/drift-fix.js +36 -15
  8. package/dist/commands/chef/drift-fix.js.map +1 -1
  9. package/dist/commands/chef/index.js +13 -3
  10. package/dist/commands/chef/index.js.map +1 -1
  11. package/dist/commands/chef/orchestrate.d.ts +1 -1
  12. package/dist/commands/chef/orchestrate.d.ts.map +1 -1
  13. package/dist/commands/chef/orchestrate.js +32 -9
  14. package/dist/commands/chef/orchestrate.js.map +1 -1
  15. package/dist/commands/docs/index.d.ts +16 -0
  16. package/dist/commands/docs/index.d.ts.map +1 -0
  17. package/dist/commands/docs/index.js +127 -0
  18. package/dist/commands/docs/index.js.map +1 -0
  19. package/dist/commands/garnish/index.d.ts +56 -0
  20. package/dist/commands/garnish/index.d.ts.map +1 -0
  21. package/dist/commands/garnish/index.js +281 -0
  22. package/dist/commands/garnish/index.js.map +1 -0
  23. package/dist/commands/garnish/trailer.d.ts +79 -0
  24. package/dist/commands/garnish/trailer.d.ts.map +1 -0
  25. package/dist/commands/garnish/trailer.js +118 -0
  26. package/dist/commands/garnish/trailer.js.map +1 -0
  27. package/dist/commands/recon/index.d.ts +1 -1
  28. package/dist/commands/recon/index.d.ts.map +1 -1
  29. package/dist/commands/recon/index.js +9 -5
  30. package/dist/commands/recon/index.js.map +1 -1
  31. package/dist/commands/recon/stale-stubs.d.ts +4 -1
  32. package/dist/commands/recon/stale-stubs.d.ts.map +1 -1
  33. package/dist/commands/recon/stale-stubs.js +6 -1
  34. package/dist/commands/recon/stale-stubs.js.map +1 -1
  35. package/dist/commands/run-mock/index.d.ts.map +1 -1
  36. package/dist/commands/run-mock/index.js +128 -21
  37. package/dist/commands/run-mock/index.js.map +1 -1
  38. package/dist/lib/read-only.d.ts +22 -0
  39. package/dist/lib/read-only.d.ts.map +1 -0
  40. package/dist/lib/read-only.js +34 -0
  41. package/dist/lib/read-only.js.map +1 -0
  42. package/package.json +7 -5
@@ -0,0 +1,56 @@
1
+ /**
2
+ * `slowcook garnish` — 0.19.0-α.15.
3
+ *
4
+ * Local commit-gate for human (or other-agent) tweaks layered on top
5
+ * of an agent's work. The PM (or engineer) edits files in the working
6
+ * tree — by hand, via DevTools Workspaces, via any editor — then runs
7
+ * `slowcook garnish`. The cli:
8
+ *
9
+ * 1. Detects uncommitted changes in the working tree.
10
+ * 2. For each changed file, identifies the agent (if any) whose last
11
+ * commit touched the file. Files last touched by humans get no
12
+ * trailer entry (no learning signal for an agent).
13
+ * 3. Runs the relevant tests scoped to the changed files (or the
14
+ * caller-provided test glob via --scope).
15
+ * 4. If tests pass: commits the staged changes with a subject naming
16
+ * the touched files + `Tweaks-output-of:` trailer lines marking
17
+ * each agent-authored file the tweak touched. Optionally pushes.
18
+ * 5. If tests fail: prints the failure summary + exits non-zero
19
+ * without committing.
20
+ *
21
+ * The trailer lines are the load-bearing piece — a future `slowcook
22
+ * reflect` command mines them to surface learning signal for the
23
+ * upstream agent (eval-set fixtures, prompt-amendment candidates).
24
+ */
25
+ /**
26
+ * 0.19.0-α.16 — runGarnish, the reusable core. Other commands
27
+ * (run-mock --garnish, for one) call this in-process on debounced
28
+ * file-save batches; doing it as a subprocess invocation would
29
+ * re-pay node startup every time.
30
+ *
31
+ * Returns a structured result; never calls process.exit. Caller
32
+ * decides what to do on each kind.
33
+ */
34
+ export interface RunGarnishOptions {
35
+ repoRoot: string;
36
+ scope?: string | null;
37
+ message?: string | null;
38
+ push?: boolean;
39
+ silent?: boolean;
40
+ }
41
+ export type RunGarnishResult = {
42
+ kind: "no-changes";
43
+ } | {
44
+ kind: "tests-failed";
45
+ touchedFiles: string[];
46
+ outputTail: string;
47
+ } | {
48
+ kind: "committed";
49
+ sha: string;
50
+ touchedFiles: string[];
51
+ agentRefCount: number;
52
+ pushed: boolean;
53
+ };
54
+ export declare function runGarnish(opts: RunGarnishOptions): Promise<RunGarnishResult>;
55
+ export declare function garnish(argv: string[], _cliVersion: string): Promise<void>;
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/garnish/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAsJH;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpE;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEN,wBAAsB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmEnF;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhF"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * `slowcook garnish` — 0.19.0-α.15.
3
+ *
4
+ * Local commit-gate for human (or other-agent) tweaks layered on top
5
+ * of an agent's work. The PM (or engineer) edits files in the working
6
+ * tree — by hand, via DevTools Workspaces, via any editor — then runs
7
+ * `slowcook garnish`. The cli:
8
+ *
9
+ * 1. Detects uncommitted changes in the working tree.
10
+ * 2. For each changed file, identifies the agent (if any) whose last
11
+ * commit touched the file. Files last touched by humans get no
12
+ * trailer entry (no learning signal for an agent).
13
+ * 3. Runs the relevant tests scoped to the changed files (or the
14
+ * caller-provided test glob via --scope).
15
+ * 4. If tests pass: commits the staged changes with a subject naming
16
+ * the touched files + `Tweaks-output-of:` trailer lines marking
17
+ * each agent-authored file the tweak touched. Optionally pushes.
18
+ * 5. If tests fail: prints the failure summary + exits non-zero
19
+ * without committing.
20
+ *
21
+ * The trailer lines are the load-bearing piece — a future `slowcook
22
+ * reflect` command mines them to surface learning signal for the
23
+ * upstream agent (eval-set fixtures, prompt-amendment candidates).
24
+ */
25
+ import { execSync, spawnSync } from "node:child_process";
26
+ import { existsSync, writeFileSync } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { composeCommitMessage, agentFromAuthor, } from "./trailer.js";
29
+ function parseArgs(argv) {
30
+ const args = {
31
+ repoRoot: process.cwd(),
32
+ scope: null,
33
+ push: false,
34
+ message: null,
35
+ dryRun: false,
36
+ };
37
+ for (let i = 0; i < argv.length; i++) {
38
+ const a = argv[i];
39
+ const next = argv[i + 1];
40
+ if (a === "--cwd" && next) {
41
+ args.repoRoot = next;
42
+ i++;
43
+ }
44
+ else if (a === "--scope" && next) {
45
+ args.scope = next;
46
+ i++;
47
+ }
48
+ else if (a === "--push") {
49
+ args.push = true;
50
+ }
51
+ else if (a === "--message" && next) {
52
+ args.message = next;
53
+ i++;
54
+ }
55
+ else if (a === "-m" && next) {
56
+ args.message = next;
57
+ i++;
58
+ }
59
+ else if (a === "--dry-run") {
60
+ args.dryRun = true;
61
+ }
62
+ else if (a === "--help" || a === "-h") {
63
+ printHelp();
64
+ process.exit(0);
65
+ }
66
+ }
67
+ return args;
68
+ }
69
+ function printHelp() {
70
+ console.log(`
71
+ slowcook garnish — commit a human (or other-agent) tweak on top of agent work
72
+
73
+ Detects uncommitted changes, runs the relevant tests, and (if green) commits
74
+ with \`Tweaks-output-of:\` trailers marking which upstream agent's work was
75
+ tweaked. A future \`slowcook reflect\` mines these trailers for learning signal.
76
+
77
+ Usage:
78
+ slowcook garnish [options]
79
+
80
+ Options:
81
+ --cwd <path> Repo root (default: cwd).
82
+ --scope <glob> Pass through to vitest (\`vitest run <glob>\`). Default:
83
+ run vitest related to the changed files.
84
+ --message <text> Optional commit body. Default: subject-only.
85
+ -m <text> Short form of --message.
86
+ --push Push to origin after committing.
87
+ --dry-run Print what would happen; do not run tests or commit.
88
+
89
+ Examples:
90
+ # Edit some files via DevTools Workspaces / by hand, then:
91
+ slowcook garnish
92
+
93
+ # Force a specific test scope:
94
+ slowcook garnish --scope 'tests/integration/story-018-*'
95
+
96
+ # Commit + push:
97
+ slowcook garnish -m "Tightened the spacing on the Pin button." --push
98
+ `);
99
+ }
100
+ function gitChangedFiles(repoRoot) {
101
+ const out = execSync(`git -C "${repoRoot}" status --porcelain`, {
102
+ encoding: "utf8",
103
+ });
104
+ const lines = out.split("\n").filter((l) => l.length > 0);
105
+ return lines
106
+ .map((line) => {
107
+ // Format: "XY path" or "XY path-old -> path-new" for renames.
108
+ // Take the rightmost path; safe enough for our use.
109
+ const status = line.slice(0, 2).trim();
110
+ const rest = line.slice(3).trim();
111
+ const path = rest.includes(" -> ") ? rest.split(" -> ")[1] : rest;
112
+ return { path, status };
113
+ })
114
+ .filter((f) => f.status !== "??"); // skip untracked-only (user can `git add` first)
115
+ }
116
+ function lastTouchingCommit(repoRoot, file) {
117
+ try {
118
+ const sha = execSync(`git -C "${repoRoot}" log -n 1 --format=%H -- "${file}"`, { encoding: "utf8" }).trim();
119
+ if (!sha)
120
+ return null;
121
+ const author = execSync(`git -C "${repoRoot}" show -s --format=%an "${sha}"`, { encoding: "utf8" }).trim();
122
+ return { sha, author };
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ function resolveUpstreamRefs(repoRoot, files) {
129
+ const refs = [];
130
+ const humanFiles = [];
131
+ for (const file of files) {
132
+ const upstream = lastTouchingCommit(repoRoot, file);
133
+ if (!upstream) {
134
+ // File doesn't exist in history yet — must be newly-added by this tweak.
135
+ humanFiles.push(file);
136
+ continue;
137
+ }
138
+ const agent = agentFromAuthor(upstream.author);
139
+ if (agent) {
140
+ refs.push({ agent, sha: upstream.sha, file });
141
+ }
142
+ else {
143
+ humanFiles.push(file);
144
+ }
145
+ }
146
+ return { refs, humanFiles };
147
+ }
148
+ function runTests(repoRoot, files, scopeOverride) {
149
+ const cmd = scopeOverride
150
+ ? `npx vitest run ${scopeOverride}`
151
+ : `npx vitest related ${files.map((f) => `"${f}"`).join(" ")}`;
152
+ const result = spawnSync(cmd, {
153
+ cwd: repoRoot,
154
+ shell: true,
155
+ encoding: "utf8",
156
+ maxBuffer: 4 * 1024 * 1024,
157
+ });
158
+ const output = (result.stdout || "") + (result.stderr || "");
159
+ const ok = result.status === 0;
160
+ return { ok, output };
161
+ }
162
+ export async function runGarnish(opts) {
163
+ const repoRoot = opts.repoRoot;
164
+ const scope = opts.scope ?? null;
165
+ const log = opts.silent ? () => undefined : (msg) => console.log(msg);
166
+ const warn = opts.silent ? () => undefined : (msg) => console.warn(msg);
167
+ const changed = gitChangedFiles(repoRoot);
168
+ if (changed.length === 0) {
169
+ return { kind: "no-changes" };
170
+ }
171
+ const touchedFiles = changed.map((c) => c.path);
172
+ const { refs, humanFiles } = resolveUpstreamRefs(repoRoot, touchedFiles);
173
+ if (!opts.silent) {
174
+ log(` ${touchedFiles.length} file(s) with uncommitted changes:`);
175
+ for (const f of touchedFiles)
176
+ log(` ${f}`);
177
+ log(` upstream: ${refs.length} agent-authored, ${humanFiles.length} human/new`);
178
+ if (refs.length > 0) {
179
+ const byAgent = {};
180
+ for (const r of refs)
181
+ byAgent[r.agent] = (byAgent[r.agent] ?? 0) + 1;
182
+ const summary = Object.entries(byAgent).map(([a, n]) => `${a}=${n}`).join(", ");
183
+ log(` (${summary})`);
184
+ }
185
+ log("\n running tests" + (scope ? ` (--scope ${scope})` : " (vitest related)") + "...");
186
+ }
187
+ const tests = runTests(repoRoot, touchedFiles, scope);
188
+ if (!tests.ok) {
189
+ const tail = tests.output.split("\n").slice(-40).join("\n");
190
+ return { kind: "tests-failed", touchedFiles, outputTail: tail };
191
+ }
192
+ log(" ✓ tests passed.");
193
+ const message = composeCommitMessage({
194
+ touchedFiles,
195
+ upstreamRefs: refs,
196
+ userMessage: opts.message ?? undefined,
197
+ });
198
+ const msgFile = join(repoRoot, ".brewing/.garnish-commit-msg.tmp");
199
+ if (!existsSync(join(repoRoot, ".brewing"))) {
200
+ execSync(`mkdir -p "${join(repoRoot, ".brewing")}"`);
201
+ }
202
+ writeFileSync(msgFile, message, "utf8");
203
+ for (const f of touchedFiles) {
204
+ try {
205
+ execSync(`git -C "${repoRoot}" add -- "${f}"`, { stdio: "ignore" });
206
+ }
207
+ catch { /* deleted file; ignore */ }
208
+ }
209
+ try {
210
+ execSync(`git -C "${repoRoot}" commit -F "${msgFile}"`, {
211
+ stdio: opts.silent ? "ignore" : "inherit",
212
+ });
213
+ }
214
+ catch (e) {
215
+ warn(` garnish commit failed: ${e.message.slice(0, 200)}`);
216
+ return { kind: "tests-failed", touchedFiles, outputTail: e.message };
217
+ }
218
+ const sha = execSync(`git -C "${repoRoot}" rev-parse HEAD`, { encoding: "utf8" }).trim();
219
+ let pushed = false;
220
+ if (opts.push) {
221
+ try {
222
+ execSync(`git -C "${repoRoot}" push`, { stdio: opts.silent ? "ignore" : "inherit" });
223
+ pushed = true;
224
+ }
225
+ catch (e) {
226
+ warn(` warn: push failed: ${e.message.slice(0, 200)}`);
227
+ }
228
+ }
229
+ return { kind: "committed", sha, touchedFiles, agentRefCount: refs.length, pushed };
230
+ }
231
+ export async function garnish(argv, _cliVersion) {
232
+ const args = parseArgs(argv);
233
+ console.log(`slowcook garnish · cwd: ${args.repoRoot.replace(process.cwd() + "/", ".")}`);
234
+ if (args.dryRun) {
235
+ const changed = gitChangedFiles(args.repoRoot);
236
+ if (changed.length === 0) {
237
+ console.log(" no uncommitted changes; nothing to garnish.");
238
+ return;
239
+ }
240
+ const touchedFiles = changed.map((c) => c.path);
241
+ const { refs, humanFiles } = resolveUpstreamRefs(args.repoRoot, touchedFiles);
242
+ console.log(` ${touchedFiles.length} file(s) with uncommitted changes:`);
243
+ for (const f of touchedFiles)
244
+ console.log(` ${f}`);
245
+ console.log(` upstream: ${refs.length} agent-authored, ${humanFiles.length} human/new`);
246
+ if (refs.length > 0) {
247
+ const byAgent = {};
248
+ for (const r of refs)
249
+ byAgent[r.agent] = (byAgent[r.agent] ?? 0) + 1;
250
+ const summary = Object.entries(byAgent).map(([a, n]) => `${a}=${n}`).join(", ");
251
+ console.log(` (${summary})`);
252
+ }
253
+ console.log("\n [dry-run] would run tests + commit; skipping.");
254
+ const message = composeCommitMessage({
255
+ touchedFiles,
256
+ upstreamRefs: refs,
257
+ userMessage: args.message ?? undefined,
258
+ });
259
+ console.log("\n commit message would be:\n");
260
+ for (const line of message.split("\n"))
261
+ console.log(` ${line}`);
262
+ return;
263
+ }
264
+ const result = await runGarnish({
265
+ repoRoot: args.repoRoot,
266
+ scope: args.scope,
267
+ message: args.message,
268
+ push: args.push,
269
+ });
270
+ if (result.kind === "no-changes") {
271
+ console.log(" no uncommitted changes; nothing to garnish.");
272
+ return;
273
+ }
274
+ if (result.kind === "tests-failed") {
275
+ console.error("\n ✗ tests failed. Garnish blocked; working tree unchanged.\n");
276
+ console.error(result.outputTail);
277
+ process.exit(1);
278
+ }
279
+ console.log(`\n ✓ garnished: ${result.sha.slice(0, 7)} (${result.agentRefCount} agent ref(s) recorded)${result.pushed ? " · pushed" : ""}`);
280
+ }
281
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/garnish/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 0.19.0-α.15 — `slowcook garnish` trailer helpers.
3
+ *
4
+ * When a human (or another agent) commits a tweak on top of an agent's
5
+ * work, we mark the commit with `Tweaks-output-of:` git trailer lines —
6
+ * one per file the tweak touched, naming the upstream agent + sha. A
7
+ * future `slowcook reflect` command mines these trailers to surface
8
+ * learning signal for the upstream agent (eval-set fixtures, prompt
9
+ * amendment candidates, drift catalogs).
10
+ *
11
+ * Trailer format (one line per file):
12
+ *
13
+ * Tweaks-output-of: agent=<name> sha=<commit-sha> file=<repo-relative-path>
14
+ *
15
+ * Examples:
16
+ * Tweaks-output-of: agent=vibe sha=a7df238 file=mock/src/components/Foo.tsx
17
+ * Tweaks-output-of: agent=plate sha=00905ae file=mock/src/components/Bar.tsx
18
+ *
19
+ * Pure module — no IO. Caller composes the trailer lines into the commit
20
+ * message and runs git separately.
21
+ */
22
+ export interface UpstreamRef {
23
+ /** Upstream agent name (vibe / plate / brew / chef / etc). */
24
+ agent: string;
25
+ /** Upstream commit SHA (short or full; renderer trims to 7). */
26
+ sha: string;
27
+ /** Repo-relative file path the tweak touched. */
28
+ file: string;
29
+ }
30
+ /**
31
+ * Format a list of upstream refs into trailer lines (one per ref).
32
+ * Pure: returns a single string with `\n` separators, no leading or
33
+ * trailing newline. Caller appends to the commit-message body.
34
+ */
35
+ export declare function formatTrailer(refs: UpstreamRef[]): string;
36
+ /**
37
+ * Parse a single trailer line, returning the parsed ref or null if the
38
+ * line doesn't match the expected shape. Tolerates leading/trailing
39
+ * whitespace + the optional " " indent some `git interpret-trailers`
40
+ * outputs add.
41
+ */
42
+ export declare function parseTrailerLine(line: string): UpstreamRef | null;
43
+ /**
44
+ * Parse all `Tweaks-output-of:` trailers from a full commit-message
45
+ * body (multi-line). Returns refs in the order they appeared.
46
+ */
47
+ export declare function parseTrailers(commitBody: string): UpstreamRef[];
48
+ /**
49
+ * Inspect a git author line + return the upstream agent name, if the
50
+ * author follows slowcook's agent convention. Otherwise null.
51
+ *
52
+ * Conventions detected:
53
+ * slowcook-vibe[bot] → "vibe"
54
+ * slowcook-plate[bot] → "plate"
55
+ * slowcook-brew[bot] → "brew"
56
+ * slowcook-chef[bot] → "chef"
57
+ * slowcook-refine[bot] → "refine"
58
+ * slowcook-testgen[bot] → "testgen"
59
+ * slowcook-recipe[bot] → "recipe"
60
+ * anything else → null (human or unrelated bot)
61
+ *
62
+ * Pure: no IO. Caller pipes git author through it.
63
+ */
64
+ export declare function agentFromAuthor(author: string): string | null;
65
+ /**
66
+ * Compose the full commit message body for a garnish commit: a one-line
67
+ * subject naming the touched files, a blank line, optional user-provided
68
+ * body, a blank line, then the trailer block.
69
+ *
70
+ * Pure: caller passes the staged-file list + the parsed upstream refs +
71
+ * the optional user message. Returns the body string ready for `git
72
+ * commit -F`.
73
+ */
74
+ export declare function composeCommitMessage(args: {
75
+ touchedFiles: string[];
76
+ upstreamRefs: UpstreamRef[];
77
+ userMessage?: string;
78
+ }): string;
79
+ //# sourceMappingURL=trailer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trailer.d.ts","sourceRoot":"","sources":["../../../src/commands/garnish/trailer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,WAAW;IAC1B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,CAIzD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAYjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAO/D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAUT"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * 0.19.0-α.15 — `slowcook garnish` trailer helpers.
3
+ *
4
+ * When a human (or another agent) commits a tweak on top of an agent's
5
+ * work, we mark the commit with `Tweaks-output-of:` git trailer lines —
6
+ * one per file the tweak touched, naming the upstream agent + sha. A
7
+ * future `slowcook reflect` command mines these trailers to surface
8
+ * learning signal for the upstream agent (eval-set fixtures, prompt
9
+ * amendment candidates, drift catalogs).
10
+ *
11
+ * Trailer format (one line per file):
12
+ *
13
+ * Tweaks-output-of: agent=<name> sha=<commit-sha> file=<repo-relative-path>
14
+ *
15
+ * Examples:
16
+ * Tweaks-output-of: agent=vibe sha=a7df238 file=mock/src/components/Foo.tsx
17
+ * Tweaks-output-of: agent=plate sha=00905ae file=mock/src/components/Bar.tsx
18
+ *
19
+ * Pure module — no IO. Caller composes the trailer lines into the commit
20
+ * message and runs git separately.
21
+ */
22
+ /**
23
+ * Format a list of upstream refs into trailer lines (one per ref).
24
+ * Pure: returns a single string with `\n` separators, no leading or
25
+ * trailing newline. Caller appends to the commit-message body.
26
+ */
27
+ export function formatTrailer(refs) {
28
+ return refs
29
+ .map((r) => `Tweaks-output-of: agent=${r.agent} sha=${r.sha.slice(0, 7)} file=${r.file}`)
30
+ .join("\n");
31
+ }
32
+ /**
33
+ * Parse a single trailer line, returning the parsed ref or null if the
34
+ * line doesn't match the expected shape. Tolerates leading/trailing
35
+ * whitespace + the optional " " indent some `git interpret-trailers`
36
+ * outputs add.
37
+ */
38
+ export function parseTrailerLine(line) {
39
+ const trimmed = line.trim();
40
+ if (!trimmed.startsWith("Tweaks-output-of:"))
41
+ return null;
42
+ const body = trimmed.slice("Tweaks-output-of:".length).trim();
43
+ // body looks like: "agent=vibe sha=abc1234 file=mock/src/X.tsx"
44
+ // file= may contain spaces in the rare case of weird paths; capture
45
+ // greedily as the last field.
46
+ const m = body.match(/^agent=(\S+)\s+sha=(\S+)\s+file=(.+)$/);
47
+ if (!m)
48
+ return null;
49
+ const [, agent, sha, file] = m;
50
+ if (!agent || !sha || !file)
51
+ return null;
52
+ return { agent, sha, file };
53
+ }
54
+ /**
55
+ * Parse all `Tweaks-output-of:` trailers from a full commit-message
56
+ * body (multi-line). Returns refs in the order they appeared.
57
+ */
58
+ export function parseTrailers(commitBody) {
59
+ const out = [];
60
+ for (const line of commitBody.split("\n")) {
61
+ const ref = parseTrailerLine(line);
62
+ if (ref)
63
+ out.push(ref);
64
+ }
65
+ return out;
66
+ }
67
+ /**
68
+ * Inspect a git author line + return the upstream agent name, if the
69
+ * author follows slowcook's agent convention. Otherwise null.
70
+ *
71
+ * Conventions detected:
72
+ * slowcook-vibe[bot] → "vibe"
73
+ * slowcook-plate[bot] → "plate"
74
+ * slowcook-brew[bot] → "brew"
75
+ * slowcook-chef[bot] → "chef"
76
+ * slowcook-refine[bot] → "refine"
77
+ * slowcook-testgen[bot] → "testgen"
78
+ * slowcook-recipe[bot] → "recipe"
79
+ * anything else → null (human or unrelated bot)
80
+ *
81
+ * Pure: no IO. Caller pipes git author through it.
82
+ */
83
+ export function agentFromAuthor(author) {
84
+ const m = author.match(/^slowcook-([a-z][\w-]*)\[bot\]$/);
85
+ if (!m)
86
+ return null;
87
+ return m[1];
88
+ }
89
+ /**
90
+ * Compose the full commit message body for a garnish commit: a one-line
91
+ * subject naming the touched files, a blank line, optional user-provided
92
+ * body, a blank line, then the trailer block.
93
+ *
94
+ * Pure: caller passes the staged-file list + the parsed upstream refs +
95
+ * the optional user message. Returns the body string ready for `git
96
+ * commit -F`.
97
+ */
98
+ export function composeCommitMessage(args) {
99
+ const subject = `[garnish] ${formatTouchedFilesSummary(args.touchedFiles)}`;
100
+ const parts = [subject, ""];
101
+ if (args.userMessage && args.userMessage.trim().length > 0) {
102
+ parts.push(args.userMessage.trim(), "");
103
+ }
104
+ if (args.upstreamRefs.length > 0) {
105
+ parts.push(formatTrailer(args.upstreamRefs));
106
+ }
107
+ return parts.join("\n");
108
+ }
109
+ function formatTouchedFilesSummary(files) {
110
+ if (files.length === 0)
111
+ return "(no files)";
112
+ if (files.length === 1)
113
+ return files[0];
114
+ if (files.length === 2)
115
+ return `${files[0]} + ${files[1]}`;
116
+ return `${files[0]} + ${files.length - 1} more`;
117
+ }
118
+ //# sourceMappingURL=trailer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trailer.js","sourceRoot":"","sources":["../../../src/commands/garnish/trailer.ts"],"names":[],"mappings":"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"}
@@ -52,7 +52,7 @@ export interface ReconResult {
52
52
  history_index_components: number;
53
53
  warnings: string[];
54
54
  }
55
- export declare function recon(argv: string[], _cliVersion: string): Promise<void>;
55
+ export declare function recon(argv: string[], cliVersion: string): Promise<void>;
56
56
  export declare function findStoryTestFiles(repoRoot: string, story: string): string[];
57
57
  export declare function extractImports(body: string): string[];
58
58
  export declare function extractTestids(body: string): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/recon/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAyClF,UAAU,cAAc;IACtB,IAAI,EAAE,WAAW,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,mBAAmB,GAAG,eAAe,GAAG,qBAAqB,GAAG,wBAAwB,CAAC;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,UAAU,CAAC;IAC/C,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,wBAAwB,EAAE,MAAM,CAAC;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA0FD,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0J9E;AAID,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAU5E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMrD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYrD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB1E;AAyBD,YAAY,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/recon/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AA0ClF,UAAU,cAAc;IACtB,IAAI,EAAE,WAAW,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,mBAAmB,GAAG,eAAe,GAAG,qBAAqB,GAAG,wBAAwB,CAAC;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,UAAU,CAAC;IAC/C,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,wBAAwB,EAAE,MAAM,CAAC;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA0FD,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0J7E;AAID,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAU5E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMrD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYrD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB1E;AAyBD,YAAY,EAAE,YAAY,EAAE,CAAC"}
@@ -25,6 +25,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
25
25
  import { dirname, join, relative } from "node:path";
26
26
  import { buildHistoryIndex } from "../refine/history-index.js";
27
27
  import { extractShape, synthesiseShapeTestFile, findMockFilesForStory } from "./shape-preserve.js";
28
+ import { isReadOnlyMode } from "../../lib/read-only.js";
28
29
  function parseArgs(argv) {
29
30
  const args = {
30
31
  story: "",
@@ -150,14 +151,14 @@ Exit codes:
150
151
  2 status=escalate (STORY_HISTORY_CONFLICT or VIBE_RECIPE_NAME_DRIFT)
151
152
  `);
152
153
  }
153
- export async function recon(argv, _cliVersion) {
154
+ export async function recon(argv, cliVersion) {
154
155
  const args = parseArgs(argv);
155
156
  if (args.reuseScan) {
156
157
  await runReuseScan(args);
157
158
  return;
158
159
  }
159
160
  if (args.stubScan) {
160
- await runStubScan(args);
161
+ await runStubScan(args, cliVersion);
161
162
  return;
162
163
  }
163
164
  console.log(`slowcook recon · story-${args.story} · cwd: ${relative(process.cwd(), args.repoRoot) || "."}`);
@@ -479,7 +480,7 @@ function walkTsFiles(root, out) {
479
480
  * reports stale ones + (when --stub-escalate) posts PM comment on each
480
481
  * stub's source issue.
481
482
  */
482
- async function runStubScan(args) {
483
+ async function runStubScan(args, cliVersion) {
483
484
  const { execSync } = await import("node:child_process");
484
485
  const { detectStubMarker, daysBetween, classifyStubAge, buildStaleStubComment, } = await import("./stale-stubs.js");
485
486
  console.log(`slowcook recon --stub-scan · root: ${args.stubRoot} · grace: ${args.stubMaxAgeDays} days · cwd: ${relative(process.cwd(), args.repoRoot) || "."}`);
@@ -539,7 +540,10 @@ async function runStubScan(args) {
539
540
  const story = s.storyId ? `story-${s.storyId}` : "(no story id)";
540
541
  console.log(` ${tag} ${ageStr} ${story.padEnd(14)} ${s.path}`);
541
542
  }
542
- if (args.stubEscalate && stale.length > 0) {
543
+ if (args.stubEscalate && stale.length > 0 && isReadOnlyMode()) {
544
+ console.log(`\n [SLOWCOOK_READ_ONLY=1] --stub-escalate set but read-only mode is on; would post comments on ${stale.length} source issues. Skipping.`);
545
+ }
546
+ else if (args.stubEscalate && stale.length > 0) {
543
547
  console.log("\n --stub-escalate set; posting PM comments on source issues...");
544
548
  let repoSlug = "";
545
549
  try {
@@ -570,7 +574,7 @@ async function runStubScan(args) {
570
574
  continue;
571
575
  }
572
576
  const issueNumber = parseInt(issueMatch[1], 10);
573
- const body = buildStaleStubComment(s, args.stubMaxAgeDays);
577
+ const body = buildStaleStubComment(s, args.stubMaxAgeDays, cliVersion);
574
578
  const tmp = "/tmp/slowcook-stale-stub-comment.md";
575
579
  writeFileSync(tmp, body, "utf8");
576
580
  try {