@erclx/aitk 3.37.1 → 3.38.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "3.37.1",
4
+ "version": "3.38.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -88,7 +88,7 @@ Behavior paths carry two spellings, the one a surface authors at and the one it
88
88
 
89
89
  - `claude/skills/` and `.claude/skills/`
90
90
  - `governance/rules/` and `.claude/rules/`
91
- - `standards/` and `.claude/standards/`, which is the authoring root and this repository's generated mirror rather than an install destination, since the corpus installs into no project
91
+ - `standards/`, which is the authoring root and reaches a reader by resolution rather than by an install, so it carries no `.claude/` spelling
92
92
  - `snippets/` and `.claude/snippets/`
93
93
  - `internal/` and `tooling/`, which hold the stack references and the seed documents a target is handed
94
94
  - `CLAUDE.md` at the repository root, named as a file because a path prefix reaches nothing that sits in no folder
@@ -77,7 +77,7 @@ Where an item's call is the operator's, pair it with a suggestion rather than a
77
77
 
78
78
  ## Step 6: write the index
79
79
 
80
- Write `00-overview.md` last, once the clusters are filed and the counts are real. It carries the format block, the cluster table, the verdict counts, the ready list, and the open questions.
80
+ Write `00-overview.md` last, once the clusters are filed and the counts are real. It carries the cluster table, the verdict counts, the ready list, and the open questions.
81
81
 
82
82
  Each open question in the index is a labeled markdown link to its owning item's heading anchor. The index points and the item owns, so no answer slot appears in the index. One question in two answerable places has no rule for which wins, and retrieval walks item headings, so an answer typed into the index is found by nothing and lost silently.
83
83
 
@@ -9,7 +9,7 @@ description: Why a standard needs its shape read from the meta-standard before a
9
9
 
10
10
  Without this skill, a standard is written from memory of what other standards look like. It arrives without the scope section that says what the file does not govern, so the next author cannot tell whether a rule belongs to it or to a sibling, and two standards end up claiming the same subject with no way to settle which one wins.
11
11
 
12
- The consumed copy is the second failure. This repository mirrors `standards/` into `.claude/standards/` and asserts the two match, so a toolkit standard written into the mirror fails that assertion while looking like a correct file. One write surface removes the choice: `standards/` is what the resolver reads first and what every author writes to, in the toolkit and in a project alike.
12
+ The second write surface is the second failure. A standard put under `.claude/standards/` looks like a correct file and reaches no resolver, since nothing installs the corpus into a project and no repository generates a copy there. One write surface removes the choice: `standards/` is what the resolver reads first and what every author writes to, in the toolkit and in a project alike.
13
13
 
14
14
  A standard written and left there is the third. The index entry and the context table are both generated or maintained downstream of the write, so a file that ships without them is catalogued nowhere and reached only by whoever already knew the path.
15
15
 
@@ -19,13 +19,13 @@ A standard written and left there is the third. The index entry and the context
19
19
  - Write to `standards/`, creating it when absent, since it is the one root the resolver reads and the only surface either kind of author has
20
20
  - Confirm the slug and the full body with the user before writing
21
21
  - Emit the written path in full, so the terminal can resolve it
22
- - State what the write implies for whoever owns the folder, since a toolkit standard needs the consumed copy and the index regenerated and a project-local one needs copying to the toolkit to ship
22
+ - State what the write implies for whoever owns the folder, since a toolkit standard needs the index regenerated and a project-local one needs copying to the toolkit to ship
23
23
 
24
24
  ## Must not
25
25
 
26
26
  - Work the standard's shape or frontmatter from memory
27
27
  - Write a snippet, which carries no frontmatter and answers to a different authoring contract
28
- - Write into `.claude/standards/`, which is this repository's generated mirror and reaches no resolver
28
+ - Write into `.claude/standards/`, which no repository generates and no resolver reads
29
29
 
30
30
  ## Out of scope
31
31
 
@@ -9,7 +9,7 @@ What each finding from `aitk context audit` means. The command surface, its flag
9
9
 
10
10
  ## Required sections
11
11
 
12
- The required-section check reports what does not declare `## Overview` and `## Layout`, the two sections `.claude/standards/context.md` marks required. The list is held in code beside the numeric checkpoints rather than parsed out of the standard, so it fails on a defect rather than on a rewrite of the wording around it. A heading at any level counts, because a domain that split into a folder carries its overview in a sibling named `overview.md` where the section is the `#` title and an `##` beneath it would repeat the filename. Matching exactly is what keeps `## Layout catalog` from satisfying `Layout`.
12
+ The required-section check reports what does not declare `## Overview` and `## Layout`, the two sections `standards/context.md` marks required. The list is held in code beside the numeric checkpoints rather than parsed out of the standard, so it fails on a defect rather than on a rewrite of the wording around it. A heading at any level counts, because a domain that split into a folder carries its overview in a sibling named `overview.md` where the section is the `#` title and an `##` beneath it would repeat the filename. Matching exactly is what keeps `## Layout catalog` from satisfying `Layout`.
13
13
 
14
14
  Which unit answers depends on the folder. Entries of the folder named under `.claude/` are one domain each, so each answers for itself and a finding names the entry. Entries of a folder a domain split into describe that one domain between them, so any sibling answers and a finding names the folder. Holding a split folder to the rule per file would report every child beside its `overview.md`, and rolling the named folder up would let one conforming entry stand in for every other domain sitting next to it.
15
15
 
@@ -19,7 +19,7 @@ It reports rather than gates by default, the closer call because a missing secti
19
19
 
20
20
  ## Reference form
21
21
 
22
- The reference-form check reports an entry naming a sibling entry by bare filename where `.claude/standards/context.md` asks for the path it sits at. A bare name resolves against whichever folder its reader is already in, so a domain that splits into subfolders strands every inbound reference and the break surfaces nowhere. A path is checkable, which is what makes the form rule worth measuring at all: once a reference spells its path, the citation gate resolves it and a split that moves the file fails the push.
22
+ The reference-form check reports an entry naming a sibling entry by bare filename where `standards/context.md` asks for the path it sits at. A bare name resolves against whichever folder its reader is already in, so a domain that splits into subfolders strands every inbound reference and the break surfaces nowhere. A path is checkable, which is what makes the form rule worth measuring at all: once a reference spells its path, the citation gate resolves it and a split that moves the file fails the push.
23
23
 
24
24
  A finding is a backticked filename carrying no folder, matched against the entries beside the one that wrote it. The backticks are required, since a filename in running prose is not a reference a reader follows.
25
25
 
@@ -33,7 +33,7 @@ It reports and never gates, which is the split the citation check already draws.
33
33
 
34
34
  ## Length
35
35
 
36
- Length quotes its checkpoint from `.claude/standards/context.md`: roughly 150 rendered lines for an entry, applied to each of the 60 entries the audit measures, 51 of them under `.claude/context/` and 9 diagram files beside them. It counts rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights.
36
+ Length quotes its checkpoint from `standards/context.md`: roughly 150 rendered lines for an entry, applied to each of the 60 entries the audit measures, 51 of them under `.claude/context/` and 9 diagram files beside them. It counts rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights.
37
37
 
38
38
  A line is wrapped at the width it renders at, so a link counts as its anchor text and a backticked path counts whole, which parts the rendered count from a source count wherever an entry cites a path in backticks rather than as a link. Entries here are authored one line per bullet, so a block of fifteen paragraph-bullets occupies fifteen source lines and renders past sixty, which source counting cannot see.
39
39
 
@@ -45,7 +45,7 @@ Only the third is mechanical, and it is the provenance check already reported be
45
45
 
46
46
  It reports and never gates, which the standard settles rather than the usual judgment-against-fact line: there is no hard cap, so an entry that answers all three and is still long is a correct outcome. The JSON record carries the joined findings as `length`, each with a `causes` array in the standard's order, and the jurisdiction the join reads as `entries[].governed`. That flag is what parts a provenance list measured and empty from one never scanned, which the count alone cannot say. The join is published rather than left to a consumer, since deriving it means restating which question the provenance count answers.
47
47
 
48
- Depth and bullet weight are quoted from `.claude/standards/markdown.md`, which states both over every markdown file rather than over a context entry, so `aitk markdown audit` measures them and this command no longer does. They share `renderedHeight` with the length measure, since the two checkpoints sit in one section of that standard and a reader compares them. What the split costs is that a session wanting both numbers for one entry runs two commands, and what it buys is that either number can be had for a file in a folder this audit refuses to resolve.
48
+ Depth and bullet weight are quoted from `standards/markdown.md`, which states both over every markdown file rather than over a context entry, so `aitk markdown audit` measures them and this command no longer does. They share `renderedHeight` with the length measure, since the two checkpoints sit in one section of that standard and a reader compares them. What the split costs is that a session wanting both numbers for one entry runs two commands, and what it buys is that either number can be had for a file in a folder this audit refuses to resolve.
49
49
 
50
50
  ## Tables
51
51
 
@@ -63,7 +63,7 @@ Fenced blocks are excluded, which keeps a pinned version in an install command f
63
63
 
64
64
  ## Narration
65
65
 
66
- The narration check reports a bullet that states the design a sibling bullet replaced instead of rewriting it. `.claude/standards/context.md` asks for the rewrite because the subject is still live and two bullets on one subject leave a reader to work out which of them is current, and no other measure sees that shape.
66
+ The narration check reports a bullet that states the design a sibling bullet replaced instead of rewriting it. `standards/context.md` asks for the rewrite because the subject is still live and two bullets on one subject leave a reader to work out which of them is current, and no other measure sees that shape.
67
67
 
68
68
  It reads structure rather than words, which the corpus decided. Measured across the 39 entries this toolkit held the day it shipped, the terms carrying clean signal for a supersession are too rare to catch anything: `superseded` appears twice, `previously` three times, and `formerly`, `originally`, and `at first` never. The one term that would have caught the case a review caught by hand is `now`, which appears 57 times across 24 entries in correct present-tense prose. A list including it reports 57 lines to catch one, and a list excluding it reports nothing.
69
69
 
@@ -101,13 +101,13 @@ The coverage report classifies each decision as carrying a countable claim, a st
101
101
 
102
102
  Three limits are stated on every run rather than hidden. The countable signal reads digits alone, so a measured claim written in words reads as uncounted. Entries are counted by heading, and one heading holding several decisions counts once. Nothing is stored, so an entry rewritten tomorrow is classified afresh the next time the verb runs and no verdict goes stale.
103
103
 
104
- The report gates nothing. Deciding whether a sentence states a claim is a judgment no parser settles, so the output names candidates for a reader. This answers a different question from the verification anchors `.claude/standards/architecture.md` describes, which record that one cited number was re-read. That mechanism says whether a marked figure held, and this one says how much of the record could be checked at all.
104
+ The report gates nothing. Deciding whether a sentence states a claim is a judgment no parser settles, so the output names candidates for a reader. This answers a different question from the verification anchors `standards/architecture.md` describes, which record that one cited number was re-read. That mechanism says whether a marked figure held, and this one says how much of the record could be checked at all.
105
105
 
106
106
  ## Which folders each check reaches
107
107
 
108
108
  The provenance, required-section, and narration checks cover `.claude/context/` alone, the reference-form check covers the split folders inside it, and length and the table finding reach every audited folder.
109
109
 
110
- What narrows the three is stated in `.claude/standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere, and a diagram entry carries a heading per kind rather than a run of bullets deciding anything. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
110
+ What narrows the three is stated in `standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere, and a diagram entry carries a heading per kind rather than a run of bullets deciding anything. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
111
111
 
112
112
  Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason: the names are the context standard's own, and neither sibling standard states a required section at all.
113
113
 
@@ -76,7 +76,7 @@ standard exits 1 after listing the catalog on stderr.
76
76
  Two roots answer, in order: `standards/` at the working directory, then the
77
77
  corpus inside the package. A project that authors standards of its own uses the
78
78
  first, and this repository's own authoring root is the same path. `.claude/standards/`
79
- is not among them. It is this repository's generated mirror, and a copy an older
79
+ is not among them, and no repository writes one any more. A copy an older
80
80
  toolkit installed into a target resolves nothing.
81
81
 
82
82
  There is no citation closure to compute, since nothing is copied. A standard
@@ -78,4 +78,4 @@ Skills branch on the reason rather than on the exit code:
78
78
  aitk intake list toolkit-overview --unread --json | jq -r '.clusters[] | .cluster'
79
79
  ```
80
80
 
81
- For the folder layout, the item format, the answer contract, and retrieval, see `.claude/standards/intake.md`.
81
+ For the folder layout, the item format, the answer contract, and retrieval, see `standards/intake.md`.
@@ -41,7 +41,7 @@ Freezing the spellings gave up a property worth naming. They were derived by app
41
41
 
42
42
  A set shipped empty is reported rather than passed. It finds nothing and would exit clean, which reports a corpus nobody checked as a corpus carrying no violation, so the run names the empty set and exits `1`. The sets ship with the package, so a defect in the build is the only cause left.
43
43
 
44
- `aitk standards <name>` still resolves a standard under `.claude/standards/`, then the authoring root, then the package corpus, and prints it, so the human catalog reads without a project copy on disk.
44
+ `aitk standards <name>` still resolves a standard at the authoring root, then the package corpus, and prints it, so the human catalog reads without a project copy on disk.
45
45
 
46
46
  ## What each check reports
47
47
 
@@ -23,7 +23,7 @@ aitk records validate intake --json
23
23
 
24
24
  It reads and never writes, and the reason splits by kind. A session record is per-machine scratch with no history behind it, so a repair that guessed wrong could not be undone. A standard installs into every target and is cited by bare filename, so a rename the verb performed would reach further than the file it moved, which is why a finding naming one says so.
25
25
 
26
- `standards` reads the authoring root at `standards/` where it exists and the installed copy at `.claude/standards/` otherwise. The authoring root wins because the installed tree is generated from it in the toolkit, where a fix written to the copy is discarded by the next regen. A project that consumed the corpus holds only the second, so one precedence serves both. The walk stays flat, matching install and the catalog.
26
+ `standards` reads the authoring root at `standards/` where it exists and a copy at `.claude/standards/` otherwise. The authoring root wins because it is the only tree anyone authors in and the only one the resolver answers from, so a finding fixed anywhere else is fixed where nothing reads it. No repository generates the second candidate any more, which leaves it as a floor under a target holding a copy an older toolkit installed. The walk stays flat, matching the catalog.
27
27
 
28
28
  Nothing fires it automatically. The five record folders are gitignored, so the standards-audit hook exits early on them and any check reading changed files from git never lists one. The corpus is tracked and still unreached, since the markdown audit reads content across the files git lists and rules on no filename. The verb runs at the moment a session claims the record is finished, which is the same placement `aitk tasks validate` takes over the board.
29
29
 
@@ -70,7 +70,7 @@ Skills branch on the findings rather than on the exit code:
70
70
  aitk records validate plans --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
71
71
  ```
72
72
 
73
- For the shapes each check enforces, see `.claude/standards/plan.md`, `.claude/standards/groundwork.md`, `.claude/standards/intake.md`, `.claude/standards/memory.md`, and `.claude/standards/standard.md`.
73
+ For the shapes each check enforces, see `standards/plan.md`, `standards/groundwork.md`, `standards/intake.md`, `standards/memory.md`, and `standards/standard.md`.
74
74
 
75
75
  ## Migrate
76
76
 
@@ -132,7 +132,7 @@ aitk records push --json
132
132
  aitk records pull
133
133
  ```
134
134
 
135
- The backed folders are `diagrams`, `groundwork`, `intake`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`, all under `.claude/`. Seven of them are the Claude ignore group the claude manifest ships, minus three entries: `.claude/.tmp`, which is deletable without loss, `.claude/worktrees/`, whose contents belong to the project repository already, and `.claude/.records.git/`, which is the history the rest are pushed into. `diagrams` and `proposals` are the two that group does not carry at all, since a target still tracks its own copies of both, which is why the list is spelled out rather than derived. Each name is a top-level record folder and every archive sits inside the one it archives, so the list stays at one entry per surface however many archives appear. It is a constant rather than configuration, and it deliberately does not match the six record kinds `validate` hardcodes.
135
+ The backed folders are `diagrams`, `groundwork`, `intake`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`, all under `.claude/`. Eight of them are the Claude ignore group the claude manifest ships, minus three entries: `.claude/.tmp`, which is deletable without loss, `.claude/worktrees/`, whose contents belong to the project repository already, and `.claude/.records.git/`, which is the history the rest are pushed into. `diagrams` is the one that group does not carry at all, since a target still tracks its own copies, which is why the list is spelled out rather than derived. Each name is a top-level record folder and every archive sits inside the one it archives, so the list stays at one entry per surface however many archives appear. It is a constant rather than configuration, and it deliberately does not match the six record kinds `validate` hardcodes.
136
136
 
137
137
  Records are gitignored by design, so the history lives in a second git directory at `.claude/.records.git` with `.claude/` as its work tree. Every path stays where it is, which is what a separate checkout could not do. The verbs stage the nine folders by explicit pathspec with `--force`, so nothing outside them can enter the index however the ignore rules read, and the project working tree and its index are never touched. Each pathspec is a bare folder name and git reads it against the current directory rather than against the work tree the same call names, so the invocation carries `-C` at the work tree beside the other two flags. That is what lets either verb run from a linked worktree under `.claude/worktrees/`, which sits inside the records work tree and would otherwise prefix every name with its own path.
138
138
 
@@ -146,7 +146,7 @@ git --git-dir=.claude/.records.git remote add origin <private-repo-url>
146
146
  printf '.claude/.records.git/\n' >> .gitignore
147
147
  ```
148
148
 
149
- The ignore line is part of the setup rather than part of the installed gitignore group, because the person running these commands is the one who creates the directory. Every path that group ships is written by a toolkit command without anyone asking, and this one is not, so a project that never sets up records would carry a rule for a directory it will never hold. Leaving it out and adding it here puts the rule where whoever creates the directory reads it.
149
+ The ignore line is repeated here rather than left to the install, because the person running these commands is the one who creates the directory and the rule is worth reading beside the command that needs it. The claude manifest ships `.claude/.records.git/` as the first entry of its group, so a project that ran `aitk claude sync` already carries it and this line is a no-op there. What the group buys is the project that never sets records up: it holds a rule for a directory it will never create, which costs nothing, and the backed-folder derivation above subtracts that entry from the group rather than from a list it does not sit in.
150
150
 
151
151
  Point it at a private repository, and at one that is not a remote of the project. Records carry the memory pen, the review reports, and the groundwork trails, so a public project publishes all of it to anyone who fetches all refs. `push` compares the configured origin against every remote of the project and refuses on a match. A read of that list which fails refuses as well, since an empty list clears the comparison for every origin and a gate that passes on its own failure is no gate.
152
152
 
@@ -191,4 +191,4 @@ Skills branch on the findings rather than on the exit code:
191
191
  aitk tasks validate --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
192
192
  ```
193
193
 
194
- For the board format, the `Pull request:` line, and the archive rules, see `.claude/standards/tasks.md`.
194
+ For the board format, the `Pull request:` line, and the archive rules, see `standards/tasks.md`.
@@ -89,7 +89,7 @@ A constraint naming a track in flight carries the same problem past the handoff,
89
89
 
90
90
  `.claude/plans/`, `.claude/review/`, and `.claude/memory/` all resolve at the main worktree root, so artifacts created in any session are visible from any sibling worktree. A session inside a worktree reads them directly, since the file-editing tools refuse a main-root path but `Read` resolves normally. It writes a whole file through the shell and makes a change inside an existing file through an `aitk` verb, which resolves the main root in-process. See [Claude Code and git worktrees](../wiki/claude/claude-worktrees.md) for the full rule and the domain-level fan-out guidance.
91
91
 
92
- The plan's shape is fixed by `.claude/standards/plan.md`: the section list, the filename, the lifecycle, and the contract its questions keep. Every question carries a `- Suggested:` line and an empty `- Answer:` slot, and a blank answer accepts the suggestion at execution time. That default is what makes a plan decision-ready in one pass, and it is the opposite of the contract an intake folder keeps, where an empty slot means nobody reached the item.
92
+ The plan's shape is fixed by `standards/plan.md`: the section list, the filename, the lifecycle, and the contract its questions keep. Every question carries a `- Suggested:` line and an empty `- Answer:` slot, and a blank answer accepts the suggestion at execution time. That default is what makes a plan decision-ready in one pass, and it is the opposite of the contract an intake folder keeps, where an empty slot means nobody reached the item.
93
93
 
94
94
  An execution that picks other than the suggestion rewrites the `- Suggested:` line as `overridden at execution to <pick>,` followed by the measurement that moved it, and leaves the slot blank. That fixed phrase is how a reader of the archived plan tells an override from a suggestion the execution accepted, since an authored suggestion often carries a number of its own. The same deviation takes one line in the open task's `## Findings`, which is the register that survives the plan being archived. A deviation from a question somebody already answered goes back to whoever answered it instead, since a filled slot is a decision already made.
95
95
 
@@ -20,7 +20,7 @@ Claude Code reads both and writes the implementation. Works for CLI tools, inter
20
20
 
21
21
  ### Seed shape
22
22
 
23
- The toolkit seed in `tooling/claude/seeds/.claude/DESIGN.md` ships a token-table template with a starting set of roles, and `.claude/standards/design.md` carries the same tables under `## Template` with placeholder rows. The column headers are what the renderer parses, so they stay verbatim in either, while the rows and values are the project's own.
23
+ The toolkit seed in `tooling/claude/seeds/.claude/DESIGN.md` ships a token-table template with a starting set of roles, and `standards/design.md` carries the same tables under `## Template` with placeholder rows. The column headers are what the renderer parses, so they stay verbatim in either, while the rows and values are the project's own.
24
24
 
25
25
  The `aitk:claude-design-extract` skill drafts the file, sourcing tokens from a project's existing prose and CLI UI surfaces, or proposing them from `.claude/REQUIREMENTS.md` and a `## Personality` paragraph when no UI code exists yet. `aitk design render` writes an HTML plus CSS preview to `.claude/review/design/` for eyeballing the current system without leaving Claude Code. See `.claude/context/design.md`.
26
26
 
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  description: Enforce shape, scope, and success criterion for authoring convention files
3
3
  paths:
4
- - '.claude/standards/**/*.md'
5
4
  - 'standards/**/*.md'
6
5
  ---
7
6
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.37.1",
4
+ "version": "3.38.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ set -o pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
7
+
8
+ GITIGNORE="$PROJECT_ROOT/.gitignore"
9
+ MANIFEST="$PROJECT_ROOT/tooling/claude/manifest.toml"
10
+ SECTION="# Claude"
11
+
12
+ # Divergences this repository has decided to keep, one `<entry>|<reason>` per
13
+ # line, matched after the trailing slash is stripped. Each names a path the
14
+ # enclosing repository ignores and the claude manifest deliberately does not
15
+ # ship, so a target keeps tracking it.
16
+ #
17
+ # This is the canonical list. `.gitignore` and the manifest carry a pointer
18
+ # beside their own entries rather than a second copy of the reason, and
19
+ # `.claude/context/tooling.md` carries the narrative behind both.
20
+ #
21
+ # A sanction that no longer describes a divergence fails the same as an
22
+ # unsanctioned one. An exception outliving its reason is what teaches the next
23
+ # reader to widen the list rather than to read it.
24
+ SANCTIONED=(
25
+ ".claude/diagrams|a target that tracks its diagrams keeps the context audit's default coverage of them, which ignoring the folder for every target would take away. This repository backs the folder through its records remote instead."
26
+ ".claude/README.md|the landing page a records pull writes back into a repository that set up a records remote. A target has not, so it never receives the file."
27
+ )
28
+
29
+ if [ ! -f "$GITIGNORE" ]; then
30
+ echo "No .gitignore at ${GITIGNORE#"$PROJECT_ROOT/"}, ignore parity unverifiable." >&2
31
+ exit 1
32
+ fi
33
+
34
+ if [ ! -f "$MANIFEST" ]; then
35
+ echo "No claude manifest at ${MANIFEST#"$PROJECT_ROOT/"}, ignore parity unverifiable." >&2
36
+ exit 1
37
+ fi
38
+
39
+ # A gitignore pattern and a manifest entry describe the same folder whether or
40
+ # not either spells the trailing slash, so presence is compared with it dropped.
41
+ # `.claude/.tmp` and `.claude/.tmp/` are the live instance of that pair.
42
+ normalize() {
43
+ printf '%s\n' "${1%/}"
44
+ }
45
+
46
+ # Every pattern the file carries, comments and blank lines dropped. The whole
47
+ # file rather than one header, because `.claude/teach/` sits under a header of
48
+ # its own and reading the `# Claude` header alone would report it missing from a
49
+ # list that carries it.
50
+ gitignore_patterns() {
51
+ awk '{ sub(/[[:space:]]+$/, "") } $0 ~ /^[[:space:]]*#/ || $0 == "" { next } { print }' "$GITIGNORE"
52
+ }
53
+
54
+ # Every entry of the named array in the manifest's `[gitignore]` table. The
55
+ # array is accumulated to its closing bracket before the quoted strings are read
56
+ # out, so a formatter wrapping it across lines does not silently empty this.
57
+ manifest_entries() {
58
+ awk -v key="\"$SECTION\"" '
59
+ /^\[/ { table = $0 }
60
+ table != "[gitignore]" { next }
61
+ index($0, key) == 1 { collecting = 1; buf = "" }
62
+ collecting { buf = buf $0 }
63
+ collecting && index($0, "]") > 0 {
64
+ collecting = 0
65
+ buf = substr(buf, index(buf, "["))
66
+ count = split(buf, parts, "\"")
67
+ for (i = 2; i <= count; i += 2) print parts[i]
68
+ }
69
+ ' "$MANIFEST"
70
+ }
71
+
72
+ contains() {
73
+ local needle=$1
74
+ shift
75
+ local candidate
76
+ for candidate in "$@"; do
77
+ [ "$candidate" = "$needle" ] || continue
78
+ return 0
79
+ done
80
+ return 1
81
+ }
82
+
83
+ ignored=()
84
+ while IFS= read -r pattern; do
85
+ [ -n "$pattern" ] || continue
86
+ ignored+=("$(normalize "$pattern")")
87
+ done < <(gitignore_patterns)
88
+
89
+ shipped=()
90
+ while IFS= read -r entry; do
91
+ [ -n "$entry" ] || continue
92
+ shipped+=("$(normalize "$entry")")
93
+ done < <(manifest_entries)
94
+
95
+ # An empty read on either side is a parse that failed rather than a repository
96
+ # ignoring nothing, and reporting parity off it would say the two lists agree
97
+ # having compared none of their entries.
98
+ if [ ${#ignored[@]} -eq 0 ]; then
99
+ echo "No patterns read from .gitignore, ignore parity unverifiable." >&2
100
+ exit 1
101
+ fi
102
+
103
+ if [ ${#shipped[@]} -eq 0 ]; then
104
+ echo "No entries read from the \"$SECTION\" array in ${MANIFEST#"$PROJECT_ROOT/"}, ignore parity unverifiable." >&2
105
+ exit 1
106
+ fi
107
+
108
+ failures=""
109
+ sanction_notes=""
110
+
111
+ # Entries the manifest ships that this repository does not ignore. A target is
112
+ # told to ignore a folder the toolkit itself tracks, which no decision sanctions,
113
+ # so this direction carries no exception list.
114
+ for entry in "${shipped[@]}"; do
115
+ contains "$entry" "${ignored[@]}" && continue
116
+ failures="$failures $entry is shipped by the manifest and absent from .gitignore"$'\n'
117
+ done
118
+
119
+ # Claude-scoped patterns this repository ignores that the manifest does not
120
+ # ship. Scoped to `.claude/` because the manifest is the claude stack and says
121
+ # nothing about `node_modules/` or `.env`.
122
+ for pattern in "${ignored[@]}"; do
123
+ case "$pattern" in
124
+ .claude/*) ;;
125
+ *) continue ;;
126
+ esac
127
+ contains "$pattern" "${shipped[@]}" && continue
128
+
129
+ matched=false
130
+ for sanction in "${SANCTIONED[@]}"; do
131
+ [ "${sanction%%|*}" = "$pattern" ] || continue
132
+ matched=true
133
+ sanction_notes="$sanction_notes $pattern stays out of the manifest: ${sanction#*|}"$'\n'
134
+ break
135
+ done
136
+
137
+ if [ "$matched" = false ]; then
138
+ failures="$failures $pattern is ignored here and absent from the manifest"$'\n'
139
+ fi
140
+ done
141
+
142
+ # A sanction naming a path that is no longer divergent, either because the
143
+ # manifest took it or because .gitignore dropped it.
144
+ for sanction in "${SANCTIONED[@]}"; do
145
+ entry="${sanction%%|*}"
146
+ if contains "$entry" "${ignored[@]}" && ! contains "$entry" "${shipped[@]}"; then
147
+ continue
148
+ fi
149
+ failures="$failures $entry is sanctioned as a divergence and is no longer one"$'\n'
150
+ done
151
+
152
+ if [ -n "$failures" ]; then
153
+ echo "The ignore set a target receives disagrees with this repository's own:" >&2
154
+ printf '%s' "$failures" >&2
155
+ echo "Add the entry to the \"$SECTION\" array in ${MANIFEST#"$PROJECT_ROOT/"} and to .gitignore, or record it in SANCTIONED in scripts/core/check-ignore-parity.sh with the reason it stays apart." >&2
156
+ exit 1
157
+ fi
158
+
159
+ if [ -n "$sanction_notes" ]; then
160
+ echo "Sanctioned divergences from the ignore set a target receives:"
161
+ printf '%s' "$sanction_notes"
162
+ fi
@@ -18,7 +18,6 @@ mirror_dir() {
18
18
  done < <(find "$src" -type f "${find_args[@]}" | sort)
19
19
  }
20
20
 
21
- mirror_dir "$PROJECT_ROOT/standards" "$PROJECT_ROOT/.claude/standards" -name "*.md" -not -path "*/bundled/*"
22
21
  mirror_dir "$PROJECT_ROOT/snippets" "$PROJECT_ROOT/.claude/snippets" -name "*.md"
23
22
 
24
23
  # `internal/` is the surface the plugin does not symlink. Mirrored on its own so
@@ -261,7 +261,7 @@ main() {
261
261
 
262
262
  log_step "Consumed copies"
263
263
  run_check "bash $PROJECT_ROOT/scripts/core/regen-claude-copies.sh" "Consumed-copy regen failed"
264
- assert_no_drift ".claude/standards .claude/snippets .claude/internal .claude/rules" "Consumed copies drifted. Run bun run check and commit .claude/standards, .claude/snippets, .claude/internal, and .claude/rules."
264
+ assert_no_drift ".claude/snippets .claude/internal .claude/rules" "Consumed copies drifted. Run bun run check and commit .claude/snippets, .claude/internal, and .claude/rules."
265
265
  log_info "Consumed copies clean"
266
266
 
267
267
  # Only the HTML is asserted. The PNG beside it is a chromium render whose bytes
@@ -278,6 +278,16 @@ main() {
278
278
  assert_no_drift "claude/skills/toolkit-cli/SKILL.md" "The overwrite contract drifted from what the stacks hold. Run bun run check and commit claude/skills/toolkit-cli/SKILL.md."
279
279
  log_info "Tooling paths clean"
280
280
 
281
+ # The claude manifest is the only route a target's ignore set travels, and it
282
+ # is hand-maintained beside this repository's own `.gitignore` with nothing
283
+ # comparing the two. A drift between them reaches every target on the next
284
+ # `aitk tooling sync` and surfaces to nobody, which is why this gates rather
285
+ # than reports. It is not an `assert_no_drift`: no generator produces either
286
+ # list, so there is nothing to regenerate and diff.
287
+ log_step "Ignore parity"
288
+ run_check "bash $PROJECT_ROOT/scripts/core/check-ignore-parity.sh" "The ignore set a target receives disagrees with this repository's own."
289
+ log_info "Ignore parity clean"
290
+
281
291
  log_step "Skill paths"
282
292
  run_check "bash $PROJECT_ROOT/scripts/core/check-skill-paths.sh" "Shipped skills reference a repo-local path."
283
293
  log_info "Skill paths clean"
@@ -570,7 +570,7 @@ export const AUDITS: readonly AuditSpec[] = [
570
570
  // `standards` is the one tracked kind here, so it takes none of the
571
571
  // per-machine default the other five inherit from their corpus. A target
572
572
  // reads standards through `aitk standards` rather than a copy in its own
573
- // tree, so carrying neither `standards/` nor `.claude/standards/` is the
573
+ // tree, so carrying no standards folder at all is the
574
574
  // ordinary state of every project but this repository.
575
575
  ...(kind === 'standards' && {
576
576
  absentReasons: ['no-folder'] satisfies RecordRefusal[],
@@ -5,11 +5,15 @@ import { INDEX_FILE, listIndexes } from '@/indexes/walk'
5
5
  /**
6
6
  * Folder names under `.claude/` audited by default.
7
7
  *
8
- * The index-plus-entry contract on its own over-collects. `.claude/standards/`
9
- * also carries a generated index beside entries with frontmatter, and auditing
10
- * it would measure the consumed copy of `standards/` against a rule written for
11
- * per-domain narrative. This list names the folders `standards/context.md`
12
- * governs, and `--folder` admits another without an edit here.
8
+ * A named list rather than the index-plus-entry contract read off disk, so a
9
+ * generated tree satisfying that contract is never measured against a rule
10
+ * written for per-domain narrative. This list names the folders
11
+ * `standards/context.md` governs, and `--folder` admits another without an edit
12
+ * here.
13
+ *
14
+ * It doubles as the citation check's scope, since `citationPattern` spells only
15
+ * these names. A `.claude/` folder left off the list is never resolved, so a
16
+ * path into one goes stale silently rather than failing a push.
13
17
  */
14
18
  export const DEFAULT_FOLDERS: readonly string[] = [
15
19
  'context',
@@ -12,10 +12,12 @@ import { gitEnv } from '@/git-env'
12
12
  * list is spelled out rather than read off that group so adding an ignore entry
13
13
  * cannot silently enlarge the payload.
14
14
  *
15
- * `diagrams` and `proposals` are the two names the manifest group does not
16
- * carry, so a target tracks both where this repository ignores them. That is
17
- * the second reason to spell the list out: the manifest bounds it rather than
18
- * producing it, and no count taken off that group matches this one.
15
+ * `diagrams` is the one name the manifest group does not carry, so a target
16
+ * tracks it where this repository ignores it. That is the second reason to
17
+ * spell the list out: the manifest bounds it rather than producing it, and no
18
+ * count taken off that group matches this one. `scripts/core/check-ignore-parity.sh`
19
+ * compares the two lists and holds the reason `diagrams` stays apart, so the
20
+ * one difference is enforced rather than assumed.
19
21
  *
20
22
  * The manifest group is the one this reads rather than the enclosing
21
23
  * repository's own `.gitignore`, which spreads the same entries across two
@@ -33,7 +33,8 @@ export interface ResolvedStandard {
33
33
 
34
34
  /**
35
35
  * The roots a standard resolves against. No toolkit standard installs into a
36
- * project any more, so `.claude/standards/` is gone from the list and a target
36
+ * project any more, so `.claude/standards/` is gone from the list, no repository
37
+ * generates one, and a target
37
38
  * carries neither entry below: the package corpus is the only root that
38
39
  * answers there, which is what leaves no precedence to reason about.
39
40
  *
@@ -13,7 +13,7 @@ const CLAUDE_DIR = '.claude'
13
13
 
14
14
  /**
15
15
  * A `CLAUDE.md` past this many lines has more in it than the always-load tier
16
- * is for. The figure is the checkpoint `.claude/standards/context.md` sets for
16
+ * is for. The figure is the checkpoint `standards/context.md` sets for
17
17
  * a context entry, raised because `CLAUDE.md` legitimately carries more than
18
18
  * one domain. It gates a proposal rather than a failure, so being approximate
19
19
  * costs a line an operator can ignore.
@@ -14,7 +14,7 @@ const ORDERING_FILE = 'priority.md'
14
14
  const BACKLOG_FILE = 'backlog.md'
15
15
 
16
16
  /**
17
- * The readiness headings `.claude/standards/tasks.md` fixes. The names are the
17
+ * The readiness headings `standards/tasks.md` fixes. The names are the
18
18
  * contract rather than a suggestion, so a board grouping under names of its own
19
19
  * reads as carrying no group at all and the run refuses instead of reporting a
20
20
  * clean board it never parsed.
@@ -42,11 +42,11 @@ An intake failing these is non-conforming even when it satisfies every shape rul
42
42
 
43
43
  ## Files
44
44
 
45
- | File | Holds | Required |
46
- | -------------------- | ------------------------------------------------------------ | -------- |
47
- | `00-overview.md` | Index: format block, cluster table, verdicts, open questions | Always |
48
- | `NN-<domain>.md` | One cluster of items, filed by the domain their fixes touch | Always |
49
- | `99-next-session.md` | What a compaction destroys that no cluster file carries | Optional |
45
+ | File | Holds | Required |
46
+ | -------------------- | ----------------------------------------------------------- | -------- |
47
+ | `00-overview.md` | Index: cluster table, verdicts, open questions | Always |
48
+ | `NN-<domain>.md` | One cluster of items, filed by the domain their fixes touch | Always |
49
+ | `99-next-session.md` | What a compaction destroys that no cluster file carries | Optional |
50
50
 
51
51
  `00` is the only reserved number. Everything else is read order, and the domain rides in the filename so a reader knows what `07-tooling.md` holds without opening it.
52
52
 
@@ -70,7 +70,6 @@ Date the folder once rather than every file. Twelve dated files leave eleven sta
70
70
 
71
71
  The index. It points at items and answers nothing itself.
72
72
 
73
- - The item format block, copied so a returning session picks the shape up from the folder
74
73
  - The answer contract stated out loud, since it inverts the plan file's
75
74
  - A cluster table of file, what it holds, item count, and open count
76
75
  - The verdict counts across the folder
@@ -168,10 +167,6 @@ date: <YYYY-MM-DD>
168
167
 
169
168
  <One line on what the dump covers and the commit it was measured against.>
170
169
 
171
- ## Item format
172
-
173
- <the item format block, copied so a returning session picks the shape up here>
174
-
175
170
  ## The answer contract
176
171
 
177
172
  `You:` ships empty and empty means unread. Accepting a verdict is typed as
@@ -9,7 +9,7 @@ Applies to each authored standard in the folder. Skip for `index.md`, which is g
9
9
 
10
10
  ## Overview
11
11
 
12
- A standard is a target-facing authoring convention for one document type, or for one attribute carried across every document. It installs into a project under `.claude/standards/` and is consumed by skills and developers alike. This file governs itself, so every rule below applies to it.
12
+ A standard is a target-facing authoring convention for one document type, or for one attribute carried across every document. Nothing installs it into a project, so a reader resolves it with `aitk standards <name>` or reads it at `standards/<name>.md`, and skills and developers consume it alike. This file governs itself, so every rule below applies to it.
13
13
 
14
14
  ## Scope
15
15
 
@@ -7,5 +7,8 @@ extends = ""
7
7
  runtime = ""
8
8
  scaffold = ""
9
9
 
10
+ # Checked against this repository's own `.gitignore` by
11
+ # scripts/core/check-ignore-parity.sh, which also holds the two `.claude/` paths
12
+ # this array deliberately omits and the reason each stays out.
10
13
  [gitignore]
11
- "# Claude" = [".claude/.records.git/", ".claude/.tmp/", ".claude/groundwork/", ".claude/intake/", ".claude/memory/", ".claude/plans/", ".claude/review/", ".claude/worktrees/", ".claude/tasks/", ".claude/teach/"]
14
+ "# Claude" = [".claude/.records.git/", ".claude/.tmp/", ".claude/groundwork/", ".claude/intake/", ".claude/memory/", ".claude/plans/", ".claude/proposals/", ".claude/review/", ".claude/worktrees/", ".claude/tasks/", ".claude/teach/"]
@@ -29,7 +29,7 @@ A project installed before the board became a folder still holds `.claude/TASKS.
29
29
 
30
30
  Convert by hand, once per project:
31
31
 
32
- 1. Split each `###` block into `.claude/tasks/vXX.Y-<slug>.md`, adding the `title` and `description` frontmatter that `.claude/standards/tasks.md` requires. Compare each new file against the block it came from rather than checking that every block landed somewhere.
32
+ 1. Split each `###` block into `.claude/tasks/vXX.Y-<slug>.md`, adding the `title` and `description` frontmatter the tasks standard requires, which `aitk standards tasks` prints. Compare each new file against the block it came from rather than checking that every block landed somewhere.
33
33
  2. Run `aitk indexes regen --no-stage --root . .claude/tasks/<any-task>.md` to build the catalog.
34
34
  3. Delete `.claude/TASKS.md`, and swap its `.gitignore` entry for `.claude/tasks/`.
35
35