stonecut 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,19 +7,25 @@ A CLI that drives PRD-driven development with agentic coding CLIs. You write the
7
7
 
8
8
  ## Workflow
9
9
 
10
- Ideas can come from anywhere — Jira tickets, Slack threads, MCP servers, or just a conversation. The pipeline starts once you're ready to act on one:
10
+ Ideas can come from anywhere — Jira tickets, Slack threads, MCP servers, or just a conversation. The pipeline has two entry points depending on where work originates:
11
+
12
+ **From a raw idea:** `roadmap issue → interview → PRD → issues → execute`
11
13
 
12
14
  1. **`/stonecut-interview`** — Stress-test the idea. Get grilled on the plan until it's solid.
13
15
  2. **`/stonecut-prd`** — Turn the validated idea into a PRD (local file or GitHub issue).
14
16
  3. **`/stonecut-issues`** — Break the PRD into independently-grabbable issues (local markdown files or GitHub sub-issues).
15
- 4. **`stonecut import`** _(optional)_ — If the PRD lives in GitHub, import it and its issues into `.stonecut/`.
17
+ 4. **`stonecut import`** _(optional)_ — If the PRD lives in GitHub, import it and its issues into `.stonecut/specs/`.
16
18
  5. **`stonecut`** — Execute the issues sequentially with an agentic coding CLI.
17
19
 
20
+ **From a technical exploration:** `RFC issue → PRD → issues → execute`
21
+
22
+ Architecture decisions and refactoring plans that emerge from a technical investigation (e.g. a codebase review) skip the interview — the exploration itself produces the design decisions. Capture the outcome as an `rfc`-labeled GitHub issue, then write the PRD referencing it.
23
+
18
24
  Steps 1–3 are Claude Code skills installed via `stonecut setup-skills`. Steps 4–5 are the Stonecut CLI.
19
25
 
20
- ### Suggested: managing your idea backlog
26
+ ### Suggested: managing your backlog
21
27
 
22
- For projects using GitHub issues, we recommend tracking ideas with a `roadmap` label. When an idea is ready, interview it, write the PRD (which closes the roadmap issue), break it into sub-issues, import with `stonecut import --github`, and execute. See [DESIGN.md](DESIGN.md#suggested-practice-managing-your-idea-backlog-with-github-labels) for the full flow.
28
+ For projects using GitHub issues, we recommend tracking ideas with a `roadmap` label and architecture decisions with an `rfc` label. When an idea is ready, interview it, write the PRD (which closes the roadmap issue), break it into sub-issues, import with `stonecut import --github`, and execute. See [DESIGN.md](DESIGN.md#entry-points) for the full flow.
23
29
 
24
30
  ## Installation
25
31
 
@@ -77,7 +83,7 @@ Running bare `stonecut` starts the interactive run wizard — the primary workfl
77
83
 
78
84
  ### `stonecut` — Interactive wizard
79
85
 
80
- Stonecut uses a local-first execution model: `stonecut run` always reads from `.stonecut/<name>/`. External sources like GitHub are handled via `stonecut import`, which pulls PRDs and issues into the local structure before execution.
86
+ Stonecut uses a local-first execution model: `stonecut run` always reads from `.stonecut/specs/<name>/`. External sources like GitHub are handled via `stonecut import`, which pulls PRDs and issues into the local structure before execution.
81
87
 
82
88
  When flags are omitted, Stonecut prompts for each missing parameter:
83
89
 
@@ -94,7 +100,7 @@ stonecut -i all
94
100
 
95
101
  You can also use `stonecut run` explicitly — it's identical to bare `stonecut`.
96
102
 
97
- The wizard scans `.stonecut/*/` for local PRDs and presents them with completion counts (e.g. "my-feature (3/7 done)"). An "Import from GitHub" option is always available at the bottom of the list for importing PRDs inline.
103
+ The wizard scans `.stonecut/specs/*/` for local PRDs and presents them with completion counts (e.g. "my-feature (3/7 done)"). An "Import from GitHub" option is always available at the bottom of the list for importing PRDs inline.
98
104
 
99
105
  Flags provided via CLI skip the corresponding prompts. When all flags are given, the command runs without any prompts.
100
106
 
@@ -137,7 +143,7 @@ When config is present, the wizard uses these as default values — you can hit
137
143
 
138
144
  ### `stonecut import` — Import from external sources
139
145
 
140
- Pulls a PRD and its sub-issues from an external source into `.stonecut/<name>/` so they can be executed locally with `stonecut run`.
146
+ Pulls a PRD and its sub-issues from an external source into `.stonecut/specs/<name>/` so they can be executed locally with `stonecut run`.
141
147
 
142
148
  ```sh
143
149
  # Import GitHub PRD #42 — spec name derived from PRD title
@@ -155,7 +161,7 @@ The import command:
155
161
  1. Fetches the PRD content and title from the source.
156
162
  2. Fetches all sub-issues, ordered by number.
157
163
  3. Derives a local spec name from the PRD title (e.g. "Add user authentication" becomes `add-user-authentication`). Override with `--name`.
158
- 4. Writes `prd.md` and numbered issue files into `.stonecut/<name>/issues/`.
164
+ 4. Writes `prd.md` and numbered issue files into `.stonecut/specs/<name>/issues/`.
159
165
  5. Creates initial `status.json` and `progress.txt`.
160
166
 
161
167
  Import errors if the spec directory already exists (to avoid overwriting in-progress work). Use `--force` to overwrite intentionally.
@@ -214,7 +220,7 @@ When issues imported from GitHub are completed, Stonecut automatically closes th
214
220
 
215
221
  | Flag | Short | Required | Description |
216
222
  | -------------- | ----- | -------- | ------------------------------------------------------------------------ |
217
- | `--local` | — | No | Local PRD name (`.stonecut/<name>/`). Prompted if omitted. |
223
+ | `--local` | — | No | Local PRD name (`.stonecut/specs/<name>/`). Prompted if omitted. |
218
224
  | `--iterations` | `-i` | No | Positive integer or `all`. Prompted with default `all` if omitted. |
219
225
  | `--runner` | — | No | Agentic CLI runner (`claude`, `codex`). Default from config or `claude`. |
220
226
 
@@ -254,10 +260,10 @@ For imported PRDs, the PR body includes a `Closes #<number>` reference to the pa
254
260
 
255
261
  ## Local PRD structure
256
262
 
257
- All execution reads from `.stonecut/<name>/` directories with this structure:
263
+ All execution reads from `.stonecut/specs/<name>/` directories with this structure:
258
264
 
259
265
  ```
260
- .stonecut/my-feature/
266
+ .stonecut/specs/my-feature/
261
267
  ├── prd.md # The full PRD
262
268
  ├── issues/
263
269
  │ ├── 01-setup.md # Issue files, numbered for ordering
@@ -273,7 +279,7 @@ PRDs and issues are committed to git; runtime state (`status.json`, `progress.tx
273
279
 
274
280
  Issue and PRD files support YAML frontmatter for metadata. Locally-authored files don't need frontmatter — it's added automatically by `stonecut import`.
275
281
 
276
- **PRD frontmatter** (`.stonecut/<name>/prd.md`):
282
+ **PRD frontmatter** (`.stonecut/specs/<name>/prd.md`):
277
283
 
278
284
  ```markdown
279
285
  ---
@@ -285,7 +291,7 @@ title: Add user authentication
285
291
  The actual PRD content starts here...
286
292
  ```
287
293
 
288
- **Issue frontmatter** (`.stonecut/<name>/issues/01-setup.md`):
294
+ **Issue frontmatter** (`.stonecut/specs/<name>/issues/01-setup.md`):
289
295
 
290
296
  ```markdown
291
297
  ---
@@ -342,13 +348,13 @@ To add a new source provider (e.g. Jira, Linear):
342
348
 
343
349
  ## Skills
344
350
 
345
- The repo ships three Claude Code skills for steps 1–3 of the workflow. Install them with:
351
+ The repo ships four Claude Code skills for the workflow. Install them with:
346
352
 
347
353
  ```sh
348
354
  stonecut setup-skills
349
355
  ```
350
356
 
351
- This creates symlinks in `~/.claude/skills/` pointing to the installed package. Once linked, they're available as `/stonecut-interview`, `/stonecut-prd`, and `/stonecut-issues` in any Claude Code session.
357
+ This creates symlinks in `~/.claude/skills/` pointing to the installed package. Once linked, they're available as `/stonecut-interview`, `/stonecut-prd`, `/stonecut-issues`, and `/stonecut-review-architecture` in any Claude Code session.
352
358
 
353
359
  For non-default Claude Code installations, pass `--target` with the Claude root path:
354
360
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stonecut",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "CLI that drives PRD-driven development with agentic coding CLIs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,6 +27,7 @@
27
27
  "lint": "eslint src/ tests/",
28
28
  "format": "prettier --write .",
29
29
  "format:check": "prettier --check .",
30
+ "typecheck": "tsc --noEmit",
30
31
  "test": "bun test",
31
32
  "prepare": "husky"
32
33
  },
package/src/cli.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  /**
4
4
  * Stonecut CLI — PRD-driven development workflow orchestrator.
5
5
  *
6
+ * Commander definitions, validation helpers, wizard prompts, and entry point.
6
7
  * Modules throw errors; only this file catches them, formats user-facing
7
8
  * messages, and calls process.exit().
8
9
  */
@@ -10,27 +11,15 @@
10
11
  import * as clack from "@clack/prompts";
11
12
  import { Command, InvalidArgumentError } from "commander";
12
13
  import { createRequire } from "module";
13
- import {
14
- checkoutOrCreateBranch,
15
- createPr,
16
- defaultBranch,
17
- ensureCleanTree,
18
- pushBranch,
19
- } from "./git";
20
- import { LocalSource } from "./local";
14
+ import { defaultBranch, ensureCleanTree } from "./git";
21
15
  import { slugifyBranchComponent } from "./naming";
22
- import { renderLocal } from "./prompt";
23
- import { Logger } from "./logger";
24
- import { defaultGitOps, runAfkLoop } from "./runner";
25
- import { getRunner } from "./runners/index";
26
16
  import { setupSkills, removeSkills } from "./skills";
27
17
  import { init } from "./init";
28
18
  import { importSpec } from "./import";
29
19
  import { loadConfig } from "./config";
30
- import { getSourceProvider } from "./sources/index";
31
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
32
- import { join } from "path";
33
- import type { Issue, IterationResult, Session } from "./types";
20
+ import { existsSync } from "fs";
21
+ import { promptForPrd } from "./prd";
22
+ import { executeLocal } from "./execute";
34
23
 
35
24
  const require = createRequire(import.meta.url);
36
25
  const { version } = require("../package.json");
@@ -75,294 +64,6 @@ export function validateRunSource(
75
64
  return { kind: "prompt" };
76
65
  }
77
66
 
78
- /** A locally available PRD with its completion status. */
79
- export interface LocalPrdEntry {
80
- name: string;
81
- completed: number;
82
- total: number;
83
- }
84
-
85
- /**
86
- * Scan .stonecut subdirectories for directories containing prd.md and compute
87
- * completion counts from status.json.
88
- */
89
- export function scanLocalPrds(baseDir: string = ".stonecut"): LocalPrdEntry[] {
90
- if (!existsSync(baseDir)) return [];
91
-
92
- const entries: LocalPrdEntry[] = [];
93
- for (const name of readdirSync(baseDir)) {
94
- const specDir = join(baseDir, name);
95
- if (!statSync(specDir).isDirectory()) continue;
96
- if (!existsSync(join(specDir, "prd.md"))) continue;
97
-
98
- const issuesDir = join(specDir, "issues");
99
- let total = 0;
100
- if (existsSync(issuesDir) && statSync(issuesDir).isDirectory()) {
101
- total = readdirSync(issuesDir).filter((f) => f.endsWith(".md")).length;
102
- }
103
-
104
- let completed = 0;
105
- const statusPath = join(specDir, "status.json");
106
- if (existsSync(statusPath)) {
107
- try {
108
- const data = JSON.parse(readFileSync(statusPath, "utf-8"));
109
- completed = Array.isArray(data.completed) ? data.completed.length : 0;
110
- } catch {
111
- // Malformed status.json — treat as 0 completed
112
- }
113
- }
114
-
115
- entries.push({ name, completed, total });
116
- }
117
- return entries.sort((a, b) => a.name.localeCompare(b.name));
118
- }
119
-
120
- /**
121
- * Format a PRD entry for display in the wizard.
122
- */
123
- function formatPrdOption(entry: LocalPrdEntry): string {
124
- if (entry.total === 0) return `${entry.name} (no issues)`;
125
- if (entry.completed === 0) return `${entry.name} (not started)`;
126
- return `${entry.name} (${entry.completed}/${entry.total} done)`;
127
- }
128
-
129
- /** Sentinel value for the "Import from GitHub" wizard option. */
130
- const IMPORT_FROM_GITHUB = "__import_from_github__";
131
-
132
- /**
133
- * Prompt the user to select a local PRD or import from GitHub.
134
- * Returns the local spec name to run.
135
- */
136
- export async function promptForPrd(): Promise<{ kind: "local"; name: string }> {
137
- const prds = scanLocalPrds();
138
-
139
- const options: Array<{ value: string; label: string }> = prds.map((entry) => ({
140
- value: entry.name,
141
- label: formatPrdOption(entry),
142
- }));
143
- options.push({ value: IMPORT_FROM_GITHUB, label: "Import from GitHub" });
144
-
145
- const selection = await clack.select({
146
- message: "Select a PRD:",
147
- options,
148
- });
149
-
150
- if (clack.isCancel(selection)) {
151
- throw new Error("Cancelled.");
152
- }
153
-
154
- if (selection === IMPORT_FROM_GITHUB) {
155
- const specName = await inlineGitHubImport();
156
- return { kind: "local", name: specName };
157
- }
158
-
159
- return { kind: "local", name: selection as string };
160
- }
161
-
162
- /**
163
- * Inline GitHub import flow within the wizard.
164
- * Lists PRDs by `prd` label, user picks one, import runs.
165
- * Returns the imported spec name.
166
- */
167
- async function inlineGitHubImport(): Promise<string> {
168
- const provider = getSourceProvider("github");
169
- const prdList = await provider.listPrds();
170
-
171
- if (prdList.length === 0) {
172
- throw new Error("No open PRDs with the 'prd' label found on GitHub.");
173
- }
174
-
175
- const prdOptions = prdList.map((p) => ({
176
- value: String(p.number),
177
- label: `#${p.number}: ${p.title}`,
178
- }));
179
-
180
- const selected = await clack.select({
181
- message: "Select a GitHub PRD to import:",
182
- options: prdOptions,
183
- });
184
-
185
- if (clack.isCancel(selected)) {
186
- throw new Error("Cancelled.");
187
- }
188
-
189
- const result = await importSpec({
190
- provider: "github",
191
- identifier: selected as string,
192
- });
193
-
194
- console.log(
195
- `Imported PRD #${result.prdIssueNumber} → ${result.specDir}/ (${result.issueCount} issues)`,
196
- );
197
-
198
- return result.specName;
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // Stonecut report
203
- // ---------------------------------------------------------------------------
204
-
205
- /**
206
- * Build the Stonecut Report section for a PR body.
207
- */
208
- export function buildReport(
209
- results: IterationResult[],
210
- runnerName: string,
211
- prdNumber?: number,
212
- ): string {
213
- const lines = ["## Stonecut Report", `**Runner:** ${runnerName}`, ""];
214
- for (const r of results) {
215
- if (r.success) {
216
- lines.push(`- #${r.issueNumber} ${r.issueFilename}: completed`);
217
- } else {
218
- const reason = r.error || "unknown error";
219
- lines.push(`- #${r.issueNumber} ${r.issueFilename}: failed — ${reason}`);
220
- }
221
- }
222
-
223
- if (prdNumber !== undefined && results.every((r) => r.success)) {
224
- lines.push("");
225
- lines.push(`Closes #${prdNumber}`);
226
- }
227
-
228
- return lines.join("\n");
229
- }
230
-
231
- // ---------------------------------------------------------------------------
232
- // Pre-execution flow
233
- // ---------------------------------------------------------------------------
234
-
235
- /**
236
- * Run pre-execution prompts and git checks.
237
- * Returns [branch, baseBranch].
238
- */
239
- export async function preExecution(
240
- suggestedBranch: string,
241
- prefilled?: { branch?: string; baseBranch?: string },
242
- ): Promise<[string, string]> {
243
- ensureCleanTree();
244
-
245
- let branch: string;
246
- if (prefilled?.branch) {
247
- branch = prefilled.branch;
248
- } else {
249
- const branchInput = await clack.text({
250
- message: "Branch name:",
251
- defaultValue: suggestedBranch,
252
- placeholder: suggestedBranch,
253
- });
254
-
255
- if (clack.isCancel(branchInput)) {
256
- throw new Error("Cancelled.");
257
- }
258
- branch = branchInput;
259
- }
260
-
261
- let baseBranch: string;
262
- if (prefilled?.baseBranch) {
263
- baseBranch = prefilled.baseBranch;
264
- } else {
265
- const detectedDefault = defaultBranch();
266
- const baseBranchInput = await clack.text({
267
- message: "Base branch / PR target:",
268
- defaultValue: detectedDefault,
269
- placeholder: detectedDefault,
270
- });
271
-
272
- if (clack.isCancel(baseBranchInput)) {
273
- throw new Error("Cancelled.");
274
- }
275
- baseBranch = baseBranchInput;
276
- }
277
-
278
- checkoutOrCreateBranch(branch);
279
- console.log("");
280
-
281
- return [branch, baseBranch];
282
- }
283
-
284
- // ---------------------------------------------------------------------------
285
- // Post-loop: push and conditionally create PR
286
- // ---------------------------------------------------------------------------
287
-
288
- export async function pushAndMaybePr(
289
- results: IterationResult[],
290
- source: { getRemainingCount(): Promise<[number, number]> },
291
- branch: string,
292
- baseBranch: string,
293
- prTitle: string,
294
- runnerName: string,
295
- logger: { log(message: string): void },
296
- prdNumber?: number,
297
- ): Promise<void> {
298
- if (!results.some((r) => r.success)) {
299
- return;
300
- }
301
-
302
- pushBranch(branch);
303
- logger.log(`Pushed branch '${branch}'.`);
304
-
305
- const [remaining, total] = await source.getRemainingCount();
306
- if (remaining === 0) {
307
- const body = buildReport(results, runnerName, prdNumber);
308
- createPr(prTitle, body, baseBranch);
309
- logger.log("Created PR.");
310
- } else {
311
- logger.log(`${remaining}/${total} issues remaining — PR deferred.`);
312
- }
313
- }
314
-
315
- // ---------------------------------------------------------------------------
316
- // Execution paths
317
- // ---------------------------------------------------------------------------
318
-
319
- export async function runLocal(
320
- name: string,
321
- iterations: number | "all",
322
- runnerName: string,
323
- prefilled?: { branch?: string; baseBranch?: string },
324
- ): Promise<void> {
325
- const runner = getRunner(runnerName);
326
- const source = new LocalSource(name);
327
- const prdIdentifier = slugifyBranchComponent(name) || "spec";
328
- const logger = new Logger(prdIdentifier);
329
-
330
- const session: Session = { logger, git: defaultGitOps, runner, runnerName };
331
-
332
- try {
333
- const suggestedBranch = prdIdentifier ? `stonecut/${prdIdentifier}` : "stonecut/spec";
334
- const [branch, baseBranch] = await preExecution(suggestedBranch, prefilled);
335
-
336
- const prdContent = await source.getPrdContent();
337
- const results = await runAfkLoop<Issue>(
338
- source,
339
- iterations,
340
- (issue) =>
341
- renderLocal({
342
- prdContent,
343
- issueNumber: issue.number,
344
- issueFilename: issue.filename,
345
- issueContent: issue.content,
346
- }),
347
- (issue) => issue.filename,
348
- (issue) => `Issue ${issue.number}: ${issue.filename}`,
349
- session,
350
- );
351
-
352
- await pushAndMaybePr(
353
- results,
354
- source,
355
- branch,
356
- baseBranch,
357
- `Stonecut: ${name}`,
358
- runnerName,
359
- logger,
360
- );
361
- } finally {
362
- logger.close();
363
- }
364
- }
365
-
366
67
  // ---------------------------------------------------------------------------
367
68
  // Program definition
368
69
  // ---------------------------------------------------------------------------
@@ -380,15 +81,17 @@ export function buildProgram(): Command {
380
81
  program
381
82
  .command("run", { isDefault: true })
382
83
  .description("Execute issues from a local PRD.")
383
- .option("--local <name>", "Local PRD name (.stonecut/<name>/)")
84
+ .option("--local <name>", "Local PRD name (.stonecut/specs/<name>/)")
384
85
  .option("-i, --iterations <value>", "Number of issues to process, or 'all'")
86
+ .option("--branch <name>", "Branch name for the run")
87
+ .option("--base-branch <name>", "Base branch / PR target")
385
88
  .option("--runner <name>", "Agentic CLI runner (claude, codex)")
386
89
  .action(async (opts) => {
90
+ ensureCleanTree();
387
91
  const config = loadConfig();
388
92
 
389
93
  const validated = validateRunSource(opts.local);
390
94
  let source: { kind: "local"; name: string };
391
-
392
95
  if (validated.kind === "local") {
393
96
  source = validated;
394
97
  } else {
@@ -419,40 +122,45 @@ export function buildProgram(): Command {
419
122
  }
420
123
 
421
124
  const isWizard = validated.kind === "prompt" || needsIterationPrompt;
422
- let prefilled: { branch?: string; baseBranch?: string } | undefined;
423
-
424
- if (isWizard) {
425
- if (!existsSync(".stonecut")) {
426
- console.log("Hint: run `stonecut init` to set up project config and gitignore.\n");
427
- }
125
+ if (isWizard && !existsSync(".stonecut")) {
126
+ console.log("Hint: run `stonecut init` to set up project config and gitignore.\n");
127
+ }
428
128
 
129
+ let branch: string;
130
+ if (opts.branch) {
131
+ branch = opts.branch;
132
+ } else {
429
133
  const branchPrefix = config?.branchPrefix ?? "stonecut/";
430
134
  const suggestedBranch = `${branchPrefix}${slugifyBranchComponent(source.name) || "spec"}`;
431
-
432
- const branch = await clack.text({
135
+ const branchInput = await clack.text({
433
136
  message: "Branch name:",
434
137
  defaultValue: suggestedBranch,
435
138
  placeholder: suggestedBranch,
436
139
  });
437
- if (clack.isCancel(branch)) {
140
+ if (clack.isCancel(branchInput)) {
438
141
  throw new Error("Cancelled.");
439
142
  }
143
+ branch = branchInput;
144
+ }
440
145
 
146
+ let baseBranch: string;
147
+ if (opts.baseBranch) {
148
+ baseBranch = opts.baseBranch;
149
+ } else {
441
150
  const detectedDefault = config?.baseBranch ?? defaultBranch();
442
- const baseBranch = await clack.text({
151
+ const baseBranchInput = await clack.text({
443
152
  message: "Base branch / PR target:",
444
153
  defaultValue: detectedDefault,
445
154
  placeholder: detectedDefault,
446
155
  });
447
- if (clack.isCancel(baseBranch)) {
156
+ if (clack.isCancel(baseBranchInput)) {
448
157
  throw new Error("Cancelled.");
449
158
  }
450
-
451
- prefilled = { branch, baseBranch };
159
+ baseBranch = baseBranchInput;
452
160
  }
453
161
 
454
162
  const runnerName: string = opts.runner ?? config?.runner ?? "claude";
455
- await runLocal(source.name, iterations, runnerName, prefilled);
163
+ await executeLocal(source.name, branch, baseBranch, iterations, runnerName);
456
164
  });
457
165
 
458
166
  program
@@ -473,14 +181,12 @@ export function buildProgram(): Command {
473
181
  if (opts.github === undefined) {
474
182
  throw new Error("Specify a source: --github <number>");
475
183
  }
476
-
477
184
  const result = await importSpec({
478
185
  provider: "github",
479
186
  identifier: String(opts.github),
480
187
  name: opts.name,
481
188
  force: opts.force,
482
189
  });
483
-
484
190
  console.log(
485
191
  `Imported PRD #${result.prdIssueNumber} → ${result.specDir}/ (${result.issueCount} issues)`,
486
192
  );
@@ -495,12 +201,8 @@ export function buildProgram(): Command {
495
201
  )
496
202
  .action((opts) => {
497
203
  const result = setupSkills(opts.target);
498
- for (const msg of result.messages) {
499
- console.log(msg);
500
- }
501
- for (const warn of result.warnings) {
502
- console.error(warn);
503
- }
204
+ for (const msg of result.messages) console.log(msg);
205
+ for (const warn of result.warnings) console.error(warn);
504
206
  });
505
207
 
506
208
  program
@@ -512,12 +214,8 @@ export function buildProgram(): Command {
512
214
  )
513
215
  .action((opts) => {
514
216
  const result = removeSkills(opts.target);
515
- for (const msg of result.messages) {
516
- console.log(msg);
517
- }
518
- for (const warn of result.warnings) {
519
- console.error(warn);
520
- }
217
+ for (const msg of result.messages) console.log(msg);
218
+ for (const warn of result.warnings) console.error(warn);
521
219
  });
522
220
 
523
221
  return program;