@warpgogol/forge 2.9.2 → 2.9.5

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 (181) hide show
  1. package/README.md +14 -8
  2. package/README.uk.md +14 -0
  3. package/dist/bin/cli.js +0 -0
  4. package/dist/os/adr/handlers/implement-stamp.d.ts.map +1 -1
  5. package/dist/os/adr/handlers/implement-stamp.js +7 -1
  6. package/dist/os/adr/handlers/implement-stamp.js.map +1 -1
  7. package/dist/os/adr/handlers/list-create.d.ts.map +1 -1
  8. package/dist/os/adr/handlers/list-create.js +40 -4
  9. package/dist/os/adr/handlers/list-create.js.map +1 -1
  10. package/dist/os/adr/handlers/validate.d.ts.map +1 -1
  11. package/dist/os/adr/handlers/validate.js +17 -3
  12. package/dist/os/adr/handlers/validate.js.map +1 -1
  13. package/dist/os/adr/types.d.ts +1 -0
  14. package/dist/os/adr/types.d.ts.map +1 -1
  15. package/dist/os/adr/types.js +1 -0
  16. package/dist/os/adr/types.js.map +1 -1
  17. package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
  18. package/dist/os/compass/handlers/compass-audit-handler.js +22 -0
  19. package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
  20. package/dist/os/compass/handlers/compass-change-summary-handler.d.ts.map +1 -1
  21. package/dist/os/compass/handlers/compass-change-summary-handler.js +6 -0
  22. package/dist/os/compass/handlers/compass-change-summary-handler.js.map +1 -1
  23. package/dist/os/compass/handlers/compass-inventory-handler.d.ts.map +1 -1
  24. package/dist/os/compass/handlers/compass-inventory-handler.js +19 -0
  25. package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
  26. package/dist/os/core/handlers/package-health.d.ts.map +1 -1
  27. package/dist/os/core/handlers/package-health.js +8 -4
  28. package/dist/os/core/handlers/package-health.js.map +1 -1
  29. package/dist/os/core/handlers/pinned-validate.d.ts.map +1 -1
  30. package/dist/os/core/handlers/pinned-validate.js +2 -2
  31. package/dist/os/core/handlers/pinned-validate.js.map +1 -1
  32. package/dist/os/exploration/handlers/archive.d.ts.map +1 -1
  33. package/dist/os/exploration/handlers/archive.js +6 -0
  34. package/dist/os/exploration/handlers/archive.js.map +1 -1
  35. package/dist/os/exploration/handlers/list.d.ts.map +1 -1
  36. package/dist/os/exploration/handlers/list.js +8 -0
  37. package/dist/os/exploration/handlers/list.js.map +1 -1
  38. package/dist/os/exploration/handlers/show.d.ts.map +1 -1
  39. package/dist/os/exploration/handlers/show.js +6 -0
  40. package/dist/os/exploration/handlers/show.js.map +1 -1
  41. package/dist/os/program/handlers/complete.d.ts.map +1 -1
  42. package/dist/os/program/handlers/complete.js +19 -1
  43. package/dist/os/program/handlers/complete.js.map +1 -1
  44. package/dist/os/program/handlers/lease.js +7 -1
  45. package/dist/os/program/handlers/lease.js.map +1 -1
  46. package/dist/os/program/handlers/seal.d.ts.map +1 -1
  47. package/dist/os/program/handlers/seal.js +13 -1
  48. package/dist/os/program/handlers/seal.js.map +1 -1
  49. package/dist/os/program/handlers/validate.js +8 -2
  50. package/dist/os/program/handlers/validate.js.map +1 -1
  51. package/dist/os/rfc/acceptance.d.ts.map +1 -1
  52. package/dist/os/rfc/acceptance.js +8 -0
  53. package/dist/os/rfc/acceptance.js.map +1 -1
  54. package/dist/os/rfc/dna-trace.js +1 -1
  55. package/dist/os/rfc/dna-trace.js.map +1 -1
  56. package/dist/os/rfc/handlers/check.d.ts.map +1 -1
  57. package/dist/os/rfc/handlers/check.js +10 -2
  58. package/dist/os/rfc/handlers/check.js.map +1 -1
  59. package/dist/os/rfc/handlers/implement-stamp.d.ts.map +1 -1
  60. package/dist/os/rfc/handlers/implement-stamp.js +7 -1
  61. package/dist/os/rfc/handlers/implement-stamp.js.map +1 -1
  62. package/dist/os/rfc/handlers/index-graph.js +4 -4
  63. package/dist/os/rfc/handlers/index-graph.js.map +1 -1
  64. package/dist/os/rfc/handlers/lifecycle.js +1 -1
  65. package/dist/os/rfc/handlers/lifecycle.js.map +1 -1
  66. package/dist/os/rfc/handlers/list-create.d.ts.map +1 -1
  67. package/dist/os/rfc/handlers/list-create.js +20 -0
  68. package/dist/os/rfc/handlers/list-create.js.map +1 -1
  69. package/dist/os/rfc/handlers/validate.d.ts.map +1 -1
  70. package/dist/os/rfc/handlers/validate.js +17 -3
  71. package/dist/os/rfc/handlers/validate.js.map +1 -1
  72. package/dist/os/session/handlers/validate.d.ts.map +1 -1
  73. package/dist/os/session/handlers/validate.js +17 -3
  74. package/dist/os/session/handlers/validate.js.map +1 -1
  75. package/dist/os/spec/live-spec-list.d.ts.map +1 -1
  76. package/dist/os/spec/live-spec-list.js +8 -0
  77. package/dist/os/spec/live-spec-list.js.map +1 -1
  78. package/dist/os/spec/live-spec-merge.js +2 -2
  79. package/dist/os/spec/live-spec-merge.js.map +1 -1
  80. package/dist/os/spec/live-spec-show.d.ts.map +1 -1
  81. package/dist/os/spec/live-spec-show.js +6 -0
  82. package/dist/os/spec/live-spec-show.js.map +1 -1
  83. package/dist/os/spec/live-spec-validate.js +2 -2
  84. package/dist/os/spec/live-spec-validate.js.map +1 -1
  85. package/dist/os/spec/spec-materialize.d.ts.map +1 -1
  86. package/dist/os/spec/spec-materialize.js +6 -0
  87. package/dist/os/spec/spec-materialize.js.map +1 -1
  88. package/dist/os/spec/spec-status.d.ts.map +1 -1
  89. package/dist/os/spec/spec-status.js +6 -0
  90. package/dist/os/spec/spec-status.js.map +1 -1
  91. package/dist/os/spec/spec-validate.js +1 -1
  92. package/dist/os/spec/spec-validate.js.map +1 -1
  93. package/dist/os/werkstatt/handlers/werkstatt-lock-recover.d.ts.map +1 -1
  94. package/dist/os/werkstatt/handlers/werkstatt-lock-recover.js +8 -0
  95. package/dist/os/werkstatt/handlers/werkstatt-lock-recover.js.map +1 -1
  96. package/dist/os/werkstatt/handlers/werkstatt-lock-status.d.ts.map +1 -1
  97. package/dist/os/werkstatt/handlers/werkstatt-lock-status.js +8 -0
  98. package/dist/os/werkstatt/handlers/werkstatt-lock-status.js.map +1 -1
  99. package/dist/os/workflow/handlers.d.ts.map +1 -1
  100. package/dist/os/workflow/handlers.js +9 -1
  101. package/dist/os/workflow/handlers.js.map +1 -1
  102. package/dist/src/onboarding/create.js +1 -1
  103. package/dist/src/onboarding/create.js.map +1 -1
  104. package/dist/src/onboarding/doctor.js +1 -1
  105. package/dist/src/onboarding/doctor.js.map +1 -1
  106. package/dist/src/onboarding/init.d.ts.map +1 -1
  107. package/dist/src/onboarding/init.js +31 -1
  108. package/dist/src/onboarding/init.js.map +1 -1
  109. package/dist/src/onboarding/profile-validate.js +1 -1
  110. package/dist/src/onboarding/profile-validate.js.map +1 -1
  111. package/dist/src/utils/fs-trash.d.ts.map +1 -1
  112. package/dist/src/utils/fs-trash.js +1 -1
  113. package/dist/src/utils/fs-trash.js.map +1 -1
  114. package/dist/src/validators/note-frontmatter-validate.js +1 -1
  115. package/dist/src/validators/note-frontmatter-validate.js.map +1 -1
  116. package/dist/src/validators/note-link-validate.js +1 -1
  117. package/dist/src/validators/note-link-validate.js.map +1 -1
  118. package/os/adr/adr-0000-template.md +46 -0
  119. package/os/adr/handlers/implement-stamp.ts +7 -1
  120. package/os/adr/handlers/list-create.ts +48 -4
  121. package/os/adr/handlers/validate.ts +15 -3
  122. package/os/adr/types.ts +1 -0
  123. package/os/compass/handlers/compass-audit-handler.ts +24 -0
  124. package/os/compass/handlers/compass-change-summary-handler.ts +6 -0
  125. package/os/compass/handlers/compass-inventory-handler.ts +19 -0
  126. package/os/core/handlers/package-health.ts +9 -5
  127. package/os/core/handlers/pinned-validate.ts +4 -2
  128. package/os/exploration/handlers/archive.ts +6 -0
  129. package/os/exploration/handlers/list.ts +9 -0
  130. package/os/exploration/handlers/show.ts +6 -0
  131. package/os/program/handlers/complete.ts +19 -1
  132. package/os/program/handlers/lease.ts +7 -1
  133. package/os/program/handlers/seal.ts +13 -1
  134. package/os/program/handlers/validate.ts +8 -2
  135. package/os/rfc/acceptance.ts +9 -0
  136. package/os/rfc/dna-trace.ts +1 -1
  137. package/os/rfc/handlers/check.ts +10 -2
  138. package/os/rfc/handlers/implement-stamp.ts +7 -1
  139. package/os/rfc/handlers/index-graph.ts +4 -4
  140. package/os/rfc/handlers/lifecycle.ts +1 -1
  141. package/os/rfc/handlers/list-create.ts +21 -0
  142. package/os/rfc/handlers/validate.ts +15 -3
  143. package/os/rfc/rfc-0000-template.md +16 -20
  144. package/os/session/handlers/validate.ts +15 -3
  145. package/os/spec/live-spec-list.ts +8 -0
  146. package/os/spec/live-spec-merge.ts +2 -2
  147. package/os/spec/live-spec-show.ts +6 -0
  148. package/os/spec/live-spec-validate.ts +2 -2
  149. package/os/spec/spec-materialize.ts +6 -0
  150. package/os/spec/spec-status.ts +6 -0
  151. package/os/spec/spec-validate.ts +1 -1
  152. package/os/werkstatt/handlers/werkstatt-lock-recover.ts +9 -0
  153. package/os/werkstatt/handlers/werkstatt-lock-status.ts +9 -0
  154. package/os/workflow/handlers.ts +10 -1
  155. package/package.json +73 -195
  156. package/skills/_shared/fo-pipeline-conventions.md +44 -5
  157. package/skills/_shared/fo-prerequisites.md +81 -0
  158. package/skills/fo/fo-extract-dna/SKILL.md +1 -1
  159. package/skills/fo/fo-idea-audit/AUDIT-AXES.md +66 -0
  160. package/skills/fo/fo-idea-audit/SKILL.md +2 -2
  161. package/skills/fo/fo-idea-create-adr/SKILL.md +1 -1
  162. package/skills/fo/fo-idea-create-rfc/SKILL.md +3 -3
  163. package/skills/fo/fo-idea-enhance/SKILL.md +2 -2
  164. package/skills/fo/fo-idea-implement/ADR-FLOW.md +179 -0
  165. package/skills/fo/fo-idea-implement/SKILL.md +1 -1
  166. package/skills/fo/fo-idea-plan/SKILL.md +4 -2
  167. package/skills/fo/fo-review/AXES.md +70 -0
  168. package/skills/fo/fo-session-retro/learned-principles.md +15 -0
  169. package/skills/fo/fo-step-commit/SKILL.md +2 -2
  170. package/skills/shared/windows-ai-tooling/.gitattributes.template +49 -0
  171. package/skills/shared/windows-ai-tooling/AGENTS_WINDOWS.md.template +42 -0
  172. package/skills/shared/writing-great-skills/GLOSSARY.md +201 -0
  173. package/src/onboarding/create.ts +1 -1
  174. package/src/onboarding/doctor.ts +1 -1
  175. package/src/onboarding/init.ts +30 -1
  176. package/src/onboarding/profile-validate.ts +1 -1
  177. package/src/tests/package-files.test.ts +5 -2
  178. package/src/tests/stack-profile.test.ts +4 -0
  179. package/src/utils/fs-trash.ts +1 -1
  180. package/src/validators/note-frontmatter-validate.ts +1 -1
  181. package/src/validators/note-link-validate.ts +1 -1
@@ -0,0 +1,46 @@
1
+ ---
2
+ id: ADR-0000
3
+ title: "Short imperative title"
4
+ # Lifecycle:
5
+ # proposed → reviewing → accepted → implemented
6
+ # any → superseded (requires supersededBy)
7
+ # any → rejected
8
+ status: proposed
9
+ scope: package
10
+ decider: architecture
11
+ createdAt: YYYY-MM-DD
12
+ updatedAt: YYYY-MM-DD
13
+ implementedAt:
14
+ closedAt:
15
+ supersedes: []
16
+ supersededBy:
17
+ related: []
18
+ # Set by the deciding human together with the status change.
19
+ # Draft scaffolds must keep this empty; do not prefill a default identity.
20
+ # Format: human:<handle>
21
+ reviewers: []
22
+ ---
23
+
24
+ # ADR-0000: Short imperative title
25
+
26
+ ## Context
27
+
28
+ Brief description of the local situation, constraints, and RFC or DNA context that bounds the decision. Name actual packages, files, or components.
29
+
30
+ ## Decision
31
+
32
+ A single sentence in the present tense stating the decision as a fact. Add 1-3 bullets clarifying scope or boundaries if needed.
33
+
34
+ ## Justification
35
+
36
+ Forces and trade-offs that led to the decision: alternatives considered, constraints, alignment with existing architecture.
37
+
38
+ ## Consequences
39
+
40
+ - Positive: what becomes simpler, safer, cheaper, or more predictable.
41
+ - Negative: trade-offs, limitations, operational overhead.
42
+ - Technical debt: what is knowingly postponed.
43
+
44
+ ## Evolution
45
+
46
+ How the decision may change: thresholds that trigger revisiting it, metrics to watch, and — for post-hoc ADRs — references to commits or PRs that implemented it.
@@ -301,6 +301,12 @@ export async function runAdrImplementStamp(
301
301
  },
302
302
  exitCode: 0,
303
303
  summary: `adr.implement.stamp: ${targetId} stamped as implemented`,
304
+ nextSteps: [
305
+ {
306
+ action: `Commit the stamp: pnpm exec forge run ecosystem.commit`,
307
+ kind: "optional",
308
+ },
309
+ ],
304
310
  };
305
311
  } finally {
306
312
  await releaseAdrLock(lockPath);
@@ -326,6 +332,6 @@ function stampFailResult(
326
332
  violations,
327
333
  },
328
334
  exitCode: 1,
329
- summary: `${prefix}adr.implement.stamp: ${violations.length} violation(s)`,
335
+ summary: `${prefix}adr.implement.stamp: ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
330
336
  };
331
337
  }
@@ -12,8 +12,11 @@
12
12
  */
13
13
 
14
14
  import fs from "node:fs/promises";
15
+ import { existsSync } from "node:fs";
15
16
  import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
16
18
  import { toKebabCase } from "../../../src/utils/string-utils.ts";
19
+ import { resolveForgeRoot } from "../../../src/config/forge-config.ts";
17
20
  import { listAdrFiles, readAndParseAdr } from "../frontmatter-io.ts";
18
21
  import type {
19
22
  ForgeCommandInput,
@@ -27,7 +30,13 @@ import type {
27
30
  AdrStatus,
28
31
  AdrScope,
29
32
  } from "../types.ts";
30
- import { ADR_DIR, ADR_TEMPLATE_FILE, ADR_SCOPES, ADR_STATUSES } from "../types.ts";
33
+ import {
34
+ ADR_DIR,
35
+ ADR_TEMPLATE_FILE,
36
+ ADR_TEMPLATE_FALLBACK_FILE,
37
+ ADR_SCOPES,
38
+ ADR_STATUSES,
39
+ } from "../types.ts";
31
40
 
32
41
  function toIsoDate(d: Date): string {
33
42
  return d.toISOString().slice(0, 10);
@@ -137,14 +146,13 @@ export async function runAdrCreate(
137
146
  const nextSlug = `adr-${paddedNum}`;
138
147
  const nextId = `ADR-${paddedNum}`;
139
148
 
140
- const templateRelPath = ADR_TEMPLATE_FILE;
141
- const templatePath = path.join(workspaceRoot, templateRelPath);
149
+ const templatePath = resolveAdrTemplate(workspaceRoot);
142
150
  let templateContent: string;
143
151
  try {
144
152
  templateContent = await fs.readFile(templatePath, "utf-8");
145
153
  } catch {
146
154
  throw new Error(
147
- `ADR template not found at ${templateRelPath}. Ensure docs/adrs/ contains the template.`,
155
+ `ADR template not found at ${templatePath}. Ensure docs/adrs/ contains the template.`,
148
156
  );
149
157
  }
150
158
 
@@ -205,3 +213,39 @@ export async function runAdrCreate(
205
213
  summary: `Created ${nextId}: ${title}`,
206
214
  };
207
215
  }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // ADR template resolver — finds adr-0000-template.md from project docs/adrs/
219
+ // first (canonical location), then falls back to the forge package copy
220
+ // (npm consumers without a project-level template).
221
+ // ---------------------------------------------------------------------------
222
+
223
+ function resolveAdrTemplate(workspaceRoot: string): string {
224
+ // 1. Try project-level docs/adrs/adr-0000-template.md (canonical)
225
+ const projectPath = path.join(workspaceRoot, ADR_TEMPLATE_FILE);
226
+ if (existsSync(projectPath)) return projectPath;
227
+
228
+ // 2. Try forge root (monorepo or npm-installed) — fallback copy
229
+ try {
230
+ const forgeRoot = resolveForgeRoot(workspaceRoot);
231
+ const p = path.join(forgeRoot, ADR_TEMPLATE_FALLBACK_FILE);
232
+ if (existsSync(p)) return p;
233
+ } catch {
234
+ // forge root not resolvable — fall through to module-relative search
235
+ }
236
+
237
+ // 3. Fallback: walk up from this module's location to find os/adr/adr-0000-template.md
238
+ const here = path.dirname(fileURLToPath(import.meta.url));
239
+ let dir = here;
240
+ for (let i = 0; i < 10; i++) {
241
+ const candidate = path.join(dir, "os", "adr", "adr-0000-template.md");
242
+ if (existsSync(candidate)) return candidate;
243
+ const parent = path.dirname(dir);
244
+ if (parent === dir) break;
245
+ dir = parent;
246
+ }
247
+
248
+ throw new Error(
249
+ `ADR template not found. Tried ${ADR_TEMPLATE_FILE} relative to ${workspaceRoot}, forge root, and module-relative search from ${here}.`,
250
+ );
251
+ }
@@ -135,10 +135,14 @@ export async function runAdrValidate(
135
135
  }
136
136
  const errorCount = violations.filter((v) => v.severity === "error").length;
137
137
  const warnCount = violations.filter((v) => v.severity === "warning").length;
138
+ const parts: string[] = [];
139
+ if (errorCount > 0) parts.push(`${errorCount} error${errorCount === 1 ? "" : "s"}`);
140
+ if (warnCount > 0) parts.push(`${warnCount} warning${warnCount === 1 ? "" : "s"}`);
141
+ const counts = parts.join(", ");
138
142
  if (errorCount > 0) {
139
- logger.error(`${errorCount} error(s), ${warnCount} warning(s)`);
143
+ logger.error(counts);
140
144
  } else {
141
- logger.warn(`${warnCount} warning(s), 0 errors — passed`);
145
+ logger.warn(`${counts} — passed`);
142
146
  }
143
147
  }
144
148
  }
@@ -152,8 +156,16 @@ export async function runAdrValidate(
152
156
  },
153
157
  exitCode: hasErrors ? 1 : 0,
154
158
  summary: hasErrors
155
- ? `${violations.filter((v) => v.severity === "error").length} error(s) found`
159
+ ? `${violations.filter((v) => v.severity === "error").length} error${violations.filter((v) => v.severity === "error").length === 1 ? "" : "s"} found`
156
160
  : `All ${filesToValidate.length} ADR(s) passed validation`,
161
+ nextSteps: hasErrors
162
+ ? [
163
+ {
164
+ action: `Fix the ${violations.filter((v) => v.severity === "error").length} error${violations.filter((v) => v.severity === "error").length === 1 ? "" : "s"} above, then re-run: pnpm exec forge run adr.validate`,
165
+ kind: "required",
166
+ },
167
+ ]
168
+ : undefined,
157
169
  };
158
170
  }
159
171
 
package/os/adr/types.ts CHANGED
@@ -92,6 +92,7 @@ export interface AdrCreateResult {
92
92
 
93
93
  export const ADR_DIR = "docs/adrs";
94
94
  export const ADR_TEMPLATE_FILE = "docs/adrs/adr-0000-template.md";
95
+ export const ADR_TEMPLATE_FALLBACK_FILE = "os/adr/adr-0000-template.md";
95
96
  export const ADR_ID_PATTERN = /^ADR-\d{4}$/;
96
97
 
97
98
  export const ADR_REQUIRED_SECTIONS = [
@@ -186,6 +186,15 @@ export async function runCompassAuditPlan(
186
186
  data: { threshold, dueCount: items.length, items },
187
187
  exitCode: 0,
188
188
  summary: `[compass.audit.plan] ${items.length} files due for audit (threshold=${threshold})`,
189
+ nextSteps:
190
+ items.length > 0
191
+ ? [
192
+ {
193
+ action: `Record an audit: pnpm exec forge run compass.audit.record --file <path> --verdict <verdict> --revision <rev>`,
194
+ kind: "optional",
195
+ },
196
+ ]
197
+ : undefined,
189
198
  };
190
199
  }
191
200
 
@@ -251,6 +260,12 @@ export async function runCompassAuditRecord(
251
260
  data: { path: filePath, action: "recorded" },
252
261
  exitCode: 0,
253
262
  summary: `[compass.audit.record] ${filePath}: ${verdict} at revision ${revision}`,
263
+ nextSteps: [
264
+ {
265
+ action: `Validate audits: pnpm exec forge run compass.audit.validate`,
266
+ kind: "optional",
267
+ },
268
+ ],
254
269
  };
255
270
  }
256
271
 
@@ -378,5 +393,14 @@ export async function runCompassAuditValidate(
378
393
  data: { strict, dueCount, diagnostics },
379
394
  exitCode: hasErrors ? 1 : 0,
380
395
  summary: dueCount > 0 ? undefined : `[compass.audit.validate] OK (0 files overdue)`,
396
+ nextSteps:
397
+ dueCount > 0
398
+ ? [
399
+ {
400
+ action: `Fix the ${dueCount} overdue file(s) above, then re-run: pnpm exec forge run compass.audit.validate`,
401
+ kind: "required",
402
+ },
403
+ ]
404
+ : undefined,
381
405
  };
382
406
  }
@@ -278,5 +278,11 @@ export async function runCompassSummaryTrim(
278
278
  },
279
279
  exitCode: 0,
280
280
  summary: `[compass.summary.trim] files=${results.length}, removed=${results.reduce((sum, r) => sum + r.removed.length, 0)}`,
281
+ nextSteps: [
282
+ {
283
+ action: `Commit the trimmed headers: pnpm exec forge run ecosystem.commit`,
284
+ kind: "optional",
285
+ },
286
+ ],
281
287
  };
282
288
  }
@@ -222,6 +222,12 @@ export async function runCompassInventory(
222
222
  return {
223
223
  data: { entries: entries.length, outputPath: INVENTORY_OUTPUT_PATH, summary },
224
224
  summary: `[compass.inventory] previewed ${INVENTORY_OUTPUT_PATH}`,
225
+ nextSteps: [
226
+ {
227
+ action: `Write the inventory: pnpm exec forge run compass.inventory`,
228
+ kind: "optional",
229
+ },
230
+ ],
225
231
  };
226
232
  }
227
233
 
@@ -232,6 +238,19 @@ export async function runCompassInventory(
232
238
  return {
233
239
  data: { entries: entries.length, outputPath, summary },
234
240
  summary: `[compass.inventory] ${context.dryRun ? "previewed" : "wrote"} ${INVENTORY_OUTPUT_PATH}`,
241
+ nextSteps: context.dryRun
242
+ ? [
243
+ {
244
+ action: `Write for real: pnpm exec forge run compass.inventory`,
245
+ kind: "optional",
246
+ },
247
+ ]
248
+ : [
249
+ {
250
+ action: `Validate compass headers: pnpm exec forge run compass.validate`,
251
+ kind: "optional",
252
+ },
253
+ ],
235
254
  };
236
255
  }
237
256
 
@@ -199,12 +199,16 @@ export async function runPackageHealth(
199
199
  `forge.package.health: all ${packagesChecked} published package(s) healthy.`,
200
200
  );
201
201
  } else if (passed) {
202
- logger.success(
203
- `forge.package.health: ${packagesChecked} package(s) checked, ${warnings.length} warning(s).`,
202
+ logger.warn(
203
+ `forge.package.health: ${packagesChecked} package(s) checked, ${warnings.length} warning${warnings.length === 1 ? "" : "s"}.`,
204
204
  );
205
205
  } else {
206
+ const parts: string[] = [];
207
+ parts.push(`${errors.length} error${errors.length === 1 ? "" : "s"}`);
208
+ if (warnings.length > 0)
209
+ parts.push(`${warnings.length} warning${warnings.length === 1 ? "" : "s"}`);
206
210
  logger.error(
207
- `forge.package.health: ${errors.length} error(s), ${warnings.length} warning(s) across ${packagesChecked} package(s).`,
211
+ `forge.package.health: ${parts.join(", ")} across ${packagesChecked} package(s).`,
208
212
  );
209
213
  }
210
214
  }
@@ -220,9 +224,9 @@ export async function runPackageHealth(
220
224
  exitCode: passed ? 0 : 1,
221
225
  summary: passed
222
226
  ? warnings.length > 0
223
- ? `All ${packagesChecked} package(s) passed with ${warnings.length} warning(s).`
227
+ ? `All ${packagesChecked} package(s) passed with ${warnings.length} warning${warnings.length === 1 ? "" : "s"}.`
224
228
  : `All ${packagesChecked} package(s) healthy.`
225
- : `${errors.length} error(s) found across ${packagesChecked} package(s).`,
229
+ : `${errors.length} error${errors.length === 1 ? "" : "s"} found across ${packagesChecked} package(s).`,
226
230
  nextSteps: errors.map((e) => ({
227
231
  action: e.fixHint ?? e.message,
228
232
  kind: "required" as const,
@@ -283,7 +283,9 @@ export async function runPinnedValidate(
283
283
  logger.success("pinned.validate: no violations");
284
284
  }
285
285
  } else {
286
- logger.error(`pinned.validate: ${remainingViolations.length} violation(s) found`);
286
+ logger.error(
287
+ `pinned.validate: ${remainingViolations.length} violation${remainingViolations.length === 1 ? "" : "s"} found`,
288
+ );
287
289
  for (const v of remainingViolations) {
288
290
  logger.error(` ${v.operation}: ${v.path} (mode: ${v.mode}) — ${v.reason}`);
289
291
  }
@@ -296,6 +298,6 @@ export async function runPinnedValidate(
296
298
  summary:
297
299
  status === "pass"
298
300
  ? `No violations${appliedOverrides.length > 0 ? ` (${appliedOverrides.length} override(s) applied)` : ""}`
299
- : `${remainingViolations.length} violation(s) found`,
301
+ : `${remainingViolations.length} violation${remainingViolations.length === 1 ? "" : "s"} found`,
300
302
  };
301
303
  }
@@ -155,5 +155,11 @@ export async function runExplorationArchive(
155
155
  related,
156
156
  },
157
157
  summary: `Archived exploration note: ${slug} (${previousStatus} → archived)`,
158
+ nextSteps: [
159
+ {
160
+ action: `Commit the archive: pnpm exec forge run ecosystem.commit`,
161
+ kind: "optional",
162
+ },
163
+ ],
158
164
  };
159
165
  }
@@ -73,5 +73,14 @@ export async function runExplorationList(
73
73
  explorations: entries,
74
74
  },
75
75
  summary: `${entries.length} exploration note(s) found`,
76
+ nextSteps:
77
+ entries.length === 0
78
+ ? [
79
+ {
80
+ action: `Create an exploration note: pnpm exec forge run exploration.show --id <slug>`,
81
+ kind: "optional",
82
+ },
83
+ ]
84
+ : undefined,
76
85
  };
77
86
  }
@@ -103,5 +103,11 @@ export async function runExplorationShow(
103
103
  note,
104
104
  },
105
105
  summary: `Exploration note: ${note.id}`,
106
+ nextSteps: [
107
+ {
108
+ action: `Archive the note when done: pnpm exec forge run exploration.archive --id ${note.id}`,
109
+ kind: "optional",
110
+ },
111
+ ],
106
112
  };
107
113
  }
@@ -237,6 +237,12 @@ export async function runComplete(
237
237
  },
238
238
  exitCode: 0,
239
239
  summary: `program.packet.complete: ${packetId} completed at ${head.slice(0, 8)}`,
240
+ nextSteps: [
241
+ {
242
+ action: `Seal the next packet: pnpm exec forge run program.packet.seal --packet <next-packet-id>`,
243
+ kind: "optional",
244
+ },
245
+ ],
240
246
  };
241
247
  }
242
248
 
@@ -390,6 +396,12 @@ async function bootstrapComplete(
390
396
  },
391
397
  exitCode: 0,
392
398
  summary: `program.packet.complete: ${packetId} bootstrap completed — program now executing`,
399
+ nextSteps: [
400
+ {
401
+ action: `Seal the first execution packet: pnpm exec forge run program.packet.seal --packet <first-packet-id>`,
402
+ kind: "optional",
403
+ },
404
+ ],
393
405
  };
394
406
  }
395
407
 
@@ -414,6 +426,12 @@ function completeFail(
414
426
  violations,
415
427
  },
416
428
  exitCode: 1,
417
- summary: `program.packet.complete: ${packetId} failed with ${violations.length} violation(s)`,
429
+ summary: `program.packet.complete: ${packetId} failed with ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
430
+ nextSteps: [
431
+ {
432
+ action: `Fix the ${violations.length} violation${violations.length === 1 ? "" : "s"} above, then re-run: pnpm exec forge run program.packet.complete --packet ${packetId}`,
433
+ kind: "required",
434
+ },
435
+ ],
418
436
  };
419
437
  }
@@ -199,6 +199,12 @@ async function leaseStart(
199
199
  },
200
200
  exitCode: 0,
201
201
  summary: `program.packet.lease: ${packetId} lease started for ${executor}`,
202
+ nextSteps: [
203
+ {
204
+ action: `Implement the packet, then complete: pnpm exec forge run program.packet.complete --packet ${packetId}`,
205
+ kind: "optional",
206
+ },
207
+ ],
202
208
  };
203
209
  }
204
210
 
@@ -428,6 +434,6 @@ function leaseFail(
428
434
  violations,
429
435
  },
430
436
  exitCode: 1,
431
- summary: `program.packet.lease: ${packetId} ${action} failed with ${violations.length} violation(s)`,
437
+ summary: `program.packet.lease: ${packetId} ${action} failed with ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
432
438
  };
433
439
  }
@@ -224,6 +224,12 @@ export async function runSeal(
224
224
  },
225
225
  exitCode: 0,
226
226
  summary: `program.packet.seal: ${packetId} sealed on ${branch}@${head.slice(0, 8)}`,
227
+ nextSteps: [
228
+ {
229
+ action: `Start the lease: pnpm exec forge run program.packet.lease --packet ${packetId} --action start --executor <executor>`,
230
+ kind: "optional",
231
+ },
232
+ ],
227
233
  };
228
234
  }
229
235
 
@@ -242,7 +248,13 @@ function sealFail(
242
248
  violations,
243
249
  },
244
250
  exitCode: 1,
245
- summary: `program.packet.seal: ${packetId} failed with ${violations.length} violation(s)`,
251
+ summary: `program.packet.seal: ${packetId} failed with ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
252
+ nextSteps: [
253
+ {
254
+ action: `Fix the ${violations.length} violation${violations.length === 1 ? "" : "s"} above, then re-run: pnpm exec forge run program.packet.seal --packet ${packetId}`,
255
+ kind: "required",
256
+ },
257
+ ],
246
258
  };
247
259
  }
248
260
 
@@ -186,7 +186,7 @@ export async function runValidate(
186
186
  summary:
187
187
  status === "pass"
188
188
  ? `program.packet.validate: ${packetId} passed (${phase})`
189
- : `program.packet.validate: ${packetId} failed with ${violations.length} violation(s)`,
189
+ : `program.packet.validate: ${packetId} failed with ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
190
190
  };
191
191
  }
192
192
 
@@ -208,6 +208,12 @@ function failResult(
208
208
  violations,
209
209
  },
210
210
  exitCode: 1,
211
- summary: `program.packet.validate: ${packetId} failed with ${violations.length} violation(s)`,
211
+ summary: `program.packet.validate: ${packetId} failed with ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
212
+ nextSteps: [
213
+ {
214
+ action: `Fix the ${violations.length} violation${violations.length === 1 ? "" : "s"} above, then re-run: pnpm exec forge run program.packet.validate --packet ${packetId}`,
215
+ kind: "required",
216
+ },
217
+ ],
212
218
  };
213
219
  }
@@ -340,5 +340,14 @@ export async function runRfcAcceptanceRun(
340
340
  data: { command: "rfc.acceptance.run", status, diagnostics, results },
341
341
  exitCode: failedCount > 0 ? 1 : 0,
342
342
  summary: `rfc.acceptance.run: ${results.length} RFC(s), ${failedCount} failed probe(s)`,
343
+ nextSteps:
344
+ failedCount > 0
345
+ ? [
346
+ {
347
+ action: `Fix the ${failedCount} failed acceptance probe(s) above, then re-run: pnpm exec forge run rfc.acceptance.run`,
348
+ kind: "required",
349
+ },
350
+ ]
351
+ : undefined,
343
352
  };
344
353
  }
@@ -233,7 +233,7 @@ export async function runRfcDnaTraceValidate(
233
233
  diagnostics,
234
234
  },
235
235
  exitCode: hasErrors ? 1 : 0,
236
- summary: `rfc.dna.trace.validate: ${registryIds.size} DNA invariants, ${tracedDnaCount} traced, ${diagnostics.filter((d) => d.severity === "error").length} error(s)`,
236
+ summary: `rfc.dna.trace.validate: ${registryIds.size} DNA invariants, ${tracedDnaCount} traced, ${diagnostics.filter((d) => d.severity === "error").length} error${diagnostics.filter((d) => d.severity === "error").length === 1 ? "" : "s"}`,
237
237
  };
238
238
  }
239
239
 
@@ -219,7 +219,7 @@ export async function runRfcCheck(
219
219
  for (const v of violations) {
220
220
  logger.error(`[${v.kind}] ${v.rfcId}: ${v.message}`);
221
221
  }
222
- logger.error(`${violations.length} violation(s) found`);
222
+ logger.error(`${violations.length} violation${violations.length === 1 ? "" : "s"} found`);
223
223
  }
224
224
  }
225
225
 
@@ -232,7 +232,15 @@ export async function runRfcCheck(
232
232
  },
233
233
  exitCode: hasErrors ? 1 : 0,
234
234
  summary: hasErrors
235
- ? `${violations.length} artifact violation(s) found across ${checkedRfcs} RFC(s)`
235
+ ? `${violations.length} artifact violation${violations.length === 1 ? "" : "s"} found across ${checkedRfcs} RFC(s)`
236
236
  : `All ${checkedRfcs} RFC(s) passed artifact check`,
237
+ nextSteps: hasErrors
238
+ ? [
239
+ {
240
+ action: `Fix the ${violations.length} artifact violation${violations.length === 1 ? "" : "s"} above, then re-run: pnpm exec forge run rfc.check`,
241
+ kind: "required",
242
+ },
243
+ ]
244
+ : undefined,
237
245
  };
238
246
  }
@@ -485,6 +485,12 @@ export async function runRfcImplementStamp(
485
485
  },
486
486
  exitCode: 0,
487
487
  summary: `rfc.implement.stamp: ${targetId} stamped as implemented`,
488
+ nextSteps: [
489
+ {
490
+ action: `Commit the stamp: pnpm exec forge run ecosystem.commit`,
491
+ kind: "optional",
492
+ },
493
+ ],
488
494
  };
489
495
  } finally {
490
496
  await releaseRfcLock(lockPath);
@@ -510,6 +516,6 @@ function stampFailResult(
510
516
  violations,
511
517
  },
512
518
  exitCode: 1,
513
- summary: `${prefix}rfc.implement.stamp: ${violations.length} violation(s)`,
519
+ summary: `${prefix}rfc.implement.stamp: ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
514
520
  };
515
521
  }
@@ -175,7 +175,7 @@ export async function runRfcIndexValidate(
175
175
  return {
176
176
  data: result,
177
177
  exitCode: 1,
178
- summary: "1 error(s) found",
178
+ summary: "1 error found",
179
179
  };
180
180
  }
181
181
 
@@ -196,7 +196,7 @@ export async function runRfcIndexValidate(
196
196
  return {
197
197
  data: result,
198
198
  exitCode: 1,
199
- summary: "1 error(s) found",
199
+ summary: "1 error found",
200
200
  };
201
201
  }
202
202
 
@@ -221,7 +221,7 @@ export async function runRfcIndexValidate(
221
221
  for (const v of violations) {
222
222
  logger.error(`[${v.rule}] ${v.message}`);
223
223
  }
224
- logger.error(`${violations.length} error(s) found`);
224
+ logger.error(`${violations.length} error${violations.length === 1 ? "" : "s"} found`);
225
225
  } else {
226
226
  logger.success(`Index valid — ${indexCount} entries match ${fileCount} RFC files.`);
227
227
  }
@@ -231,7 +231,7 @@ export async function runRfcIndexValidate(
231
231
  data: result,
232
232
  exitCode: hasErrors ? 1 : 0,
233
233
  summary: hasErrors
234
- ? `${violations.length} error(s) found`
234
+ ? `${violations.length} error${violations.length === 1 ? "" : "s"} found`
235
235
  : `Index valid — ${indexCount} entries match ${fileCount} RFC files`,
236
236
  };
237
237
  }
@@ -243,7 +243,7 @@ export async function runRfcCommandLifecycleValidate(
243
243
  },
244
244
  exitCode: hasErrors ? 1 : 0,
245
245
  summary: hasErrors
246
- ? `${violations.filter((v) => v.severity === "error").length} command lifecycle error(s) found`
246
+ ? `${violations.filter((v) => v.severity === "error").length} command lifecycle error${violations.filter((v) => v.severity === "error").length === 1 ? "" : "s"} found`
247
247
  : `All ${checkedCount} RFC(s) passed command lifecycle validation`,
248
248
  };
249
249
  }
@@ -131,6 +131,15 @@ export async function runRfcList(
131
131
  entries,
132
132
  },
133
133
  summary: `${entries.length} RFC(s) found`,
134
+ nextSteps:
135
+ entries.length === 0
136
+ ? [
137
+ {
138
+ action: `Create a new RFC: pnpm exec forge run rfc.create --title "..."`,
139
+ kind: "optional",
140
+ },
141
+ ]
142
+ : undefined,
134
143
  };
135
144
  }
136
145
 
@@ -253,6 +262,12 @@ export async function runRfcCreate(
253
262
  consultedDecisions,
254
263
  },
255
264
  summary: `Created ${nextId}: ${title}`,
265
+ nextSteps: [
266
+ {
267
+ action: `Validate the RFC: pnpm exec forge run rfc.validate --file ${relativeFile}`,
268
+ kind: "optional",
269
+ },
270
+ ],
256
271
  };
257
272
  }
258
273
 
@@ -292,5 +307,11 @@ export async function runRfcNextId(
292
307
  scannedFiles: files.length,
293
308
  },
294
309
  summary: `Next RFC number: ${nextId}`,
310
+ nextSteps: [
311
+ {
312
+ action: `Create the RFC: pnpm exec forge run rfc.create --title "..."`,
313
+ kind: "optional",
314
+ },
315
+ ],
295
316
  };
296
317
  }