@erclx/canon 5.31.0 → 5.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +4 -16
- package/claude/skills/auto-ship/SKILL.md +13 -58
- package/claude/skills/auto-ship/references/draft-mark.md +2 -2
- package/claude/skills/auto-ship/references/failure-recovery.md +1 -5
- package/claude/skills/auto-ship/references/tier-failures.md +1 -1
- package/claude/skills/auto-ship/references/ui-checklist.md +2 -2
- package/claude/skills/auto-ship/references/verb-absent.md +3 -27
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/canon-cli/REQUIREMENT.md +4 -6
- package/claude/skills/canon-cli/SKILL.md +2 -2
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
- package/claude/skills/canon-feedback/SKILL.md +10 -13
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +13 -14
- package/claude/skills/canon-feedback-triage/SKILL.md +49 -30
- package/claude/skills/canvas/REQUIREMENT.md +3 -4
- package/claude/skills/canvas/SKILL.md +5 -5
- package/{standards → claude/skills/canvas/references}/canvas.md +12 -1
- package/claude/skills/ci-workflow/REQUIREMENT.md +0 -1
- package/claude/skills/context-fold/REQUIREMENT.md +8 -20
- package/claude/skills/context-fold/SKILL.md +20 -44
- package/claude/skills/context-fold/references/classify.md +7 -8
- package/claude/skills/context-fold/references/completion-report.md +2 -6
- package/claude/skills/context-fold/references/context-refresh.md +5 -15
- package/claude/skills/context-fold/references/promoted-pages.md +3 -3
- package/claude/skills/context-fold/references/receipt-sweep.md +6 -17
- package/claude/skills/context-fold/references/unusable-baseline.md +4 -6
- package/claude/skills/create-rule/REQUIREMENT.md +0 -1
- package/claude/skills/create-skill/REQUIREMENT.md +6 -4
- package/claude/skills/create-skill/SKILL.md +8 -7
- package/{standards → claude/skills/create-skill/references}/skill-paths.md +1 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +0 -2
- package/claude/skills/design-taste/SKILL.md +1 -3
- package/claude/skills/design-taste/references/vocabulary.md +1 -1
- package/claude/skills/docs-sync/REQUIREMENT.md +3 -3
- package/claude/skills/document-health/REQUIREMENT.md +2 -2
- package/claude/skills/document-health/SKILL.md +4 -5
- package/claude/skills/document-health/references/axes.md +4 -6
- package/claude/skills/draft-and-pick/REQUIREMENT.md +16 -14
- package/claude/skills/draft-and-pick/SKILL.md +40 -32
- package/claude/skills/draft-and-pick/references/live-arms.md +9 -9
- package/claude/skills/draft-doc/REQUIREMENT.md +5 -5
- package/claude/skills/draft-doc/SKILL.md +9 -10
- package/claude/skills/draft-doc/references/requirements.md +61 -0
- package/claude/skills/draft-doc/references/wiki-kind.md +53 -0
- package/claude/skills/draft-doc/references/wiki.md +50 -30
- package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
- package/claude/skills/draft-figure/SKILL.md +4 -4
- package/claude/skills/draft-figure/references/architecture-set.md +4 -4
- package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
- package/claude/skills/draft-figure/references/mermaid-path.md +55 -0
- package/claude/skills/draft-figure/references/mermaid.md +43 -34
- package/claude/skills/draft-identity/REQUIREMENT.md +1 -1
- package/claude/skills/draft-identity/SKILL.md +9 -7
- package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
- package/claude/skills/draft-ready/SKILL.md +6 -6
- package/claude/skills/draft-ready/references/assembly.md +1 -1
- package/{standards → claude/skills/draft-ready/references}/ready.md +13 -5
- package/claude/skills/git-issue/references/issue.md +3 -3
- package/claude/skills/git-ship/REQUIREMENT.md +0 -2
- package/claude/skills/git-ship/SKILL.md +16 -24
- package/claude/skills/plan-feature/SKILL.md +2 -6
- package/{standards → claude/skills/plan-feature/references}/plan.md +9 -10
- package/claude/skills/plan-groundwork/SKILL.md +5 -6
- package/{standards → claude/skills/plan-groundwork/references}/groundwork.md +17 -4
- package/claude/skills/plan-intake/SKILL.md +2 -3
- package/{standards → claude/skills/plan-intake/references}/intake.md +17 -2
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -1
- package/claude/skills/review-address/SKILL.md +1 -1
- package/claude/skills/review-craft/REQUIREMENT.md +3 -3
- package/claude/skills/review-craft/SKILL.md +2 -2
- package/claude/skills/review-craft/references/ui.md +4 -4
- package/claude/skills/review-pr/REQUIREMENT.md +1 -1
- package/claude/skills/review-pr/SKILL.md +5 -7
- package/claude/skills/review-ui/REQUIREMENT.md +0 -1
- package/claude/skills/review-ui/SKILL.md +1 -1
- package/claude/skills/role-orchestrator/REQUIREMENT.md +0 -3
- package/claude/skills/role-orchestrator/SKILL.md +8 -8
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +3 -3
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +7 -10
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +0 -3
- package/claude/skills/role-orchestrator/references/orchestrator-resume.md +1 -1
- package/claude/skills/role-planner/SKILL.md +2 -2
- package/claude/skills/role-reviewer/SKILL.md +1 -1
- package/claude/skills/role-worker/SKILL.md +2 -2
- package/claude/skills/session-compact/REQUIREMENT.md +1 -4
- package/claude/skills/session-compact/SKILL.md +3 -13
- package/claude/skills/session-compact/references/handoff-note.md +2 -2
- package/claude/skills/session-map/REQUIREMENT.md +1 -9
- package/claude/skills/session-map/SKILL.md +8 -16
- package/{standards → claude/skills/session-map/references}/session.md +17 -11
- package/claude/skills/session-relay/REQUIREMENT.md +1 -1
- package/claude/skills/session-resume/REQUIREMENT.md +4 -6
- package/claude/skills/session-resume/SKILL.md +3 -9
- package/claude/skills/session-worktree/SKILL.md +1 -1
- package/claude/skills/session-worktree/references/main-root-writes.md +2 -2
- package/claude/skills/systematic-debugging/REQUIREMENT.md +0 -1
- package/claude/skills/target-check/REQUIREMENT.md +3 -3
- package/claude/skills/target-check/SKILL.md +1 -1
- package/claude/skills/target-check/references/domains.md +1 -1
- package/claude/skills/target-setup/REQUIREMENT.md +4 -4
- package/claude/skills/target-setup/SKILL.md +3 -3
- package/claude/skills/target-setup/references/detect.md +1 -1
- package/claude/skills/task-board/SKILL.md +4 -4
- package/claude/skills/task-board/references/archive.md +1 -1
- package/{standards → claude/skills/task-board/references}/board.md +9 -0
- package/claude/skills/task-board/references/decline.md +1 -1
- package/{standards → claude/skills/task-board/references}/tasks.md +13 -8
- package/claude/skills/teach-workspace/SKILL.md +1 -1
- package/claude/skills/teach-workspace/references/revisit.md +1 -1
- package/{standards → claude/skills/teach-workspace/references}/teach.md +17 -5
- package/claude/skills/ux-audit/REQUIREMENT.md +2 -2
- package/claude/skills/ux-audit/SKILL.md +2 -3
- package/claude/skills/write-human/REQUIREMENT.md +0 -1
- package/claude/skills/write-human/SKILL.md +1 -1
- package/docs/agents/audits.md +1 -1
- package/docs/agents/canvas.md +1 -1
- package/docs/agents/commands.md +1 -6
- package/docs/agents/context-audit-checks.md +9 -23
- package/docs/agents/context-audit.md +7 -15
- package/docs/agents/context-classify.md +5 -5
- package/docs/agents/counts.md +5 -5
- package/docs/agents/drift-surfaces.md +2 -2
- package/docs/agents/index.md +3 -5
- package/docs/agents/install-and-sync.md +6 -6
- package/docs/agents/intake.md +1 -1
- package/docs/agents/records-stale.md +5 -5
- package/docs/agents/records-validate.md +1 -1
- package/docs/agents/records.md +2 -2
- package/docs/agents/restated.md +1 -1
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/skills-audit.md +2 -2
- package/docs/agents/state-scoped-risk.md +2 -2
- package/docs/agents/superseded.md +2 -2
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/tasks.md +1 -1
- package/docs/agents/teach.md +1 -1
- package/docs/agents/upstream.md +26 -2
- package/docs/target/migrations.md +24 -0
- package/docs/target/projects.md +5 -7
- package/docs/target/scenarios.md +2 -2
- package/docs/target/sync.md +3 -3
- package/docs/workflow/ai-workflow.md +7 -38
- package/docs/workflow/index.md +1 -1
- package/docs/workflow/operating-model.md +8 -8
- package/docs/workflow/parallel-features.md +7 -7
- package/docs/workflow/skill-map-on-request.md +12 -15
- package/docs/workflow/skill-map.md +20 -32
- package/docs/workflow/visual-design-workflow.md +11 -14
- package/governance/renames.toml +0 -19
- package/governance/rules/canon/605-worktrees.md +1 -1
- package/governance/rules/{standards → canon}/plan.md +4 -5
- package/governance/rules/canon/tasks.md +18 -0
- package/governance/stacks/base.toml +1 -1
- package/package.json +1 -1
- package/scripts/core/install-check.sh +1 -1
- package/scripts/core/regen-web-previews.ts +1 -1
- package/src/audits/catalog.ts +5 -97
- package/src/claude/cases/authoring.ts +0 -18
- package/src/claude/cases/misc.ts +0 -13
- package/src/claude/cases/setup.ts +3 -20
- package/src/claude/cases/workflow.ts +0 -33
- package/src/claude/seeds.ts +1 -7
- package/src/claude/skills-audit.ts +1 -1
- package/src/cli/help.ts +1 -4
- package/src/cli.ts +0 -4
- package/src/commands/claude.ts +8 -14
- package/src/commands/context.ts +18 -280
- package/src/commands/feedback.ts +22 -91
- package/src/commands/records.ts +5 -5
- package/src/commands/sync.ts +5 -3
- package/src/commands/upstream.ts +135 -0
- package/src/context/audit.ts +5 -14
- package/src/context/classify/extract.ts +6 -13
- package/src/context/classify/patterns.ts +2 -41
- package/src/context/classify/prompts.ts +2 -6
- package/src/context/classify/run.ts +3 -3
- package/src/context/folders.ts +7 -12
- package/src/context/gate.ts +11 -91
- package/src/context/requirements.ts +13 -4
- package/src/counts/scan.ts +3 -3
- package/src/design/components.ts +2 -2
- package/src/feedback/body.ts +0 -12
- package/src/gate/measures.ts +6 -57
- package/src/gov/citations.ts +9 -3
- package/src/gov/restated.ts +3 -3
- package/src/markdown/structure.ts +2 -2
- package/src/migrate/record-layout.ts +3 -3
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +2 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/roots/record.ts +1 -1
- package/src/sandbox/scenario.ts +115 -11
- package/src/shipped/references.ts +3 -3
- package/src/standards/read.ts +31 -0
- package/src/sync/seeds-report.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/validate.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/upstream/cursor.ts +1 -1
- package/src/upstream/due.ts +128 -0
- package/src/upstream/releases.ts +2 -1
- package/standards/context.md +3 -5
- package/standards/design.md +2 -3
- package/standards/index.md +0 -22
- package/standards/publish.md +1 -1
- package/standards/skill.md +1 -1
- package/tooling/base/reference.md +1 -1
- package/tooling/claude/reference.md +6 -20
- package/tooling/claude/seeds/.claude/hooks/records-index.sh +8 -20
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +3 -3
- package/tooling/claude/seeds/CLAUDE.md +1 -3
- package/tooling/claude/seeds/canon/REQUIREMENTS.md +2 -9
- package/tooling/web/configs/e2e/screenshot.ts +0 -1
- package/claude/skills/auto-ship/references/review-findings.md +0 -20
- package/claude/skills/bash-cli-script/REQUIREMENT.md +0 -43
- package/claude/skills/bash-cli-script/SKILL.md +0 -53
- package/claude/skills/bash-cli-script/references/template.md +0 -19
- package/claude/skills/canon-operator/REQUIREMENT.md +0 -62
- package/claude/skills/canon-operator/SKILL.md +0 -113
- package/claude/skills/canon-rollout/REQUIREMENT.md +0 -59
- package/claude/skills/canon-rollout/SKILL.md +0 -140
- package/claude/skills/canon-rollout/references/failure-recovery.md +0 -20
- package/claude/skills/context-fold/references/anchor-sweep.md +0 -58
- package/claude/skills/context-fold/references/wireframe-sweep.md +0 -45
- package/claude/skills/create-standard/REQUIREMENT.md +0 -33
- package/claude/skills/create-standard/SKILL.md +0 -31
- package/claude/skills/design-extract/REQUIREMENT.md +0 -42
- package/claude/skills/design-extract/SKILL.md +0 -108
- package/claude/skills/draft-and-pick/references/frame.html +0 -118
- package/claude/skills/draft-doc/references/wireframes.md +0 -48
- package/claude/skills/markdown-propose/REQUIREMENT.md +0 -47
- package/claude/skills/markdown-propose/SKILL.md +0 -122
- package/claude/skills/markdown-propose/references/format.md +0 -118
- package/claude/skills/memory-capture/REQUIREMENT.md +0 -50
- package/claude/skills/memory-capture/SKILL.md +0 -117
- package/claude/skills/memory-review/REQUIREMENT.md +0 -52
- package/claude/skills/memory-review/SKILL.md +0 -129
- package/claude/skills/memory-review/references/later-phases.md +0 -118
- package/claude/skills/memory-review/references/receipt-format.md +0 -64
- package/claude/skills/project-commands/REQUIREMENT.md +0 -42
- package/claude/skills/project-commands/SKILL.md +0 -87
- package/claude/skills/repo-metadata/REQUIREMENT.md +0 -37
- package/claude/skills/repo-metadata/SKILL.md +0 -54
- package/claude/skills/restate-plainly/REQUIREMENT.md +0 -41
- package/claude/skills/restate-plainly/SKILL.md +0 -41
- package/claude/skills/review-branch/REQUIREMENT.md +0 -40
- package/claude/skills/review-branch/SKILL.md +0 -137
- package/claude/skills/seed-sync/REQUIREMENT.md +0 -45
- package/claude/skills/seed-sync/SKILL.md +0 -158
- package/claude/skills/sketch-design/REQUIREMENT.md +0 -38
- package/claude/skills/sketch-design/SKILL.md +0 -94
- package/claude/skills/ux-walkthrough/REQUIREMENT.md +0 -56
- package/claude/skills/ux-walkthrough/SKILL.md +0 -53
- package/claude/skills/ux-walkthrough/references/builds.md +0 -15
- package/claude/skills/ux-walkthrough/references/candidate-pages.md +0 -11
- package/claude/skills/ux-walkthrough/references/measuring.md +0 -22
- package/claude/skills/ux-walkthrough/references/record.md +0 -36
- package/claude/skills/ux-walkthrough/references/relay.md +0 -25
- package/docs/agents/architecture-record.md +0 -36
- package/docs/agents/review-classification.md +0 -76
- package/governance/rules/canon/603-memory.md +0 -11
- package/governance/rules/lang/120-bash.md +0 -18
- package/governance/rules/standards/architecture.md +0 -13
- package/governance/rules/standards/groundwork.md +0 -13
- package/governance/rules/standards/intake.md +0 -12
- package/governance/rules/standards/memory.md +0 -12
- package/governance/rules/standards/ready.md +0 -13
- package/governance/rules/standards/requirements.md +0 -12
- package/governance/rules/standards/session.md +0 -13
- package/governance/rules/standards/tasks.md +0 -15
- package/governance/rules/standards/teach.md +0 -14
- package/governance/rules/standards/wiki.md +0 -13
- package/governance/rules/standards/wireframes.md +0 -13
- package/src/autoship/classify.ts +0 -75
- package/src/autoship/paths.ts +0 -50
- package/src/commands/autoship.ts +0 -129
- package/src/commands/repo.ts +0 -393
- package/src/context/architecture.ts +0 -654
- package/src/context/wireframe-states.ts +0 -238
- package/src/repo/metadata.ts +0 -206
- package/standards/architecture.md +0 -127
- package/standards/memory.md +0 -158
- package/standards/mermaid.md +0 -64
- package/standards/requirements.md +0 -79
- package/standards/wiki.md +0 -77
- package/standards/wireframes.md +0 -143
- package/tooling/claude/seeds/.claude/memory/index.md +0 -8
- package/tooling/claude/seeds/canon/ARCHITECTURE.md +0 -23
- package/tooling/claude/seeds/canon/wireframes/index.md +0 -8
- /package/{standards → claude/skills/create-skill/references}/skill-practice.md +0 -0
- /package/{standards → claude/skills/create-skill/references}/skill-requirement.md +0 -0
- /package/{standards → claude/skills/plan-feature/references}/plan-lifecycle.md +0 -0
- /package/{standards → claude/skills/plan-groundwork/references}/groundwork-spikes.md +0 -0
- /package/{standards → claude/skills/task-board/references}/tasks-decline.md +0 -0
- /package/{standards → claude/skills/teach-workspace/references}/glossary.md +0 -0
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
2
|
-
import { basename, dirname, join, relative } from 'node:path'
|
|
3
|
-
import type { AuditedFolder } from '@/context/folders'
|
|
4
|
-
import { type BodyLine, bodyLines } from '@/markdown/scan'
|
|
5
|
-
|
|
6
|
-
/** `standards/wireframes.md`'s `## States` heading, matched by text alone. */
|
|
7
|
-
const STATES_HEADING = /^##\s+States\s*$/i
|
|
8
|
-
const SECTION_HEADING = /^##\s+\S/
|
|
9
|
-
const TABLE_ROW = /^\s*\|/
|
|
10
|
-
const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
|
|
11
|
-
|
|
12
|
-
/** The one exception the standard admits to the states/evidence bijection. */
|
|
13
|
-
const NOT_CAPTURED = 'not captured'
|
|
14
|
-
|
|
15
|
-
/** A fenced block's opening delimiter naming the `plaintext` info string. */
|
|
16
|
-
const PLAINTEXT_FENCE = /^`{3,}\s*plaintext\s*$/i
|
|
17
|
-
|
|
18
|
-
export interface StateRow {
|
|
19
|
-
readonly line: number
|
|
20
|
-
readonly state: string
|
|
21
|
-
/** The evidence cell as written, backticks and all. */
|
|
22
|
-
readonly evidence: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface MissingFolderFinding {
|
|
26
|
-
readonly line: number
|
|
27
|
-
readonly state: string
|
|
28
|
-
/** The cited path, so a report names what to go create or fix. */
|
|
29
|
-
readonly path: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface UnlistedFolderFinding {
|
|
33
|
-
/** The evidence root the folder sits under, cited by at least one row. */
|
|
34
|
-
readonly root: string
|
|
35
|
-
readonly folder: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface WireframeStatesReport {
|
|
39
|
-
readonly rel: string
|
|
40
|
-
readonly rows: readonly StateRow[]
|
|
41
|
-
/** A row whose evidence cell names a path with no folder at it. */
|
|
42
|
-
readonly missingFolders: readonly MissingFolderFinding[]
|
|
43
|
-
/** A folder under a cited root that no row names. */
|
|
44
|
-
readonly unlistedFolders: readonly UnlistedFolderFinding[]
|
|
45
|
-
/** Line of the `plaintext` fence, absent from an entry carrying no sketch. */
|
|
46
|
-
readonly sketchLine?: number
|
|
47
|
-
/** Whether that sketch sits beside a state whose evidence already exists. */
|
|
48
|
-
readonly sketchWithEvidence: boolean
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function cells(row: string): string[] {
|
|
52
|
-
const parts = row.split('|')
|
|
53
|
-
if ((parts[0] ?? '').trim() === '') parts.shift()
|
|
54
|
-
if ((parts[parts.length - 1] ?? '').trim() === '') parts.pop()
|
|
55
|
-
return parts.map((cell) => cell.trim())
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function columnIndex(headers: readonly string[], name: string): number {
|
|
59
|
-
return headers.findIndex(
|
|
60
|
-
(header) => header.toLowerCase() === name.toLowerCase(),
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Reads the States table under the `## States` heading, matching its `State`
|
|
66
|
-
* and `Evidence` columns by header text rather than position.
|
|
67
|
-
*
|
|
68
|
-
* Column order is the one part of the standard's own table the standard could
|
|
69
|
-
* still move during its own review, per the plan this ships under. Matching
|
|
70
|
-
* position would break silently on a reorder, where matching text breaks the
|
|
71
|
-
* same way a renamed header would: visibly, by finding nothing.
|
|
72
|
-
*/
|
|
73
|
-
export function parseStatesTable(lines: readonly BodyLine[]): StateRow[] {
|
|
74
|
-
let index = 0
|
|
75
|
-
while (index < lines.length && !STATES_HEADING.test(lines[index].text)) {
|
|
76
|
-
index++
|
|
77
|
-
}
|
|
78
|
-
if (index >= lines.length) return []
|
|
79
|
-
index++
|
|
80
|
-
|
|
81
|
-
while (index < lines.length && !SECTION_HEADING.test(lines[index].text)) {
|
|
82
|
-
const separator = lines[index + 1]
|
|
83
|
-
if (
|
|
84
|
-
TABLE_ROW.test(lines[index].text) &&
|
|
85
|
-
separator !== undefined &&
|
|
86
|
-
TABLE_SEPARATOR.test(separator.text)
|
|
87
|
-
) {
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
index++
|
|
91
|
-
}
|
|
92
|
-
if (
|
|
93
|
-
index >= lines.length ||
|
|
94
|
-
SECTION_HEADING.test(lines[index].text) ||
|
|
95
|
-
!TABLE_ROW.test(lines[index].text)
|
|
96
|
-
) {
|
|
97
|
-
return []
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const headers = cells(lines[index].text)
|
|
101
|
-
const stateCol = columnIndex(headers, 'State')
|
|
102
|
-
const evidenceCol = columnIndex(headers, 'Evidence')
|
|
103
|
-
if (stateCol === -1 || evidenceCol === -1) return []
|
|
104
|
-
|
|
105
|
-
index += 2
|
|
106
|
-
const rows: StateRow[] = []
|
|
107
|
-
while (index < lines.length && TABLE_ROW.test(lines[index].text)) {
|
|
108
|
-
const row = cells(lines[index].text)
|
|
109
|
-
rows.push({
|
|
110
|
-
line: lines[index].number,
|
|
111
|
-
state: row[stateCol] ?? '',
|
|
112
|
-
evidence: row[evidenceCol] ?? '',
|
|
113
|
-
})
|
|
114
|
-
index++
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return rows
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Line of the first `plaintext`-fenced sketch, or nothing. */
|
|
121
|
-
export function findSketchLine(lines: readonly BodyLine[]): number | undefined {
|
|
122
|
-
for (const line of lines) {
|
|
123
|
-
if (PLAINTEXT_FENCE.test(line.text.trim())) return line.number
|
|
124
|
-
}
|
|
125
|
-
return undefined
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Reads an evidence cell into the path it cites, or nothing for the one cell
|
|
130
|
-
* value the standard exempts from having one.
|
|
131
|
-
*/
|
|
132
|
-
function evidencePath(cell: string): string | undefined {
|
|
133
|
-
const stripped = cell.replace(/^`+|`+$/g, '').trim()
|
|
134
|
-
if (stripped.toLowerCase() === NOT_CAPTURED) return undefined
|
|
135
|
-
return stripped.replace(/\/+$/, '')
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function isDirectory(path: string): Promise<boolean> {
|
|
139
|
-
try {
|
|
140
|
-
return (await stat(path)).isDirectory()
|
|
141
|
-
} catch {
|
|
142
|
-
return false
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function subfolders(path: string): Promise<string[]> {
|
|
147
|
-
try {
|
|
148
|
-
const entries = await readdir(path, { withFileTypes: true })
|
|
149
|
-
return entries
|
|
150
|
-
.filter((entry) => entry.isDirectory())
|
|
151
|
-
.map((entry) => entry.name)
|
|
152
|
-
} catch {
|
|
153
|
-
return []
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Measures one wireframe entry against its own States table.
|
|
159
|
-
*
|
|
160
|
-
* The evidence cell resolves as the literal path it names against `root`,
|
|
161
|
-
* per the plan's operator answer, since the standard puts the path there and a
|
|
162
|
-
* root guessed from convention reads wrong in a monorepo carrying more than
|
|
163
|
-
* one evidence tree.
|
|
164
|
-
*
|
|
165
|
-
* An unlisted folder is reported only under a root at least one row already
|
|
166
|
-
* cites, per the same operator answer, so a project holding evidence with no
|
|
167
|
-
* states table at all reports nothing. `roots` is built from the rows this
|
|
168
|
-
* loop already resolved rather than scanned separately, which is what keeps
|
|
169
|
-
* the two readings in the same pass.
|
|
170
|
-
*/
|
|
171
|
-
export async function measureWireframeStates(
|
|
172
|
-
root: string,
|
|
173
|
-
rel: string,
|
|
174
|
-
source: string,
|
|
175
|
-
): Promise<WireframeStatesReport> {
|
|
176
|
-
const lines = bodyLines(source)
|
|
177
|
-
const rows = parseStatesTable(lines)
|
|
178
|
-
const sketchLine = findSketchLine(lines)
|
|
179
|
-
|
|
180
|
-
const missingFolders: MissingFolderFinding[] = []
|
|
181
|
-
const cited = new Map<string, Set<string>>()
|
|
182
|
-
let anyCaptured = false
|
|
183
|
-
|
|
184
|
-
for (const row of rows) {
|
|
185
|
-
const path = evidencePath(row.evidence)
|
|
186
|
-
if (path === undefined) continue
|
|
187
|
-
|
|
188
|
-
if (!(await isDirectory(join(root, path)))) {
|
|
189
|
-
missingFolders.push({ line: row.line, state: row.state, path })
|
|
190
|
-
continue
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
anyCaptured = true
|
|
194
|
-
const evidenceRoot = dirname(path)
|
|
195
|
-
const folder = basename(path)
|
|
196
|
-
const named = cited.get(evidenceRoot) ?? new Set<string>()
|
|
197
|
-
named.add(folder)
|
|
198
|
-
cited.set(evidenceRoot, named)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const unlistedFolders: UnlistedFolderFinding[] = []
|
|
202
|
-
for (const [evidenceRoot, named] of cited) {
|
|
203
|
-
for (const folder of await subfolders(join(root, evidenceRoot))) {
|
|
204
|
-
if (!named.has(folder)) {
|
|
205
|
-
unlistedFolders.push({ root: evidenceRoot, folder })
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return {
|
|
211
|
-
rel,
|
|
212
|
-
rows,
|
|
213
|
-
missingFolders,
|
|
214
|
-
unlistedFolders,
|
|
215
|
-
...(sketchLine !== undefined && { sketchLine }),
|
|
216
|
-
sketchWithEvidence: sketchLine !== undefined && anyCaptured,
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** Measures every wireframe entry in a resolved `wireframes` folder. */
|
|
221
|
-
export async function measureWireframeFolder(
|
|
222
|
-
root: string,
|
|
223
|
-
folder: AuditedFolder,
|
|
224
|
-
): Promise<WireframeStatesReport[]> {
|
|
225
|
-
const reports: WireframeStatesReport[] = []
|
|
226
|
-
|
|
227
|
-
for (const path of folder.entries) {
|
|
228
|
-
reports.push(
|
|
229
|
-
await measureWireframeStates(
|
|
230
|
-
root,
|
|
231
|
-
relative(root, path),
|
|
232
|
-
await readFile(path, 'utf8'),
|
|
233
|
-
),
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return reports
|
|
238
|
-
}
|
package/src/repo/metadata.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* What this run can compute from the tree alone. A field stays absent rather
|
|
6
|
-
* than empty when nothing local resolves it, so a caller never reads
|
|
7
|
-
* "nothing to propose" as "propose removing what is already there."
|
|
8
|
-
*/
|
|
9
|
-
export interface MetadataProposal {
|
|
10
|
-
readonly description?: string
|
|
11
|
-
readonly homepage?: string
|
|
12
|
-
readonly topics?: readonly string[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** What the remote already carries, read by the caller rather than here. */
|
|
16
|
-
export interface CurrentMetadata {
|
|
17
|
-
readonly description: string
|
|
18
|
-
readonly homepage: string
|
|
19
|
-
readonly topics: readonly string[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface MetadataDiff {
|
|
23
|
-
readonly description?: { readonly current: string; readonly proposed: string }
|
|
24
|
-
readonly homepage?: { readonly current: string; readonly proposed: string }
|
|
25
|
-
readonly topics?: {
|
|
26
|
-
readonly added: readonly string[]
|
|
27
|
-
readonly removed: readonly string[]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** GitHub's own cap on the About field. */
|
|
32
|
-
const MAX_DESCRIPTION_LENGTH = 350
|
|
33
|
-
|
|
34
|
-
/** GitHub's own cap on topics per repository. */
|
|
35
|
-
const MAX_TOPICS = 20
|
|
36
|
-
|
|
37
|
-
/** GitHub's own shape for a topic: lowercase, alphanumeric, internal hyphens. */
|
|
38
|
-
const TOPIC_PATTERN = /^[a-z0-9][a-z0-9-]*$/
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Whether an already-trimmed, already-lowercased string is a shape GitHub
|
|
42
|
-
* accepts as a topic. Exported so a caller validating an operator-supplied
|
|
43
|
-
* topic list checks against the same rule this reader silently filters
|
|
44
|
-
* `package.json`'s `keywords` through, rather than reimplementing it against
|
|
45
|
-
* a looser test such as non-emptiness alone.
|
|
46
|
-
*/
|
|
47
|
-
export function isValidTopic(topic: string): boolean {
|
|
48
|
-
return TOPIC_PATTERN.test(topic)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* A bare image, or an image wrapped in a link, which is the shape a shields.io
|
|
53
|
-
* badge takes. The wrapped alternative goes first, since the bare form would
|
|
54
|
-
* otherwise match its inner image alone and leave the wrapping link behind.
|
|
55
|
-
*/
|
|
56
|
-
const BADGE_TOKEN = /\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)|!\[[^\]]*\]\([^)]*\)/g
|
|
57
|
-
|
|
58
|
-
/** True once every badge token is stripped and nothing remains. */
|
|
59
|
-
function isBadgeLine(line: string): boolean {
|
|
60
|
-
return line.replace(BADGE_TOKEN, '').trim() === ''
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function stripInlineMarkdown(line: string): string {
|
|
64
|
-
return line
|
|
65
|
-
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
66
|
-
.replace(/(\*\*|__)(.+?)\1/g, '$2')
|
|
67
|
-
.replace(/(\*|_|`)(.+?)\1/g, '$2')
|
|
68
|
-
.trim()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The first prose line past the title and any badge row, stripped of inline
|
|
73
|
-
* markdown and capped at GitHub's About field length.
|
|
74
|
-
*
|
|
75
|
-
* `undefined` only when the file carries nothing past its title and badges,
|
|
76
|
-
* per the skill requirement's refusal boundary: a resolvable line that reads
|
|
77
|
-
* as a title rather than a sentence is still proposed, since rejecting it is
|
|
78
|
-
* a judgment for the person reading the proposal rather than for this reader.
|
|
79
|
-
*/
|
|
80
|
-
export function extractOpeningLine(readme: string): string | undefined {
|
|
81
|
-
for (const raw of readme.split('\n')) {
|
|
82
|
-
const line = raw.trim()
|
|
83
|
-
if (line === '' || line.startsWith('#') || isBadgeLine(line)) continue
|
|
84
|
-
|
|
85
|
-
const stripped = stripInlineMarkdown(line)
|
|
86
|
-
if (stripped === '') continue
|
|
87
|
-
|
|
88
|
-
return stripped.length > MAX_DESCRIPTION_LENGTH
|
|
89
|
-
? `${stripped.slice(0, MAX_DESCRIPTION_LENGTH - 1)}…`
|
|
90
|
-
: stripped
|
|
91
|
-
}
|
|
92
|
-
return undefined
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* `package.json`'s `keywords` read as candidate topics, the field the wider
|
|
97
|
-
* npm ecosystem already uses for this. Invalid entries are dropped silently
|
|
98
|
-
* rather than refused, since a manifest mixing free-text keywords with
|
|
99
|
-
* topic-shaped ones is ordinary and only the shaped half transfers.
|
|
100
|
-
*/
|
|
101
|
-
function readTopics(keywords: unknown): readonly string[] | undefined {
|
|
102
|
-
if (!Array.isArray(keywords)) return undefined
|
|
103
|
-
|
|
104
|
-
const topics = new Set<string>()
|
|
105
|
-
for (const entry of keywords) {
|
|
106
|
-
if (typeof entry !== 'string') continue
|
|
107
|
-
const topic = entry.trim().toLowerCase()
|
|
108
|
-
if (isValidTopic(topic)) topics.add(topic)
|
|
109
|
-
if (topics.size === MAX_TOPICS) break
|
|
110
|
-
}
|
|
111
|
-
return [...topics]
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
interface PackageFields {
|
|
115
|
-
readonly homepage?: unknown
|
|
116
|
-
readonly keywords?: unknown
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function readManifest(root: string): Promise<PackageFields | undefined> {
|
|
120
|
-
try {
|
|
121
|
-
const parsed: unknown = JSON.parse(
|
|
122
|
-
await readFile(join(root, 'package.json'), 'utf8'),
|
|
123
|
-
)
|
|
124
|
-
return (parsed ?? undefined) as PackageFields | undefined
|
|
125
|
-
} catch {
|
|
126
|
-
return undefined
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Computes what this tree can propose for its own repository metadata, with
|
|
132
|
-
* no network call: an About text from the README's opening line, and a
|
|
133
|
-
* homepage and a topic set from `package.json`, the one manifest every
|
|
134
|
-
* target project this ships to already carries.
|
|
135
|
-
*
|
|
136
|
-
* A field a target project declares nowhere stays absent rather than empty,
|
|
137
|
-
* so the caller comparing this against the remote never treats a field this
|
|
138
|
-
* reader has no opinion on as a proposal to clear it.
|
|
139
|
-
*/
|
|
140
|
-
export async function proposeMetadata(root: string): Promise<MetadataProposal> {
|
|
141
|
-
const [readme, manifest] = await Promise.all([
|
|
142
|
-
readFile(join(root, 'README.md'), 'utf8').catch(() => undefined),
|
|
143
|
-
readManifest(root),
|
|
144
|
-
])
|
|
145
|
-
|
|
146
|
-
const description =
|
|
147
|
-
readme === undefined ? undefined : extractOpeningLine(readme)
|
|
148
|
-
|
|
149
|
-
const homepage =
|
|
150
|
-
typeof manifest?.homepage === 'string' && manifest.homepage.trim() !== ''
|
|
151
|
-
? manifest.homepage.trim()
|
|
152
|
-
: undefined
|
|
153
|
-
|
|
154
|
-
const topics = readTopics(manifest?.keywords)
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
...(description !== undefined && { description }),
|
|
158
|
-
...(homepage !== undefined && { homepage }),
|
|
159
|
-
...(topics !== undefined && topics.length > 0 && { topics }),
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Compares a computed proposal against what the remote already carries.
|
|
165
|
-
*
|
|
166
|
-
* A field the proposal has no opinion on is never diffed, which is what
|
|
167
|
-
* keeps a target project with no `keywords` field from seeing every existing
|
|
168
|
-
* topic reported as a removal.
|
|
169
|
-
*/
|
|
170
|
-
export function compareMetadata(
|
|
171
|
-
current: CurrentMetadata,
|
|
172
|
-
proposed: MetadataProposal,
|
|
173
|
-
): MetadataDiff {
|
|
174
|
-
const diff: {
|
|
175
|
-
description?: { current: string; proposed: string }
|
|
176
|
-
homepage?: { current: string; proposed: string }
|
|
177
|
-
topics?: { added: readonly string[]; removed: readonly string[] }
|
|
178
|
-
} = {}
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
proposed.description !== undefined &&
|
|
182
|
-
proposed.description !== current.description
|
|
183
|
-
) {
|
|
184
|
-
diff.description = {
|
|
185
|
-
current: current.description,
|
|
186
|
-
proposed: proposed.description,
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
proposed.homepage !== undefined &&
|
|
192
|
-
proposed.homepage !== current.homepage
|
|
193
|
-
) {
|
|
194
|
-
diff.homepage = { current: current.homepage, proposed: proposed.homepage }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (proposed.topics !== undefined) {
|
|
198
|
-
const currentSet = new Set(current.topics)
|
|
199
|
-
const proposedSet = new Set(proposed.topics)
|
|
200
|
-
const added = proposed.topics.filter((topic) => !currentSet.has(topic))
|
|
201
|
-
const removed = current.topics.filter((topic) => !proposedSet.has(topic))
|
|
202
|
-
if (added.length > 0 || removed.length > 0) diff.topics = { added, removed }
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return diff
|
|
206
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Architecture reference
|
|
3
|
-
description: Shape and content rules for canon/ARCHITECTURE.md
|
|
4
|
-
paths:
|
|
5
|
-
- 'canon/ARCHITECTURE.md'
|
|
6
|
-
rule:
|
|
7
|
-
- "Add a decision here only when it fills one of the standard's slots, and write any other decision into the context entry for the domain it constrains. At the record's stated cap, merge or retire an entry before adding one."
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Architecture reference
|
|
11
|
-
|
|
12
|
-
Applies to `canon/ARCHITECTURE.md`. Describes the system shape and the decisions behind it, not a tutorial, setup guide, or implementation walkthrough. Pair it with `CLAUDE.md`: session behavior lives there, and the system's shape lives here. Update when a decision is made or a risk is resolved.
|
|
13
|
-
|
|
14
|
-
## Scope
|
|
15
|
-
|
|
16
|
-
Governs the system-shape document at `canon/ARCHITECTURE.md`: the overview, the decision entries, and the open risks.
|
|
17
|
-
|
|
18
|
-
Does not govern:
|
|
19
|
-
|
|
20
|
-
- Per-domain structure and narrative: `context.md`
|
|
21
|
-
- Setup commands and install instructions: `readme.md`
|
|
22
|
-
- Product scope, goals, and non-goals: `requirements.md`
|
|
23
|
-
|
|
24
|
-
## What goes in
|
|
25
|
-
|
|
26
|
-
- A high-level overview of how the system is structured and why
|
|
27
|
-
- Key technical decisions as named H3 entries, each filling one of the five slots below: what was chosen and why over the alternatives
|
|
28
|
-
- Risks and open questions still unresolved
|
|
29
|
-
|
|
30
|
-
A decision belongs here only when it fills a slot:
|
|
31
|
-
|
|
32
|
-
- Stack and runtime: the language, the runtime, and the major libraries the system is built on
|
|
33
|
-
- Delivery: how the system's content or code reaches the place it runs
|
|
34
|
-
- Enforced boundaries: a boundary the system checks mechanically, and what enforces it
|
|
35
|
-
- Layout: how the project's top-level roots and context tiers divide what they hold
|
|
36
|
-
- Build principles: a rule for how a behavior is built that changes work in more than one domain, stated in one sentence
|
|
37
|
-
|
|
38
|
-
The slots come from a scale test. Ask whether the decision would still be in this file if the project were ten times its size. At that size an always-loaded file holding one entry per decision fits no context window, and what survives is the shape: the stack, the delivery, the boundaries, the layout, and the principles. A decision that fits no slot is a domain decision, and its home is that domain's `canon/context/<domain>.md` entry under `## Decisions`, however many domains its reasoning touches. Reach is not the test, since nearly every decision reaches a second domain.
|
|
39
|
-
|
|
40
|
-
## What does not go in
|
|
41
|
-
|
|
42
|
-
- How individual functions work line by line. The code carries its own behavior.
|
|
43
|
-
- Full type definitions. They live in code. Reference the shape conceptually if needed.
|
|
44
|
-
- A decision that fills no slot. It lives in the domain context entry it constrains, and this file carries at most one line pointing at it.
|
|
45
|
-
- A measurement paragraph specific to one domain's own mechanism, even behind a slot decision. Route it to that domain's context entry and keep the choice, the alternative that lost, and one reason here.
|
|
46
|
-
- The instances of a build principle. The principle takes one sentence here, and each instance lives in the domain entry where it applies.
|
|
47
|
-
- The history of how a decision was reached or revised: rounds of candidates, a figure followed by its correction, a branch or change that moved a number. Cut it. Git and the change that introduced it keep the trail.
|
|
48
|
-
|
|
49
|
-
## Sections
|
|
50
|
-
|
|
51
|
-
Use `## Overview`, `## Key technical decisions` with one named H3 per decision, and `## Risks / open questions`. Name each decision and give the reasoning, especially for non-obvious choices. Skip entries where the rationale is self-evident.
|
|
52
|
-
|
|
53
|
-
## Keeping it current
|
|
54
|
-
|
|
55
|
-
- Rewrite a decision a later one changed rather than appending the change beside it. A reader should find the design that stands in one place, with the alternative that lost stated once.
|
|
56
|
-
- Hold only what is open under `## Risks / open questions`. An entry leaves the section in the change that settles it, becoming a decision here when it fills a slot and moving to the domain context entry it constrains when it does not.
|
|
57
|
-
- Carry an optional `reviewed: YYYY-MM-DD` frontmatter field naming the day someone last read the record whole for drift. Whoever finishes that review sets it as the review's last edit, and `canon records stale canonical` reads it to count the releases shipped since. A record with no field reads as never reviewed.
|
|
58
|
-
|
|
59
|
-
## Verification anchors
|
|
60
|
-
|
|
61
|
-
A decision's reasoning stays correct while the numbers it cites move. The anchor records what a measured claim was read against, so a reader can tell a number that was checked and held from one nobody has looked at since.
|
|
62
|
-
|
|
63
|
-
- Close a decision entry whose reasoning cites a measured number with a trailing sentence naming the short commit SHA and the ISO date that number was read: `Measured at <short-sha> on <YYYY-MM-DD>.`
|
|
64
|
-
- Name a commit, never a pull request number or a branch. A branch is gone after merge, and a pull request number resolves only on the forge.
|
|
65
|
-
- Carry one anchor per figure. When a number is re-measured, rewrite the number and its anchor in place, and leave the old value to the change that moved it.
|
|
66
|
-
- Point at a generated file that already records a figure and its commit, rather than copying the figure and anchoring the copy.
|
|
67
|
-
- Anchor on the number alone. A decision citing none takes no anchor whatever its reasoning rests on, because a marker over a claim nobody can re-measure is one no reader can falsify.
|
|
68
|
-
- Anchor a decision when writing it or when amending its reasoning. Leave an entry written before the rule unanchored rather than dating it by blame, which is archaeology for a marker nothing reads back.
|
|
69
|
-
- Read an absent anchor as unchecked rather than as current. On an entry citing no number there is nothing to check. On one citing a number the number is due a read.
|
|
70
|
-
- Do not edit a claim in the pass that first anchors it. The anchor states what the claim was measured against, so changing both at once leaves nothing to check the anchor against.
|
|
71
|
-
- Refresh the anchor whenever the number is re-read, whether or not it moved. A confirmed number and an unread one are the same text without the date.
|
|
72
|
-
|
|
73
|
-
## Revisit condition
|
|
74
|
-
|
|
75
|
-
A decision says why it won and rarely says when that reason stops holding, so an entry can go stale while every word in it stays true. The revisit sentence names the finding that would show the harm the decision prevents has gone or moved.
|
|
76
|
-
|
|
77
|
-
- Close each decision with one sentence of the form `Revisit when <finding>.` Name something a reader could notice, such as a platform release, a measured count crossing a line, or a dependency dropping a behavior.
|
|
78
|
-
- Draft it from the harm the decision prevents, never from the mechanism it uses. A mechanism can keep working long after the reason for choosing it is gone, so re-checking it confirms the wrong fact.
|
|
79
|
-
- Place the sentence after the reasoning and before any verification anchor, so the anchor stays the entry's last sentence.
|
|
80
|
-
- Give a heading holding several principles one sentence, for the premise they share, rather than one per principle.
|
|
81
|
-
- Adopt the requirement by stating the clause `Every decision closes with a revisit sentence.` in the record itself. The clause is the record's own, like the caps, so a checker reads it from the file and gates only a record that states it. A record stating none is reported and never gated.
|
|
82
|
-
- A checker reads presence alone. A vacuous sentence passes it, so the sentence is only as useful as the finding it names.
|
|
83
|
-
|
|
84
|
-
## Caps
|
|
85
|
-
|
|
86
|
-
Every session pays for this file before any work starts, so a heavy read is a real cost. Weight comes from two places, too many decisions and decisions written too long, so the record caps both, and caps the open risks beside them.
|
|
87
|
-
|
|
88
|
-
- State each cap in the record itself. A checker reads only these spellings, so a clause worded another way is reported and never gated:
|
|
89
|
-
- `This record holds at most <n> decisions.`
|
|
90
|
-
- `at most <n> words a decision`, counting a decision's prose below its heading, fenced lines left out
|
|
91
|
-
- `at most <n> risk bullets`, counting every list item under `## Risks / open questions`, nested ones included
|
|
92
|
-
- The three can share one sentence, such as `This record holds at most 12 decisions, at most 150 words a decision, and at most 6 risk bullets.` Each cap is the record's own, so a record stating none is measured and never gated. The template and the seed a new project installs state all three, and a project loosens one by editing the number in its own record.
|
|
93
|
-
- Set the decision cap near one decision per slot plus a small margin, the word cap near what one trade takes to state, about 150, and the risk cap at what a reader holds in mind at once, about 6.
|
|
94
|
-
- At the decision cap, merge two decisions or retire one before adding another, and name which in the change that does it. Never pack two decisions under one heading, which the count cannot see.
|
|
95
|
-
- At the word cap, cut a decision to the choice, the alternative that lost, the reason, and the revisit sentence. What it sheds is a mechanism or a measurement, which goes to the domain context entry it constrains, never under a second heading.
|
|
96
|
-
- At the risk cap, drop what is no longer open before adding a risk.
|
|
97
|
-
- Retire a decision by moving it to the domain context entry it constrains, not by deleting its reasoning.
|
|
98
|
-
- The whole file's word count stays a report, read alongside the judgment, and never gates.
|
|
99
|
-
- Yield to the paragraph weight checkpoint in `markdown.md`. A paragraph past the checkpoint is a defect no length guideline licenses.
|
|
100
|
-
|
|
101
|
-
## Template
|
|
102
|
-
|
|
103
|
-
The revisit sentence closes the reasoning of every decision. The anchor sentence follows it on a decision whose reasoning cites a measured number and is absent from one that cites none. Each heading below names a slot, so a record starts with one decision per slot and renames each heading to the choice it records.
|
|
104
|
-
|
|
105
|
-
```markdown
|
|
106
|
-
# Architecture
|
|
107
|
-
|
|
108
|
-
## Overview
|
|
109
|
-
|
|
110
|
-
This record holds at most 12 decisions, at most 150 words a decision, and at most 6 risk bullets. Every decision closes with a revisit sentence.
|
|
111
|
-
|
|
112
|
-
## Key technical decisions
|
|
113
|
-
|
|
114
|
-
### Stack and runtime
|
|
115
|
-
|
|
116
|
-
What was chosen, the alternative that lost, and why. Revisit when <finding>. Measured at <short-sha> on <YYYY-MM-DD>.
|
|
117
|
-
|
|
118
|
-
### Delivery
|
|
119
|
-
|
|
120
|
-
### Enforced boundaries
|
|
121
|
-
|
|
122
|
-
### Layout
|
|
123
|
-
|
|
124
|
-
### Build principles
|
|
125
|
-
|
|
126
|
-
## Risks / open questions
|
|
127
|
-
```
|