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.
- package/.agents/README.md +18 -13
- package/.agents/audit-checklists/architecture.md +24 -0
- package/.agents/audit-checklists/clean-code.md +24 -0
- package/.agents/audit-checklists/dependencies.md +14 -0
- package/.agents/audit-checklists/devops.md +17 -0
- package/.agents/audit-checklists/documentation.md +22 -0
- package/.agents/audit-checklists/lighthouse.md +15 -0
- package/.agents/audit-checklists/navigability.md +14 -0
- package/.agents/audit-checklists/performance.md +22 -0
- package/.agents/audit-checklists/privacy.md +21 -0
- package/.agents/audit-checklists/quality.md +18 -0
- package/.agents/audit-checklists/security.md +22 -0
- package/.agents/audit-checklists/seo.md +16 -0
- package/.agents/audit-checklists/sre.md +24 -0
- package/.agents/audit-checklists/ux-ui.md +21 -0
- package/.agents/docs/SDLC.md +63 -16
- package/.agents/docs/configuration.md +5 -3
- package/.agents/instructions.md +51 -21
- package/.agents/personas/architect.md +10 -7
- package/.agents/personas/engineer.md +4 -3
- package/.agents/personas/project-manager.md +5 -2
- package/.agents/personas/refactorer.md +5 -3
- package/.agents/rules/git-conventions.md +77 -0
- package/.agents/schemas/agentrc.schema.json +16 -4
- package/.agents/schemas/audit-rules.json +16 -2
- package/.agents/schemas/audit-rules.schema.json +7 -6
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +38 -0
- package/.agents/schemas/signal-event.schema.json +28 -13
- package/.agents/scripts/acceptance-spec-reconciler.js +6 -4
- package/.agents/scripts/check-context-budget.js +320 -0
- package/.agents/scripts/diagnose-friction.js +4 -4
- package/.agents/scripts/epic-audit-prepare.js +30 -2
- package/.agents/scripts/epic-audit-recheck.js +46 -13
- package/.agents/scripts/epic-deliver-prepare.js +80 -8
- package/.agents/scripts/epic-plan-spec.js +4 -8
- package/.agents/scripts/generate-lens-checklists.js +180 -0
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +300 -0
- package/.agents/scripts/lib/audit-suite/findings.js +27 -0
- package/.agents/scripts/lib/audit-suite/index.js +9 -0
- package/.agents/scripts/lib/audit-suite/lens-checklist.js +212 -0
- package/.agents/scripts/lib/audit-suite/selector.js +136 -5
- package/.agents/scripts/lib/checks/loop-health.js +340 -0
- package/.agents/scripts/lib/cli-args.js +8 -0
- package/.agents/scripts/lib/config/explain.js +4 -0
- package/.agents/scripts/lib/config/runners.js +21 -2
- package/.agents/scripts/lib/config/temp-paths.js +24 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +23 -3
- package/.agents/scripts/lib/config-settings-schema-quality.js +7 -0
- package/.agents/scripts/lib/doc-tiers.js +291 -0
- package/.agents/scripts/lib/epic-body-sections.js +5 -2
- package/.agents/scripts/lib/epic-merge-lock.js +83 -0
- package/.agents/scripts/lib/epic-plan-clarity.js +3 -1
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +66 -20
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +395 -86
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +299 -72
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +438 -0
- package/.agents/scripts/lib/gates/friction.js +15 -5
- package/.agents/scripts/lib/observability/perf-aggregator.js +30 -104
- package/.agents/scripts/lib/observability/perf-report-readers.js +1 -1
- package/.agents/scripts/lib/observability/signal-validator.js +204 -0
- package/.agents/scripts/lib/observability/signals-writer.js +157 -54
- package/.agents/scripts/lib/observability/tool-trace-hook.js +42 -4
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +74 -4
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +213 -0
- package/.agents/scripts/lib/orchestration/doc-reader.js +4 -96
- package/.agents/scripts/lib/orchestration/docs-digest.js +34 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +56 -19
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +22 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +188 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +6 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +69 -8
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +11 -2
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +47 -61
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +39 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +144 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +258 -14
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +6 -0
- package/.agents/scripts/lib/orchestration/merge-block-class.js +218 -0
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +1 -1
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -3
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +63 -34
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +167 -52
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +49 -2
- package/.agents/scripts/lib/orchestration/retro-proposals.js +12 -55
- package/.agents/scripts/lib/orchestration/retro-runner.js +9 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +419 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +35 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +353 -69
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +66 -4
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +60 -9
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +7 -5
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +24 -2
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +167 -8
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +163 -0
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +20 -9
- package/.agents/scripts/lib/planning-corpus.js +306 -0
- package/.agents/scripts/lib/signals/detectors/common.js +10 -10
- package/.agents/scripts/lib/signals/detectors/index.js +4 -4
- package/.agents/scripts/lib/signals/detectors/retry.js +19 -18
- package/.agents/scripts/lib/signals/detectors/rework.js +1 -1
- package/.agents/scripts/lib/signals/schema.js +56 -81
- package/.agents/scripts/lib/signals/span-tree.js +6 -5
- package/.agents/scripts/lib/story-plan.js +3 -0
- package/.agents/scripts/lib/wave-runner/tick.js +10 -2
- package/.agents/scripts/lifecycle-emit.js +39 -8
- package/.agents/scripts/providers/github/issues.js +12 -1
- package/.agents/scripts/resolve-doc-tiers.js +83 -0
- package/.agents/scripts/retro-run.js +51 -0
- package/.agents/scripts/signals-view.js +1 -1
- package/.agents/scripts/single-story-close.js +20 -1
- package/.agents/scripts/standalone-feedback-rollup.js +188 -0
- package/.agents/scripts/story-close.js +48 -0
- package/.agents/scripts/story-plan.js +51 -12
- package/.agents/scripts/validate-docs-freshness.js +69 -15
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +58 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +5 -3
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +20 -7
- package/.agents/skills/core/scope-triage/SKILL.md +61 -0
- package/.agents/skills/skills.index.json +3 -3
- package/.agents/workflows/audit-documentation.md +82 -2
- package/.agents/workflows/helpers/code-review.md +193 -44
- package/.agents/workflows/helpers/deliver-epic.md +128 -39
- package/.agents/workflows/helpers/deliver-stories.md +26 -0
- package/.agents/workflows/helpers/epic-audit.md +116 -283
- package/.agents/workflows/helpers/epic-deliver-story.md +14 -0
- package/.agents/workflows/helpers/epic-plan-decompose.md +18 -200
- package/.agents/workflows/helpers/epic-plan-spec.md +18 -180
- package/.agents/workflows/helpers/plan-epic.md +141 -105
- package/.agents/workflows/helpers/plan-story.md +32 -0
- package/.agents/workflows/helpers/single-story-deliver.md +43 -0
- package/.agents/workflows/loops/nightly-audit.md +9 -7
- package/docs/CHANGELOG.md +29 -0
- package/lib/cli/doctor.js +44 -0
- package/package.json +4 -3
- package/.agents/scripts/epic-plan-spec-validate.js +0 -111
- package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +0 -207
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +0 -58
- 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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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({
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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 {
|
|
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>` —
|
|
15
|
-
*
|
|
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> [--
|
|
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
|
-
|
|
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
|
|
185
|
+
reason: `changelog body annotation references #${epicId}`,
|
|
153
186
|
};
|
|
154
187
|
}
|
|
155
188
|
return {
|
|
156
189
|
file,
|
|
157
190
|
pass: false,
|
|
158
|
-
reason:
|
|
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
|
-
/**
|
|
200
|
-
|
|
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
|
-
`
|
|
204
|
-
`
|
|
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
|
|
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.
|
|
439
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
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-
|
|
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":
|
|
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":
|
|
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
|