mandrel 1.87.0 → 1.89.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.
Files changed (140) hide show
  1. package/.agents/README.md +18 -13
  2. package/.agents/audit-checklists/architecture.md +24 -0
  3. package/.agents/audit-checklists/clean-code.md +24 -0
  4. package/.agents/audit-checklists/dependencies.md +14 -0
  5. package/.agents/audit-checklists/devops.md +17 -0
  6. package/.agents/audit-checklists/documentation.md +22 -0
  7. package/.agents/audit-checklists/lighthouse.md +15 -0
  8. package/.agents/audit-checklists/navigability.md +14 -0
  9. package/.agents/audit-checklists/performance.md +22 -0
  10. package/.agents/audit-checklists/privacy.md +21 -0
  11. package/.agents/audit-checklists/quality.md +18 -0
  12. package/.agents/audit-checklists/security.md +22 -0
  13. package/.agents/audit-checklists/seo.md +16 -0
  14. package/.agents/audit-checklists/sre.md +24 -0
  15. package/.agents/audit-checklists/ux-ui.md +21 -0
  16. package/.agents/docs/SDLC.md +63 -16
  17. package/.agents/docs/configuration.md +5 -3
  18. package/.agents/instructions.md +51 -21
  19. package/.agents/personas/architect.md +10 -7
  20. package/.agents/personas/engineer.md +4 -3
  21. package/.agents/personas/project-manager.md +5 -2
  22. package/.agents/personas/refactorer.md +5 -3
  23. package/.agents/rules/git-conventions.md +77 -0
  24. package/.agents/schemas/agentrc.schema.json +16 -4
  25. package/.agents/schemas/audit-rules.json +16 -2
  26. package/.agents/schemas/audit-rules.schema.json +7 -6
  27. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +38 -0
  28. package/.agents/schemas/signal-event.schema.json +28 -13
  29. package/.agents/scripts/acceptance-spec-reconciler.js +6 -4
  30. package/.agents/scripts/check-context-budget.js +320 -0
  31. package/.agents/scripts/diagnose-friction.js +4 -4
  32. package/.agents/scripts/epic-audit-prepare.js +30 -2
  33. package/.agents/scripts/epic-audit-recheck.js +46 -13
  34. package/.agents/scripts/epic-deliver-prepare.js +80 -8
  35. package/.agents/scripts/epic-plan-spec.js +4 -8
  36. package/.agents/scripts/generate-lens-checklists.js +180 -0
  37. package/.agents/scripts/lib/audit-suite/checklist-threading.js +300 -0
  38. package/.agents/scripts/lib/audit-suite/findings.js +27 -0
  39. package/.agents/scripts/lib/audit-suite/index.js +9 -0
  40. package/.agents/scripts/lib/audit-suite/lens-checklist.js +212 -0
  41. package/.agents/scripts/lib/audit-suite/selector.js +136 -5
  42. package/.agents/scripts/lib/checks/loop-health.js +340 -0
  43. package/.agents/scripts/lib/cli-args.js +8 -0
  44. package/.agents/scripts/lib/config/explain.js +4 -0
  45. package/.agents/scripts/lib/config/runners.js +21 -2
  46. package/.agents/scripts/lib/config/temp-paths.js +24 -0
  47. package/.agents/scripts/lib/config-settings-schema-delivery.js +23 -3
  48. package/.agents/scripts/lib/config-settings-schema-quality.js +7 -0
  49. package/.agents/scripts/lib/doc-tiers.js +291 -0
  50. package/.agents/scripts/lib/epic-body-sections.js +5 -2
  51. package/.agents/scripts/lib/epic-merge-lock.js +83 -0
  52. package/.agents/scripts/lib/epic-plan-clarity.js +3 -1
  53. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +66 -20
  54. package/.agents/scripts/lib/feedback-loop/graduator-core.js +395 -86
  55. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +299 -72
  56. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +438 -0
  57. package/.agents/scripts/lib/gates/friction.js +15 -5
  58. package/.agents/scripts/lib/observability/perf-aggregator.js +30 -104
  59. package/.agents/scripts/lib/observability/perf-report-readers.js +1 -1
  60. package/.agents/scripts/lib/observability/signal-validator.js +204 -0
  61. package/.agents/scripts/lib/observability/signals-writer.js +157 -54
  62. package/.agents/scripts/lib/observability/tool-trace-hook.js +42 -4
  63. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
  64. package/.agents/scripts/lib/orchestration/code-review.js +74 -4
  65. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +213 -0
  66. package/.agents/scripts/lib/orchestration/doc-reader.js +4 -96
  67. package/.agents/scripts/lib/orchestration/docs-digest.js +34 -0
  68. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +56 -19
  69. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +22 -0
  70. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +188 -0
  71. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +6 -0
  72. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +69 -8
  73. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +11 -2
  74. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +47 -61
  75. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +39 -3
  76. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +144 -0
  77. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +258 -14
  78. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +6 -0
  79. package/.agents/scripts/lib/orchestration/merge-block-class.js +218 -0
  80. package/.agents/scripts/lib/orchestration/plan-review-routing.js +1 -1
  81. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -3
  82. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +63 -34
  83. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +167 -52
  84. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +49 -2
  85. package/.agents/scripts/lib/orchestration/retro-proposals.js +12 -55
  86. package/.agents/scripts/lib/orchestration/retro-runner.js +9 -0
  87. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -0
  88. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +419 -0
  89. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +35 -2
  90. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +353 -69
  91. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +66 -4
  92. package/.agents/scripts/lib/orchestration/spec-section-validator.js +60 -9
  93. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +7 -5
  94. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +24 -2
  95. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +167 -8
  96. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +163 -0
  97. package/.agents/scripts/lib/orchestration/ticketing/reads.js +20 -9
  98. package/.agents/scripts/lib/planning-corpus.js +306 -0
  99. package/.agents/scripts/lib/signals/detectors/common.js +10 -10
  100. package/.agents/scripts/lib/signals/detectors/index.js +4 -4
  101. package/.agents/scripts/lib/signals/detectors/retry.js +19 -18
  102. package/.agents/scripts/lib/signals/detectors/rework.js +1 -1
  103. package/.agents/scripts/lib/signals/schema.js +56 -81
  104. package/.agents/scripts/lib/signals/span-tree.js +6 -5
  105. package/.agents/scripts/lib/story-plan.js +3 -0
  106. package/.agents/scripts/lib/wave-runner/tick.js +10 -2
  107. package/.agents/scripts/lifecycle-emit.js +39 -8
  108. package/.agents/scripts/providers/github/issues.js +12 -1
  109. package/.agents/scripts/resolve-doc-tiers.js +83 -0
  110. package/.agents/scripts/retro-run.js +51 -0
  111. package/.agents/scripts/signals-view.js +1 -1
  112. package/.agents/scripts/single-story-close.js +20 -1
  113. package/.agents/scripts/standalone-feedback-rollup.js +188 -0
  114. package/.agents/scripts/story-close.js +48 -0
  115. package/.agents/scripts/story-plan.js +51 -12
  116. package/.agents/scripts/validate-docs-freshness.js +69 -15
  117. package/.agents/skills/core/documentation-and-adrs/SKILL.md +58 -0
  118. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +5 -3
  119. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +20 -7
  120. package/.agents/skills/core/scope-triage/SKILL.md +61 -0
  121. package/.agents/skills/skills.index.json +3 -3
  122. package/.agents/workflows/audit-documentation.md +82 -2
  123. package/.agents/workflows/helpers/code-review.md +193 -44
  124. package/.agents/workflows/helpers/deliver-epic.md +128 -39
  125. package/.agents/workflows/helpers/deliver-stories.md +26 -0
  126. package/.agents/workflows/helpers/epic-audit.md +116 -283
  127. package/.agents/workflows/helpers/epic-deliver-story.md +14 -0
  128. package/.agents/workflows/helpers/epic-plan-decompose.md +18 -200
  129. package/.agents/workflows/helpers/epic-plan-spec.md +18 -180
  130. package/.agents/workflows/helpers/plan-epic.md +141 -105
  131. package/.agents/workflows/helpers/plan-story.md +32 -0
  132. package/.agents/workflows/helpers/single-story-deliver.md +43 -0
  133. package/.agents/workflows/loops/nightly-audit.md +9 -7
  134. package/docs/CHANGELOG.md +29 -0
  135. package/lib/cli/doctor.js +44 -0
  136. package/package.json +4 -3
  137. package/.agents/scripts/epic-plan-spec-validate.js +0 -111
  138. package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +0 -207
  139. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +0 -58
  140. package/.agents/scripts/lib/signals/detectors/hotspot.js +0 -292
@@ -10,9 +10,11 @@
10
10
  *
11
11
  * 1. `--emit-context` mode — given a `--idea`/`--from-notes` seed,
12
12
  * build the context envelope (seed, refine heuristic, persona,
13
- * body template, duplicate candidates, tech-stack summary) and
14
- * print it as JSON on stdout. Logs route to stderr so the
15
- * envelope is byte-clean for `JSON.parse`.
13
+ * body template, duplicate candidates, tech-stack summary, and a
14
+ * corpus-aware `corpusContext` the docs digest plus relevant
15
+ * existing-Epic Tech Spec excerpts, Story #4432) and print it as
16
+ * JSON on stdout. Logs route to stderr so the envelope is
17
+ * byte-clean for `JSON.parse`.
16
18
  * 2. Persist mode — given a `--body <file>` authored by the host
17
19
  * LLM after operator confirmation, validate the shape and persist
18
20
  * via `provider.createIssue` (which also adds the new Story to
@@ -30,12 +32,14 @@
30
32
  */
31
33
 
32
34
  import { readFile } from 'node:fs/promises';
35
+ import path from 'node:path';
33
36
  import { parseArgs } from 'node:util';
34
37
  import { runAsCli } from './lib/cli-utils.js';
35
38
  import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
36
39
  import { exec as ghExec } from './lib/gh-exec.js';
37
40
  import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
38
41
  import { TYPE_LABELS } from './lib/label-constants.js';
42
+ import { buildCorpusContext } from './lib/planning-corpus.js';
39
43
  import { createProvider } from './lib/provider-factory.js';
40
44
  import {
41
45
  buildContextEnvelope,
@@ -130,7 +134,17 @@ export function extractTitle(body) {
130
134
  return m ? m[1].trim() : 'Untitled standalone Story';
131
135
  }
132
136
 
133
- async function runEmitContext({ values, provider, projectRoot }) {
137
+ async function runEmitContext({
138
+ values,
139
+ provider,
140
+ projectRoot,
141
+ config,
142
+ // Injectable stdout port so unit tests can capture the emitted envelope
143
+ // without stubbing the process-global stream (mirrors the `runPersist`
144
+ // pattern above — raw stdout writes corrupt the `node --test` runner's
145
+ // structured report stream).
146
+ write = (s) => process.stdout.write(s),
147
+ }) {
134
148
  const seed = await resolveSeed({
135
149
  idea: values.idea,
136
150
  fromNotes: values['from-notes'],
@@ -139,11 +153,29 @@ async function runEmitContext({ values, provider, projectRoot }) {
139
153
  const refine = shouldRefine({ seed, override });
140
154
  const persona = values.persona ?? 'engineer';
141
155
 
142
- const [bodyTemplate, openStories, techStack] = await Promise.all([
143
- loadBodyTemplate(projectRoot),
144
- fetchOpenStories(provider),
145
- readTechStackSummary(projectRoot),
146
- ]);
156
+ // Corpus lookup uses the raw (un-defaulted) docsContextFiles list, same
157
+ // as the `/deliver` per-Epic digest builder: `config.project` fills in
158
+ // the framework's default four-file set even when the operator
159
+ // configured nothing, so a null-vs-configured distinction requires
160
+ // reading `config.raw` directly.
161
+ const docsContextFiles = config?.raw?.project?.docsContextFiles ?? [];
162
+ // Resolve docsRoot against PROJECT_ROOT (not process.cwd()) so the
163
+ // corpus digest reads the project's actual docs directory regardless
164
+ // of the directory this CLI happens to be invoked from — matching the
165
+ // sibling resolution pattern in
166
+ // epic-plan-spec/phases/authoring-context.js.
167
+ const docsRoot = path.resolve(
168
+ PROJECT_ROOT,
169
+ config?.project?.paths?.docsRoot ?? 'docs',
170
+ );
171
+
172
+ const [bodyTemplate, openStories, techStack, corpusContext] =
173
+ await Promise.all([
174
+ loadBodyTemplate(projectRoot),
175
+ fetchOpenStories(provider),
176
+ readTechStackSummary(projectRoot),
177
+ buildCorpusContext({ seed, provider, docsContextFiles, docsRoot }),
178
+ ]);
147
179
 
148
180
  const duplicateCandidates = rankDuplicateCandidates({
149
181
  seed,
@@ -157,12 +189,13 @@ async function runEmitContext({ values, provider, projectRoot }) {
157
189
  bodyTemplate,
158
190
  duplicateCandidates,
159
191
  techStack,
192
+ corpusContext,
160
193
  });
161
194
 
162
195
  const json = values.pretty
163
196
  ? JSON.stringify(envelope, null, 2)
164
197
  : JSON.stringify(envelope);
165
- process.stdout.write(`${json}\n`);
198
+ write(`${json}\n`);
166
199
  }
167
200
 
168
201
  async function runPersist({
@@ -267,7 +300,7 @@ async function main() {
267
300
  // unconditionally parseable by `JSON.parse`. Mirrors the contract
268
301
  // `epic-plan-spec.js` enforces for its own --emit-context mode.
269
302
  routeAllOutputToStderr();
270
- return runEmitContext({ values, provider, projectRoot });
303
+ return runEmitContext({ values, provider, projectRoot, config });
271
304
  }
272
305
 
273
306
  return runPersist({
@@ -281,4 +314,10 @@ runAsCli(import.meta.url, main, { source: 'story-plan' });
281
314
 
282
315
  // Test surface — exported so unit tests can drive the helpers
283
316
  // without importing the CLI side.
284
- export { fetchOpenStories, renderGhArgv, runPersist };
317
+ export {
318
+ fetchOpenStories,
319
+ renderGhArgv,
320
+ resolveSeed,
321
+ runEmitContext,
322
+ runPersist,
323
+ };
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /* node:coverage ignore file -- pre-push docs-freshness gate; pure git-mtime walk with no testable branching beyond filesystem state */
3
2
 
4
3
  /**
5
4
  * .agents/scripts/validate-docs-freshness.js — Documentation Freshness Gate
@@ -10,18 +9,28 @@
10
9
  *
11
10
  * 1. `git log --all --grep="#<epicId>" -- <file>` returns a commit —
12
11
  * the Epic ID was referenced in a commit message that touched the
13
- * file.
14
- * 2. The file's current body contains `#<epicId>` — a human annotation
15
- * (e.g., a CHANGELOG entry) explicitly ties the change to this Epic.
12
+ * file. This is the pass path for **every** doc.
13
+ * 2. The file's current body contains `#<epicId>` — but this
14
+ * body-annotation path is accepted **only for changelog-class files**
15
+ * (basename matches `/changelog/i`), where an appended release note
16
+ * keyed to the Epic is the legitimate, expected update. Any other doc
17
+ * (architecture, decisions, README, …) MUST pass via condition 1: the
18
+ * living doc has to be **rewritten in an Epic-referencing commit**, not
19
+ * merely annotated with `#<epicId>`.
16
20
  *
17
21
  * The prior gate accepted any diff against the base branch — a stray
18
22
  * whitespace edit or a one-line unrelated cleanup passed, defeating the
19
23
  * purpose of the check. Requiring an Epic-ID reference makes "did you
20
24
  * update the docs for this Epic?" a falsifiable question instead of a
21
- * checkbox.
25
+ * checkbox. The changelog-only restriction on condition 2 closes the
26
+ * follow-on perverse incentive: without it, the gate rewarded appending
27
+ * `#<epicId>` history into living docs (manufacturing fake provenance) to
28
+ * satisfy the check. Restricting the annotation path to changelog files
29
+ * makes the gate ask "was this doc rewritten for the Epic?" rather than
30
+ * "does it mention the Epic?".
22
31
  *
23
32
  * Usage:
24
- * node .agents/scripts/validate-docs-freshness.js --epic <EPIC_ID> [--base main] [--docs <comma-separated>] [--json]
33
+ * node .agents/scripts/validate-docs-freshness.js --epic <EPIC_ID> [--docs <comma-separated>] [--json]
25
34
  *
26
35
  * `--json` emits a single JSON object on stdout with
27
36
  * { ok, epicId, results: [{ file, pass, reason }, ...] }
@@ -76,6 +85,20 @@ export function resolveDocList(config) {
76
85
  return Array.from(new Set(resolved));
77
86
  }
78
87
 
88
+ /**
89
+ * A doc is "changelog-class" when its basename matches `/changelog/i`
90
+ * (e.g. `CHANGELOG.md`, `docs/CHANGELOG.md`, `changelog.mdx`). Only these
91
+ * files may satisfy the freshness gate via a body annotation (pass
92
+ * condition 2); every other doc must pass via an Epic-referencing commit
93
+ * (pass condition 1).
94
+ *
95
+ * @param {string} file
96
+ * @returns {boolean}
97
+ */
98
+ export function isChangelogClass(file) {
99
+ return /changelog/i.test(path.basename(file));
100
+ }
101
+
79
102
  function epicRefMatcher(epicId) {
80
103
  // Match `#N` as a standalone token. `(?!\d)` prevents `#10` from
81
104
  // satisfying a search for `#1` — a subtle bug the prior diff-only gate
@@ -83,6 +106,8 @@ function epicRefMatcher(epicId) {
83
106
  return new RegExp(`#${epicId}(?!\\d)`);
84
107
  }
85
108
 
109
+ /* node:coverage disable -- real `git log` shell-out; exercised via the
110
+ injectable `commitsForFile` seam in runFreshnessGate, not directly. */
86
111
  function commitsMentioningEpic(docPath, epicId, cwd = PROJECT_ROOT) {
87
112
  const res = gitSpawn(
88
113
  cwd,
@@ -99,6 +124,7 @@ function commitsMentioningEpic(docPath, epicId, cwd = PROJECT_ROOT) {
99
124
  .map((s) => s.trim())
100
125
  .filter(Boolean);
101
126
  }
127
+ /* node:coverage enable */
102
128
 
103
129
  function fileBodyMentionsEpic(
104
130
  docPath,
@@ -145,17 +171,29 @@ export function runFreshnessGate({
145
171
  reason: `${commits.length} commit(s) reference Epic #${epicId}`,
146
172
  };
147
173
  }
148
- if (fileBodyMentionsEpic(file, epicId, cwd, readFileImpl)) {
174
+ // Pass condition 2 (body annotation) is restricted to changelog-class
175
+ // files. For every other doc, an appended `#<epicId>` no longer passes —
176
+ // the living doc must be rewritten in an Epic-referencing commit.
177
+ const changelogClass = isChangelogClass(file);
178
+ if (
179
+ changelogClass &&
180
+ fileBodyMentionsEpic(file, epicId, cwd, readFileImpl)
181
+ ) {
149
182
  return {
150
183
  file,
151
184
  pass: true,
152
- reason: `body mentions #${epicId}`,
185
+ reason: `changelog body annotation references #${epicId}`,
153
186
  };
154
187
  }
155
188
  return {
156
189
  file,
157
190
  pass: false,
158
- reason: `no commit message or body reference to #${epicId}`,
191
+ reason: changelogClass
192
+ ? `no commit message or changelog body reference to #${epicId}`
193
+ : `${file} was not rewritten in an Epic-referencing commit for #${epicId} — ` +
194
+ `living docs must be REWRITTEN in a commit whose message references ` +
195
+ `#${epicId} (not annotated with #${epicId}); the body-annotation path ` +
196
+ `passes only for changelog-class files`,
159
197
  };
160
198
  });
161
199
  return { ok: results.every((r) => r.pass), results };
@@ -172,7 +210,6 @@ export function parseFreshnessArgs(argv) {
172
210
  args: argv,
173
211
  options: {
174
212
  epic: { type: 'string' },
175
- base: { type: 'string' },
176
213
  docs: { type: 'string' },
177
214
  json: { type: 'boolean', default: false },
178
215
  },
@@ -196,12 +233,25 @@ export function renderFreshnessLine(result) {
196
233
  return `[docs-freshness] ${result.pass ? '✅' : '❌'} ${result.file} — ${result.reason}`;
197
234
  }
198
235
 
199
- /** Pure: build the failure message for the operator. */
200
- export function renderFreshnessFailureMessage(epicId) {
236
+ /**
237
+ * Pure: build the failure message for the operator. Names the failing
238
+ * file(s) and states the rewrite-not-append contract explicitly.
239
+ *
240
+ * @param {number} epicId
241
+ * @param {Array<{ file: string, pass: boolean }>} [results]
242
+ */
243
+ export function renderFreshnessFailureMessage(epicId, results = []) {
244
+ const failing = results.filter((r) => !r.pass).map((r) => r.file);
245
+ const fileList = failing.length > 0 ? failing.join(', ') : '(see rows above)';
201
246
  return (
202
247
  `[docs-freshness] ❌ Documentation freshness gate FAILED for Epic #${epicId}.\n\n` +
203
- `Update each failing file so its commit message or body references #${epicId}, ` +
204
- `then re-run /deliver.`
248
+ `Failing file(s): ${fileList}\n\n` +
249
+ `Living docs satisfy this gate by being REWRITTEN in an Epic-referencing ` +
250
+ `commit — a commit whose message references #${epicId} and touches the ` +
251
+ `file — NOT by appending a #${epicId} annotation to the body. The ` +
252
+ `body-annotation path passes ONLY for changelog-class files (basename ` +
253
+ `matches /changelog/i). Rewrite each failing file for the Epic, then ` +
254
+ `re-run /deliver.`
205
255
  );
206
256
  }
207
257
 
@@ -210,6 +260,9 @@ export function renderFreshnessSuccessMessage(epicId, count) {
210
260
  return `[docs-freshness] ✅ All ${count} doc(s) reference Epic #${epicId}.`;
211
261
  }
212
262
 
263
+ /* node:coverage disable -- process I/O + real config/git wiring (stdout,
264
+ process.exit, resolveConfig, runAsCli); the pure logic these thin wrappers
265
+ call is covered directly above. */
213
266
  function reportEmptyDocs(epicId, json) {
214
267
  if (json) {
215
268
  process.stdout.write(
@@ -234,7 +287,7 @@ function reportGateOutcome({ epicId, json, ok, results }) {
234
287
  Logger.info(renderFreshnessSuccessMessage(epicId, results.length));
235
288
  return;
236
289
  }
237
- Logger.error(renderFreshnessFailureMessage(epicId));
290
+ Logger.error(renderFreshnessFailureMessage(epicId, results));
238
291
  process.exit(1);
239
292
  }
240
293
 
@@ -257,3 +310,4 @@ async function main() {
257
310
  }
258
311
 
259
312
  runAsCli(import.meta.url, main, { source: 'validate-docs-freshness' });
313
+ /* node:coverage enable */
@@ -316,6 +316,64 @@ For shipped features:
316
316
  - Task list now loads 50 items per page (was 20) for better UX (#126)
317
317
  ```
318
318
 
319
+ ## Pruning & Archiving
320
+
321
+ Living docs accrete history — dated changelog entries, closed decision-log
322
+ rows, completed rollout checklists, resolved runbook incidents. Left
323
+ unpruned, that verbatim history crowds out the live guidance a reader (human
324
+ or agent) actually needs, and every task that loads the doc re-pays the cost.
325
+ The fix is to **archive, don't delete**: relocate the cold history so the live
326
+ doc stays lean while the record stays recoverable.
327
+
328
+ ### The archive-don't-delete rule
329
+
330
+ **History is preserved by _moving_ it, never by deleting it.** Pruning a doc
331
+ never destroys its past — the verbatim content is relocated to a dated archive
332
+ file under version control, so the full record remains diffable and
333
+ recoverable. Deleting history outright (even with "git has it") is the
334
+ anti-pattern this convention exists to prevent: the archive is discoverable
335
+ from the live doc, a buried git revision is not.
336
+
337
+ ### How to prune a doc
338
+
339
+ 1. **Extract the still-live signal first — before you archive anything.**
340
+ Gotchas, traps, and hard-won caveats buried in the history are the most
341
+ valuable lines in the doc. Lift them into the live doc's standing guidance
342
+ (a "Known gotchas" list, an inline warning, or an ADR) **before** the
343
+ history moves. Archiving first risks stranding a live trap in a cold file
344
+ nobody rereads.
345
+ 2. **Move the verbatim history to a dated archive file.** Relocate the cold
346
+ content — untouched, word-for-word — to
347
+ `docs/archive/<name>-<YYYY-MM>.md`, where `<name>` is the source doc's base
348
+ name and `<YYYY-MM>` is the archive date (e.g. `docs/archive/changelog-2025-01.md`,
349
+ `docs/archive/decisions-2024-11.md`). The archive is an exact copy of what
350
+ was live; do not summarize or rewrite it in the move.
351
+ 3. **Collapse completed checklists to a one-line summary.** A finished
352
+ checklist (a rollout runbook, a migration plan, a release gate) does not
353
+ need to keep every ticked box in the live doc. Replace it with a single
354
+ line recording the outcome and date — e.g.
355
+ `Auth-migration rollout — completed 2025-01-18, all 12 steps green` — and
356
+ let the archived copy carry the full detail.
357
+ 4. **Leave a one-line pointer behind.** Every archived doc leaves exactly one
358
+ line in the live doc pointing at where its history went, so the record is
359
+ never orphaned — e.g.
360
+ `Older entries archived to docs/archive/changelog-2024.md`. The pointer is
361
+ what makes "moved, not deleted" true from the reader's vantage point.
362
+
363
+ ### When to prune
364
+
365
+ - A changelog, decision log, or runbook has grown long enough that the live
366
+ entries are hard to find among the historical ones.
367
+ - A checklist or rollout plan is fully complete and its step-by-step detail is
368
+ now reference-only.
369
+ - A doc reloaded into agent context on many tasks carries more cold history
370
+ than live guidance.
371
+
372
+ Do **not** prune ADRs by archiving — an ADR that no longer holds is
373
+ **superseded** in place (see [ADR Lifecycle](#adr-lifecycle)), keeping the
374
+ numbered chain intact. Archiving is for the accreted history of living docs,
375
+ not for the immutable decision record.
376
+
319
377
  ## Documentation for Agents
320
378
 
321
379
  Special consideration for AI agent context:
@@ -17,7 +17,8 @@ allowed_tools:
17
17
 
18
18
  - Run only after `epic-plan-decompose.js --emit-context` has written `temp/epic-<Epic_ID>/decomposer-context.json`; fail loudly if the file is missing.
19
19
  - Emit exactly one artifact: `temp/epic-<Epic_ID>/tickets.json` (a JSON array). Do not write anywhere else, and never call the GitHub API from this Skill — persistence belongs to the script.
20
- - Output is JSON only — no prose, no Markdown fence. The downstream validator (`lib/orchestration/ticket-validator.js`) is the authoritative gate; re-author rather than hand-patching when it rejects.
20
+ - Output is JSON only — no prose, no Markdown fence. The downstream validator (`lib/orchestration/ticket-validator.js`) is the authoritative gate.
21
+ - **Re-emit rule (amend, don't regenerate — Story #4431).** On a re-emit — the validator rejecting the draft, or a Phase 8.3/8.4/8.5 critic flagging specific Stories — apply **targeted edits** to the existing `temp/epic-<Epic_ID>/tickets.json`, fixing only the rejected or flagged Stories, rather than re-authoring the whole array from scratch. `helpers/plan-epic.md` bounds each critic's feedback to **one refinement pass** — apply the edit and re-run the downstream step once; do not loop.
21
22
  - Treat **`maxTickets`** from the context envelope as a **reviewability budget**, not a hard authoring cap (Story #2798). Merge narrow, single-module Stories into their capability first; if the plan genuinely needs more, emit the full plan and add a compact `over_budget_rationale` note inside the first Story's `## Goal` section explaining why the plan exceeds the budget. Operator persistence then requires the explicit `--allow-over-budget` override on `epic-plan-decompose.js`; without it the persist step rejects the over-budget array. Never truncate the JSON array to fit.
22
23
  - Honour the 2-tier hierarchy: every ticket is a **Story** attached directly to the Epic. Stories carry the implementation scope inline; no Feature and no lower ticket tier exists. Thematic grouping is prose in the Epic body / Tech Spec, never a ticket.
23
24
  - **Decompose at deliverable granularity, not module/task level.** A Story is a capability slice a frontier model delivers and self-verifies in one pass — a shippable slice a reviewer would accept as a single PR — not a single module or file. See the STORY SIZING section for the full guidance and the single-consumer merge rule.
@@ -435,5 +436,6 @@ any logical ordering requirement via Story-level `depends_on`.
435
436
  loudly. Instruct the caller to run `--emit-context` first.
436
437
  - The validator
437
438
  ([`lib/orchestration/ticket-validator.js`](../../../scripts/lib/orchestration/ticket-validator.js))
438
- is the authoritative gate. Re-author when it rejects rather than
439
- patching tickets by hand.
439
+ is the authoritative gate. On rejection, apply the re-emit rule above —
440
+ a targeted edit to the existing `tickets.json` fixing only what was
441
+ rejected — rather than re-authoring the array from scratch.
@@ -34,6 +34,7 @@ allowed_tools:
34
34
 
35
35
  - Run only during `/plan` Phase 7, after `epic-plan-spec.js --emit-context` has written `temp/epic-<Epic_ID>/planner-context.json`; fail loudly if the file is missing rather than fabricating context.
36
36
  - Write exactly three artifacts and only inside `temp/epic-<Epic_ID>/`: `techspec.md`, `risk-verdict.json`, `acceptance-spec.md`. All three MUST exist on disk before returning.
37
+ - **Re-emit rule (amend, don't regenerate — Story #4431).** On a re-emit — the Phase 7 persist call rejecting an artifact (e.g. a missing `## Delivery Slicing` heading, a schema-invalid risk verdict) — apply **targeted edits** to the existing `temp/epic-<Epic_ID>/` artifact that fix only what the rejection named; do NOT rewrite an artifact wholesale from a blank draft. `helpers/plan-epic.md` bounds this to **one refinement pass** per invocation (the same shape as the Epic Clarity Gate's own "one refinement pass per invocation" contract) — do not loop.
37
38
  - Start each markdown artifact at the correct `##` heading (Tech Spec → `## Delivery Slicing`, Acceptance Spec → `## Acceptance Table` — never the Epic's own `## Acceptance Criteria` heading, which stays the ideation bullets) — never emit a top-level `#` heading. `risk-verdict.json` is raw JSON conforming to `.agents/schemas/risk-verdict.schema.json`.
38
39
  - The Tech Spec MUST open with `## Delivery Slicing` and MUST NOT restate the Epic's Context, Goal, or Scope — your output lands as sections of the same Epic body, which travels into every downstream story agent's prompt, so any restatement is duplication and a drift risk. A `## Technical Overview` section is optional and, when present, is a 2–3 sentence orientation of the *technical approach* only (which subsystems are touched and reused), never a re-narration of the problem statement, goals, or scope.
39
40
  - Judge risk from what the change *does* (the Epic body / Tech Spec you just wrote), never from keyword presence — "out of scope: billing" is not a billing change; "rotate the credential vault" is high-risk even without a security keyword.
@@ -75,15 +76,24 @@ reads:
75
76
  Fields:
76
77
  - `epic.id`, `epic.title`, `epic.body` (or `epic.bodySummary` when the
77
78
  planning-context budget downgrades the body to a summary)
78
- - `docsContext.items[]` — bounded project docs scraped from the configured
79
- `docsRoot` (start with these for "how does the codebase do X today?"
80
- context; the validator already capped their size)
79
+ - `docsContext` — digest-first (Story #4433, hard cutover of the § 3.1
80
+ planning read contract): `{ mode: 'digest', digestPath }` pointing at
81
+ the per-Epic docs digest (`<tempRoot>/epic-<Epic_ID>/docs-digest.md`
82
+ the same file the `/deliver` story sub-agents already consume, Story
83
+ #4338 / #4324), or `null` when `project.docsContextFiles` is unset. Read
84
+ the digest at `digestPath` for "how does the codebase do X today?"
85
+ orientation (path, byte size, heading outline with line numbers, first
86
+ paragraph per `##` section), then pull the full file on demand with your
87
+ own Read tool — jump straight to the line the digest names — only when a
88
+ section bears on the spec. There is no full-content `items[]` field;
89
+ embedding whole/summarized doc bodies in this envelope was retired in
90
+ the same cutover that made the `/deliver` children digest-first.
81
91
  - `codebaseSnapshot` — Story #2634 structural view of the consumer repo
82
92
  (file tree, `package.json` exports + scripts, recently-touched
83
93
  directories, detected test runner + BDD feature roots, and — at the
84
94
  `medium` tier — per-file export signatures). Prefer module / file
85
95
  names that appear in this snapshot over names that appear only in
86
- `docsContext.items[]`; the docs may be stale relative to the actual
96
+ the docs digest; the docs may be stale relative to the actual
87
97
  source tree. When the spec needs to cite a file that is **not** in
88
98
  `codebaseSnapshot.files`, surface that as a `<!-- DRIFT -->` callout
89
99
  in the Tech Spec body naming the cited path, so the freshness gate
@@ -151,13 +161,16 @@ or (for the verdict) schema-invalid.
151
161
 
152
162
  Read `temp/epic-<Epic_ID>/planner-context.json` with the `Read` tool. Pull
153
163
  the Epic title, body (or body summary, including the Epic's `## User Stories`
154
- section), the `docsContext` items, and (for reference) the two system prompts.
164
+ section), and the `docsContext` digest pointer (`digestPath`, or `null`).
165
+ When non-null, read the digest file at `digestPath` next — that outline,
166
+ not the planner-context envelope, is where the actual doc orientation
167
+ lives; pull a full doc on demand only when a section looks relevant.
155
168
 
156
169
  ### Step 2 — Author the Tech Spec (Engineering Architect persona)
157
170
 
158
171
  Apply the Tech Spec system prompt below to the Epic body (Context / Goal /
159
- Scope / User Stories), the
160
- `docsContext` items, and the `codebaseSnapshot` envelope (so the spec is
172
+ Scope / User Stories), the docs digest (plus any full file pulled on
173
+ demand), and the `codebaseSnapshot` envelope (so the spec is
161
174
  grounded in the actual codebase, not hallucinated patterns). Cite module
162
175
  and file names from `codebaseSnapshot.files` / `codebaseSnapshot.signatures`
163
176
  before reaching for names that appear only in the documentation. Write to
@@ -86,6 +86,67 @@ or a small Epic, and neither call is clearly right. **Present the choice rather
86
86
  than deciding for the operator.** Do not force a verdict to avoid the third
87
87
  option; a borderline scope surfaced as borderline is the correct output.
88
88
 
89
+ ## Change-Request Triage Rubric
90
+
91
+ A **change request** — "fix this", "tweak that", "extend the existing X" —
92
+ is the common case this rubric routes cheaply: a delta against a surface the
93
+ project already shipped, not a from-scratch capability. Route it to `story`
94
+ by default when all three delta signals hold; treat any one signal's absence
95
+ as a prompt to re-check, not an automatic `epic` bump.
96
+
97
+ ### Delta signals
98
+
99
+ - **Delta to an already-delivered surface.** The request references a
100
+ concrete existing capability (a module, workflow, script, or shipped
101
+ feature) rather than proposing a new one. "Fix the flaky retry in
102
+ `evidence-gate.js`" is a delta; "add a retry framework" is not.
103
+ - **Existing corpus covers the touched area.** The project's docs digest
104
+ and/or an already-closed or in-flight Epic's Tech Spec section already
105
+ describe the surface being changed — the standalone-Story path can draft
106
+ against that inherited context (`corpusContext` in the `/plan --idea`
107
+ envelope) instead of re-deriving architecture from a blank slate. When no
108
+ corpus hit exists for the touched area, that is a signal the request may
109
+ be reaching into genuinely new territory — re-check the `epic` signals
110
+ below before defaulting to `story`.
111
+ - **Footprint fits Story sizing.** The plausible file footprint and
112
+ acceptance-criteria count still fit the Story width in
113
+ `DEFAULT_TASK_SIZING` (per the `story` verdict signals above) — a change
114
+ request that fans out across independent subsystems is sized like an Epic
115
+ regardless of how small the originating request sounded.
116
+
117
+ ### Story-verdict rationale template
118
+
119
+ Use this template to record the verdict — it names which delta signal
120
+ carried the call, so a reviewer can sanity-check the routing decision without
121
+ re-deriving it:
122
+
123
+ ```text
124
+ Verdict: story
125
+ Delta: <the existing surface this change targets>
126
+ Corpus hit: <docs digest section / Epic # and Tech Spec excerpt that covers
127
+ this area, or "none — re-checked epic signals, still story-sized">
128
+ Footprint: <rough file/AC count vs DEFAULT_TASK_SIZING>
129
+ ```
130
+
131
+ ### Worked example
132
+
133
+ > Request: "The `/plan` standalone-Story path always drafts from a blank
134
+ > slate even when a change request is a small delta against something we
135
+ > already shipped — thread the existing docs digest and matching Epic
136
+ > Tech Spec sections into the draft context."
137
+ >
138
+ > ```text
139
+ > Verdict: story
140
+ > Delta: story-plan.js's --emit-context envelope and the plan-story.md
141
+ > Phase 2 drafting instructions — both already exist and ship today.
142
+ > Corpus hit: Epic #4429's own Tech Spec section (this rubric's parent
143
+ > Epic) already describes the docs-digest reuse pattern from
144
+ > orchestration/docs-digest.js.
145
+ > Footprint: one new lib module (planning-corpus.js), one envelope field,
146
+ > one helper-doc instruction — comfortably inside DEFAULT_TASK_SIZING's
147
+ > softFiles/softAcceptanceCount band.
148
+ > ```
149
+
89
150
  ## Handoff & no-re-triage rule
90
151
 
91
152
  A workflow entered via a scope-triage **handoff** MUST NOT re-triage. A handoff
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-07-08T12:24:41.101Z",
2
+ "generatedAt": "2026-07-11T11:32:48.235Z",
3
3
  "generator": "generate-skills-index.js@1",
4
4
  "skills": [
5
5
  {
@@ -138,7 +138,7 @@
138
138
  "category": "core",
139
139
  "path": ".agents/skills/core/epic-plan-decompose-author/SKILL.md",
140
140
  "description": "Author the Story ticket JSON for an Epic from the decomposer authoring context emitted by `epic-plan-decompose.js --emit-context`. Use during Phase 8 of `/plan` when the host LLM needs to write the ticket array before `epic-plan-decompose.js` validates and persists it.",
141
- "policyCapsuleBullets": 14,
141
+ "policyCapsuleBullets": 15,
142
142
  "allowedTools": ["Read", "Write", "Bash"],
143
143
  "vendor": null
144
144
  },
@@ -158,7 +158,7 @@
158
158
  "category": "core",
159
159
  "path": ".agents/skills/core/epic-plan-spec-author/SKILL.md",
160
160
  "description": "Author the Tech Spec, Acceptance Table markdown, and risk-verdict JSON for an Epic from the planner authoring context emitted by `epic-plan-spec.js --emit-context`. Use during Phase 7 of `/plan` when the host LLM needs to write the three artifacts before `epic-plan-spec.js` folds them into the Epic body's managed sections.",
161
- "policyCapsuleBullets": 13,
161
+ "policyCapsuleBullets": 14,
162
162
  "allowedTools": ["Read", "Write", "Bash"],
163
163
  "vendor": null
164
164
  },
@@ -135,6 +135,7 @@ node .agents/scripts/check-lifecycle-doc-drift.js
135
135
  node .agents/scripts/generate-config-docs.js --check
136
136
  node .agents/scripts/generate-lifecycle-docs.js --check
137
137
  node .agents/scripts/generate-workflows-doc.js --check
138
+ node .agents/scripts/resolve-doc-tiers.js --json
138
139
  ```
139
140
 
140
141
  Fold the results in as findings:
@@ -148,6 +149,13 @@ Fold the results in as findings:
148
149
  remediation is "rerun `npm run docs:gen` / `npm run sync:commands` and
149
150
  commit the regenerated output".
150
151
 
152
+ The **read-tier map** — `resolve-doc-tiers.js --json` — is not itself a
153
+ finding. It emits `{ tiers: { alwaysLoaded, mandatoryRead, digestVisible,
154
+ onDemand } }`, each a `[{ path, bytes }]` list classifying every doc by how
155
+ often it is loaded into agent context. Hold that map for the Context Economy
156
+ severity-weighting rule (Step 2.5) — it decides how much a Context-Economy
157
+ finding's location amplifies its cost.
158
+
151
159
  This lens orchestrates the existing checkers only; it does not add new
152
160
  deterministic checker scripts.
153
161
 
@@ -182,6 +190,56 @@ works (wrong command, deleted script, contract mismatch); **Medium** =
182
190
  materially outdated description or missing coverage of a major surface;
183
191
  **Low** = cosmetic drift, stale examples, tone/format inconsistencies.
184
192
 
193
+ ## Step 2.5: Context Economy
194
+
195
+ Steps 1–2 verify the docs are **accurate**. This step verifies they are
196
+ **economical** — that a doc still earns the context every reader (human or
197
+ agent) pays to load it. A doc can be entirely accurate and still cost far
198
+ more than it returns: it accretes finished-work history, states the same fact
199
+ two incompatible ways, or claims an authority the code has outgrown. Flag
200
+ these under the three Context Economy categories below. Each carries a
201
+ recognition heuristic — a shape you can spot from the prose itself.
202
+
203
+ - **History Bloat:** the doc carries verbatim finished-work history that
204
+ crowds out its live guidance — fully-checked (all-`[x]`) checklists,
205
+ step-by-step phase / rollout logs, and past-tense "shipped" / "completed"
206
+ narratives no reader acts on anymore. Recognition: a section whose every
207
+ checkbox is ticked, or a changelog / decision run whose historical rows
208
+ dwarf the live ones. **Remediation:** apply the documentation-and-adrs
209
+ [Pruning & Archiving](../skills/core/documentation-and-adrs/SKILL.md#pruning--archiving)
210
+ convention — lift any still-live gotcha into the live doc **first**, then
211
+ **archive, don't delete**: relocate the verbatim history to a dated
212
+ `docs/archive/<name>-<YYYY-MM>.md`, collapse each completed checklist to a
213
+ one-line outcome, and leave a one-line pointer behind. Never prune ADRs by
214
+ archiving — supersede them in place.
215
+ - **Contradiction:** the doc states the same fact two incompatible ways, so a
216
+ reader cannot tell which is current. Recognition: a footnote- or
217
+ parenthetical-corrected table cell (a value carrying an inline `(now X)` /
218
+ `~~old~~` correction), or two prose statements that assert different values
219
+ for the same command, path, count, or contract. **Remediation:** collapse
220
+ to the single verified-current statement and delete the stale twin.
221
+ - **Authority Drift:** the doc is crowned the source of truth for a surface
222
+ the verified code has since outgrown — the prose still presents itself as
223
+ canonical, but the implementation is now the real authority. Recognition: a
224
+ doc that declares itself SSOT / "canonical" / "single source of truth" for
225
+ a contract whose current shape you had to read the code to confirm, because
226
+ the doc no longer matches it. **Remediation:** either re-sync the doc to the
227
+ code and keep the SSOT claim, or demote the claim and point at the code as
228
+ the authority.
229
+
230
+ ### Read-tier severity weighting
231
+
232
+ A Context-Economy finding's cost scales with how often the doc is actually
233
+ read. Step 1 ran `resolve-doc-tiers.js --json`, which classifies every
234
+ target-set doc into a read tier (`alwaysLoaded`, `mandatoryRead`,
235
+ `digestVisible`, `onDemand`). **A finding whose doc falls in the
236
+ `alwaysLoaded` or `mandatoryRead` tier escalates one severity band**
237
+ (Low→Medium, Medium→High): bloat, contradiction, or drift in a doc every task
238
+ loads costs far more than the same defect in an on-demand reference. Apply the
239
+ escalation **after** assigning the base severity from the Step 2 guidance, and
240
+ name the doc's tier in the finding's Current State so the escalation is
241
+ auditable.
242
+
185
243
  ## Step 3: Output Requirements
186
244
 
187
245
  Generate and save a highly structured Markdown audit report to
@@ -208,8 +266,8 @@ match the code), the deterministic-gate verdicts, and primary drift themes.]
208
266
 
209
267
  ### [Short Title of the Issue]
210
268
 
211
- - **Category:** [Broken Instruction | Stale Description | Missing Coverage | Generator Drift | Link Integrity]
212
- - **Impact:** [High | Medium | Low]
269
+ - **Category:** [Broken Instruction | Stale Description | Missing Coverage | Generator Drift | Link Integrity | History Bloat | Contradiction | Authority Drift]
270
+ - **Impact:** [High | Medium | Low] — for a Context Economy finding, this is the base severity **after** any read-tier escalation (Step 2.5); state the doc's tier in Current State.
213
271
  - **Current State:** [The doc, the exact claim, and what the code actually
214
272
  does — cite file paths and lines on both sides]
215
273
  - **Recommendation & Rationale:** [The specific doc edit (or generator
@@ -218,6 +276,28 @@ match the code), the deterministic-gate verdicts, and primary drift themes.]
218
276
  `[A copy-pasteable, highly specific prompt to execute this doc fix independently]`
219
277
  ```
220
278
 
279
+ ## Periodic full-scope sweep
280
+
281
+ Context Economy findings accrete slowly — a doc that is lean today grows a
282
+ bloated tail over many Epics, and no single change-set-scoped `/deliver`
283
+ Phase 4 run sees the whole picture. Run this lens **full-scope** on a
284
+ recurring cadence so the drift is caught before it compounds:
285
+
286
+ - **Cron loop** — [`loops:nightly-audit`](loops/nightly-audit.md)
287
+ runs the audit sweep unattended each night; the host (`/schedule` or a
288
+ cron-driven `/loop`) owns the cadence. Point it at this lens full-scope
289
+ (no `--paths`, no change-set filter — the `{{changedFiles}}` block renders
290
+ the literal token, so the whole target-set union is audited).
291
+ - **Scheduled invocation** — `/schedule` running `/audit-documentation`
292
+ full-scope on a daily/weekly cron is the equivalent one-shot form when a
293
+ standing loop is not wired up.
294
+
295
+ Route the resulting `audit-documentation-results.md` through
296
+ [`/audit-to-stories`](audit-to-stories.md), which groups the findings,
297
+ deduplicates them against existing Issues by fingerprint, and opens
298
+ remediation Stories (or chains into `/plan --idea`) so the Context-Economy
299
+ findings land as actionable, tracked work rather than a report nobody reads.
300
+
221
301
  ## Constraint
222
302
 
223
303
  This workflow is **read-only** with respect to the repository: run the