@erclx/aitk 3.37.0 → 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.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-autoship/SKILL.md +1 -1
- package/claude/skills/claude-intake/SKILL.md +1 -1
- package/claude/skills/create-standard/REQUIREMENT.md +3 -3
- package/docs/agents/context-audit-checks.md +7 -7
- package/docs/agents/install-and-sync.md +1 -1
- package/docs/agents/intake.md +1 -1
- package/docs/agents/markdown-audit.md +1 -1
- package/docs/agents/records.md +5 -5
- package/docs/agents/tasks.md +7 -1
- package/docs/ai-workflow.md +1 -1
- package/docs/visual-design-workflow.md +1 -1
- package/governance/rules/claude/591-standard-authoring.md +0 -1
- package/package.json +1 -1
- package/scripts/core/check-ignore-parity.sh +162 -0
- package/scripts/core/regen-claude-copies.sh +0 -1
- package/scripts/core/verify.sh +11 -1
- package/src/audits/catalog.ts +1 -1
- package/src/context/folders.ts +9 -5
- package/src/records/backup.ts +24 -11
- package/src/standards/read.ts +2 -1
- package/src/sync/reverse.ts +1 -1
- package/src/tasks/validate.ts +1 -1
- package/standards/intake.md +5 -10
- package/standards/standard.md +1 -1
- package/standards/tasks.md +4 -26
- package/tooling/claude/manifest.toml +4 -1
- package/tooling/claude/reference.md +1 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
package/docs/agents/intake.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
package/docs/agents/records.md
CHANGED
|
@@ -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
|
|
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
|
|
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,9 +132,9 @@ 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/`.
|
|
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
|
-
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.
|
|
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
|
|
|
139
139
|
### Setup
|
|
140
140
|
|
|
@@ -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
|
|
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
|
|
package/docs/agents/tasks.md
CHANGED
|
@@ -28,6 +28,10 @@ Exit codes: `0` archived, `1` refused. Every gate is a refusal rather than a war
|
|
|
28
28
|
|
|
29
29
|
`bad-input` covers a malformed command line, which all three task verbs answer the same way. It is separate from `ambiguous` and `no-match` because those describe the board, and a caller that passed two selectors would otherwise be sent to repair a task citation that is fine.
|
|
30
30
|
|
|
31
|
+
The row is matched by the link in its first cell rather than by a pattern against the whole line. A row names the task it is about in the first cell, so a link anywhere after it is a reference, such as a blocker naming what it waits on, and matching the line would drop the referring task's row too.
|
|
32
|
+
|
|
33
|
+
The row removal reaches `priority.md` alone. A task gets to a merge by being planned and handed out, and both steps move it onto the board first, so one archived straight off `backlog.md` leaves its bullet standing and `aitk tasks validate` reports that bullet as naming a file that is gone.
|
|
34
|
+
|
|
31
35
|
The board is shared scratch at the main worktree root, so `--root` defaults to the first entry of `git worktree list` rather than the working directory. A linked worktree archives against the same board every other session reads.
|
|
32
36
|
|
|
33
37
|
Skills branch on the reason rather than on the exit code:
|
|
@@ -47,6 +51,8 @@ aitk tasks archive --pull-request 673 --json | jq -r 'if .ok then .task else .re
|
|
|
47
51
|
|
|
48
52
|
The record carries `location`, one of `unstated`, `live`, `archived`, or `outside`, and `citedBy`, the other live tasks whose `Plan:` line lands on the same file. Exit codes: `0` read, `1` refused with `no-board` or `no-match`.
|
|
49
53
|
|
|
54
|
+
The target resolves against `.claude/tasks/` and against the project root both, so `../plans/x.md` and `.claude/plans/x.md` land on the same file and one plan two tasks spelled differently counts once.
|
|
55
|
+
|
|
50
56
|
`aitk tasks archive` gates on this same answer, so a caller wanting the count reads it here rather than scanning the board. The `claude-docs` plans sweep is the exception and still states the rule in its own body, because a plugin skill reaches a target on merge while the CLI reaches one on release, so a sweep calling a verb the installed `aitk` predates gets no record back and archives nothing.
|
|
51
57
|
|
|
52
58
|
Branch on `reason` rather than on the exit code, which is the rule the archive section above already states and which this verb needs for a second reason. An operator's shell profile may wrap `aitk` in a function that runs the binary and then another command and takes the second status, which masks every non-zero exit rather than only an absent verb. The binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike, so the record is the only signal that survives the wrapper.
|
|
@@ -185,4 +191,4 @@ Skills branch on the findings rather than on the exit code:
|
|
|
185
191
|
aitk tasks validate --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
|
|
186
192
|
```
|
|
187
193
|
|
|
188
|
-
For the board format, the `Pull request:` line, and the archive rules, see
|
|
194
|
+
For the board format, the `Pull request:` line, and the archive rules, see `standards/tasks.md`.
|
package/docs/ai-workflow.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
package/package.json
CHANGED
|
@@ -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
|
package/scripts/core/verify.sh
CHANGED
|
@@ -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/
|
|
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"
|
package/src/audits/catalog.ts
CHANGED
|
@@ -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
|
|
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[],
|
package/src/context/folders.ts
CHANGED
|
@@ -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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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',
|
package/src/records/backup.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
3
|
import { $ } from 'bun'
|
|
4
4
|
import { gitEnv } from '@/git-env'
|
|
5
5
|
|
|
@@ -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`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
@@ -124,19 +126,30 @@ interface GitResult {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/**
|
|
127
|
-
* Runs one git command against the records history.
|
|
129
|
+
* Runs one git command against the records history, from the work tree.
|
|
128
130
|
*
|
|
129
|
-
*
|
|
131
|
+
* All three flags go on every call. `git --git-dir=<path> init` writes
|
|
130
132
|
* `core.bare = true`, and an explicit `--work-tree` is what overrides it, so
|
|
131
|
-
* dropping
|
|
133
|
+
* dropping that flag on a single call reads the enclosing project as the tree
|
|
132
134
|
* and stages everything in it.
|
|
135
|
+
*
|
|
136
|
+
* `-C` is what makes a bare pathspec like `groundwork` mean the work-tree root
|
|
137
|
+
* wherever the caller stands. Git derives a pathspec prefix from the current
|
|
138
|
+
* directory, so without it a caller sitting inside `.claude/`, which is every
|
|
139
|
+
* session in a linked worktree under `.claude/worktrees/<name>/`, prefixes
|
|
140
|
+
* each name with its own path and matches nothing. The root a caller names
|
|
141
|
+
* does not reach that prefix, so `--root` cannot stand in for this.
|
|
142
|
+
*
|
|
143
|
+
* Both paths are absolute because `-C` takes effect before the other two flags
|
|
144
|
+
* are read, so a relative root would otherwise send them looking inside the
|
|
145
|
+
* work tree.
|
|
133
146
|
*/
|
|
134
147
|
async function records(root: string, args: string[]): Promise<GitResult> {
|
|
135
|
-
const gitDir =
|
|
136
|
-
const workTree =
|
|
148
|
+
const gitDir = resolve(root, RECORDS_GIT_DIR)
|
|
149
|
+
const workTree = resolve(root, WORK_TREE)
|
|
137
150
|
|
|
138
151
|
const result =
|
|
139
|
-
await $`git --git-dir=${gitDir} --work-tree=${workTree} ${args}`
|
|
152
|
+
await $`git -C ${workTree} --git-dir=${gitDir} --work-tree=${workTree} ${args}`
|
|
140
153
|
.env(gitEnv())
|
|
141
154
|
.quiet()
|
|
142
155
|
.nothrow()
|
package/src/standards/read.ts
CHANGED
|
@@ -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
|
|
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
|
*
|
package/src/sync/reverse.ts
CHANGED
|
@@ -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
|
|
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.
|
package/src/tasks/validate.ts
CHANGED
|
@@ -14,7 +14,7 @@ const ORDERING_FILE = 'priority.md'
|
|
|
14
14
|
const BACKLOG_FILE = 'backlog.md'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* The readiness headings
|
|
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.
|
package/standards/intake.md
CHANGED
|
@@ -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
|
|
46
|
-
| -------------------- |
|
|
47
|
-
| `00-overview.md` | Index:
|
|
48
|
-
| `NN-<domain>.md` | One cluster of items, filed by the domain their fixes touch
|
|
49
|
-
| `99-next-session.md` | What a compaction destroys that no cluster file carries
|
|
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
|
package/standards/standard.md
CHANGED
|
@@ -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.
|
|
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
|
|
package/standards/tasks.md
CHANGED
|
@@ -133,13 +133,7 @@ Add no fourth readiness group in place of this file. The three group names are t
|
|
|
133
133
|
|
|
134
134
|
## Validation
|
|
135
135
|
|
|
136
|
-
`aitk tasks validate` reads the
|
|
137
|
-
|
|
138
|
-
A cited task is settled by being archived, or by closing every outcome and carrying a `Pull request:` line the trunk holds. The closed checkbox alone settles nothing, because the ship chain marks outcomes as its first step and opens the pull request several steps later, so a row read off the checkbox reports settled while the branch is still in review. A task that closed every outcome and names no pull request, and one whose pull request the run could not read against the trunk, are both reported as untested. Degrading either back to the checkbox would reproduce the defect under a name claiming it was fixed.
|
|
139
|
-
|
|
140
|
-
The trunk is read as the clone already holds it, `origin/main` first and local `main` behind it, and no run fetches. A validate happens several times a sweep and a fetch per run is a cost this check does not carry, so a clone behind the remote under-reports rather than claiming work landed.
|
|
141
|
-
|
|
142
|
-
A task file is accounted for when a row on `priority.md` or a line on `backlog.md` names it, and reported when neither does. One check across both surfaces is what lets a task move between them without the move looking like a dropped file, and a task named by both is reported for the same reason a task in two groups is: it claims two things about itself and only one of them can hold. A project carrying no `backlog.md` is read as an empty backlog rather than refused, which leaves the one-to-one mapping this check ran before the second surface existed.
|
|
136
|
+
`aitk tasks validate` reads the board against the tree, and what it checks, what it refuses on, and what it reports are at `docs/agents/tasks.md`. Run it when the readiness claim is made rather than on a schedule, since the board is gitignored per-machine scratch and no shared moment exists to hang it on.
|
|
143
137
|
|
|
144
138
|
## Filenames
|
|
145
139
|
|
|
@@ -247,30 +241,14 @@ The line is what lets a merge close its own task. Every merge on `main` is a squ
|
|
|
247
241
|
|
|
248
242
|
## Archiving
|
|
249
243
|
|
|
250
|
-
Never delete a task file. A shipped task moves to `.claude/tasks/archive/` under its own name, and the live index regenerates without it. `aitk tasks archive` owns
|
|
244
|
+
Never delete a task file. A shipped task moves to `.claude/tasks/archive/` under its own name, and the live index regenerates without it. `aitk tasks archive` owns that move, and what it does and what it refuses on are at `docs/agents/tasks.md`.
|
|
251
245
|
|
|
252
246
|
The archive nests inside `.claude/tasks/` rather than sitting beside it as a flat `.claude/task-archive/`. Nesting is what lets a reader tell the two shapes apart on sight: the flat sibling is what a binary predating this convention still writes, so meeting one names an older checkout rather than a second archive to reconcile against this one.
|
|
253
247
|
|
|
254
|
-
Two callers reach that command. The `claude-tasks` skill runs it inside a session, and the `post-merge` hook runs it unattended after a pull that merged the work. Both go through the command rather than moving the file themselves, so the two paths cannot drift into archiving differently. Every gate the command applies refuses with a non-zero exit rather than reporting, because a caller with nobody watching cannot act on a warning.
|
|
255
|
-
|
|
256
248
|
One destination rather than a per-project choice is what lets the move happen without asking. It mirrors the plans archive at `.claude/plans/archive/`, sitting inside the folder it archives the same way, and it inherits the board's own ignore entry rather than needing one of its own. The cost is that an archived task does not appear in diffs, which is the cost the live board already carries.
|
|
257
249
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Archiving a task does not archive its plan. `claude-docs` owns the plans sweep and moves a plan only when the closing task is its last live citation. The archive clears the task's row from `priority.md` itself, since a shipped task left in the ordering reads as ready to hand a worker. It leaves prose naming the task alone for a person to resolve.
|
|
261
|
-
|
|
262
|
-
The row is matched by the link in its first cell rather than by a pattern against the whole line. A row names the task it is about in the first cell, so a link anywhere after that is a reference, such as a blocker pointing at what it waits on. Matching the line would delete the referring task's row too, on a board that is gitignored and has nothing to recover it from.
|
|
263
|
-
|
|
264
|
-
Sweep the plan before archiving the task. The sweep finds its work by scanning the live folder, so a task archived first is beyond its reach for good, and the plan is left with no live task citing it and an archived task pointing at a path nothing will retarget. The archive refuses the last task pointing at a live plan for that reason, which puts the ordering under a gate rather than under a convention the unattended caller cannot follow.
|
|
265
|
-
|
|
266
|
-
The gate counts the other live tasks citing the same plan rather than reading which folder the plan sits in. A plan several tasks share stays in the live folder by design, because the sweep is correct to leave a plan another live task still cites, so a gate reading the folder alone refuses every one of those tasks and the board and the sweep block each other with neither in the wrong. Counting the citations asks the question the gate means: a plan nothing else holds is one the sweep has yet to reach, and a plan a sibling still holds is one the sweep already decided about.
|
|
267
|
-
|
|
268
|
-
The count resolves the target against `.claude/tasks/` and against the project root both, so `../plans/x.md` and `.claude/plans/x.md` land on the same file and one plan two tasks spelled differently counts once. `aitk tasks plan-citations` exposes that count for a caller that wants it, and the gate reads it.
|
|
269
|
-
|
|
270
|
-
The `claude-docs` sweep states the rule rather than calling that verb, which is a duplication accepted with a reason rather than an oversight. A skill reaches a target the moment it merges and the CLI reaches one only when a release publishes, so a body calling a verb the installed `aitk` predates gets no record back and sweeps nothing. The two spellings therefore have to agree by hand until a release carries the verb, and the failure they guard against is a plan stranded by the form its citation was written in.
|
|
271
|
-
|
|
272
|
-
A caller reads the outcome off the record's `reason` field and never off the exit code. An operator's shell profile may wrap `aitk` in a function that runs the binary and then another command, taking its status from the second, which masks an ordinary refusal exactly as it masks an absent verb.
|
|
250
|
+
Archiving a task does not archive its plan. `claude-docs` owns the plans sweep and moves a plan only when the closing task is its last live citation, so the sweep runs before the archive rather than after it. The sweep finds its work by scanning the live folder, and a task archived first is beyond its reach for good, leaving the plan with no live task citing it and an archived task pointing at a path nothing will retarget.
|
|
273
251
|
|
|
274
|
-
|
|
252
|
+
The `claude-docs` sweep states that ordering in its own body rather than reading it back from a command, which is a duplication accepted with a reason rather than an oversight. A skill reaches a target the moment it merges and the CLI reaches one only when a release publishes, so a body calling a verb the installed `aitk` predates gets no record back and sweeps nothing. The two spellings therefore have to agree by hand until a release carries the verb, and the failure they guard against is a plan stranded by the form its citation was written in.
|
|
275
253
|
|
|
276
254
|
A task with an open outcome stays on the board. Close it, or cut it from the task when the work is being abandoned, so what was dropped is recorded rather than inferred from an archived file. The sweep is gated on the same condition, so archiving around an open outcome also leaves the plan behind.
|
|
@@ -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
|
|
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
|
|