@nurix/etna 0.20.35 → 0.20.37
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/data/catalog/rules/changelog.md +1 -1
- package/data/catalog/skills/nustack/SKILL.md +2 -2
- package/data/catalog/skills/nustack/references/cli-design.md +35 -35
- package/data/catalog/skills/nustack/references/monorepo-master-list.md +6 -6
- package/data/catalog/skills/nustack/references/monorepo-setup.md +13 -13
- package/data/catalog/skills/nustack/references/publishing.md +40 -39
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +4 -4
- package/data/harnesses/code/agents/brainstorm.md +2 -2
- package/data/harnesses/code/agents/lld-mode.md +2 -2
- package/data/harnesses/code/agents/maintenance.md +17 -17
- package/data/harnesses/code/codex/agents/brainstorm.toml +6 -2
- package/data/harnesses/code/codex/agents/lld-mode.toml +2 -2
- package/data/harnesses/code/codex/agents/maintenance.toml +18 -17
- package/data/harnesses/code/docs/README.md +9 -17
- package/data/harnesses/code/docs/history/CHANGELOG.md +19 -18
- package/data/harnesses/code/docs/structure.md +60 -45
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/compliance.mjs +8 -2
- package/data/harnesses/code/hooks/lib/sweep.mjs +3 -2
- package/data/harnesses/code/rules/changelog.md +1 -1
- package/data/harnesses/code/skills/boundary/SKILL.md +33 -22
- package/data/harnesses/code/skills/boundary/references/index.md +2 -4
- package/data/harnesses/code/skills/boundary/references/mechanisms.md +3 -10
- package/data/harnesses/code/skills/boundary/references/packs/frontend.md +17 -17
- package/data/harnesses/code/skills/boundary/references/packs/generic-tail.md +1 -1
- package/data/harnesses/code/skills/brainstorm/README.md +11 -13
- package/data/harnesses/code/skills/brainstorm/SKILL.md +149 -130
- package/data/harnesses/code/skills/brainstorm/references/doctrine.md +48 -51
- package/data/harnesses/code/skills/brainstorm/references/harvest.md +51 -57
- package/data/harnesses/code/skills/brainstorm/references/session-schema.md +48 -42
- package/data/harnesses/code/skills/design/SKILL.md +31 -35
- package/data/harnesses/code/skills/design/data/iconography.md +2 -2
- package/data/harnesses/code/skills/design/references/compose.md +14 -16
- package/data/harnesses/code/skills/design/references/foundations.md +5 -5
- package/data/harnesses/code/skills/design/references/intake.md +6 -6
- package/data/harnesses/code/skills/design/references/object-pass.md +1 -1
- package/data/harnesses/code/skills/design/references/ship-gate.md +5 -5
- package/data/harnesses/code/skills/design/references/ux-patterns/Readme.md +4 -4
- package/data/harnesses/code/skills/design/references/ux-patterns/ai-layer.md +1 -1
- package/data/harnesses/code/skills/design/references/ux-patterns/container-choice.md +1 -1
- package/data/harnesses/code/skills/design/references/ux-patterns/dashboards.md +1 -1
- package/data/harnesses/code/skills/design/references/ux-patterns/modes.md +1 -1
- package/data/harnesses/code/skills/design/references/ux-patterns/object-model.md +2 -2
- package/data/harnesses/code/skills/design/references/ux-patterns/promotion-demotion.md +1 -1
- package/data/harnesses/code/skills/design/references/ux-patterns/settings.md +1 -1
- package/data/harnesses/code/skills/design/references/ux-patterns/standard-pages.md +1 -1
- package/data/harnesses/code/skills/documentation/SKILL.md +92 -143
- package/data/harnesses/code/skills/documentation/references/examples.md +45 -72
- package/data/harnesses/code/skills/documentation/references/frontmatter.md +8 -8
- package/data/harnesses/code/skills/documentation/references/instruction-style.md +3 -3
- package/data/harnesses/code/skills/documentation/references/source-comments.md +19 -0
- package/data/harnesses/code/skills/documentation/references/style-guide.md +25 -11
- package/data/harnesses/code/skills/documentation/references/untemplated.md +11 -10
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +181 -40
- package/data/harnesses/code/skills/documentation/templates/Readme.md +68 -41
- package/data/harnesses/code/skills/documentation/templates/acceptance-criteria.md +11 -19
- package/data/harnesses/code/skills/documentation/templates/api-reference.md +8 -3
- package/data/harnesses/code/skills/documentation/templates/architecture-decision.md +8 -12
- package/data/harnesses/code/skills/documentation/templates/backlog.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/behavioral-checklist.md +10 -17
- package/data/harnesses/code/skills/documentation/templates/bookkeeping.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/changelog-entry.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/changelog-index.md +21 -29
- package/data/harnesses/code/skills/documentation/templates/docs-readme.md +4 -4
- package/data/harnesses/code/skills/documentation/templates/doctrine.md +3 -3
- package/data/harnesses/code/skills/documentation/templates/domain-model.md +10 -5
- package/data/harnesses/code/skills/documentation/templates/environment-registry.md +7 -2
- package/data/harnesses/code/skills/documentation/templates/feature.md +6 -6
- package/data/harnesses/code/skills/documentation/templates/glossary.md +6 -1
- package/data/harnesses/code/skills/documentation/templates/handover.md +17 -17
- package/data/harnesses/code/skills/documentation/templates/implementation-map.md +10 -5
- package/data/harnesses/code/skills/documentation/templates/journal.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/memory-main.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/migration-runbook.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/non-goals.md +8 -3
- package/data/harnesses/code/skills/documentation/templates/operations-procedure.md +8 -3
- package/data/harnesses/code/skills/documentation/templates/package.md +5 -9
- package/data/harnesses/code/skills/documentation/templates/plan.md +5 -11
- package/data/harnesses/code/skills/documentation/templates/protected-files.md +7 -2
- package/data/harnesses/code/skills/documentation/templates/root-readme.md +5 -5
- package/data/harnesses/code/skills/documentation/templates/security-overview.md +9 -4
- package/data/harnesses/code/skills/documentation/templates/setup.md +6 -6
- package/data/harnesses/code/skills/documentation/templates/system-overview.md +12 -7
- package/data/harnesses/code/skills/documentation/templates/system.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/testing-strategy.md +10 -5
- package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +14 -5
- package/data/harnesses/code/skills/issue-bridge/SKILL.md +51 -16
- package/data/harnesses/code/skills/issue-bridge/references/trackers/github.md +5 -5
- package/data/harnesses/code/skills/issue-bridge/references/trackers/jira.md +11 -8
- package/data/harnesses/code/skills/issue-bridge/references/trackers/linear.md +16 -11
- package/data/harnesses/code/skills/nustack-graph/SKILL.md +38 -29
- package/data/harnesses/code/skills/polygraph/references/dispatch.md +31 -18
- package/data/harnesses/code/skills/polygraph/references/rows.md +11 -6
- package/data/harnesses/code/skills/schema-design/SKILL.md +39 -41
- package/data/harnesses/code/skills/test/SKILL.md +38 -42
- package/data/harnesses/code/skills/testing/SKILL.md +83 -73
- package/data/harnesses/code/skills/workflow-mapper/SKILL.md +156 -193
- package/data/harnesses/code/skills/workflow-mapper/references/patterns.md +15 -15
- package/data/harnesses/delivery/CLAUDE.md +1 -1
- package/data/harnesses/delivery/agents/brainstorm.md +2 -2
- package/data/harnesses/delivery/agents/lld-mode.md +2 -2
- package/data/harnesses/delivery/agents/maintenance.md +17 -17
- package/data/harnesses/delivery/codex/agents/brainstorm.toml +6 -2
- package/data/harnesses/delivery/codex/agents/lld-mode.toml +2 -2
- package/data/harnesses/delivery/codex/agents/maintenance.toml +18 -17
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/rules/changelog.md +1 -1
- package/data/harnesses/delivery/skills/brainstorm/README.md +11 -13
- package/data/harnesses/delivery/skills/brainstorm/SKILL.md +149 -130
- package/data/harnesses/delivery/skills/brainstorm/references/doctrine.md +48 -51
- package/data/harnesses/delivery/skills/brainstorm/references/harvest.md +51 -57
- package/data/harnesses/delivery/skills/brainstorm/references/session-schema.md +48 -42
- package/data/harnesses/delivery/skills/cost-report/SKILL.md +10 -10
- package/data/harnesses/delivery/skills/documentation/SKILL.md +92 -143
- package/data/harnesses/delivery/skills/documentation/references/examples.md +45 -72
- package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +8 -8
- package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +3 -3
- package/data/harnesses/delivery/skills/documentation/references/source-comments.md +19 -0
- package/data/harnesses/delivery/skills/documentation/references/style-guide.md +25 -11
- package/data/harnesses/delivery/skills/documentation/references/untemplated.md +11 -10
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +181 -40
- package/data/harnesses/delivery/skills/documentation/templates/Readme.md +68 -41
- package/data/harnesses/delivery/skills/documentation/templates/acceptance-criteria.md +11 -19
- package/data/harnesses/delivery/skills/documentation/templates/api-reference.md +8 -3
- package/data/harnesses/delivery/skills/documentation/templates/architecture-decision.md +8 -12
- package/data/harnesses/delivery/skills/documentation/templates/backlog.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/behavioral-checklist.md +10 -17
- package/data/harnesses/delivery/skills/documentation/templates/bookkeeping.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/changelog-entry.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/changelog-index.md +21 -29
- package/data/harnesses/delivery/skills/documentation/templates/docs-readme.md +4 -4
- package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +3 -3
- package/data/harnesses/delivery/skills/documentation/templates/domain-model.md +10 -5
- package/data/harnesses/delivery/skills/documentation/templates/environment-registry.md +7 -2
- package/data/harnesses/delivery/skills/documentation/templates/feature.md +6 -6
- package/data/harnesses/delivery/skills/documentation/templates/glossary.md +6 -1
- package/data/harnesses/delivery/skills/documentation/templates/handover.md +17 -17
- package/data/harnesses/delivery/skills/documentation/templates/implementation-map.md +10 -5
- package/data/harnesses/delivery/skills/documentation/templates/journal.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/memory-main.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/migration-runbook.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/non-goals.md +8 -3
- package/data/harnesses/delivery/skills/documentation/templates/operations-procedure.md +8 -3
- package/data/harnesses/delivery/skills/documentation/templates/package.md +5 -9
- package/data/harnesses/delivery/skills/documentation/templates/plan.md +5 -11
- package/data/harnesses/delivery/skills/documentation/templates/protected-files.md +7 -2
- package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +5 -5
- package/data/harnesses/delivery/skills/documentation/templates/security-overview.md +9 -4
- package/data/harnesses/delivery/skills/documentation/templates/setup.md +6 -6
- package/data/harnesses/delivery/skills/documentation/templates/system-overview.md +12 -7
- package/data/harnesses/delivery/skills/documentation/templates/system.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/testing-strategy.md +10 -5
- package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +14 -5
- package/data/harnesses/delivery/skills/escalate/SKILL.md +11 -11
- package/data/harnesses/delivery/skills/prompt-eval/SKILL.md +8 -8
- package/data/harnesses/delivery/skills/prompt-library/SKILL.md +30 -9
- package/data/harnesses/delivery/skills/route/SKILL.md +8 -8
- package/data/harnesses/etna/CLAUDE.md +2 -2
- package/data/harnesses/etna/agents/brainstorm.md +2 -2
- package/data/harnesses/etna/agents/lld-mode.md +2 -2
- package/data/harnesses/etna/agents/maintenance.md +17 -17
- package/data/harnesses/etna/codex/agents/brainstorm.toml +6 -2
- package/data/harnesses/etna/codex/agents/lld-mode.toml +2 -2
- package/data/harnesses/etna/codex/agents/maintenance.toml +18 -17
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/rules/changelog.md +1 -1
- package/data/harnesses/etna/skills/boundary/SKILL.md +33 -22
- package/data/harnesses/etna/skills/boundary/references/index.md +2 -4
- package/data/harnesses/etna/skills/boundary/references/mechanisms.md +3 -10
- package/data/harnesses/etna/skills/boundary/references/packs/frontend.md +17 -17
- package/data/harnesses/etna/skills/boundary/references/packs/generic-tail.md +1 -1
- package/data/harnesses/etna/skills/brainstorm/README.md +11 -13
- package/data/harnesses/etna/skills/brainstorm/SKILL.md +149 -130
- package/data/harnesses/etna/skills/brainstorm/references/doctrine.md +48 -51
- package/data/harnesses/etna/skills/brainstorm/references/harvest.md +51 -57
- package/data/harnesses/etna/skills/brainstorm/references/session-schema.md +48 -42
- package/data/harnesses/etna/skills/documentation/SKILL.md +92 -143
- package/data/harnesses/etna/skills/documentation/references/examples.md +45 -72
- package/data/harnesses/etna/skills/documentation/references/frontmatter.md +8 -8
- package/data/harnesses/etna/skills/documentation/references/instruction-style.md +3 -3
- package/data/harnesses/etna/skills/documentation/references/source-comments.md +19 -0
- package/data/harnesses/etna/skills/documentation/references/style-guide.md +25 -11
- package/data/harnesses/etna/skills/documentation/references/untemplated.md +11 -10
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +181 -40
- package/data/harnesses/etna/skills/documentation/templates/Readme.md +68 -41
- package/data/harnesses/etna/skills/documentation/templates/acceptance-criteria.md +11 -19
- package/data/harnesses/etna/skills/documentation/templates/api-reference.md +8 -3
- package/data/harnesses/etna/skills/documentation/templates/architecture-decision.md +8 -12
- package/data/harnesses/etna/skills/documentation/templates/backlog.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/behavioral-checklist.md +10 -17
- package/data/harnesses/etna/skills/documentation/templates/bookkeeping.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/changelog-entry.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/changelog-index.md +21 -29
- package/data/harnesses/etna/skills/documentation/templates/docs-readme.md +4 -4
- package/data/harnesses/etna/skills/documentation/templates/doctrine.md +3 -3
- package/data/harnesses/etna/skills/documentation/templates/domain-model.md +10 -5
- package/data/harnesses/etna/skills/documentation/templates/environment-registry.md +7 -2
- package/data/harnesses/etna/skills/documentation/templates/feature.md +6 -6
- package/data/harnesses/etna/skills/documentation/templates/glossary.md +6 -1
- package/data/harnesses/etna/skills/documentation/templates/handover.md +17 -17
- package/data/harnesses/etna/skills/documentation/templates/implementation-map.md +10 -5
- package/data/harnesses/etna/skills/documentation/templates/journal.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/memory-main.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/migration-runbook.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/non-goals.md +8 -3
- package/data/harnesses/etna/skills/documentation/templates/operations-procedure.md +8 -3
- package/data/harnesses/etna/skills/documentation/templates/package.md +5 -9
- package/data/harnesses/etna/skills/documentation/templates/plan.md +5 -11
- package/data/harnesses/etna/skills/documentation/templates/protected-files.md +7 -2
- package/data/harnesses/etna/skills/documentation/templates/root-readme.md +5 -5
- package/data/harnesses/etna/skills/documentation/templates/security-overview.md +9 -4
- package/data/harnesses/etna/skills/documentation/templates/setup.md +6 -6
- package/data/harnesses/etna/skills/documentation/templates/system-overview.md +12 -7
- package/data/harnesses/etna/skills/documentation/templates/system.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/testing-strategy.md +10 -5
- package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +14 -5
- package/data/harnesses/etna/skills/workflow-mapper/SKILL.md +156 -193
- package/data/harnesses/etna/skills/workflow-mapper/references/patterns.md +15 -15
- package/data/harnesses/hr/harness.json +1 -1
- package/data/harnesses/hr/skills/evaluator/SKILL.md +71 -73
- package/data/harnesses/hr/skills/evaluator/criteria/code-quality.md +28 -47
- package/data/harnesses/hr/skills/evaluator/criteria/deployment-readiness.md +33 -43
- package/data/harnesses/hr/skills/evaluator/criteria/maintainability.md +34 -46
- package/data/harnesses/hr/skills/evaluator/criteria/mechanical-ambition.md +13 -13
- package/data/harnesses/hr/skills/evaluator/criteria/problem-substantiveness.md +7 -7
- package/data/harnesses/hr/skills/evaluator/criteria/testing.md +36 -41
- package/data/harnesses/hr/skills/evaluator/criteria/ux-mechanics.md +25 -11
- package/data/harnesses/hr/skills/evaluator/criteria/value-articulation.md +7 -7
- package/data/harnesses/hr/skills/evaluator/preflight.md +27 -46
- package/data/harnesses/hr/skills/interview-eval/SKILL.md +37 -42
- package/data/harnesses/hr/skills/interview-eval/rubric.md +21 -23
- package/package.json +2 -2
|
@@ -1,49 +1,147 @@
|
|
|
1
|
-
// skills/documentation/scripts/doc-lint.mjs - mechanical checks over markdown docs (tense narration, dates, stubs,
|
|
1
|
+
// skills/documentation/scripts/doc-lint.mjs - mechanical checks over markdown docs (tense and history narration, dates, redirect stubs, frontmatter, index shape, owning-document body)
|
|
2
2
|
// Usage: node doc-lint.mjs <file.md ...> | node doc-lint.mjs --changed (git-changed + untracked .md)
|
|
3
|
-
// Exit 1 when any block-severity finding survives. Escape hatch: <!-- doc-lint-disable: id[, id] -->
|
|
3
|
+
// Exit 1 when any block-severity finding survives. Escape hatch: <!-- doc-lint-disable: id[, id] --> alone on its
|
|
4
|
+
// own line (file-wide, with a reason comment beside it) or at the end of the offending line (that line only).
|
|
5
|
+
// Genre facts (nature, type, location, required keys and sections) come from ../templates/Readme.md § Registry.
|
|
4
6
|
|
|
5
7
|
import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
|
|
6
8
|
import { execSync } from 'node:child_process';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
const MONTH = '(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)';
|
|
12
|
+
const DATE = new RegExp(`\\b20\\d{2}[-/.]\\d{1,2}[-/.]\\d{1,2}\\b|\\b\\d{1,2}/\\d{1,2}/20\\d{2}\\b|\\b${MONTH}\\.? (\\d{1,2},? )?20\\d{2}\\b|\\b\\d{1,2} ${MONTH}\\.? 20\\d{2}\\b`);
|
|
7
13
|
|
|
8
14
|
const RULES = [
|
|
9
15
|
{ id: 'stale-tripwire', severity: 'block', scope: 'truth', pattern: /treat (any )?[\w`./-]+ ?(references? )?as stale|superseded (by|the)|\bused to (be|target|live|hold)\b/i, message: 'stale-reference tripwire / supersession talk — fix the references, state the present fact' },
|
|
10
|
-
{ id: 'history-narration', severity: 'block', scope: 'truth', pattern: /\
|
|
16
|
+
{ id: 'history-narration', severity: 'block', scope: 'truth', pattern: /\b(was|were|has been|have been) (renamed|extracted|folded|migrated|replaced|superseded)\b|\bmoved (to|from|into) [\w`./-]+ \(?20\d{2}/i, message: 'history narration — the journey belongs in a dated record' },
|
|
17
|
+
// A ticked box in a committed checklist or procedure records one run's state inside a document about the present.
|
|
18
|
+
{ id: 'ticked-checkbox', severity: 'warn', scope: 'truth', only: /(^|\/)docs\/(tests|operations|acceptance)\//, pattern: /^\s*[-*] \[[xX]\]/, message: 'ticked box in a truth document — a run\'s result belongs to the changelog entry of the unit that ran it; commit the box unticked' },
|
|
11
19
|
{ id: 'progress-marker', severity: 'block', scope: 'truth', pattern: /\bphases?\s+\d[^.\n]{0,40}\b(complete|done)\b|\b(build|implementation) (is )?(\d+%|partially) (complete|done)\b/i, message: 'progress marker — progress lives in the plan/backlog record' },
|
|
12
20
|
{ id: 'temporal-hedge', severity: 'warn', scope: 'truth', pattern: /\bnot yet\b|\bfor now\b|\bcurrently\b|\bstill (unsupported|missing|pending|unimplemented|in progress)\b/i, message: 'temporal hedge — state the fact without "currently / not yet / for now"' },
|
|
13
|
-
// "previously-fetched" is a hyphenated adjective naming a thing, not a predecessor design — don't fire on it.
|
|
14
|
-
|
|
21
|
+
// "previously-fetched" is a hyphenated adjective naming a thing, not a predecessor design — don't fire on it. "No longer"
|
|
22
|
+
// and "was removed" also describe a state a person meets ("a removed member no longer sees the panel"), so they warn.
|
|
23
|
+
{ id: 'predecessor-frame', severity: 'warn', scope: 'truth', pattern: /\bpreviously\b(?!-)|\bthe old \w+\b|\blegacy \w+ (was|is)\b|\bno longer\b|\bwas removed\b|\bv\d+(\.\d+)*\s*(→|->)\s*v?\d+/i, message: 'predecessor framing — describe the one current design' },
|
|
15
24
|
// docs/tests/ behavioral checklists use `- [ ]` as test-step notation (step → **Assert**) and docs/operations/
|
|
16
25
|
// procedures use it for operator verification steps — neither is progress tracking. Every other truth doc still bans checkboxes.
|
|
17
|
-
{ id: 'status-glyph', severity: 'warn', scope: 'truth', exempt: /(^|\/)docs\/(tests|operations)\//, pattern: /(^|\s)(✅|⏳|- \[[
|
|
26
|
+
{ id: 'status-glyph', severity: 'warn', scope: 'truth', exempt: /(^|\/)docs\/(tests|operations|acceptance)\//, pattern: /(^|\s)(✅|⏳|- \[[ xX]\](?!\())/u, message: 'status glyph / checklist in a truth doc — tracking belongs in a record' },
|
|
18
27
|
// Truth docs are supposed to cite records, and record filenames are dated — so a date inside a link
|
|
19
|
-
// target
|
|
20
|
-
{ id: 'date-in-truth-doc', severity: 'block', scope: 'truth', strip: /\[[^\]]*\]\([^)]*\)
|
|
28
|
+
// target is a path, not narration. Code spans are stripped before every rule.
|
|
29
|
+
{ id: 'date-in-truth-doc', severity: 'block', scope: 'truth', strip: /\[[^\]]*\]\([^)]*\)|^(last-reviewed|last-updated|rev|harness-version):.*$/gm, pattern: DATE, message: 'date in a truth doc — cite the record by name and link; the closed exceptions are the frontmatter tooling keys, an AD Decision Changelog tail and a dated path inside a link or code span' },
|
|
21
30
|
];
|
|
22
31
|
|
|
23
|
-
//
|
|
24
|
-
const BRIDGE_HEADINGS = /^#{1,4}\s+
|
|
25
|
-
// Instruction files, the three harness-installed mirrors
|
|
26
|
-
//
|
|
27
|
-
|
|
32
|
+
// The one section inside a truth doc where dated history is sanctioned: an AD's Decision Changelog.
|
|
33
|
+
const BRIDGE_HEADINGS = /^#{1,4}\s+decision changelog\b/i;
|
|
34
|
+
// Instruction files, the three harness-installed mirrors, generated trees and the hook-written bookkeeping queue
|
|
35
|
+
// (hooks/lib/state.mjs, a fixed header and a dated row grammar) are out of scope; the authoring roots are anchored
|
|
36
|
+
// to the repo root.
|
|
37
|
+
const SKIP = /^(\.claude|\.agents|\.codex|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\.|(^|\/)docs\/operations\/bookkeeping\.md$/;
|
|
28
38
|
// `operations/issues/YYYY-MM-DD-{slug}.md` is a sanctioned record genre (`type: issue`, the
|
|
29
39
|
// `issue-bridge` skill's local pack) living outside the record folders, so it is named here too —
|
|
30
40
|
// graded as a truth document it fails the date rule for carrying the date its filename already does.
|
|
31
41
|
const RECORD = /(^|\/)docs\/(history|research|memory)\/|(^|\/)docs\/operations\/issues\/|(^|\/)docs\/operations\/release-notes\/(?!readme\.md$)[^/]+\.md$/i;
|
|
32
42
|
|
|
33
|
-
const RECORD_TYPES = /^(changelog-entry|plan|migration|migration-runbook|research|brainstorm|journal|record|ledger|issue|triage|project-memory|agent-memory)$/;
|
|
43
|
+
const RECORD_TYPES = /^(changelog-entry|plan|migration|migration-runbook|research|brainstorm|journal|record|ledger|issue|triage|handover|project-memory|agent-memory)$/;
|
|
34
44
|
const TRUTH_LOCATIONS = /(^|\/)docs\/(features|architecture|tests|acceptance)\//;
|
|
45
|
+
// Types other skills stamp on documents this registry does not template (product-dna's ledger, generic records).
|
|
46
|
+
const EXTERNAL_TYPES = /^(ledger|record|migration|migration-runbook|changelog-entry|plan|journal)$/;
|
|
47
|
+
|
|
48
|
+
// The genre registry (templates/Readme.md § Registry), one entry per row, or null when the skill's templates are
|
|
49
|
+
// not installed beside this script — every registry check then skips, and scope falls back to the path rules.
|
|
50
|
+
function loadRegistry() {
|
|
51
|
+
let text;
|
|
52
|
+
try { text = readFileSync(new URL('../templates/Readme.md', import.meta.url), 'utf8'); } catch { return null; }
|
|
53
|
+
const at = text.search(/^## Registry\s*$/m);
|
|
54
|
+
if (at === -1) return null;
|
|
55
|
+
const ticks = (cell) => [...cell.matchAll(/`([^`]+)`/g)].map((m) => m[1]);
|
|
56
|
+
const rows = [];
|
|
57
|
+
for (const line of text.slice(at).split('\n').slice(1)) {
|
|
58
|
+
if (/^## /.test(line)) break;
|
|
59
|
+
if (!line.startsWith('|') || /^\|\s*(Genre|-{3})/.test(line)) continue;
|
|
60
|
+
const cells = line.split('|').slice(1, -1).map((c) => c.trim());
|
|
61
|
+
if (cells.length !== 7) continue;
|
|
62
|
+
const [genre, , nature, type, location, keys, sections] = cells;
|
|
63
|
+
const where = ticks(location)[0] || null;
|
|
64
|
+
rows.push({
|
|
65
|
+
genre,
|
|
66
|
+
nature,
|
|
67
|
+
type: ticks(type)[0] || null,
|
|
68
|
+
location: where,
|
|
69
|
+
match: where ? locationPattern(where) : null,
|
|
70
|
+
// Literal characters outside placeholders: the most specific location wins a tie (`{surface}-ux.md` over `{feature}.md`).
|
|
71
|
+
specificity: where ? where.replace(/\{[^}]+\}|YYYY-MM-DD|NNN|X\.Y\.Z/g, '').length : 0,
|
|
72
|
+
keys: ticks(keys),
|
|
73
|
+
sections: sections === '—' ? [] : sections.split('·').map((x) => x.trim()).filter(Boolean),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return rows.length ? rows : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A registry location as a path RegExp: `{name}` is one segment, the date and number placeholders their digits, a trailing slash any file beneath.
|
|
80
|
+
function locationPattern(location) {
|
|
81
|
+
const body = location
|
|
82
|
+
.replace(/[.+?^$()[\]\\|]/g, '\\$&')
|
|
83
|
+
.replace(/\\\{[^}]+\}|\{[^}]+\}/g, '[^/]+')
|
|
84
|
+
.replace(/YYYY-MM-DD/g, '\\d{4}-\\d{2}-\\d{2}')
|
|
85
|
+
.replace(/NNN/g, '\\d{3}')
|
|
86
|
+
.replace(/X\\\.Y\\\.Z/g, '[\\d.]+');
|
|
87
|
+
// A location with no folder (the root README) sits at the repo root only, never at any depth.
|
|
88
|
+
return new RegExp(`${location.includes('/') ? '(^|/)' : '^(\\./)?'}${body}${location.endsWith('/') ? '' : '$'}`, 'i');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const REGISTRY = loadRegistry();
|
|
92
|
+
|
|
93
|
+
// The registry row a document belongs to: its declared `type:` first (the location breaking a tie between genres
|
|
94
|
+
// sharing a type), else the most specific location it sits at; null when neither names one.
|
|
95
|
+
function genreOf(path, text) {
|
|
96
|
+
if (!REGISTRY) return null;
|
|
97
|
+
const declared = text === undefined ? null : declaredType(text);
|
|
98
|
+
// A folder's Readme is an index whatever folder it sits in, and only an index location can claim one.
|
|
99
|
+
const isIndex = /(^|\/)readme\.md$/i.test(path);
|
|
100
|
+
const at = REGISTRY.filter((r) => r.match && r.match.test(path) && /(^|\/)readme\.md$/i.test(r.location) === isIndex)
|
|
101
|
+
.sort((a, b) => b.specificity - a.specificity);
|
|
102
|
+
if (declared) {
|
|
103
|
+
const typed = REGISTRY.filter((r) => r.type === declared);
|
|
104
|
+
return typed.find((r) => at.includes(r)) || typed[0] || null;
|
|
105
|
+
}
|
|
106
|
+
return at[0] || null;
|
|
107
|
+
}
|
|
35
108
|
|
|
36
109
|
// The genre a document declares for itself, from its own frontmatter `type:` key, or null.
|
|
37
110
|
function declaredType(text) {
|
|
38
111
|
if (!text.startsWith('---')) return null;
|
|
39
112
|
const end = text.indexOf('\n---', 3);
|
|
40
|
-
const m = /^type:\s*(\S+)\s
|
|
41
|
-
return m ? m[1] : null;
|
|
113
|
+
const m = /^type:\s*(\S+)\s*(#.*)?$/m.exec(end === -1 ? text : text.slice(0, end));
|
|
114
|
+
return m ? m[1].replace(/^["']|["']$/g, '') : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Top-level frontmatter keys and their raw values (a block list joined one item per line), or null with no block.
|
|
118
|
+
function frontmatter(text) {
|
|
119
|
+
if (!/^---\r?\n/.test(text)) return null;
|
|
120
|
+
const end = text.indexOf('\n---', 3);
|
|
121
|
+
if (end === -1) return null;
|
|
122
|
+
const keys = new Map();
|
|
123
|
+
let last = null;
|
|
124
|
+
for (const line of text.slice(text.indexOf('\n') + 1, end).split('\n')) {
|
|
125
|
+
const key = /^([A-Za-z][\w-]*):[ \t]*(.*)$/.exec(line);
|
|
126
|
+
if (key) { last = key[1]; keys.set(last, key[2].replace(/\s+#.*$/, '').trim()); continue; }
|
|
127
|
+
const item = /^\s+-\s+(.*)$/.exec(line);
|
|
128
|
+
if (item && last) keys.set(last, [keys.get(last), item[1].replace(/\s+#.*$/, '').trim()].filter(Boolean).join('\n'));
|
|
129
|
+
}
|
|
130
|
+
return keys;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// A frontmatter value as a list: a flow list, a block list, or one scalar; quotes dropped, empties removed.
|
|
134
|
+
function listValue(raw) {
|
|
135
|
+
if (!raw) return [];
|
|
136
|
+
const items = raw.startsWith('[') ? raw.replace(/^\[|\]$/g, '').split(',') : raw.split('\n');
|
|
137
|
+
return items.map((v) => v.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
|
42
138
|
}
|
|
43
139
|
|
|
44
|
-
// Classify a document: record or
|
|
140
|
+
// Classify a document: truth, record, queue or out of scope — the registry's nature first, then the path rules.
|
|
45
141
|
function scopeOf(path, text) {
|
|
46
142
|
if (SKIP.test(path)) return 'skip';
|
|
143
|
+
const genre = genreOf(path, text);
|
|
144
|
+
if (genre) return genre.nature === 'truth' ? 'truth' : genre.nature === 'queue' ? 'queue' : 'record';
|
|
47
145
|
if (RECORD.test(path)) return 'record';
|
|
48
146
|
const declared = text === undefined ? null : declaredType(text);
|
|
49
147
|
return declared && RECORD_TYPES.test(declared) ? 'record' : 'truth';
|
|
@@ -80,14 +178,27 @@ function proseLines(text) {
|
|
|
80
178
|
return out;
|
|
81
179
|
}
|
|
82
180
|
|
|
83
|
-
// Collect file-wide disabled rule ids
|
|
181
|
+
// Collect file-wide disabled rule ids: a <!-- doc-lint-disable: a, b --> comment alone on its own line. One that shares
|
|
182
|
+
// a line with prose disables that line only, and is honoured where each rule reads its lines.
|
|
84
183
|
function fileDisables(text) {
|
|
85
184
|
const ids = new Set();
|
|
86
|
-
for (const m of text.matchAll(
|
|
185
|
+
for (const m of text.matchAll(/^\s*<!--\s*doc-lint-disable:\s*([\w-,\s]+?)\s*-->\s*$/gm))
|
|
87
186
|
m[1].split(',').forEach((id) => ids.add(id.trim()));
|
|
88
187
|
return ids;
|
|
89
188
|
}
|
|
90
189
|
|
|
190
|
+
// File-wide disables with no reason beside them: no comment on the next non-blank line, the way SKILL.md asks.
|
|
191
|
+
function unexplainedDisables(text) {
|
|
192
|
+
const lines = text.split('\n');
|
|
193
|
+
const out = [];
|
|
194
|
+
lines.forEach((line, i) => {
|
|
195
|
+
if (!/^\s*<!--\s*doc-lint-disable:[^>]*-->\s*$/.test(line)) return;
|
|
196
|
+
const next = lines.slice(i + 1).find((l) => l.trim());
|
|
197
|
+
if (!next || !/^\s*<!--/.test(next) || /doc-lint-disable:/.test(next)) out.push({ n: i + 1, line });
|
|
198
|
+
});
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
91
202
|
function lintFile(path, findings) {
|
|
92
203
|
if (SKIP.test(path) || !existsSync(path)) return;
|
|
93
204
|
const text = readFileSync(path, 'utf8');
|
|
@@ -99,11 +210,17 @@ function lintFile(path, findings) {
|
|
|
99
210
|
if (scope === 'truth' && nonEmpty <= 10 && /\b(moved to|relocated to|see [\w`./-]+ instead)\b/i.test(text) && !disabled.has('redirect-doc'))
|
|
100
211
|
findings.push({ path, n: 1, severity: 'block', id: 'redirect-doc', message: 'redirect stub — delete the file and repoint every inbound link' });
|
|
101
212
|
|
|
213
|
+
for (const { n, line } of unexplainedDisables(text))
|
|
214
|
+
if (!disabled.has('disable-without-reason')) findings.push({ path, n, severity: 'warn', id: 'disable-without-reason', message: 'file-wide doc-lint-disable with no reason comment on the next line — say why the rule does not apply here', excerpt: line.trim().slice(0, 90) });
|
|
215
|
+
|
|
102
216
|
for (const rule of RULES) {
|
|
103
217
|
if (rule.scope !== scope || disabled.has(rule.id)) continue;
|
|
104
218
|
if (rule.exempt && rule.exempt.test(path)) continue;
|
|
219
|
+
if (rule.only && !rule.only.test(path)) continue;
|
|
105
220
|
for (const { n, line } of lines) {
|
|
106
|
-
|
|
221
|
+
// A code span quotes a name or a phrase rather than saying it, so no tense rule reads inside one.
|
|
222
|
+
const bare = line.replace(/`[^`]*`/g, '');
|
|
223
|
+
const subject = rule.strip ? bare.replace(rule.strip, '') : bare;
|
|
107
224
|
if (!rule.pattern.test(subject)) continue;
|
|
108
225
|
if (line.includes(`doc-lint-disable: ${rule.id}`) || new RegExp(`doc-lint-disable:[^>]*\\b${rule.id}\\b`).test(line)) continue;
|
|
109
226
|
findings.push({ path, n, severity: rule.severity, id: rule.id, message: rule.message, excerpt: line.trim().slice(0, 90) });
|
|
@@ -117,17 +234,20 @@ function lintFile(path, findings) {
|
|
|
117
234
|
findings.push({ path, n: 1, severity: 'block', id: 'record-type-in-truth-location', message: `\`type: ${declared}\` is a record genre declared in a truth location — move the file to \`docs/history/\` if it is a record, or give it its location's genre` });
|
|
118
235
|
}
|
|
119
236
|
|
|
120
|
-
// A folder index (Readme.md/README.md) is a browse page
|
|
121
|
-
|
|
237
|
+
// A folder index (Readme.md/README.md) is a browse page that owns no code; a Readme declaring an owning genre is
|
|
238
|
+
// that folder's landing document and is graded as its genre instead.
|
|
239
|
+
const isFolderIndex = /(^|\/)(readme)\.md$/i.test(path) && !['feature', 'package', 'system'].includes(declaredType(text));
|
|
122
240
|
|
|
123
|
-
// An index names folders, never files (SKILL.md §
|
|
241
|
+
// An index names folders, never files (SKILL.md § Indexes and descriptions). A link from a folder index or
|
|
124
242
|
// the authority map to an individual document is a row that must be edited on every add, rename
|
|
125
243
|
// and retirement; the only files an index may link are the mandated scaffolds and a sibling
|
|
126
244
|
// folder's own Readme.
|
|
127
245
|
const isAuthorityMap = /(^|\/)docs\/structure\.md$/.test(path);
|
|
128
246
|
const isRootReadme = /^(\.\/)?README\.md$/i.test(path.replace(process.cwd() + '/', ''));
|
|
129
|
-
|
|
130
|
-
|
|
247
|
+
// Only the docs tree's indexes and the repo-root README are graded as indexes: a package's README links its own CONTRIBUTING.md.
|
|
248
|
+
const inDocs = /(^|\/)docs\//.test(path);
|
|
249
|
+
if (scope === 'truth' && ((isFolderIndex && (inDocs || isRootReadme)) || isAuthorityMap) && !disabled.has('index-lists-files')) {
|
|
250
|
+
const SCAFFOLD = /(^|\/)(readme|structure|changelog|backlog|main|health-log|journal|claude|agents|contributing|license|security|code_of_conduct)\.md$|(^|\/)operations\/setup\.md$/i;
|
|
131
251
|
for (const { n, line } of lines) {
|
|
132
252
|
if (line.includes('doc-lint-disable: index-lists-files')) continue;
|
|
133
253
|
for (const m of line.matchAll(/\]\(([^)\s#]+\.md)(#[^)]*)?\)/g)) {
|
|
@@ -142,18 +262,16 @@ function lintFile(path, findings) {
|
|
|
142
262
|
}
|
|
143
263
|
}
|
|
144
264
|
// A folder index describes directories; a drawn tree is a file roster in another costume
|
|
145
|
-
// (SKILL.md § Indexes
|
|
265
|
+
// (SKILL.md § Indexes and descriptions). structure.md is the authority map and keeps its tree.
|
|
146
266
|
if (scope === 'truth' && isFolderIndex && (isRootReadme || /(^|\/)docs\//.test(path)) && !disabled.has('index-file-tree')) {
|
|
147
267
|
// Raw lines, not proseLines: a tree is drawn inside a code fence, which proseLines drops. The repo-root README routes by a Map table; its tree is docs/structure.md's too (templates/root-readme.md).
|
|
148
268
|
const rawLines = text.split('\n').map((line, i) => ({ n: i + 1, line }));
|
|
149
269
|
const tree = rawLines.find(({ line }) => /^\s*[│├└]/.test(line));
|
|
150
270
|
if (tree) findings.push({ path, n: tree.n, severity: 'block', id: 'index-file-tree', message: 'a tree drawn in a folder index — describe each directory in a sentence; the tree is docs/structure.md\'s', excerpt: tree.line.trim().slice(0, 90) });
|
|
151
271
|
}
|
|
152
|
-
// A file or record named without a link is the same per-file row as a link (SKILL.md §
|
|
272
|
+
// A file or record named without a link is the same per-file row as a link (SKILL.md § Indexes and descriptions).
|
|
153
273
|
// docs/dna/ is the product-dna skill's generated tree, laid out by that skill's own schema.
|
|
154
274
|
const isGeneratedTree = /(^|\/)docs\/dna\//.test(path);
|
|
155
|
-
// The bookkeeping queue is written by the harness hooks from a fixed header (hooks/lib/state.mjs); it carries no frontmatter.
|
|
156
|
-
const isHookQueue = /(^|\/)docs\/operations\/bookkeeping\.md$/.test(path);
|
|
157
275
|
if (scope === 'truth' && isFolderIndex && /(^|\/)docs\//.test(path) && !isGeneratedTree && !disabled.has('index-names-records')) {
|
|
158
276
|
const SCAFFOLD_NAME = /^(readme|structure|changelog|backlog|main|health-log|journal|claude|agents|setup)\.md$/i;
|
|
159
277
|
for (const { n, line } of lines) {
|
|
@@ -164,7 +282,7 @@ function lintFile(path, findings) {
|
|
|
164
282
|
}
|
|
165
283
|
}
|
|
166
284
|
// Every truth document and folder index under docs/ describes itself in at most 300 characters (references/frontmatter.md § description:).
|
|
167
|
-
if (scope === 'truth' && /(^|\/)docs\//.test(path) && !isGeneratedTree && !isAuthorityMap && !
|
|
285
|
+
if (scope === 'truth' && /(^|\/)docs\//.test(path) && !isGeneratedTree && !isAuthorityMap && !disabled.has('description-missing')) {
|
|
168
286
|
const fmEnd = text.startsWith('---') ? text.indexOf('\n---', 3) : -1;
|
|
169
287
|
const fmText = fmEnd === -1 ? '' : text.slice(0, fmEnd);
|
|
170
288
|
const m = /^description:[ \t]*(.*)$/m.exec(fmText);
|
|
@@ -177,7 +295,7 @@ function lintFile(path, findings) {
|
|
|
177
295
|
for (const l of rest) { if (/^\s+\S/.test(l)) block.push(l.trim()); else break; }
|
|
178
296
|
value = block.join(' ');
|
|
179
297
|
}
|
|
180
|
-
value = value.replace(/^(["'])(.*)\1
|
|
298
|
+
value = /^["']/.test(value) ? value.replace(/^(["'])(.*)\1\s*(#.*)?$/, '$2') : value.replace(/\s+#.*$/, '');
|
|
181
299
|
if (!value) findings.push({ path, n: 1, severity: 'block', id: 'description-missing', message: '`description:` is empty — at most 300 characters saying what this document settles and where its boundary sits' });
|
|
182
300
|
else if (value.length > 300 && !disabled.has('description-length')) findings.push({ path, n: 1, severity: 'block', id: 'description-length', message: `\`description:\` is ${value.length} characters — at most 300` });
|
|
183
301
|
}
|
|
@@ -190,24 +308,47 @@ function lintFile(path, findings) {
|
|
|
190
308
|
const chrome = raw.findIndex((l) => /shields\.io|<img\b|<div\b|<p align|^!\[/.test(l));
|
|
191
309
|
if (!disabled.has('root-readme-chrome') && chrome !== -1) findings.push({ path, n: chrome + 1, severity: 'block', id: 'root-readme-chrome', message: 'badge, image or HTML layout in the root README — agents cannot read it and the team does not need it', excerpt: raw[chrome].trim().slice(0, 90) });
|
|
192
310
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
311
|
+
// The genre's own contract, from the registry: its required keys, its required sections, and a `type:` it knows.
|
|
312
|
+
const genre = genreOf(path, text);
|
|
313
|
+
const fmKeys = frontmatter(text) || new Map();
|
|
314
|
+
const declared = declaredType(text);
|
|
315
|
+
if (REGISTRY && declared && !REGISTRY.some((r) => r.type === declared) && !EXTERNAL_TYPES.test(declared) && !disabled.has('type-unknown'))
|
|
316
|
+
findings.push({ path, n: 1, severity: 'warn', id: 'type-unknown', message: `\`type: ${declared}\` names no genre in the registry (templates/Readme.md) — use its row's value` });
|
|
317
|
+
// An owning genre's keys are what the ownership scan and the graph read, so their absence blocks; elsewhere it warns.
|
|
318
|
+
const owningGenre = genre && ['feature', 'package', 'system'].includes(genre.type);
|
|
319
|
+
if (scope === 'truth' && genre && !isFolderIndex && !disabled.has('required-key') && !(genre.type === 'feature' && disabled.has('feature-frontmatter'))) {
|
|
320
|
+
for (const key of genre.keys) {
|
|
321
|
+
// `description` carries its own rule above, and an owning document's `objects` its own below.
|
|
322
|
+
if (key === 'description' || (key === 'objects' && owningGenre) || fmKeys.has(key)) continue;
|
|
323
|
+
findings.push({ path, n: 1, severity: owningGenre ? 'block' : 'warn', id: 'required-key', message: `${genre.genre.toLowerCase()} document missing \`${key}:\` frontmatter (templates/Readme.md § Registry)` });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (scope === 'truth' && genre && genre.sections.length && !isFolderIndex && fmKeys.get('status') !== 'deprecated' && !disabled.has('section-missing')) {
|
|
327
|
+
const headings = new Set(lines.map(({ line }) => /^##\s+(.+?)\s*$/.exec(line)).filter(Boolean).map((m) => m[1].toLowerCase()));
|
|
328
|
+
for (const section of genre.sections)
|
|
329
|
+
if (!headings.has(section.toLowerCase())) findings.push({ path, n: 1, severity: 'warn', id: 'section-missing', message: `${genre.genre.toLowerCase()} document has no \`## ${section}\` section (templates/Readme.md § Registry)` });
|
|
330
|
+
}
|
|
331
|
+
// A `verifies:` edge is the one doc → doc edge a person authors; a target that does not exist is a typo or a deletion.
|
|
332
|
+
for (const target of listValue(fmKeys.get('verifies'))) {
|
|
333
|
+
if (disabled.has('dangling-edge') || existsSync(target) || existsSync(join(dirname(path), target))) continue;
|
|
334
|
+
findings.push({ path, n: 1, severity: 'block', id: 'dangling-edge', message: `\`verifies: ${target}\` names no document — repo-relative path of the document whose promises this proves` });
|
|
197
335
|
}
|
|
198
336
|
|
|
199
337
|
// An owning document (feature, package, system) is intent and promises; the sections that narrate
|
|
200
338
|
// mechanism — a file roster, a walk through the code, a schema or route table — are the graph's
|
|
201
|
-
// to compute and are retired from the genre (SKILL.md §
|
|
202
|
-
const isOwningDoc = scope === 'truth' && !isFolderIndex && /(^|\/)docs\/(features
|
|
339
|
+
// to compute and are retired from the genre (SKILL.md § Truth documents).
|
|
340
|
+
const isOwningDoc = scope === 'truth' && !isFolderIndex && /(^|\/)docs\/(features\/(?:[^/]+\/)*|architecture\/(packages|systems)\/)[^/]+\.md$/.test(path);
|
|
341
|
+
if (isOwningDoc && /(^|\/)docs\/features\/[^/]+\/.+\.md$/.test(path) && !disabled.has('nested-feature'))
|
|
342
|
+
findings.push({ path, n: 1, severity: 'warn', id: 'nested-feature', message: 'feature document in a subfolder — `docs/features/` is flat; a subject too large for one file is two files' });
|
|
203
343
|
if (isOwningDoc && !disabled.has('mechanism-section')) {
|
|
204
|
-
|
|
344
|
+
// Whole headings only: "## Data retention" is a promise, "## Data" the retired table.
|
|
345
|
+
const RETIRED = /^#{2,4}\s+(Files|Key files|Current Behavior|Key Functions|Data|Data model|Data flow|API|API surface|Refusals?|Architecture|State|State management|Redux|Affected Modules|Conventions to Follow|Ownership Conflicts|Tests|The shape|Layout|Build and test|Commands|Routes|Schema|Wire)\s*:?\s*$/i;
|
|
205
346
|
for (const { n, line } of lines) {
|
|
206
347
|
if (!RETIRED.test(line) || line.includes('doc-lint-disable: mechanism-section')) continue;
|
|
207
348
|
findings.push({ path, n, severity: 'block', id: 'mechanism-section', message: 'mechanism section in an owning document — columns, routes, guards, functions and files are the graph\'s to compute; keep the decision or the promise, point by name, cut the rest', excerpt: line.trim().slice(0, 90) });
|
|
208
349
|
}
|
|
209
|
-
|
|
210
|
-
|
|
350
|
+
if (!disabled.has('objects-missing') && !listValue(fmKeys.get('objects')).length)
|
|
351
|
+
findings.push({ path, n: 1, severity: 'block', id: 'objects-missing', message: 'owning document names no `objects:` — the product objects it designs are its edge into the graph and its discovery terms' });
|
|
211
352
|
}
|
|
212
353
|
|
|
213
354
|
// The body of an owning document is scanned by people; its "For the agent" footer holds the pointers
|
|
@@ -1,41 +1,68 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
# Genre registry
|
|
2
|
+
|
|
3
|
+
Every fact about a genre lives in one row of the table below: its template, its nature, its `type:` value, where it lives, the frontmatter keys it must carry and the sections it must have. `SKILL.md`, the `maintenance` agent and the shipped `docs/structure.md` name genres and defer here for these facts, and `scripts/doc-lint.mjs` reads this table to check them. The column order is a contract: the lint parses the table, and `packages/cli/tests/scripts/genreRegistry.test.mjs` in the forge fails when a row stops parsing or a template stops agreeing with it.
|
|
4
|
+
|
|
5
|
+
## The three natures
|
|
6
|
+
|
|
7
|
+
| Nature | Holds | Changes by | Dates | Ends |
|
|
8
|
+
| --- | --- | --- | --- | --- |
|
|
9
|
+
| `truth` | What is true now | Rewritten in place, present tense; no history in the body (an AD's Decision Changelog tail is the one bridge) | Frontmatter tooling keys only | Deleted, or `status: deprecated`, when its subject is gone |
|
|
10
|
+
| `record` | What happened | Written while live, then frozen at its terminal status; never edited after | In the filename or header | Never; it is the archive |
|
|
11
|
+
| `queue` | Work not done yet | Rows appended at the end and deleted when resolved, drained or promoted; nothing else is edited | Per row, as row metadata | Never; an empty queue is the healthy state |
|
|
12
|
+
|
|
13
|
+
## Registry
|
|
14
|
+
|
|
15
|
+
| Genre | Template | Nature | `type:` | Location | Required keys | Required sections |
|
|
16
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
17
|
+
| Feature | [feature.md](feature.md) | truth | `feature` | `docs/features/{feature}.md` | `type` `description` `status` `objects` | Scope · Promises · For the agent |
|
|
18
|
+
| Package | [package.md](package.md) | truth | `package` | `docs/architecture/packages/{package}.md` | `type` `description` `status` `objects` | Scope · Ownership · Usage · Promises · For the agent |
|
|
19
|
+
| System | [system.md](system.md) | truth | `system` | `docs/architecture/systems/{system}.md` | `type` `description` `status` `objects` | Scope · Flow · Promises · For the agent |
|
|
20
|
+
| Doctrine | [doctrine.md](doctrine.md) | truth | `doctrine` | `docs/architecture/common/{concern}.md` | `type` `description` `status` `objects` | The model · For the agent |
|
|
21
|
+
| Architecture decision | [architecture-decision.md](architecture-decision.md) | truth | `architecture-decision` | `docs/architecture/decisions/AD-NNN-{subject}.md` | `type` `title` `description` `status` `last-updated` | Decision · Context · Consequences |
|
|
22
|
+
| UX behavior | [ux-behavior.md](ux-behavior.md) | truth | `ux` | `docs/features/{surface}-ux.md` | `type` `description` `status` `objects` | Purpose · Anatomy · States · Affordances · Promises · Transitions |
|
|
23
|
+
| System overview | [system-overview.md](system-overview.md) | truth | `system-overview` | `docs/architecture/system-design.md` | `type` `description` | — |
|
|
24
|
+
| Domain model | [domain-model.md](domain-model.md) | truth | `domain-model` | `docs/architecture/domain-model.md` | `type` `description` | — |
|
|
25
|
+
| Glossary | [glossary.md](glossary.md) | truth | `glossary` | `docs/architecture/glossary.md` | `type` `description` | — |
|
|
26
|
+
| Non-goals | [non-goals.md](non-goals.md) | truth | `non-goals` | `docs/architecture/non-goals.md` | `type` `description` | — |
|
|
27
|
+
| Security overview | [security-overview.md](security-overview.md) | truth | `security-overview` | `docs/architecture/security.md` | `type` `description` | — |
|
|
28
|
+
| Testing strategy | [testing-strategy.md](testing-strategy.md) | truth | `testing-strategy` | `docs/architecture/common/testing.md` | `type` `description` | — |
|
|
29
|
+
| API reference | [api-reference.md](api-reference.md) | truth | `api-reference` | `docs/architecture/api.md` | `type` `description` | — |
|
|
30
|
+
| Environment registry | [environment-registry.md](environment-registry.md) | truth | `environment-registry` | `docs/operations/environment.md` | `type` `description` | — |
|
|
31
|
+
| Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | truth | `checklist` | `docs/tests/{feature}.md` | `type` `description` `verifies` | — |
|
|
32
|
+
| Acceptance criteria | [acceptance-criteria.md](acceptance-criteria.md) | truth | `acceptance-criteria` | `docs/acceptance/{capability}.md` | `type` `description` `status` | — |
|
|
33
|
+
| Setup | [setup.md](setup.md) | truth | `operations` | `docs/operations/setup.md` | `type` `description` | Installation · First run · Verification |
|
|
34
|
+
| Operations procedure | [operations-procedure.md](operations-procedure.md) | truth | `operations` | `docs/operations/{procedure}.md` | `type` `description` | Steps · Verification |
|
|
35
|
+
| Implementation map | [implementation-map.md](implementation-map.md) | truth | `implementation-map` | `docs/operations/implementation-map.md` | `type` `description` | Waves · Active wave |
|
|
36
|
+
| Protected files | [protected-files.md](protected-files.md) | truth | `protected-files` | `docs/operations/protected-files.md` | `type` `description` | — |
|
|
37
|
+
| Docs index | [docs-readme.md](docs-readme.md) | truth | `docs-readme` | `docs/README.md` | `type` `description` | — |
|
|
38
|
+
| Folder index | — | truth | `folder-index` | `docs/{folder}/Readme.md` | `type` `description` | — |
|
|
39
|
+
| Changelog guide | [changelog-index.md](changelog-index.md) | truth | `changelog-guide` | `docs/history/CHANGELOG.md` | `type` `description` | — |
|
|
40
|
+
| Root README | [root-readme.md](root-readme.md) | truth | — | `README.md` | — | — |
|
|
41
|
+
| Plan | [plan.md](plan.md) | record | — | `docs/history/plans/YYYY-MM-DD-{slug}.md` | — | Background · Proposed Changes · Doc Obligations · Implementation Steps · Test Obligations |
|
|
42
|
+
| Changelog entry | [changelog-entry.md](changelog-entry.md) | record | — | `docs/history/changelog/YYYY-MM-DD-{three-words}.md` | — | — |
|
|
43
|
+
| Migration runbook | [migration-runbook.md](migration-runbook.md) | record | — | `docs/history/migrations/YYYY-MM-DD-{slug}.md` | — | — |
|
|
44
|
+
| Hand-over | [handover.md](handover.md) | record | `handover` | `docs/history/handovers/YYYY-MM-DD-{slug}.md` | `type` `description` `status` | — |
|
|
45
|
+
| Health log | — | record | — | `docs/history/health-log.md` | — | — |
|
|
46
|
+
| Deep research | — | record | `research` | `docs/history/research/{topic}/` | — | — |
|
|
47
|
+
| Brainstorm session | — | record | `brainstorm` | `docs/research/{topic}-brainstorm/` | — | — |
|
|
48
|
+
| Issue | — | record | `issue` | `docs/operations/issues/YYYY-MM-DD-{slug}.md` | — | — |
|
|
49
|
+
| Triage dossier | — | record | `triage` | `docs/operations/triage/YYYY-MM-DD-{slug}.md` | — | — |
|
|
50
|
+
| Release note | — | record | — | `docs/operations/release-notes/YYYY-MM-DD-vX.Y.Z.md` | — | — |
|
|
51
|
+
| Backlog | [backlog.md](backlog.md) | queue | — | `docs/history/backlog.md` | — | — |
|
|
52
|
+
| Journal | [journal.md](journal.md) | queue | — | `docs/history/journal.md` | — | — |
|
|
53
|
+
| Bookkeeping | [bookkeeping.md](bookkeeping.md) | queue | — | `docs/operations/bookkeeping.md` | — | — |
|
|
54
|
+
| Project memory | [memory-main.md](memory-main.md) | queue | `project-memory` | `docs/memory/main.md` | `type` `description` | — |
|
|
55
|
+
| Agent memory | — | queue | `agent-memory` | `docs/memory/{agent}.md` | — | — |
|
|
56
|
+
|
|
57
|
+
- **`type:` values:** a genre's `type:` value is the one its template's skeleton carries, and two genres may share one (setup and operations procedure are both `operations`). A dash means the genre carries no frontmatter, or carries none it must have. A value this table does not name is reported by the lint as `type-unknown`.
|
|
58
|
+
- **Locations:** `{name}` is one kebab-case path segment, `YYYY-MM-DD` a date, `AD-NNN` a zero-padded number. A location is where the genre lives in a repository that uses it; a folder nothing uses is absent.
|
|
59
|
+
- **Required sections:** the `##` headings every document of the genre carries, by name; the template states the optional ones and when each is written. A dash means the template's sections are guidance, not a fixed set. The lint checks them on truth documents; a plan's are checked by the hooks that read it.
|
|
60
|
+
- **Rows without a template:** each is written by its own tool (the `maintenance` agent's Health Log, the `brainstorm` skill, the deep-research and `issue-bridge` packs, a release pipeline) and is listed so its nature and place are settled here too.
|
|
61
|
+
|
|
62
|
+
## Ownership
|
|
63
|
+
|
|
64
|
+
Three genres speak for code — **feature**, **package**, **system** — and a **UX** document speaks for a surface's components where none of them does (`ux-behavior.md`). A document names the objects it designs in `objects:`; a source file names the document that speaks for it in its path header. The harness's ownership scan reads one edge, the transitional `owns:` region glob on each owning document (`packages/{name}/**` on a package, a machine's subtrees on a system, a feature's own directory on a feature), and the narrower glob wins. The header pointer is written alongside it so the scan can switch to it and the object closure without a second pass over the code (`references/frontmatter.md`).
|
|
65
|
+
|
|
66
|
+
## Baseline scaffolds
|
|
67
|
+
|
|
68
|
+
The `maintenance` agent's baseline creates these when missing, from their templates: the docs index, the changelog guide, the backlog, project memory, setup (when the repo has a lockfile and a runnable root script; never a second first-run document) and the root README (never overwriting one). `docs/structure.md` is not among them: the installer ships it whole and overwrites it on every refresh.
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
# Acceptance
|
|
1
|
+
# Acceptance criteria
|
|
2
2
|
|
|
3
|
-
Truth document. Use for: the promises one capability must keep, stated as machine-parseable
|
|
4
|
-
claims. Lives in `docs/acceptance/`, one `kebab-case.md` per capability. Written at specification
|
|
5
|
-
time — before implementation — and read by verification surfaces, which parse each `AC-n` line as
|
|
6
|
-
a claim to grade the built product against.
|
|
3
|
+
Truth document. Use for: the promises one capability must keep, stated as machine-parseable claims. Lives in `docs/acceptance/`, one `kebab-case.md` per capability. Written at specification time — before implementation — and read by verification surfaces, which parse each `AC-n` line as a claim to grade the built product against.
|
|
7
4
|
|
|
8
5
|
### Rules
|
|
9
6
|
|
|
10
|
-
- **Frontmatter is required**: `type: acceptance-criteria`, `status:` (`draft` → `active` →
|
|
11
|
-
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
graded still resolves.
|
|
15
|
-
- **Each criterion is one observable assert**, phrased as a checkable statement about the running
|
|
16
|
-
product — never about the code's internals. If it cannot be observed from outside, it is a
|
|
17
|
-
design note, not a criterion.
|
|
18
|
-
- **The `AC-n:` line grammar is load-bearing** — verification parses these lines. Keep the key at
|
|
19
|
-
line start, exactly `AC-<number>:`, followed by the assert sentence. Formatting that buries the
|
|
20
|
-
key (tables, emphasis-wrapped keys, nested bullets) breaks the parse.
|
|
7
|
+
- **Frontmatter is required**: `type: acceptance-criteria`, `status:` (`draft` → `active` → `deprecated`). No `owns:` — acceptance criteria own no code.
|
|
8
|
+
- **One `AC-n` key per criterion, stable forever.** Keys are never renumbered and never reused: a criterion that no longer applies is marked retired in place, so every claim a past verification graded still resolves.
|
|
9
|
+
- **Each criterion is one observable assert**, phrased as a checkable statement about the running product — never about the code's internals. If it cannot be observed from outside, it is a design note, not a criterion.
|
|
10
|
+
- **The `AC-n:` line grammar is load-bearing** — verification parses these lines. Keep the key at line start, exactly `AC-<number>:`, followed by the assert sentence. Formatting that buries the key (tables, emphasis-wrapped keys, nested bullets) breaks the parse.
|
|
21
11
|
|
|
22
12
|
### Template
|
|
23
13
|
|
|
@@ -32,7 +22,9 @@ status: draft
|
|
|
32
22
|
|
|
33
23
|
Scope: {one line — what part of the product these criteria bind}.
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
AC-1: {observable assert about the running product}.
|
|
26
|
+
|
|
27
|
+
AC-2: {observable assert}.
|
|
28
|
+
|
|
29
|
+
AC-3: (retired) {kept verbatim, marked retired — never renumbered}.
|
|
38
30
|
```
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
# API
|
|
1
|
+
# API reference (docs/architecture/api.md)
|
|
2
2
|
|
|
3
|
-
Truth document. Use for: the consolidated catalog of the system's HTTP surface — every live route in one place. This is the **
|
|
3
|
+
Truth document. Use for: the consolidated catalog of the system's HTTP surface — every live route in one place. This is the **reference document** for the API surface: a route's shape lives here or in the generated spec, never in a feature doc, which states what the route promises and leaves its shape to this catalog. If the repo generates an OpenAPI spec, this doc points at it and carries only what the spec can't (auth model, conventions, ownership links).
|
|
4
4
|
|
|
5
5
|
Required: **Conventions**, the **route tables**. One row per live route; a route that isn't here doesn't exist.
|
|
6
6
|
|
|
7
|
-
```
|
|
7
|
+
```markdown
|
|
8
|
+
---
|
|
9
|
+
type: api-reference
|
|
10
|
+
description: "{at most 300 characters: what this document settles and where its boundary sits}"
|
|
11
|
+
---
|
|
12
|
+
|
|
8
13
|
# API Reference
|
|
9
14
|
|
|
10
15
|
## Conventions
|