@erclx/aitk 0.104.1 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/claude-address-review/SKILL.md +4 -3
  3. package/claude/skills/claude-design-extract/SKILL.md +3 -3
  4. package/claude/skills/claude-docs/SKILL.md +1 -1
  5. package/claude/skills/claude-groundwork/SKILL.md +3 -2
  6. package/claude/skills/claude-memory-capture/SKILL.md +2 -2
  7. package/claude/skills/claude-pr-review/SKILL.md +1 -1
  8. package/claude/skills/claude-standards-audit/SKILL.md +8 -6
  9. package/claude/skills/claude-worktree/SKILL.md +6 -1
  10. package/claude/skills/create-skill/SKILL.md +2 -2
  11. package/claude/skills/create-snippet/SKILL.md +2 -2
  12. package/claude/skills/create-snippet/references/snippets.md +2 -2
  13. package/claude/skills/create-standard/SKILL.md +2 -2
  14. package/claude/skills/docs-sync/SKILL.md +2 -2
  15. package/claude/skills/git-issue/SKILL.md +2 -2
  16. package/claude/skills/git-issue/references/issue.md +2 -2
  17. package/claude/skills/git-pr/SKILL.md +2 -2
  18. package/claude/skills/git-pr/references/pr.md +2 -2
  19. package/claude/skills/git-split/references/pr.md +2 -2
  20. package/claude/skills/git-stage/SKILL.md +2 -2
  21. package/claude/skills/migration-standards/SKILL.md +1 -1
  22. package/claude/skills/setup-indexes/SKILL.md +1 -1
  23. package/claude/skills/write-human/REQUIREMENT.md +46 -0
  24. package/claude/skills/write-human/SKILL.md +68 -0
  25. package/claude/skills/write-human/references/density.md +38 -0
  26. package/claude/skills/write-human/references/machine-tells.md +107 -0
  27. package/claude/skills/write-human/references/source-material.md +37 -0
  28. package/docs/agents/commands.md +2 -1
  29. package/docs/agents/index.md +1 -0
  30. package/docs/agents/markdown-audit.md +7 -7
  31. package/docs/agents/test-order.md +56 -0
  32. package/docs/ai-workflow.md +3 -0
  33. package/docs/target-projects.md +1 -1
  34. package/governance/rules/claude/500-prose.md +7 -5
  35. package/governance/rules/claude/501-markdown.md +5 -4
  36. package/governance/rules/core/070-planning.md +1 -0
  37. package/package.json +1 -1
  38. package/scripts/core/install-check.sh +1 -1
  39. package/src/commands/gov.ts +135 -0
  40. package/src/commands/markdown.ts +2 -2
  41. package/src/comments/vocabulary.ts +1 -1
  42. package/src/gov/test-order.ts +383 -0
  43. package/src/markdown/bans.ts +2 -2
  44. package/src/standards/closure.ts +1 -1
  45. package/standards/bundled/issue.md +2 -2
  46. package/standards/bundled/pr.md +2 -2
  47. package/standards/bundled/snippets.md +2 -2
  48. package/standards/diagrams.md +5 -5
  49. package/standards/glossary.md +2 -2
  50. package/standards/groundwork.md +6 -2
  51. package/standards/index.md +1 -2
  52. package/standards/intake.md +2 -2
  53. package/standards/markdown.md +55 -6
  54. package/standards/memory.md +2 -2
  55. package/standards/plan.md +2 -2
  56. package/standards/publish.md +2 -2
  57. package/standards/readme.md +4 -4
  58. package/standards/skill.md +2 -2
  59. package/standards/standard.md +2 -2
  60. package/standards/teach.md +2 -2
  61. package/standards/versioning.md +2 -2
  62. package/standards/wireframes.md +3 -3
  63. package/tooling/astro/manifest.toml +5 -5
  64. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +2 -2
  65. package/tooling/vite-react/manifest.toml +4 -4
  66. package/tooling/web/configs/scripts/worktree-port.sh +44 -6
  67. package/tooling/web/manifest.toml +3 -3
  68. package/tooling/web/reference.md +4 -1
  69. package/standards/prose.md +0 -89
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: Source material
3
+ description: Which external writing patterns this skill adopted, which it declined, and the measurement behind each decision
4
+ ---
5
+
6
+ # Source material
7
+
8
+ Two external writing skills were read on 2026-08-19 as source material for this one. The first catalogs 31 patterns of machine-sounding prose. The second layers document mode, sentence construction, information density, and ambiguity prevention.
9
+
10
+ Neither was imported wholesale, and one measurement settles why. Read this before adding a rule from outside this corpus, and extend the position below rather than deriving a second one.
11
+
12
+ ## What was adopted
13
+
14
+ Roughly half the pattern catalog had no counterpart in this corpus, and six of those patterns are now in `machine-tells.md`: rule-of-three, synonym cycling, false ranges, inline-header lists, adverb propping, and passive voice with an unnamed actor. Each is a shape rather than a token, which is what kept it out of a ban list that matches words.
15
+
16
+ The density layer had no counterpart at all and is now `density.md`. Its useful half is the split between what a compression pass may cut and what it may not, since a terse register already instructs a session to cut and names nothing that has to survive.
17
+
18
+ The ambiguity rules travel with that layer for the same reason. Compression is what creates the ambiguity, so a rule about pronouns and modifiers belongs beside the rule that shortened the sentence.
19
+
20
+ ## What was declined
21
+
22
+ - **The ban on abstract metaphor nouns.** The nouns it names are load-bearing terms in this corpus rather than decoration, and the count below is what decides it.
23
+ - **Document mode as a selector.** The source has the writer pick a mode before drafting. In this corpus a document's shape is already fixed by the standard governing its type, so a mode selector adds a second authority over one question and the two will disagree.
24
+ - **Any restatement of a banned word set.** The bans ship as package data that `aitk markdown audit` reads, and a copy here would be a second source for one list with a machine reading the other.
25
+ - **A voice register per surface.** One baseline covers every surface. A surface needing a warmer register states its own voice and claims the yield, which lifts voice alone and leaves the bans, the rhythm rules, and the tells in force.
26
+
27
+ The first of those four rests on a count. The source names `surface` among the nouns it bans, and tracked markdown in the authoring repository used `surface` 593 times, `harness` 95, `scaffolding` 17, and `primitive` 4, measured at `57ee7467` on 2026-08-19. Adopting the item either rewrites established vocabulary across the corpus or is ignored in silence, and a reader arriving later cannot tell which happened.
28
+
29
+ ## Where this diverges
30
+
31
+ The source material treats repetition of a noun as a defect. This skill treats it as precision and bans the synonym cycling that avoids it, which reverses the item for reference prose where a reader has to know that two names are one thing.
32
+
33
+ The source material also prefers shorter sentences without bound. This skill measures the spread instead, since a passage of uniformly short sentences is the reported failure rather than the fix for it.
34
+
35
+ ## The industry-guide record
36
+
37
+ The cadence rules and the bans they pair with trace to three industry style guides, being Google's developer documentation style guide, Microsoft's writing style guide, and Apple's style guide. The authoring toolkit keeps the per-rule attributions and the dates each guide was read, and no install carries that record, so this file is the position a target reads.
@@ -45,6 +45,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
45
45
  | `aitk markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints |
46
46
  | `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
47
47
  | `aitk claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD` (`--json`) |
48
+ | `aitk gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
48
49
  | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
49
50
 
50
51
  ## Domain commands
@@ -56,7 +57,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
56
57
  | `tooling` | `list`, `sync`, `ref`, `create`, `verify`, `inject`, `prune-gitignore` |
57
58
  | `snippets` | `list`, `install`, `sync`, `create` |
58
59
  | `standards` | `list`, `install`, `sync`, `<name>` |
59
- | `gov` | `list`, `install`, `sync`, `build` |
60
+ | `gov` | `list`, `install`, `sync`, `build`, `regen`, `test-order` |
60
61
  | `claude` | `init`, `sync`, `seeds list`, `skills list`, `skills audit`, `skills drift`, `setup [dest]` |
61
62
  | `wiki` | `init` |
62
63
  | `design` | `render` |
@@ -27,3 +27,4 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
27
27
  - [Skill audit](skills-audit.md): Measuring both skill corpora against standards/skill.md, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
28
28
  - [Tasks](tasks.md): Selecting a shipped task by stem or pull request, recording a number and closing an outcome, the refusal reasons, the board and backlog checks validate runs, and why the board root defaults to the main worktree
29
29
  - [Teach](teach.md): Listing learning workspaces and the ordinal a new one takes, opening one with its required files, recording sources and glossary terms, resolving what the next lesson needs before it is written, the refusal reasons, and why every write here runs through a verb
30
+ - [Test order](test-order.md): Reading where an implementation reached history ahead of its test, how a pair is decided, the three verdicts, the coverage the pairing cannot reach, and why the check reports rather than gates
@@ -5,7 +5,7 @@ description: Running the audit over any markdown path, where its bans and checkp
5
5
 
6
6
  # Markdown audit
7
7
 
8
- `aitk markdown audit [path...]` reports any markdown file against the two attribute standards, `markdown.md` and `prose.md`. An attribute standard governs a file rather than a folder, so this resolves no folder and requires no `index.md`, which is what puts `.claude/rules/`, `governance/`, and `snippets/` in reach. Folder-shaped findings stay in `aitk context audit`, described in `context-audit.md`.
8
+ `aitk markdown audit [path...]` reports any markdown file against the attribute standard `markdown.md`. An attribute standard governs a file rather than a folder, so this resolves no folder and requires no `index.md`, which is what puts `.claude/rules/`, `governance/`, and `snippets/` in reach. Folder-shaped findings stay in `aitk context audit`, described in `context-audit.md`.
9
9
 
10
10
  ```bash
11
11
  aitk markdown audit
@@ -29,9 +29,9 @@ A bare run measures every markdown file git lists, tracked plus untracked-and-no
29
29
 
30
30
  The three ban sets and all six checkpoints ship with the `aitk` package as data, in `src/markdown/bans.ts` and `src/markdown/structure.ts`. Every project is measured against the same sets whether or not it installed any standards, and no file has to resolve for a run to mean something.
31
31
 
32
- Reading them out of the standards per run was the original design. It put a parser contract on two documents authored for people, and `prose.md` had to carry a paragraph of its own warning an author that a one-word backticked example in a `- Do not use ` bullet would be lifted into a literal ban set and ban that word everywhere. A rule existing to protect a parser from the prose it parses is the argument for separating them.
32
+ Reading them out of the standards per run was the original design. It put a parser contract on a document authored for people, and the standard had to carry a paragraph of its own warning an author that a one-word backticked example in a `- Do not use ` bullet would be lifted into a literal ban set and ban that word everywhere. A rule existing to protect a parser from the prose it parses is the argument for separating them.
33
33
 
34
- `markdown.md` and `prose.md` still state every ban and every checkpoint, and a reader follows those rather than the code. Nothing compares the two, so a number moved in one place and left in the other drifts silently. Move both in the same change.
34
+ `markdown.md` still states every ban and every checkpoint, and a reader follows it rather than the code. Nothing compares the two, so a number moved in one place and left in the other drifts silently. Move both in the same change.
35
35
 
36
36
  The sets are closed rather than extensible, so a project cannot add a term by editing a file. What decides that is the measurement behind them: 21 terms across 483 markdown files report a clean exit, and every occurrence of a banned word in the corpus sits inside the ban list itself or inside an example demonstrating the ban. The set is a prior an author already knows rather than a filter that has caught anything, and enumeration cannot close the gap it aims at, since `just`, `allows`, and `very` carry honest uses no literal match separates.
37
37
 
@@ -45,7 +45,7 @@ A set shipped empty is reported rather than passed. It finds nothing and would e
45
45
 
46
46
  ### Bans
47
47
 
48
- Three closed sets report a hit: the characters `markdown.md` bans under `## Punctuation`, the single lowercase words `prose.md` bans under `## Language`, and the British spellings of the American examples that section lists.
48
+ Three closed sets report a hit: the characters `markdown.md` bans under `## Punctuation`, the single lowercase words it bans under `## Language`, and the British spellings of the American examples that section lists.
49
49
 
50
50
  Deriving the spellings rather than pattern-matching a suffix is what keeps `exercises`, `promises`, and `revised` out of the report. A suffix pattern over the same corpus produced 46 false positives from words of that shape, and a closed set of whole words reaches none of them.
51
51
 
@@ -120,7 +120,7 @@ Rewrite the sentence rather than swapping the banned token for a near-synonym. T
120
120
 
121
121
  A code span clears the report too, since the ban scan walks around one, and it is the answer only where the token is genuinely an identifier under discussion. `## Code and identifiers` in `markdown.md` reserves the span for commands, API names, file paths, and identifiers, so backticking a quoted utterance spends one rule to satisfy another and leaves the corpus no cleaner.
122
122
 
123
- A hit the closed set cannot separate from correct prose is the case with no third option. `prose.md` bans vague qualifiers and lists the tokens those qualifiers happen to spell, so the temporal `just` reports as the vague one. The rule as written reaches neither, and rewriting the sentence is what the toolkit settled on over building an exemption path, for the reasons below.
123
+ A hit the closed set cannot separate from correct prose is the case with no third option. `markdown.md` bans vague qualifiers and lists the tokens those qualifiers happen to spell, so the temporal `just` reports as the vague one. The rule as written reaches neither, and rewriting the sentence is what the toolkit settled on over building an exemption path, for the reasons below.
124
124
 
125
125
  ### Where the rules are enforced
126
126
 
@@ -150,9 +150,9 @@ Masking took 7 of the weight-only paragraphs the checkpoint reported at 400 and
150
150
 
151
151
  ### How the ban count reached zero
152
152
 
153
- Eight word hits stood between the baseline and a gate, and only three carried the sense `prose.md` bans. `leverage` sat in the requirements worldview, `allows` in the claude stack reference, and one `just` was the vague qualifier in a skill body. Those three lost the qualifier rather than the word.
153
+ Eight word hits stood between the baseline and a gate, and only three carried the sense the standard bans. `leverage` sat in the requirements worldview, `allows` in the claude stack reference, and one `just` was the vague qualifier in a skill body. Those three lost the qualifier rather than the word.
154
154
 
155
- The other five were correct prose the closed set cannot separate from a violation. Four were the temporal `just`, meaning a moment ago, in phrases like the implementation `just` completed and the field the user `just` edited. The fifth quoted an anti-pattern a skill exists to forbid. `prose.md` bans vague qualifiers and lists the tokens those qualifiers happen to spell, so the rule as written reaches none of the five while the scan reaches all of them.
155
+ The other five were correct prose the closed set cannot separate from a violation. Four were the temporal `just`, meaning a moment ago, in phrases like the implementation `just` completed and the field the user `just` edited. The fifth quoted an anti-pattern a skill exists to forbid. `markdown.md` bans vague qualifiers and lists the tokens those qualifiers happen to spell, so the rule as written reaches none of the five while the scan reaches all of them.
156
156
 
157
157
  ### Why they were rewritten rather than exempted
158
158
 
@@ -0,0 +1,56 @@
1
+ ---
2
+ title: Test order
3
+ description: Reading where an implementation reached history ahead of its test, how a pair is decided, the three verdicts, the coverage the pairing cannot reach, and why the check reports rather than gates
4
+ ---
5
+
6
+ # Test order
7
+
8
+ `aitk gov test-order` reports where an implementation reached a commit ahead of the test covering the same behavior. It answers the rule in `.claude/rules/core/070-planning.md` that asks for the test first, which until this verb existed was prose loaded on a glob match with nothing measuring it.
9
+
10
+ ```bash
11
+ aitk gov test-order
12
+ aitk gov test-order --base origin/main --json
13
+ aitk gov test-order --root ../my-app
14
+ ```
15
+
16
+ | Option | Behavior |
17
+ | --------------- | ----------------------------------------------------------------- |
18
+ | `--base <ref>` | Far side of the range, defaulting to the merge base against trunk |
19
+ | `--root <path>` | Repository to read, defaulting to the current directory |
20
+ | `--json` | Add a machine-readable record on stdout, keeping the frame |
21
+
22
+ Under `--json` the record holds stdout alone and the frame still renders on stderr, refusals included, which is the split `output-shape.md` fixes for every mode. A consumer reading stdout sees pure data, and an operator reading the terminal sees why a run refused rather than a command that appeared to do nothing.
23
+
24
+ ## The range it reads
25
+
26
+ The default range is the branch against the trunk, resolved as the merge base against `origin/main` and then local `main`. A repository carrying neither falls back to the root commit, so a fresh checkout still answers rather than refusing.
27
+
28
+ Reading all history on every run measures work nobody is reviewing and buries the finding that matters under the ones already merged. `--base` widens or narrows it when a reader wants a different window.
29
+
30
+ Only history is read. A file sitting in the working tree and in no commit is invisible here, which is the point: the verification run sees a tree at one moment, and the ordering exists nowhere but history.
31
+
32
+ ## How a pair is decided
33
+
34
+ A test is paired to an implementation by the module path with the test suffix removed, so `src/parser.test.ts` covers `src/parser.ts`. Every record is keyed on the implementation path, which is what a reader goes looking for.
35
+
36
+ Pairing is the whole difficulty. A test and its implementation are related by behavior rather than by filename, so a behavior split across two modules pairs wrongly or not at all. That is what the unclassified verdict exists to catch rather than hide.
37
+
38
+ ## The three verdicts
39
+
40
+ - **Satisfied.** The implementation was added in the range and its test reached history no later. A test that predates the range counts, and so does one commit carrying both sides, since the rule asks that the test not come after and a single commit is the shape a small change takes.
41
+ - **Implementation-first.** The implementation was added in the range and its test was added in a later commit of the same range. This is the only verdict that moves the exit code.
42
+ - **Unclassified.** Everything the pairing cannot read, each carrying its own reason. A module the range modified rather than added lands here, because a refactor and a new behavior cannot be told apart from history. So does an implementation no test names, and a test whose implementation the range never introduced.
43
+
44
+ ## What it reads past
45
+
46
+ Only `.ts` and `.tsx` are paired, since a test sits beside its subject under one name across that corpus and a bash script has no such partner. Declaration files carry no behavior to test. Every other changed path is counted under `Read past` rather than dropped silently.
47
+
48
+ Coverage is narrower than the rule the check answers to. The rule speaks to every behavior, and the verb speaks to the behaviors it can identify, so the report states its own scope on every run rather than implying it looked at everything.
49
+
50
+ ## Exit codes
51
+
52
+ Exit codes are `0` when nothing reached history ahead of its test, `1` for a refusal, and `2` for at least one implementation-first finding. Unclassified rows move no exit code.
53
+
54
+ Nothing wires this into `bun run check` or into a hook. The unclassified class is large and known, and gating a measure carrying a known false-positive class is what teaches contributors to route around the stage. `aitk tasks validate` is the precedent: it exits `2` on findings and is called where the claim is made rather than on every push.
55
+
56
+ An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the JSON record's `findings` array rather than the exit when a skill consumes this.
@@ -55,6 +55,8 @@ The routing test is whether the repository can answer an item today. A session g
55
55
 
56
56
  A groundwork track may run experiments to settle a question, writing a fixture it reads itself under `.claude/.tmp/groundwork-fixtures/<slug>/` and spawning up to three billed headless runs before it asks. A fixture a headless run is pointed at sits outside the repository, since a session started under the project root inherits that project's `CLAUDE.md` and rules and would measure them instead of the arm.
57
57
 
58
+ What a spike produces goes somewhere else again. An input the run reads is re-runnable and cited by nothing, so the scratch path above is the right lifetime for it, while a recording or a render the track cites as evidence for a finding is what a later reader opens to check the claim. Evidence therefore lives in `evidence/` inside the track beside the file citing it, since the scratch tree holds only what can be deleted without loss.
59
+
58
60
  #### Session 2
59
61
 
60
62
  Start a fresh Claude Code session. The diff is sufficient context for both review and ship.
@@ -279,6 +281,7 @@ This section is the corpus the coverage claim is measured against: every name `a
279
281
  | `aitk:toolkit-cli` | Before a sync or install, to learn what it overwrites, merges, or leaves alone |
280
282
  | `aitk:youtube-transcripts` | When a video transcript is wanted in the repo as context |
281
283
  | `aitk:claude-teach` | To learn a subject across sessions, in a workspace that holds the progress |
284
+ | `aitk:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
282
285
 
283
286
  Every row answers a question rather than marking a point in a project's life, so a phase above would send a reader to the wrong group.
284
287
 
@@ -88,7 +88,7 @@ Run `aitk tooling list --json` and `aitk gov list --json` to see the current cat
88
88
 
89
89
  `governance`, `standards`, and `wiki` are skippable:
90
90
 
91
- - `--skip governance`: leave `.claude/rules/` empty. Standards still install, so `.claude/standards/prose.md` lands with nothing pointing at it and no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
91
+ - `--skip governance`: leave `.claude/rules/` empty. Standards still install, so `.claude/standards/markdown.md` lands with nothing pointing at it and no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
92
92
  - `--skip standards`: leave standards out. The governance rules still reference `.claude/standards/`, so their authority lines resolve to nothing for a reader following the path. Toolkit skills are unaffected, since each falls back to the copy in its own plugin root. `aitk standards <name>` searches the corpus inside the CLI's own package behind both project roots, so it prints a standard in a project that skipped the install, and `aitk markdown audit` needs no standard at all, its ban sets and checkpoints shipping with the package as data. What the skip costs is the editable copy rather than the enforcement.
93
93
  - `--skip wiki`: skip the `.claude/wiki/` scaffold. A target that already carries a root `wiki/` keeps it, since the verb reports that folder rather than migrating it.
94
94
 
@@ -1,13 +1,15 @@
1
1
  ---
2
- description: Route markdown edits to the prose standard for voice, language, and frontmatter wording
2
+ description: Route markdown edits to the write-human skill for voice, rhythm, sentence construction, and information density
3
3
  paths:
4
4
  - '**/*.md'
5
5
  ---
6
6
 
7
- # Markdown prose standards
7
+ # Markdown voice standards
8
8
 
9
9
  ## Authority
10
10
 
11
- - Follow `.claude/standards/prose.md` for voice, language, and the wording of a `title` or `description`. It is the single source.
12
- - Read it before a substantial prose edit. Do not work the banned words from memory.
13
- - Punctuation, formatting, and file references are a separate topic. `501-markdown` routes them.
11
+ - Load the `aitk:write-human` skill for voice, rhythm, sentence construction, and information density. It is the single source for all four.
12
+ - Load it before drafting a passage, not after revising one.
13
+ - Report it rather than proceeding silently when the skill does not resolve.
14
+ - Do not work these rules from memory.
15
+ - Banned words, spellings, punctuation, formatting, and file references are a separate topic. `501-markdown` routes them.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Route markdown edits to the markdown standard for headings, lists, punctuation, and file references
2
+ description: Route markdown edits to the markdown standard for headings, lists, punctuation, banned words, and frontmatter wording
3
3
  paths:
4
4
  - '**/*.md'
5
5
  ---
@@ -8,6 +8,7 @@ paths:
8
8
 
9
9
  ## Authority
10
10
 
11
- - Follow `.claude/standards/markdown.md` for headings, paragraph and list structure, code spans, punctuation, emphasis, and file references. It is the single source.
12
- - Read it before a substantial markdown edit. Do not work the banned characters from memory.
13
- - Voice, language, and frontmatter wording are a separate topic. `500-prose` routes them.
11
+ - Follow `.claude/standards/markdown.md` for headings, paragraph and list structure, code spans, punctuation, emphasis, file references, the banned words and spellings, and the wording of a `title` or `description`. It is the single source.
12
+ - Read it before a substantial markdown edit. Do not work the banned words or characters from memory.
13
+ - Run `aitk markdown audit <path>` after the edit, and rewrite the sentence carrying a hit rather than swapping the token for a near-synonym.
14
+ - Voice, rhythm, and sentence construction are a separate topic. `500-prose` routes them.
@@ -13,4 +13,5 @@ description: Enforce planning standards before implementation
13
13
  - Propose the simplest solution that satisfies the requirement before implementing complex patterns.
14
14
  - Write or update tests as part of every implementation plan.
15
15
  - Write the test for a behavior before the code that implements it. Confirm visual output after implementing it, not before.
16
+ - Run `aitk gov test-order` before shipping a branch. Fix what it names as reaching history ahead of its test.
16
17
  - Do not modify code without a confirmed plan.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.104.1",
4
+ "version": "0.106.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -79,7 +79,7 @@ log_step "Assert scaffold"
79
79
  # The snippets path has to name a slug the default preset still carries, since
80
80
  # init resolves snippets through `essentials`. Editing that preset without
81
81
  # editing this line fails the gate on a correct install.
82
- for path in "CLAUDE.md" ".claude/snippets/decision-help.md" ".claude/standards/prose.md" ".claude/standards/markdown.md" ".claude/wiki/index.md" ".claude" ".claude/context/index.md" ".claude/wireframes/index.md" ".claude/diagrams/index.md" \
82
+ for path in "CLAUDE.md" ".claude/snippets/decision-help.md" ".claude/standards/skill.md" ".claude/standards/markdown.md" ".claude/wiki/index.md" ".claude" ".claude/context/index.md" ".claude/wireframes/index.md" ".claude/diagrams/index.md" \
83
83
  ".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
84
84
  ".claude/rules/core/000-constitution.md"; do
85
85
  if [ ! -e "$TARGET_DIR/$path" ]; then
@@ -14,6 +14,11 @@ import {
14
14
  mergeExtraRules,
15
15
  resolveRules,
16
16
  } from '@/gov/stacks'
17
+ import {
18
+ type PairRecord,
19
+ readTestOrder,
20
+ type TestOrderReport,
21
+ } from '@/gov/test-order'
17
22
  import { recordStamp, runDomainSync } from '@/sync/engine'
18
23
  import { resolveTarget } from '@/target'
19
24
  import {
@@ -48,6 +53,12 @@ interface ListOptions {
48
53
  readonly json?: boolean
49
54
  }
50
55
 
56
+ interface TestOrderOptions {
57
+ readonly base?: string
58
+ readonly root?: string
59
+ readonly json?: boolean
60
+ }
61
+
51
62
  export function register(program: Command): void {
52
63
  const gov = program
53
64
  .command('gov')
@@ -133,6 +144,130 @@ export function register(program: Command): void {
133
144
  .action((opts: ListOptions) => {
134
145
  process.exitCode = runList(opts)
135
146
  })
147
+
148
+ gov
149
+ .command('test-order')
150
+ .description(
151
+ 'Report where an implementation reached history before its test',
152
+ )
153
+ .helpOption('-h, --help', 'Show this help message')
154
+ .option('--base <ref>', 'Far side of the range, defaulting to the trunk')
155
+ .option('--root <path>', 'Repository to read, defaulting to the cwd')
156
+ .option('--json', 'Add a machine-readable record on stdout')
157
+ .addHelpText(
158
+ 'after',
159
+ [
160
+ '',
161
+ 'Measures the rule in .claude/rules/core/070-planning.md that asks for',
162
+ 'the test before the code. It reports and never gates, because pairing',
163
+ 'a test to an implementation is a judgment.',
164
+ '',
165
+ 'Coverage:',
166
+ ' a test sits beside its subject under the same name, minus .test',
167
+ ' only .ts and .tsx are paired, and every other path is named as read past',
168
+ ' a module the range modified rather than added is unclassified, since',
169
+ ' a refactor and a new behavior cannot be told apart from history',
170
+ '',
171
+ 'Exit codes:',
172
+ ' 0 no implementation reached history ahead of its test',
173
+ ' 1 refused, with the reason on stderr or in the JSON record',
174
+ ' 2 at least one implementation reached history ahead of its test',
175
+ '',
176
+ 'Examples:',
177
+ ' aitk gov test-order',
178
+ ' aitk gov test-order --base origin/main --json',
179
+ '',
180
+ ].join('\n'),
181
+ )
182
+ .action((opts: TestOrderOptions) => {
183
+ process.exitCode = runTestOrder(opts)
184
+ })
185
+ }
186
+
187
+ /**
188
+ * Reports and never gates, so the finding count moves the exit code without
189
+ * anything wiring it into a push. `aitk tasks validate` set that shape: a
190
+ * measure carrying a known false-positive class is what forces contributors to
191
+ * route around a stage, and the unclassified bucket here is that class.
192
+ */
193
+ function runTestOrder(opts: TestOrderOptions): number {
194
+ const root = resolve(opts.root ?? process.cwd())
195
+ const report = readTestOrder(root, { base: opts.base })
196
+ const emitJson = opts.json ?? false
197
+
198
+ // The frame renders on stderr in both modes and the record goes to stdout
199
+ // alone, which is the split `docs/agents/output-shape.md` fixes. A consumer
200
+ // reading stdout sees pure data either way, and an operator reading the
201
+ // terminal sees the refusal rather than a command that appeared to do nothing.
202
+ if (report.kind === 'unreadable') {
203
+ intro('aitk gov test-order')
204
+ logStep('Refused')
205
+ logError(report.reason)
206
+ outro()
207
+
208
+ if (emitJson) {
209
+ process.stdout.write(
210
+ `${JSON.stringify({ ok: false, reason: report.reason })}\n`,
211
+ )
212
+ }
213
+
214
+ return 1
215
+ }
216
+
217
+ reportTestOrder(report, root)
218
+
219
+ if (emitJson) {
220
+ process.stdout.write(`${JSON.stringify({ ok: true, root, ...report })}\n`)
221
+ }
222
+
223
+ return report.findings.length > 0 ? 2 : 0
224
+ }
225
+
226
+ function describePair(record: PairRecord): string {
227
+ const test = record.test === null ? 'no test' : record.test
228
+ return `${record.subject} → ${test}: ${record.reason}`
229
+ }
230
+
231
+ function reportTestOrder(
232
+ report: Extract<TestOrderReport, { kind: 'measured' }>,
233
+ root: string,
234
+ ): void {
235
+ intro('aitk gov test-order')
236
+
237
+ logStep('Range')
238
+ logInfo(`${report.base.slice(0, 8)}..${report.head.slice(0, 8)} in ${root}`)
239
+
240
+ logStep(report.findings.length === 0 ? 'Clean' : 'Findings')
241
+ if (report.findings.length === 0) {
242
+ logInfo('no implementation reached history ahead of the test covering it')
243
+ } else {
244
+ for (const finding of report.findings) logWarn(describePair(finding))
245
+ }
246
+
247
+ logStep('Satisfied')
248
+ logInfo(`${report.satisfied.length} pair(s) whose test came first`)
249
+
250
+ // The unclassified rows carry the warn glyph and move no exit code. A pass
251
+ // over a change the pairing could not read is the claim this check exists to
252
+ // avoid making, so the rows are named rather than counted into the clean line.
253
+ logStep('Unclassified')
254
+ if (report.unclassified.length === 0) {
255
+ logInfo('every changed module paired')
256
+ } else {
257
+ logWarn(
258
+ `${report.unclassified.length} change(s) the pairing could not read`,
259
+ )
260
+ for (const record of report.unclassified) logWarn(describePair(record))
261
+ }
262
+
263
+ // Coverage is narrower than the rule, and a report that did not say so would
264
+ // read as a verdict over every behavior in the range.
265
+ logStep('Read past')
266
+ logInfo(
267
+ `${report.ignored.length} path(s) outside ${report.scope.extensions.join(', ')}`,
268
+ )
269
+
270
+ outro()
136
271
  }
137
272
 
138
273
  /**
@@ -87,8 +87,8 @@ export function register(program: Command): void {
87
87
  '',
88
88
  'Bans and checkpoints ship with the aitk package rather than being read',
89
89
  'out of a standards file, so a project that installed no standards is',
90
- 'measured the same as one that did. markdown.md and prose.md still state',
91
- 'every rule for a reader. No folder has to resolve and no index.md has',
90
+ 'measured the same as one that did. markdown.md still states every rule',
91
+ 'for a reader. No folder has to resolve and no index.md has',
92
92
  'to exist, so .claude/rules/, governance/, and snippets/ are in reach.',
93
93
  '',
94
94
  'Examples:',
@@ -57,7 +57,7 @@ export function parseVocabulary(markdown: string): string[] | undefined {
57
57
  *
58
58
  * Reading the list out of the rule rather than hardcoding it is what keeps one
59
59
  * definition when the rule installs into a target, the same way
60
- * `.claude/hooks/standards-audit.sh` reads its word bans out of `prose.md`.
60
+ * `aitk markdown audit` owns one copy of the ban sets for every caller.
61
61
  */
62
62
  export async function loadVocabulary(root: string): Promise<Vocabulary> {
63
63
  for (const ruleRoot of RULE_ROOTS) {