@erclx/aitk 0.94.0 → 0.96.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": "0.94.0",
4
+ "version": "0.96.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -38,7 +38,7 @@ The roadmap is optional and this skill does not require it. It carries why a seq
38
38
 
39
39
  A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook when they do.
40
40
 
41
- On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It captures what the session learned, then writes `.claude/tasks/session.md` with the state of play, the decisions taken under delegated authority, the mistakes worth not repeating, and the standing cautions. That capture is the only one this session runs, since the refill sweep reports it as owed rather than paying it. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
41
+ On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It captures what the session learned, then writes a session map per `.claude/standards/session.md`, or `${CLAUDE_SKILL_DIR}/../../standards/session.md` when the project does not have it, carrying the state of play, the mistakes worth not repeating, and the standing cautions, with the decisions taken under delegated authority added as this role's extension. That capture is the only one this session runs, since the refill sweep reports it as owed rather than paying it. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
42
42
 
43
43
  On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md`, which reads that file back with the board and the groundwork behind the live work.
44
44
 
@@ -1,58 +1,30 @@
1
1
  ---
2
2
  title: Orchestrator handoff runbook
3
- description: Memory capture at the close of a session, what to write to .claude/tasks/session.md before a compaction, what to leave to the board, and the resume invocation the file carries out
3
+ description: The two sections an orchestrating session adds over the shared session map, how capture runs from a session that never commits, and the resume invocation the file carries out
4
4
  ---
5
5
 
6
- Capture what the session learned, then write the pre-compact handoff as orchestrator. Do both before a compaction, because a compaction keeps conclusions and drops the reasoning that produced them, and no other file in the repository carries that reasoning.
6
+ Write the pre-compaction handoff as orchestrator. Follow `.claude/standards/session.md`, or `${CLAUDE_SKILL_DIR}/../../standards/session.md` when the project does not have it, for the filename, the three core sections, the write procedure, and the citation rule. Everything below is the extension this role adds over that core, and none of it belongs to a session holding no delegated authority.
7
7
 
8
- 1. Invoke `aitk:claude-memory-capture` and tell it this session does not commit. Capture reads the session and this handoff summarizes it, so capturing first lets the handoff cite what was written instead of restating the same lesson in prose.
9
- 2. Run `aitk claude skills drift <commit this session started from>` and re-read any body it names before writing anything below. Nothing on the machine records that commit, so recover it from how long the session has been running with `git log -1 --format=%H --before='<duration> ago'`, rounding the duration up rather than down. A ref older than the oldest load over-reports and confirming a name costs one read of the body, so the generous end is the safe one and a guess at the exact commit is not worth making. A skill body enters a session once and re-invoking the skill replays the held copy rather than the file, so the drift is worst at exactly this moment and a name here is a body this session has been following out of date. Record what it named under `## Standing cautions`. The verb answers where the working directory carries `claude/skills/` with history behind it, which is the toolkit repository itself, and refuses by naming the absent tree anywhere else. A project consuming the plugin from a marketplace cache is that second case, so read the refusal as the boundary rather than as a fault.
10
- 3. Resolve the main worktree root with `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`. Write `.claude/tasks/session.md` under it.
11
- 4. Write only what a compaction destroys and no other file already carries. The board holds the ordering and what each task waits on, a task file holds its own findings, and a groundwork folder holds its track.
12
- 5. Use this shape, resolving `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md` and `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` to absolute paths as you write it and pasting each in place of `<RESUME_RUNBOOK>` and `<POLL_RUNBOOK>`:
8
+ 1. Run the capture step the core opens with, and tell `aitk:claude-memory-capture` that this session does not commit.
9
+ 2. Add `## Decisions taken under delegated authority` directly after `## State`, holding each decision and why it went that way, so nobody re-proposes it. It sits there rather than after the core because a decision is read against the state it was taken in.
10
+ 3. Close the file with the block below, resolving `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md` and `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` to absolute paths as you write it and pasting each in place of `<RESUME_RUNBOOK>` and `<POLL_RUNBOOK>`:
13
11
 
14
12
  ```markdown
15
- ---
16
- title: Session map
17
- description: <what the board cannot show, and the date it was written>
18
- ---
19
-
20
- # Session map
21
-
22
- <one line marking the file throwaway and naming the board as the real source>
23
-
24
- ## State
25
-
26
- <what is clean, what is running, what is open, and any untracked file that needs committing>
27
-
28
- ## Decisions taken under delegated authority
29
-
30
- <each decision and why it went that way, so nobody re-proposes it>
31
-
32
- ## Mistakes worth not repeating
33
-
34
- <what went wrong and the rule it yields>
35
-
36
- ## Standing cautions
37
-
38
- <commands that lie, tools that measure the wrong tree, and anything unbacked>
39
-
40
13
  Resume by loading the orchestrator skill and asking it to resume after a compaction. This repository spells that `/aitk:claude-orchestrate` followed by the request. Following <RESUME_RUNBOOK> reaches the same place with no skill loaded at all.
41
14
 
42
- That resume reads the board and stops. It restarts nothing, so the review poll is a second thing owed here, and <POLL_RUNBOOK> holds the prompt and the condition. Do not reach for `session-resume`, which reads tracked work and knows nothing about this board or the workers on it.
15
+ That resume reads the board and stops. It restarts nothing, so the review poll is a second thing owed here, and <POLL_RUNBOOK> holds the prompt and the condition. Do not reach for `session-resume`, which reads tracked work and reports the newest map without restarting this loop.
43
16
  ```
44
17
 
45
- 6. Cite a commit, a task, or a file and line for every claim, so the next session can tell a read from a recall.
46
- 7. Overwrite the previous handoff rather than appending to it. A stale entry read as current is worse than no handoff.
47
-
48
- The substitution belongs in step 5 because step 7 ends the write. A reader who treats the list as finished there ships the literal placeholders, and the variable expands while this runbook renders rather than in the turn that reads the handoff back, so a path left unresolved reaches a session holding no skill as a string matching nothing. `orchestrator-poll.md` resolves its script at the same point and for the same reason.
18
+ The delegated-authority section is the orchestrator's alone because a worker holds no delegation to have exercised, and a section a session cannot fill teaches its reader to skip the file. The closing block is the orchestrator's for the same reason from the other direction: it restarts a review poll no other role runs.
49
19
 
50
- Add a section only for content that fits none of the four and would otherwise be lost. Do not restate the board, and do not summarize the work that shipped, because git already carries it. The closing block is the one exception, and the paragraph below states why.
20
+ The substitution belongs in step 3 because that step ends the write. A reader who treats the list as finished elsewhere ships the literal placeholders, and the variable expands while this runbook renders rather than in the turn that reads the handoff back, so a path left unresolved reaches a session holding no skill as a string matching nothing. `orchestrator-poll.md` resolves its script at the same point and for the same reason.
51
21
 
52
- That block sits in this runbook and again in the file this runbook writes, which is the fix rather than a copy for a later pass to collapse. A session has to already be holding this runbook to read it, and a compaction that took the skill body took the routing to it too, so the session that most needs the resume is the one that can no longer find it. `session.md` survives that, so it carries the invocation itself. Each of the two reaches a reader the other cannot.
22
+ That block sits in this runbook and again in the file this runbook writes, which is the fix rather than a copy for a later pass to collapse. A session has to already be holding this runbook to read it, and a compaction that took the skill body took the routing to it too, so the session that most needs the resume is the one that can no longer find it. The map survives that, so it carries the invocation itself. Each of the two reaches a reader the other cannot.
53
23
 
54
24
  The requirement is a resume request to the orchestrator skill with that skill loaded first. The command the block carries is this repository's spelling rather than the only one, since the skill ships to every target holding the plugin and each runs whatever client it runs. The poll restart is named beside it because the resume performs none.
55
25
 
56
26
  Step 1 exists because both other callers of capture are ship-chain skills and this session never ships. Without a call here, the session that receives every operator correction is the one session that records none. A compaction arriving with no warning takes the capture with it, and firing it once per batch of merges leaves the same window open across a long planning stretch, since a sweep runs only on a merge. The refill sweep reports the debt between handoffs so the operator knows one is owed.
57
27
 
58
28
  Capture is told this session does not commit, so it skips routing and writes memory files alone. A routed fact lands in a context entry, which is a tracked file, and the orchestrator's boundaries forbid writing one from this session. That split is correct rather than a limitation, since a domain fact belongs to the task that owns the surface and goes in that task's Findings, while what this session produces is feedback about how to work, which is the class the memory folder keeps.
29
+
30
+ The core's drift step answers where the working directory carries `claude/skills/` with history behind it, which is the toolkit repository itself, and refuses by naming the absent tree anywhere else. A project consuming the plugin from a marketplace cache is that second case, so read the refusal as the boundary rather than as a fault.
@@ -6,7 +6,7 @@ description: What to read back after a compaction, and how to treat a groundwork
6
6
  Resume as orchestrator after a compaction. Read the board and the groundwork behind the live work before answering anything, because a compaction summary keeps conclusions and drops the reasoning that produced them.
7
7
 
8
8
  1. Resolve the main worktree root with `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`. Every path below resolves against it.
9
- 2. Read `.claude/tasks/priority.md` for the ordering and what each task waits on. Read `.claude/tasks/session.md` when it exists, and treat it as this session's scratch rather than a source.
9
+ 2. Read `.claude/tasks/priority.md` for the ordering and what each task waits on. Read the newest `.claude/tasks/session-*.md` when one exists, per `.claude/standards/session.md`, and treat it as the previous session's scratch rather than a source.
10
10
  3. Read the task file for every entry under `## Run now`, plus any entry a live pull request names.
11
11
  4. Read the groundwork folder each of those tasks cites. Read `README.md` for the file map, then `06-decision.md` when the track is closed, or `07-next-session.md` and the numbered topic files when it is open. A task's Findings section is a lossy summary of its track, so planning against the summary re-derives what the folder already settled.
12
12
  5. Run `gh pr list --state open` and `git log --oneline -5` to see what shipped since the folder was written.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-resume
3
- description: Why resuming reads an index before task files, resolves scratch at the main root, and never mutates what it reports
3
+ description: Why resuming reads the handoff before the board, reads an index before task files, resolves scratch at the main root, and never mutates what it reports
4
4
  ---
5
5
 
6
6
  # Session resume requirement
@@ -9,14 +9,20 @@ description: Why resuming reads an index before task files, resolves scratch at
9
9
 
10
10
  Without this skill, a session picking up old work reconstructs state from the git log, which records what shipped and not what is queued, so finished work reads as pending and the actual next item goes unmentioned. The alternative failure is worse for a different reason. A session that reads every task file and every plan to be thorough spends the context the work needs before the work starts, and the summary arrives in a session with no room left to act on it.
11
11
 
12
+ A compaction keeps conclusions and drops the reasoning that produced them, and the previous session can write that reasoning to a handoff before it goes. A resume that reads the board, the plans, and the memory index alone walks past that file, so the one artifact written to survive the compaction reaches nobody and the next session re-derives what it holds.
13
+
14
+ The task index is a folder catalog rather than a task filter, so it carries a row for the ordering file and one for every handoff on the board. A session reporting each row as a backlog item queues work nobody filed, and the count grows with every session that ever wrote a handoff.
15
+
12
16
  From a linked worktree the shared scratch folders resolve against the worktree rather than the main root, where they are empty. That reports no tracked work on a repository carrying a full backlog, and the report is indistinguishable from the true empty case.
13
17
 
14
18
  A resume is a read, and a session that treats it as a cleanup pass offers to archive finished entries or refresh a memory it decided was stale. Both change tracked state on the strength of a summary the user has not confirmed yet.
15
19
 
16
20
  ## Must
17
21
 
18
- - Resolve the plans, memory, and tasks folders at the main worktree root
22
+ - Read the newest handoff before the board, and report what it carries attributed to its writer
23
+ - Resolve the handoff, plans, memory, and tasks folders at the main worktree root
19
24
  - Read the task index before any individual task file, and open only the task files the summary needs
25
+ - Drop the board siblings from the index before reporting it as the backlog, since the catalog filters nothing and carries a row per handoff
20
26
  - Preserve the index's order in the report, since the order is the priority
21
27
  - Surface only the memory entries that inform the top item
22
28
  - Close with one recommendation naming the first item and whether a plan backs it
@@ -24,15 +30,20 @@ A resume is a read, and a session that treats it as a cleanup pass offers to arc
24
30
  ## Must not
25
31
 
26
32
  - Read the whole tasks folder to build a summary
33
+ - Report the absence of a handoff, which is the common case and would train a reader to skip the line on the run where one exists
34
+ - Restate a handoff's counts, sizes, or costs as current. Each was true when written.
27
35
  - Offer to remove, archive, or reorder an entry. Resume reports and does not mutate.
28
36
  - Update memory, which changes when a recorded fact becomes wrong rather than on a resume
29
37
 
30
38
  ## Guards
31
39
 
32
- - All three surfaces absent or empty reports no tracked work and stops, rather than inventing a next step from the repository
40
+ - All four surfaces absent or empty reports no tracked work and stops, rather than inventing a next step from the repository
41
+ - A handoff found beside an empty board recommends what the handoff leaves open, since the guard passes on the handoff alone and the recommendation has no backlog item to name
33
42
 
34
43
  ## Out of scope
35
44
 
45
+ - Writing a handoff, which happens at the close of a session rather than at its start. This skill names the standard that governs one and follows it no further.
46
+ - The sections a role adds over the core handoff, which belong to that role's own surface
36
47
  - Archiving a shipped task out of the folder: `claude-tasks`
37
48
  - Archiving a plan and marking an outcome, which `claude-docs` does when the work ships
38
49
  - Implementing the item it recommends, which is the next request rather than part of this one
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-resume
3
- description: Resumes a previous session by reading tracked work and relevant context. Use when starting a new session, or when asked to "pick up where we left off", "what was I working on", or "resume".
3
+ description: Resumes a previous session by reading the handoff it left behind, tracked work, and relevant context. Use when starting a new session, or when asked to "pick up where we left off", "what was I working on", or "resume".
4
4
  ---
5
5
 
6
6
  # Session resume
@@ -11,19 +11,24 @@ Resolve `.claude/plans/`, `.claude/memory/`, and `.claude/tasks/` at the main wo
11
11
 
12
12
  Read these in parallel, skipping any that do not exist:
13
13
 
14
- - `.claude/tasks/index.md`: the backlog, one line per task. Read this before any individual task file.
14
+ - the newest `.claude/tasks/session-*.md`: the handoff a previous session wrote before a compaction, per `.claude/standards/session.md`, or `${CLAUDE_SKILL_DIR}/../../standards/session.md` when the project does not have it. It leads the report rather than the reads.
15
+ - `.claude/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the backlog from it by dropping the `index`, `priority`, and `session-` rows, which are siblings rather than tasks.
15
16
  - `.claude/plans/*.md`: execution detail for in-progress tasks
16
17
  - `.claude/memory/index.md` and any memory files relevant to the top backlog item
17
18
 
18
19
  Then read only the task files the summary needs, typically the top one. Do not read the whole folder.
19
20
 
20
- If all three surfaces are absent or empty, stop: `✅ No tracked work found. Start a new task.`
21
+ Most projects carry no handoff. Say nothing about its absence, since a line reporting it every run trains a reader to skip the line on the run where a handoff exists.
22
+
23
+ If all four surfaces are absent or empty, stop: `✅ No tracked work found. Start a new task.`
21
24
 
22
25
  ## Step 2: summarize
23
26
 
24
- Output three sections:
27
+ Output these sections, omitting the first when no handoff was found:
28
+
29
+ **Carried over:** the reasoning the handoff holds, as its writer stated it. Attribute it to the map rather than restating it as fact, and re-measure any count, size, or cost before acting on one.
25
30
 
26
- **Up next:** one line per entry in `.claude/tasks/index.md`, preserving order.
31
+ **Up next:** one line per task row in `.claude/tasks/index.md`, preserving order. A board carrying handoffs has a row per session that wrote one, so a report listing every row queues work nobody filed.
27
32
 
28
33
  **Active plans:** one line per file in `.claude/plans/`, linking each to its task file in `.claude/tasks/`. Say "None" if empty.
29
34
 
@@ -33,6 +38,14 @@ Output three sections:
33
38
 
34
39
  End with one line: `Start with: <first Up next item>` and note whether it has a linked plan.
35
40
 
41
+ When the board is empty and a handoff was found, name what the handoff leaves open instead: `Start with: <the open thread the handoff names>`. A recommendation slot filled with nothing reads as a failed run, and a handoff on an empty board is the shape a session leaves when it was reasoning rather than shipping.
42
+
36
43
  Do not offer to remove entries. A completed task is archived out of `.claude/tasks/` when work ships. The git log is the authoritative record of shipped work. Plan files are archived per the plan lifecycle rule in `CLAUDE.md`.
37
44
 
38
45
  Memory is updated only when a recorded fact becomes wrong, never on resume. A domain fact reaches a session through `.claude/context/`, which `claude-memory-capture` routes to and the three-tier model loads on demand, so the memory folder read here is the residue no context entry owns.
46
+
47
+ ## Writing the next one
48
+
49
+ This skill reads a handoff and never writes one. Reading and writing are two jobs, and the write happens at the close of a session rather than at its start.
50
+
51
+ Name the standard when the session asks how to leave a handoff behind, and let the session follow it directly. Any session may write one, whatever role it holds, so nothing here routes the request to another skill. A role carrying sections of its own adds them over the core per that role's own runbook.
@@ -32,7 +32,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
32
32
  | `aitk tasks validate` | Report board rows whose plan, task file, group, or file set does not hold (`--json`) |
33
33
  | `aitk intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`) |
34
34
  | `aitk intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`) |
35
- | `aitk records validate` | Report a session record against the standard governing it, per kind (`--json`) |
35
+ | `aitk records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
36
36
  | `aitk records push` | Commit the eight backed record folders and push them to a private records remote (`--json`) |
37
37
  | `aitk records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`) |
38
38
  | `aitk sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
@@ -20,7 +20,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
20
20
  - [Markdown audit](markdown-audit.md): Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why the ban half gates while the structural half reports
21
21
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
22
22
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
23
- - [Records](records.md): Validating the gitignored session records under .claude/, the per-kind checks, the refusal reasons, backing the folders to a private remote, and why the root defaults to the main worktree
23
+ - [Records](records.md): Validating the session records under .claude/ and the standards corpus, the per-kind checks, the refusal reasons, backing the folders to a private remote, and which root each kind defaults to
24
24
  - [Sandbox](sandbox.md): Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
25
25
  - [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
26
26
  - [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, the liveness confidence field, the unresolved reasons, and what the read depends on
@@ -1,28 +1,31 @@
1
1
  ---
2
2
  title: Records
3
- description: Validating the gitignored session records under .claude/, the per-kind checks, the refusal reasons, backing the folders to a private remote, and why the root defaults to the main worktree
3
+ description: Validating the session records under .claude/ and the standards corpus, the per-kind checks, the refusal reasons, backing the folders to a private remote, and which root each kind defaults to
4
4
  ---
5
5
 
6
6
  # Records
7
7
 
8
8
  ## Validate
9
9
 
10
- `aitk records validate <kind>` reports where a session record and the standard governing it disagree. The four kinds are `plans`, `groundwork`, `intake`, and `memory`, each a gitignored folder under `.claude/`.
10
+ `aitk records validate <kind>` reports where a file and the standard governing it disagree. Four kinds are gitignored folders under `.claude/`: `plans`, `groundwork`, `intake`, and `memory`. The fifth is `standards`, the authoring corpus, which is tracked and installed rather than scratch.
11
11
 
12
12
  ```bash
13
13
  aitk records validate plans
14
14
  aitk records validate memory
15
+ aitk records validate standards
15
16
  aitk records validate intake --json
16
17
  ```
17
18
 
18
- | Option | Behavior |
19
- | --------------- | --------------------------------------------- |
20
- | `--json` | Add a machine-readable record on stdout |
21
- | `--root <path>` | Project root, defaulting to the main worktree |
19
+ | Option | Behavior |
20
+ | --------------- | ------------------------------------------------------------------- |
21
+ | `--json` | Add a machine-readable record on stdout |
22
+ | `--root <path>` | Project root, defaulting to the main worktree except on `standards` |
22
23
 
23
- It reads and never writes. Each folder is per-machine scratch with no history behind it, so a repair that guessed wrong could not be undone, and the report names the record for a session to fix.
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.
24
25
 
25
- Nothing fires it automatically. The folders are gitignored, so the standards-audit hook exits early on them and any check reading changed files from git never lists one. 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.
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, so `standards/bundled/` is out of range: its members are named for the skill that reads them rather than for a path they govern.
27
+
28
+ Nothing fires it automatically. The four 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.
26
29
 
27
30
  ### What each kind checks
28
31
 
@@ -32,6 +35,7 @@ Nothing fires it automatically. The folders are gitignored, so the standards-aud
32
35
  | `groundwork` | A track with no `README.md` or no `01-current-state.md`, a file missing `title` or `description`, a `README.md` with no `date` as `YYYY-MM-DD`, an unnumbered file, and a track holding a decision without its handoff or the reverse |
33
36
  | `intake` | A dump with no `00-overview.md`, the same frontmatter and numbering checks, an item missing any of `Problem`, `Fix`, `Worth it`, or `You`, and an item carrying `Open` with no `Suggested` |
34
37
  | `memory` | A filename whose prefix names none of the four types, an entry missing `title`, `description`, or `category`, a `category` disagreeing with that prefix, a title repeating the filename, and a rule-bearing body missing a part |
38
+ | `standards` | A standard missing `title` or `description`, an absent `## Scope` section, a scope section carrying no `Does not govern:` list, a statement anchoring nothing, and a filename naming no part of the path the statement governs |
35
39
 
36
40
  The half-closed track is the groundwork check a reader cannot run by eye. A folder holding `06` without `07` reads as closed to anyone scanning filenames while the file a returning session actually opens is absent.
37
41
 
@@ -39,15 +43,23 @@ The item check skips `00-overview.md` and `99-next-session.md`, since neither ho
39
43
 
40
44
  A memory `category` is compared against the sentence-case form of the filename prefix rather than checked field by field, so one finding covers a prefix outside the four types, a field disagreeing with the prefix, and a casing drift that would open a second group in the catalog. The body check runs on `feedback` and `project` entries alone, because a `user` or `reference` entry is a single sentence by design and has no rule to apply.
41
45
 
46
+ The standards filename check derives a word from the governed path rather than counting words in the name. Every member of the corpus is named for the artifact its scope statement governs, and one word is what that produces rather than the rule itself, so a check keyed on word count would pass a conforming single word naming the wrong artifact. Each path segment offers its own word, a dotted container segment offers none, a hyphenated segment offers its parts, and the singular and the plural both match.
47
+
48
+ The scope statement is read exactly as `scripts/standards/list.sh` reads it for the catalog's `appliesTo` field: backticked spans in the first sentence alone, with an attribute standard resolving where that sentence backticks nothing and the statement says it governs an attribute. One sentence read two ways would let a standard pass the check while publishing a different jurisdiction to every consumer of the catalog. An attribute standard is exempt from the filename derivation because it governs no path to derive from, and a statement that anchors nothing and claims no attribute is reported as `scope-unanchored` rather than passed, since silence there would let the backticks be removed to disable the check.
49
+
50
+ An absent `## Scope` section suppresses the filename finding. The name derives from the statement, so a missing section leaves nothing to derive against, and reporting both would name one defect twice and point the fix at the wrong file. The walk skips `index.md`, which is generated from its siblings rather than authored.
51
+
42
52
  A plan section opens as a bold label or as an H2 and the check counts both, naming the standard's spelling when it reports one missing. The corpus splits roughly four to one between the two forms, so failing the variant would report nearly every plan on a rule that costs a reader nothing.
43
53
 
44
54
  A section runs to the next marker-shaped line whatever it names, so a plan carrying a label of its own closes the section above it rather than collecting into it. Fenced blocks are dropped before any of this, since a plan showing the shape it writes puts real-looking bullets and headings inside a fence.
45
55
 
46
56
  ### Exit codes and refusals
47
57
 
48
- Exit codes: `0` every check passed, `1` refused, `2` at least one record carries a finding. A `reason` field carries which gate fired: `no-folder` when the kind's directory does not exist, and `unknown-kind` when the argument names no published kind.
58
+ Exit codes: `0` every check passed, `1` refused, `2` at least one record carries a finding. A `reason` field carries which gate fired: `no-folder` when none of the kind's directories exist, and `unknown-kind` when the argument names no published kind. A `no-folder` message names every candidate, so the `standards` refusal names both roots.
59
+
60
+ The four record folders are 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 validates the same records every other session reads.
49
61
 
50
- The folders are 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 validates the same records every other session reads.
62
+ `standards` takes the other default, the root of the checkout the caller stands in. The corpus is tracked, so a linked worktree holds its own edited copy, and resolving the main root there would report on a tree the session never touched while saying nothing about which one it read. A session that adds or renames a standard inside a worktree is the case, and it is the one the check exists for.
51
63
 
52
64
  Skills branch on the findings rather than on the exit code:
53
65
 
@@ -55,7 +67,7 @@ Skills branch on the findings rather than on the exit code:
55
67
  aitk records validate plans --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
56
68
  ```
57
69
 
58
- For the shapes each check enforces, see `.claude/standards/plan.md`, `.claude/standards/groundwork.md`, `.claude/standards/intake.md`, and `.claude/standards/memory.md`.
70
+ 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`.
59
71
 
60
72
  ## Push and pull
61
73
 
@@ -103,7 +103,7 @@ The collision check is the one a person cannot run by eye. Paths come from the b
103
103
 
104
104
  Exit codes: `0` every check passed, `1` refused, `2` at least one finding. The `reason` field carries which gate refused: `no-board`, `no-ordering`, or `no-groups`. A board grouping under headings of its own trips `no-groups` rather than being read against columns it never declared.
105
105
 
106
- Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index`, `priority`, and `session` siblings are skipped, since none of the three is a task.
106
+ Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index` and `priority` siblings are skipped, along with every pre-compaction handoff, which takes one file per session under a `session-` prefix. None of them is a task, and a handoff counted as one would be reported as a task carrying no row on every session that wrote one.
107
107
 
108
108
  Skills branch on the findings rather than on the exit code:
109
109
 
@@ -164,27 +164,120 @@ Before the first feature session on a UI-heavy project, pick a design tier. The
164
164
 
165
165
  ## Skills
166
166
 
167
- | Skill | When to use |
168
- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- |
169
- | `aitk:claude-intake` | File a brain dump into an inventory under `.claude/intake/`, one item per finding with a verdict |
170
- | `aitk:claude-groundwork` | Before a plan is warranted, measure an unknown in a track folder under `.claude/groundwork/` |
171
- | `aitk:claude-feature` | Before implementation, scan for conflicts and ambiguities |
172
- | `aitk:decision-escalate` | Batch every open decision that turns on your preference into one set of questions, each with a recommended default |
173
- | `aitk:claude-roadmap` | Sequence MVP scope into ordered versions in `.claude/ROADMAP.md` |
174
- | `aitk:claude-orchestrate` | Assert the orchestrator role, refill the ready queue, and dispatch the feature, review, and worktree skills |
175
- | `aitk:claude-diagram` | Draft per-kind mermaid entries under `.claude/diagrams/` from architecture and code, then verify each rendered image |
176
- | `aitk:claude-design-extract` | Draft `.claude/DESIGN.md`, sourcing tokens from existing UI code or proposing them from requirements on day one |
177
- | `aitk:claude-review` | In a fresh session, review all changes since main |
178
- | `aitk:claude-pr-review` | Review an open PR from an independent session, then re-review the commits added since until nothing is open |
179
- | `aitk:claude-address-review` | Address PR findings and CI status, refresh stale docs, rebase a branch that stopped merging, then push a follow-up |
180
- | `aitk:claude-tasks` | Add a task to `.claude/tasks/` or archive a shipped one out of the folder |
181
- | `aitk:claude-docs` | When decisions diverged from plan, update `.claude/` docs |
182
- | `aitk:claude-ui-test` | After UI changes, generate and run e2e tests + visual checklist |
183
- | `aitk:claude-ux-audit` | Audit existing UI surfaces for missing states, edge cases, inconsistencies |
184
- | `aitk:claude-ux-measure` | Measure paint, processor, and layout cost against a running interface, on the harness the project already carries |
185
- | `aitk:claude-autoship` | After plan approval, chain implement → verify → review → draft PR |
186
- | `aitk:systematic-debugging` | When a test fails or bug surfaces, enforce root-cause investigation before fixes |
187
- | `aitk:git-ship` | Post-feature: sync docs, commit, rename branch, open PR |
167
+ Groups run in the order a project meets them, so a reader at a known point scans to that group and reads across. The set reconciles the scenarios above with the lifecycle [target projects](target-projects.md) describes, rather than inventing a third vocabulary beside those two, so a group name matches neither source exactly and every moment either one names has a group. Each row says when to reach for the skill. What it does is the skill's own description.
168
+
169
+ This section is the corpus the coverage claim is measured against: every name `aitk claude skills list --names` reports takes exactly one row here. A skill serving two moments sits at the earlier one, and mentions elsewhere in this file are prose rather than routing.
170
+
171
+ ### Set up a project
172
+
173
+ | Skill | When to use |
174
+ | ---------------------------- | --------------------------------------------------------------------------------------- |
175
+ | `aitk:setup-init` | On a fresh scaffold, to detect the stack and run the whole install chain in one pass |
176
+ | `aitk:toolkit-operator` | On a project that already exists, to read what it carries before an install is picked |
177
+ | `aitk:setup-gov` | When the governance rules are wanted without the tooling chain |
178
+ | `aitk:setup-indexes` | When a markdown-heavy folder needs an `index.md` a session can browse |
179
+ | `aitk:setup-plugins` | On a new machine, to install the community and official plugins user-scoped |
180
+ | `aitk:setup-verify` | After the agent generates configs, to run the installed scripts and report pass or fail |
181
+ | `aitk:claude-design-extract` | Before the first UI feature, to draft `.claude/DESIGN.md` |
182
+ | `aitk:claude-diagram` | Once the architecture is written, to draft per-kind entries under `.claude/diagrams/` |
183
+
184
+ ### Decide what to build
185
+
186
+ | Skill | When to use |
187
+ | --------------------------- | ------------------------------------------------------------------------------- |
188
+ | `aitk:claude-intake` | When the input is a pile of findings rather than one feature |
189
+ | `aitk:claude-intake-answer` | When an intake folder holds unread slots waiting on your decision |
190
+ | `aitk:claude-groundwork` | When the state is unmeasured and more than one approach is live |
191
+ | `aitk:decision-escalate` | When open decisions turn on your preference and want batching into one set |
192
+ | `aitk:claude-roadmap` | When MVP scope needs sequencing into ordered versions |
193
+ | `aitk:claude-tasks` | When a decided item needs a file on the board, or a shipped one needs archiving |
194
+ | `aitk:claude-feature` | When the approach is settled and the next step is a plan |
195
+
196
+ ### Build the feature
197
+
198
+ | Skill | When to use |
199
+ | --------------------------- | ------------------------------------------------------------------- |
200
+ | `aitk:claude-worktree` | At the plan-to-execute boundary, to get an isolated tree and branch |
201
+ | `aitk:claude-autoship` | After plan approval, to chain implement, verify, review, draft PR |
202
+ | `aitk:project-commands` | When the project's own command needs running |
203
+ | `aitk:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
204
+ | `aitk:claude-ui-test` | After a UI change, to generate e2e tests and a visual checklist |
205
+
206
+ ### Check the work before it leaves the branch
207
+
208
+ | Skill | When to use |
209
+ | ----------------------------- | --------------------------------------------------------------------- |
210
+ | `aitk:claude-review` | On the local branch diff, before anything is pushed |
211
+ | `aitk:claude-standards-audit` | When changed markdown has to answer to the authoring standards |
212
+ | `aitk:claude-ux-audit` | To read UI source for missing states, edge cases, and inconsistencies |
213
+ | `aitk:claude-ux-measure` | To start the interface and measure paint, processor, and layout cost |
214
+
215
+ ### Ship it
216
+
217
+ | Skill | When to use |
218
+ | ---------------------------- | ---------------------------------------------------------------------------------- |
219
+ | `aitk:git-ship` | To run the whole post-feature chain from docs sync through open PR |
220
+ | `aitk:claude-memory-capture` | First in that chain, to route what the session learned to the surface owning it |
221
+ | `aitk:claude-docs` | When decisions diverged from the plan, or a shipped task needs its outcomes marked |
222
+ | `aitk:docs-sync` | When a change since main left `README.md` or `docs/` stale |
223
+ | `aitk:git-stage` | When the staged set spans several concerns and wants one commit each |
224
+ | `aitk:git-commit` | When the staged set is one concern, or was staged hunk by hand |
225
+ | `aitk:git-branch` | When a branch name needs generating or renaming to conventional form |
226
+ | `aitk:git-pr` | When a pull request needs a title and body written from the diff |
227
+ | `aitk:claude-memory-review` | After capture writes an entry, to propose where each one belongs |
228
+
229
+ ### After the pull request opens
230
+
231
+ | Skill | When to use |
232
+ | ---------------------------- | -------------------------------------------------------------------- |
233
+ | `aitk:claude-pr-review` | From an independent session, to post findings on the PR itself |
234
+ | `aitk:claude-address-review` | On the worker's side, to fix posted findings and push a follow-up |
235
+ | `aitk:git-followup` | For a small self-review edit on a branch whose PR is already open |
236
+ | `aitk:git-split` | When a branch turns out to carry unrelated commits |
237
+ | `aitk:git-issue` | When something surfaced that belongs on the tracker rather than here |
238
+ | `aitk:git-worktree` | After a PR merges, to list worktrees and reclaim the slot |
239
+
240
+ ### Run several tracks at once
241
+
242
+ | Skill | When to use |
243
+ | ------------------------- | ------------------------------------------------------------------------------ |
244
+ | `aitk:claude-orchestrate` | To assert the control session that owns the queue and reviews each worker's PR |
245
+ | `aitk:session-resume` | At the start of a session, to pick up what a previous one left |
246
+
247
+ ### Keep the project current with the toolkit
248
+
249
+ | Skill | When to use |
250
+ | --------------------------- | ---------------------------------------------------------------------------------- |
251
+ | `aitk:claude-seed-sync` | After a toolkit update, to reconcile installed seeds without losing customizations |
252
+ | `aitk:migration-claude-md` | When `CLAUDE.md` grew past what always-load context should carry |
253
+ | `aitk:migration-context` | When `docs/` holds agent-flavored files belonging in `.claude/context/` |
254
+ | `aitk:migration-standards` | When standards sit at the root while rules point into `.claude/` |
255
+ | `aitk:migration-superseded` | When a drift report names a `.claude/` file a folder has replaced |
256
+ | `aitk:toolkit-feedback` | When something in the toolkit is broken, missing, or off |
257
+ | `aitk:toolkit-triage` | In the toolkit repo, to work through the open feedback issues |
258
+
259
+ ### Generate an artifact on demand
260
+
261
+ | Skill | When to use |
262
+ | -------------------------- | --------------------------------------------------------------------- |
263
+ | `aitk:create-rule` | For a project-specific governance rule the toolkit does not ship |
264
+ | `aitk:create-skill` | For a new `SKILL.md` |
265
+ | `aitk:create-snippet` | For a reusable prompt |
266
+ | `aitk:create-standard` | For a new authoring convention |
267
+ | `aitk:bash-script` | For an interactive, human-facing shell tool |
268
+ | `aitk:cli-script` | For a non-interactive automation, CI, or pipeline script |
269
+ | `aitk:ci-workflow` | For a GitHub Actions workflow file |
270
+ | `aitk:claude-slides-draft` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
271
+ | `aitk:claude-screencast` | For a recording script with beats and defaults already seeded |
272
+
273
+ ### Answer a question at any point
274
+
275
+ | Skill | When to use |
276
+ | -------------------------- | ------------------------------------------------------------------------------ |
277
+ | `aitk:toolkit-cli` | Before a sync or install, to learn what it overwrites, merges, or leaves alone |
278
+ | `aitk:youtube-transcripts` | When a video transcript is wanted in the repo as context |
279
+
280
+ Both rows answer a question rather than mark a point in a project's life, so a phase above would send a reader to the wrong group.
188
281
 
189
282
  ## Feedback routing
190
283
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.94.0",
4
+ "version": "0.96.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -9,7 +9,9 @@ import {
9
9
  import {
10
10
  type Finding,
11
11
  isRecordKind,
12
+ isSharedScratch,
12
13
  RECORD_KINDS,
14
+ type RecordKind,
13
15
  type ValidateOutcome,
14
16
  validateRecords,
15
17
  } from '@/records/validate'
@@ -22,7 +24,7 @@ import {
22
24
  outro,
23
25
  pipeOutput,
24
26
  } from '@/ui'
25
- import { mainWorktreeRoot } from '@/worktree'
27
+ import { currentWorktreeRoot, mainWorktreeRoot } from '@/worktree'
26
28
 
27
29
  /** Returned when a record carries a finding, which is the gating result. */
28
30
  const EXIT_FINDINGS = 2
@@ -38,17 +40,20 @@ export function register(program: Command): void {
38
40
  const records = program
39
41
  .command('records')
40
42
  .description(
41
- 'Check and back up the gitignored session records under .claude/',
43
+ 'Check a governed corpus against its standard, and back up the session records under .claude/',
42
44
  )
43
45
  .helpOption('-h, --help', 'Show this help message')
44
46
 
45
47
  records
46
48
  .command('validate')
47
- .description('Report where a record and the standard governing it disagree')
49
+ .description('Report where a file and the standard governing it disagree')
48
50
  .argument('<kind>', `Record folder: ${RECORD_KINDS.join(', ')}`)
49
51
  .helpOption('-h, --help', 'Show this help message')
50
52
  .option('--json', 'Add a machine-readable record on stdout')
51
- .option('--root <path>', 'Project root, defaulting to the main worktree')
53
+ .option(
54
+ '--root <path>',
55
+ 'Project root, defaulting to the main worktree except on standards',
56
+ )
52
57
  .addHelpText(
53
58
  'after',
54
59
  [
@@ -58,18 +63,22 @@ export function register(program: Command): void {
58
63
  ' groundwork README and current-state files, numbering, dating, and a half-closed track',
59
64
  ' intake overview file, numbering, dating, and the four bullets every item carries',
60
65
  ' memory filename and type prefix, frontmatter, and the body shape each type carries',
66
+ ' standards frontmatter, the scope section and its handoff list, and a filename',
67
+ ' derived from the governed path',
61
68
  '',
62
69
  'Exit codes:',
63
70
  ' 0 every check passed',
64
71
  ' 1 refused, with the reason on stderr or in the JSON record',
65
72
  ' 2 at least one record carries a finding',
66
73
  '',
67
- 'It reports and never writes. Each folder is per-machine scratch with no',
68
- 'history behind it, so a session fixes the record the report names.',
74
+ 'It reports and never writes, so a session fixes what the report names. A',
75
+ 'session record has no history to undo a wrong repair from, and a standard',
76
+ 'is installed and cited everywhere, so a rename is larger than a file move.',
69
77
  '',
70
78
  'Examples:',
71
79
  ' aitk records validate plans',
72
80
  ' aitk records validate memory',
81
+ ' aitk records validate standards',
73
82
  ' aitk records validate intake --json',
74
83
  '',
75
84
  ].join('\n'),
@@ -218,6 +227,15 @@ function reportRefusal(
218
227
  return 1
219
228
  }
220
229
 
230
+ /**
231
+ * A shared-scratch kind reads the main worktree root, so a linked worktree
232
+ * validates the records every other session reads. A tracked corpus reads the
233
+ * checkout the caller stands in, which is the copy that session has edited.
234
+ */
235
+ function defaultRoot(kind: RecordKind): Promise<string> {
236
+ return isSharedScratch(kind) ? mainWorktreeRoot() : currentWorktreeRoot()
237
+ }
238
+
221
239
  async function runValidate(
222
240
  kind: string,
223
241
  opts: ValidateCommandOptions,
@@ -236,7 +254,7 @@ async function runValidate(
236
254
  )
237
255
  }
238
256
 
239
- const root = opts.root ?? (await mainWorktreeRoot())
257
+ const root = opts.root ?? (await defaultRoot(kind))
240
258
 
241
259
  return report(await validateRecords(root, kind), emitJson, root)
242
260
  }
@@ -10,8 +10,9 @@ import { gitEnv } from '@/git-env'
10
10
  * enclosing repository already. The list is spelled out rather than read off
11
11
  * that group so adding an ignore entry cannot silently enlarge the payload.
12
12
  *
13
- * `RECORD_KINDS` in `validate.ts` names four of these. The two lists differ on
14
- * purpose: one is what a standard governs, this is what a disk loss would take.
13
+ * `RECORD_KINDS` in `validate.ts` overlaps this on four names and carries one
14
+ * more that no backup reaches. The two lists differ on purpose: one is what a
15
+ * standard governs, this is what a disk loss would take.
15
16
  */
16
17
  export const BACKED_FOLDERS = [
17
18
  'groundwork',
@@ -4,15 +4,31 @@ import { join } from 'node:path'
4
4
  import { parseFrontmatter, readField } from '@/indexes/frontmatter'
5
5
  import { linesOutsideFences } from '@/markdown/scan'
6
6
 
7
- export const RECORD_KINDS = ['plans', 'groundwork', 'intake', 'memory'] as const
7
+ export const RECORD_KINDS = [
8
+ 'plans',
9
+ 'groundwork',
10
+ 'intake',
11
+ 'memory',
12
+ 'standards',
13
+ ] as const
8
14
 
9
15
  export type RecordKind = (typeof RECORD_KINDS)[number]
10
16
 
11
- const FOLDER_BY_KIND: Readonly<Record<RecordKind, string>> = {
12
- plans: join('.claude', 'plans'),
13
- groundwork: join('.claude', 'groundwork'),
14
- intake: join('.claude', 'intake'),
15
- memory: join('.claude', 'memory'),
17
+ /**
18
+ * The folders each kind reads, in precedence order.
19
+ *
20
+ * Standards carry two because the corpus authors at the project root and
21
+ * installs under `.claude/`. The authoring root wins where both exist, since the
22
+ * installed tree is a generated copy here and a finding fixed there is
23
+ * overwritten by the next regen. A project that consumed the corpus holds only
24
+ * the second, so one order serves both.
25
+ */
26
+ const FOLDERS_BY_KIND: Readonly<Record<RecordKind, readonly string[]>> = {
27
+ plans: [join('.claude', 'plans')],
28
+ groundwork: [join('.claude', 'groundwork')],
29
+ intake: [join('.claude', 'intake')],
30
+ memory: [join('.claude', 'memory')],
31
+ standards: ['standards', join('.claude', 'standards')],
16
32
  }
17
33
 
18
34
  /**
@@ -30,6 +46,7 @@ export const FINDING_KINDS = [
30
46
  'title-missing',
31
47
  'title-is-slug',
32
48
  'section-missing',
49
+ 'scope-unanchored',
33
50
  'entry-unreasoned',
34
51
  'suggestion-missing',
35
52
  'question-unanswerable',
@@ -67,14 +84,37 @@ export interface ValidateRefused {
67
84
 
68
85
  export type ValidateOutcome = ValidateReport | ValidateRefused
69
86
 
87
+ /** Every folder a kind would accept, whether or not it is on disk. */
88
+ export function recordDirs(root: string, kind: RecordKind): string[] {
89
+ return FOLDERS_BY_KIND[kind].map((folder) => join(root, folder))
90
+ }
91
+
92
+ /**
93
+ * The folder a kind reads. The first candidate on disk wins, and the first
94
+ * candidate stands in when none exists, so a refusal and a test fixture both
95
+ * name the location the kind prefers.
96
+ */
70
97
  export function recordsDir(root: string, kind: RecordKind): string {
71
- return join(root, FOLDER_BY_KIND[kind])
98
+ const dirs = recordDirs(root, kind)
99
+ return dirs.find((dir) => existsSync(dir)) ?? dirs[0]
72
100
  }
73
101
 
74
102
  export function isRecordKind(value: string): value is RecordKind {
75
103
  return (RECORD_KINDS as readonly string[]).includes(value)
76
104
  }
77
105
 
106
+ /**
107
+ * Whether a kind's folder is shared session scratch at the main worktree root.
108
+ *
109
+ * The four record folders are, so every session validates the records every
110
+ * other session reads. The corpus is tracked instead, so a linked worktree holds
111
+ * its own edited copy, and defaulting that kind to the main root would report on
112
+ * a tree the session never touched and say nothing about which one it read.
113
+ */
114
+ export function isSharedScratch(kind: RecordKind): boolean {
115
+ return kind !== 'standards'
116
+ }
117
+
78
118
  const NONE_IDENTIFIED = 'None identified.'
79
119
  const NUMBERED_FILE = /^\d{2}-[a-z0-9]+(-[a-z0-9]+)*\.md$/
80
120
 
@@ -673,6 +713,165 @@ function checkMemoryBody(
673
713
  return findings
674
714
  }
675
715
 
716
+ const STANDARD_INDEX = 'index.md'
717
+ const STANDARD_FIELDS = ['title', 'description'] as const
718
+
719
+ const SCOPE_HEADING = /^##[ \t]+Scope[ \t]*$/
720
+ const ANY_HEADING = /^#{1,6}[ \t]+\S/
721
+ const DOES_NOT_GOVERN = 'Does not govern:'
722
+ const ATTRIBUTE_MARKER = 'attribute standard'
723
+ const CODE_SPAN = /`([^`]+)`/g
724
+
725
+ interface Scope {
726
+ /** The first non-blank line under the heading, which is the statement. */
727
+ readonly statement: string
728
+ readonly lines: readonly string[]
729
+ }
730
+
731
+ export function readScope(text: string): Scope | undefined {
732
+ const lines = linesOutsideFences(text)
733
+ const opened = lines.findIndex((line) => SCOPE_HEADING.test(line.trim()))
734
+ if (opened === -1) return undefined
735
+
736
+ const body: string[] = []
737
+
738
+ for (const line of lines.slice(opened + 1)) {
739
+ if (ANY_HEADING.test(line.trim())) break
740
+ body.push(line)
741
+ }
742
+
743
+ const statement = body.find((line) => line.trim().length > 0)
744
+
745
+ return { statement: statement?.trim() ?? '', lines: body }
746
+ }
747
+
748
+ /**
749
+ * The paths a scope statement declares, read the way `scripts/standards/list.sh`
750
+ * reads them for the catalog's `appliesTo` field: backticked spans in the first
751
+ * sentence alone. One sentence read two ways would let a standard pass here
752
+ * while publishing a different jurisdiction to every consumer of the catalog.
753
+ */
754
+ export function governedPaths(statement: string): string[] {
755
+ const [sentence] = statement.split('. ')
756
+ return [...sentence.matchAll(CODE_SPAN)].map((match) => match[1])
757
+ }
758
+
759
+ /**
760
+ * The words a governed path offers a filename. Each segment gives its own word
761
+ * and, where it carries a prefix or a placeholder, the parts either side of a
762
+ * hyphen, so `.claude/tasks/session-<slug>.md` offers `tasks` and `session`.
763
+ *
764
+ * A dotted segment gives nothing. It names the folder holding the artifact
765
+ * rather than the artifact, and a standard named for it would pass this check
766
+ * while naming the container every sibling shares.
767
+ */
768
+ export function pathWords(path: string): string[] {
769
+ const words: string[] = []
770
+
771
+ for (const segment of path.split('/')) {
772
+ if (segment.startsWith('.')) continue
773
+
774
+ const stem = segment.replace(/\.[a-z]+$/i, '').toLowerCase()
775
+ words.push(stem)
776
+ if (stem.includes('-')) words.push(...stem.split('-'))
777
+ }
778
+
779
+ return words.filter((word) => /^[a-z]+$/.test(word))
780
+ }
781
+
782
+ /**
783
+ * Accepts the singular and the plural of one word. A standard over a single
784
+ * document is named for the document and one over a folder of them is named for
785
+ * either, and picking a side would report a conforming half of the corpus.
786
+ */
787
+ function namesWord(stem: string, word: string): boolean {
788
+ return stem === word || `${stem}s` === word || stem === `${word}s`
789
+ }
790
+
791
+ function checkStandardName(name: string, statement: string): Finding[] {
792
+ const paths = governedPaths(statement)
793
+
794
+ // The marker is read only where the first sentence backticks nothing, which
795
+ // is the catalog's own rule. A statement naming a path publishes that path
796
+ // however the rest of the statement describes itself.
797
+ if (paths.length === 0) {
798
+ if (statement.includes(ATTRIBUTE_MARKER)) return []
799
+
800
+ return [
801
+ finding(
802
+ 'scope-unanchored',
803
+ name,
804
+ name,
805
+ 'backticks no path in its first scope sentence and does not call itself an attribute standard, so it names no artifact to be named for.',
806
+ ),
807
+ ]
808
+ }
809
+
810
+ const stem = name.replace(/\.md$/, '')
811
+ const words = paths.flatMap(pathWords)
812
+
813
+ if (words.some((word) => namesWord(stem, word))) return []
814
+
815
+ return [
816
+ finding(
817
+ 'name-malformed',
818
+ name,
819
+ name,
820
+ `names no part of ${paths.join(', ')}, which is what it governs. A rename reaches every target that installed the corpus and every surface citing it by bare filename.`,
821
+ ),
822
+ ]
823
+ }
824
+
825
+ export function checkStandard(name: string, text: string): Finding[] {
826
+ const findings: Finding[] = []
827
+ const frontmatter = parseFrontmatter(text)
828
+
829
+ const missing = STANDARD_FIELDS.filter(
830
+ (field) => !readField(frontmatter, field),
831
+ )
832
+
833
+ if (missing.length > 0) {
834
+ findings.push(
835
+ finding(
836
+ 'frontmatter-incomplete',
837
+ name,
838
+ name,
839
+ `carries no ${missing.join(' and no ')}.`,
840
+ ),
841
+ )
842
+ }
843
+
844
+ const scope = readScope(text)
845
+
846
+ // The name derives from the scope statement, so an absent section leaves
847
+ // nothing to derive against. Reporting the name as well would name one defect
848
+ // twice and point the fix at the wrong file.
849
+ if (!scope) {
850
+ return [
851
+ ...findings,
852
+ finding(
853
+ 'section-missing',
854
+ name,
855
+ '## Scope',
856
+ 'is absent, so the standard claims no jurisdiction and can refuse no rule.',
857
+ ),
858
+ ]
859
+ }
860
+
861
+ if (!scope.lines.some((line) => line.trim().startsWith(DOES_NOT_GOVERN))) {
862
+ findings.push(
863
+ finding(
864
+ 'section-missing',
865
+ name,
866
+ DOES_NOT_GOVERN,
867
+ 'is absent from the scope section, so no boundary names the owner it hands off to.',
868
+ ),
869
+ )
870
+ }
871
+
872
+ return [...findings, ...checkStandardName(name, scope.statement)]
873
+ }
874
+
676
875
  function refuse(reason: ValidateRefusal, message: string): ValidateRefused {
677
876
  return { ok: false, reason, message }
678
877
  }
@@ -696,9 +895,12 @@ async function validateFiles(
696
895
  }
697
896
 
698
897
  /**
699
- * Reports what every record in one gitignored folder claims against the shape
700
- * its standard fixes. It writes nothing: the folder is per-machine scratch with
701
- * no history behind it, so a repair that guessed wrong could not be undone.
898
+ * Reports what every record in one folder claims against the shape its standard
899
+ * fixes. It writes nothing whichever kind runs, and the reason differs by kind.
900
+ * A session record is per-machine scratch with no history behind it, so a repair
901
+ * that guessed wrong could not be undone. A standard installs into every target
902
+ * and is cited by bare filename, so a rename costs more than the file move it
903
+ * looks like.
702
904
  */
703
905
  export async function validateRecords(
704
906
  root: string,
@@ -707,7 +909,10 @@ export async function validateRecords(
707
909
  const dir = recordsDir(root, kind)
708
910
 
709
911
  if (!existsSync(dir)) {
710
- return refuse('no-folder', `No ${kind} folder at ${dir}.`)
912
+ return refuse(
913
+ 'no-folder',
914
+ `No ${kind} folder at ${recordDirs(root, kind).join(' or ')}.`,
915
+ )
711
916
  }
712
917
 
713
918
  if (kind === 'plans') return validateFiles(dir, kind, checkPlan)
@@ -721,6 +926,18 @@ export async function validateRecords(
721
926
  )
722
927
  }
723
928
 
929
+ // The walk stays flat, matching install and the catalog. `standards/bundled/`
930
+ // is a subfolder whose members are named for the skill that reads them rather
931
+ // than for a path they govern, so the derivation below reports every one.
932
+ if (kind === 'standards') {
933
+ return validateFiles(
934
+ dir,
935
+ kind,
936
+ checkStandard,
937
+ (file) => file === STANDARD_INDEX,
938
+ )
939
+ }
940
+
724
941
  const folders = await listFolders(dir)
725
942
  const check = kind === 'groundwork' ? checkTrack : checkDump
726
943
  const perFolder = await Promise.all(
@@ -220,15 +220,39 @@ function checkPaths(
220
220
  )
221
221
  }
222
222
 
223
+ /**
224
+ * An entry carrying `*` is matched as a glob, which is what lets an arm forbid
225
+ * a file whose name a run derives rather than fixes. Pinning one spelling of a
226
+ * derived name passes vacuously against every other spelling, which reads as
227
+ * coverage the arm does not have. Returns the offending path so a failure names
228
+ * the file the run wrote rather than the pattern that caught it.
229
+ */
230
+ function writtenUnder(pattern: string, sandboxDir: string): string | undefined {
231
+ if (!pattern.includes('*')) {
232
+ return existsSync(join(sandboxDir, pattern)) ? pattern : undefined
233
+ }
234
+
235
+ for (const match of new Bun.Glob(pattern).scanSync({
236
+ cwd: sandboxDir,
237
+ dot: true,
238
+ })) {
239
+ return match
240
+ }
241
+
242
+ return undefined
243
+ }
244
+
223
245
  function checkAbsent(
224
246
  expectation: Expectation,
225
247
  sandboxDir: string,
226
248
  ): AssertionResult[] {
227
- return expectation.absent.map((path) =>
228
- existsSync(join(sandboxDir, path))
229
- ? { ok: false, message: `should not exist: ${path}` }
230
- : { ok: true, message: `absent: ${path}` },
231
- )
249
+ return expectation.absent.map((path) => {
250
+ const written = writtenUnder(path, sandboxDir)
251
+
252
+ return written
253
+ ? { ok: false, message: `should not exist: ${written}` }
254
+ : { ok: true, message: `absent: ${path}` }
255
+ })
232
256
  }
233
257
 
234
258
  /**
@@ -9,12 +9,21 @@ const PLANS_DIR = join('.claude', 'plans')
9
9
 
10
10
  /**
11
11
  * Siblings that sit on the board without being tasks: the generated index, the
12
- * hand-maintained ordering, and the pre-compaction session map. `validate`
13
- * reads the same list, so neither verb can count a sibling as a task the other
14
- * does not.
12
+ * hand-maintained ordering, and a handoff a session wrote before the file took
13
+ * one name per session. `validate` reads the same list, so neither verb can
14
+ * count a sibling as a task the other does not.
15
15
  */
16
16
  export const RESERVED_STEMS = ['index', 'priority', 'session'] as const
17
17
 
18
+ /** The pre-compaction handoff takes one file per session, so its stems vary. */
19
+ const SESSION_MAP_PREFIX = 'session-'
20
+
21
+ export function isReservedStem(stem: string): boolean {
22
+ const reserved: readonly string[] = RESERVED_STEMS
23
+
24
+ return reserved.includes(stem) || stem.startsWith(SESSION_MAP_PREFIX)
25
+ }
26
+
18
27
  /**
19
28
  * `bad-input` describes the command line rather than the board, which is the
20
29
  * split `record.ts` draws for the same reason. A caller naming two selectors
@@ -190,12 +199,11 @@ function isLivePlan(target: string, dir: string, root: string): boolean {
190
199
 
191
200
  export async function listTaskStems(dir: string): Promise<string[]> {
192
201
  const entries = await readdir(dir)
193
- const reserved: readonly string[] = RESERVED_STEMS
194
202
 
195
203
  return entries
196
204
  .filter((entry) => entry.endsWith('.md'))
197
205
  .map((entry) => entry.slice(0, -'.md'.length))
198
- .filter((stem) => !reserved.includes(stem))
206
+ .filter((stem) => !isReservedStem(stem))
199
207
  .sort()
200
208
  }
201
209
 
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { readdir, readFile } from 'node:fs/promises'
3
3
  import { join, resolve } from 'node:path'
4
- import { RESERVED_STEMS, tasksDir } from '@/tasks/archive'
4
+ import { isReservedStem, tasksDir } from '@/tasks/archive'
5
5
 
6
6
  const ORDERING_FILE = 'priority.md'
7
7
 
@@ -204,12 +204,11 @@ export function readBoard(text: string): {
204
204
 
205
205
  async function listTaskStems(dir: string): Promise<string[]> {
206
206
  const entries = await readdir(dir)
207
- const reserved: readonly string[] = RESERVED_STEMS
208
207
 
209
208
  return entries
210
209
  .filter((entry) => entry.endsWith('.md'))
211
210
  .map((entry) => entry.slice(0, -'.md'.length))
212
- .filter((stem) => !reserved.includes(stem))
211
+ .filter((stem) => !isReservedStem(stem))
213
212
  .sort()
214
213
  }
215
214
 
package/src/worktree.ts CHANGED
@@ -1,5 +1,21 @@
1
1
  import { $ } from 'bun'
2
2
 
3
+ /**
4
+ * Resolves the root of the checkout the caller is standing in, which is the
5
+ * linked worktree rather than the main one when a session is inside one.
6
+ *
7
+ * A tracked tree differs per worktree, so a verb reading one answers about the
8
+ * files the session has edited only if it resolves the root this way. The
9
+ * working directory is not a substitute, since a caller invoking from a
10
+ * subdirectory would resolve a root holding none of the trees a verb reads.
11
+ */
12
+ export async function currentWorktreeRoot(): Promise<string> {
13
+ const result = await $`git rev-parse --show-toplevel`.quiet().nothrow()
14
+ if (result.exitCode !== 0) return process.cwd()
15
+
16
+ return result.stdout.toString().trim() || process.cwd()
17
+ }
18
+
3
19
  /**
4
20
  * Resolves the root every shared-scratch folder lives under. `git worktree
5
21
  * list` puts the main worktree first, and trusting the working directory
@@ -21,6 +21,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
21
21
  - [Readme reference](readme.md): Readme voice, structure, and content conventions
22
22
  - [Requirements reference](requirements.md): Shape and content rules for .claude/REQUIREMENTS.md
23
23
  - [Governance rule reference](rule.md): Rule frontmatter, body shape, and voice for .claude/rules files
24
+ - [Session map reference](session.md): Filename and location, the sections a handoff carries, the write and read procedures, and how a role extends it
24
25
  - [Claude skill reference](skill.md): Claude skill structure and authoring rules
25
26
  - [Slug reference](slug.md): Transform from a git branch name to a slug, and the three responses to an empty result
26
27
  - [Standard reference](standard.md): Shape and content rules for authoring a standard
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Session map reference
3
+ description: Filename and location, the sections a handoff carries, the write and read procedures, and how a role extends it
4
+ ---
5
+
6
+ # Session map reference
7
+
8
+ Applies to `.claude/tasks/session-<slug>.md`. A session writes one before a compaction, because a compaction keeps conclusions and drops the reasoning that produced them. It is rewritten whenever the session that owns it learns something the next session would otherwise re-derive, and it is optional: a project whose sessions never approach a compaction carries none.
9
+
10
+ Any session writes one. The role a session holds decides which sections it adds on top of the core, never whether it may write at all.
11
+
12
+ ## Scope
13
+
14
+ Governs the pre-compaction handoff at `.claude/tasks/session-<slug>.md`: its filename, its sections, what a writer puts in each, and how a reader picks one up.
15
+
16
+ Does not govern:
17
+
18
+ - The task board the file sits beside, its filenames, and its archiving: `tasks.md`
19
+ - The transform from a branch name to the slug in the filename: `slug.md`
20
+ - The role-specific sections a caller adds over the core, which belong to that caller
21
+ - Which memories a session captures before writing, which is the capture surface's own subject
22
+
23
+ ## What a working session map looks like
24
+
25
+ A session map works when the session reading it can act on the reasoning without asking the session that wrote it:
26
+
27
+ - What is clean, what is running, and what is open right now?
28
+ - Which mistake already made would this session repeat?
29
+ - Which command, tool, or held instruction lies about the tree?
30
+ - Where did each claim come from, so a reader can tell a read from a recall?
31
+
32
+ A session map failing these is non-conforming even when it satisfies every shape rule below.
33
+
34
+ ## Filename and location
35
+
36
+ - Write one file per session, named `session-<slug>.md`, where `<slug>` is the branch-derived slug
37
+ - Fall back to `session-latest.md` when the branch resolves empty, since a handoff is scratch rather than a commit and a stop would lose the reasoning it exists to save
38
+ - Resolve the containing folder at the main worktree root, never inside a linked worktree
39
+ - Overwrite the file the writing session already owns. A stale entry read as current is worse than no handoff.
40
+ - Never write into a file another session owns. One file per session is what keeps two sessions closing near each other from each writing the whole file, where the loser leaves no trace.
41
+
42
+ ## Frontmatter
43
+
44
+ - `title` (required): `Session map`, so every handoff reads the same in a generated catalog
45
+ - `description` (required): what the board cannot show, and the date the file was written
46
+
47
+ ## Sections
48
+
49
+ Three sections form the core, in this order, and every session fills all three.
50
+
51
+ - `## State`: what is clean, what is running, what is open, and any untracked file that needs committing
52
+ - `## Mistakes worth not repeating`: what went wrong and the rule it yields
53
+ - `## Standing cautions`: commands that lie, tools that measure the wrong tree, and anything unbacked
54
+
55
+ Add a section only for content that fits none of the three and would otherwise be lost.
56
+
57
+ ## Writing one
58
+
59
+ 1. Capture what the session learned first, so the map cites what was written instead of restating the same lesson in prose.
60
+ 2. Run `aitk claude skills drift <the commit this session started from>` and record what it names under `## Standing cautions`. A skill body enters a session once and re-invoking the skill replays the held copy rather than the file, so the drift is worst at exactly this moment and a name here is a body the session has been following out of date. This step belongs to session length rather than to any role, so every writer runs it, and a refusal names the boundary of what the verb can read rather than a fault.
61
+ 3. Recover that commit from how long the session has been running with `git log -1 --format=%H --before='<duration> ago'`, rounding the duration up rather than down. Nothing on the machine records it. A ref older than the oldest load over-reports, and confirming a name costs one read of the body, so the generous end is the safe one and a guess at the exact commit is not worth making.
62
+ 4. Write only what a compaction destroys and no other artifact already carries. The board holds the ordering and what each task waits on, a task file holds its own findings, and a measurement folder holds its track.
63
+ 5. Cite a commit, a task, or a file and line for every claim, so the next session can tell a read from a recall.
64
+ 6. Fill a section from reasoning the session actually holds. A session with no cross-feature picture that fills `## State` from what is already in git has written a summary of the tree, which the reader can produce faster than they can read it.
65
+
66
+ Do not restate the board, and do not summarize the work that shipped, since version control already carries it.
67
+
68
+ ## Reading one back
69
+
70
+ - Read the newest map in the folder, by modification time, and treat it as the previous session's scratch rather than as a source
71
+ - Say nothing when no map exists. Absence is the common case, and a line reporting it every run is the noise that makes the present case unnoticeable.
72
+ - Re-measure every count, size, and cost the map states, since each was true when written
73
+
74
+ ## Extending it
75
+
76
+ - Carry every core section whatever else a role adds, and state in the surface that adds a section why the core cannot carry it
77
+ - Let the adding surface fix where its own section sits, so a role can place one between core sections where that reads better. The core order holds among the core sections alone.
78
+ - Keep a section out of the core when a session holding no such role would leave it unfilled. An unfilled section teaches a reader to skip the file.
79
+ - Carry a resume instruction inside the map itself when the routing to that instruction is what a compaction takes. The map survives what the instruction was loaded from, so each reaches a reader the other cannot.
80
+
81
+ ## Template
82
+
83
+ ```markdown
84
+ ---
85
+ title: Session map
86
+ description: <what the board cannot show, and the date it was written>
87
+ ---
88
+
89
+ # Session map
90
+
91
+ <one line marking the file throwaway and naming the board as the real source>
92
+
93
+ ## State
94
+
95
+ <what is clean, what is running, what is open, and any untracked file that needs committing>
96
+
97
+ ## Mistakes worth not repeating
98
+
99
+ <what went wrong and the rule it yields>
100
+
101
+ ## Standing cautions
102
+
103
+ <commands that lie, tools that measure the wrong tree, and anything unbacked>
104
+ ```
105
+
106
+ A role-specific section is placed by the surface that adds it, so the skeleton above shows the core alone.
@@ -46,6 +46,14 @@ A standard failing these questions is non-conforming even when it satisfies ever
46
46
  - Separate a jurisdiction exclusion from a content exclusion. The first names a concern another standard owns and belongs in `## Scope`. The second names what does not belong inside the document and stays with the shape rules. Merging them puts a boundary claim where no sibling will read it.
47
47
  - Stay silent on a section the standard holds today but should not own. Claiming it makes the scope statement false the moment it moves, and the mismatch is the evidence that moves it.
48
48
 
49
+ ### Naming the file
50
+
51
+ - Name the file for the artifact the scope statement governs, taking the word from the deepest path segment that names the artifact rather than the folder holding it. `plan.md` over `.claude/plans/feature-<slug>.md` and `session.md` over `.claude/tasks/session-<slug>.md` both follow it.
52
+ - Take the singular or the plural of that word, whichever reads as the artifact. A standard over one document takes the singular, and one over a set may take either.
53
+ - Name an attribute standard for the attribute itself, since it governs no path to derive a word from.
54
+ - Write the derived word alone. A standard installs as a sibling and is cited by bare filename, so a qualifier makes an outlier that every later citation has to carry.
55
+ - Rename an outlier at the moment it is found, and state the reach in the change that does it. The name is installed in every target and cited in surfaces that never see the rename, so the cost only grows.
56
+
49
57
  ### Staying inside it
50
58
 
51
59
  - Name no path, filename, or folder outside the document type the standard governs. A standard reaches projects whose layout is their own, so a path borrowed from the authoring repository is wrong in a target and nothing reports it.
@@ -21,6 +21,7 @@ Does not govern:
21
21
  - Phase-label format and which surfaces a label may appear on: `versioning.md`
22
22
  - Sequencing across versions and why the order is what it is: `roadmap.md`
23
23
  - Architectural reasoning that outlives a task: `architecture.md`
24
+ - The pre-compaction handoff sitting in the folder, its filename and its sections: `session.md`
24
25
  - When a project opens a task at all, which is project policy rather than a shape rule
25
26
 
26
27
  ## Layout
@@ -29,17 +30,21 @@ Does not govern:
29
30
  .claude/tasks/
30
31
  ├── index.md ← generated, never hand-edited
31
32
  ├── priority.md ← hand-maintained execution order
32
- ├── session.md ← optional, what a compaction is about to destroy
33
+ ├── session-<slug>.md ← optional, what a compaction is about to destroy
33
34
  ├── v09.0-sync-paths.md
34
35
  └── v13.0-toolkit-drift.md
35
36
  ```
36
37
 
37
38
  One file per task is what keeps the board safe under parallel sessions. Two sessions working different tasks never write the same file, which matters because a gitignored board has no history to recover a clobbered write from.
38
39
 
39
- Three siblings sit in the folder without being tasks, and each earns its place by being governed somewhere. `index.md` and `priority.md` are governed here. `session.md` is the pre-compaction handoff, written by `orchestrator-handoff` and read by `orchestrator-resume`, and it is optional: a project running no orchestrator carries no such file. Anything reading the folder as a task list skips all three, so a name outside the set is a task whatever it holds.
40
+ Siblings sit in the folder without being tasks, and each earns its place by being governed somewhere. `index.md` and `priority.md` are governed here. Every `session-` file is a pre-compaction handoff governed by `session.md`, and each is optional: a project whose sessions never approach a compaction carries none. Anything filtering the folder to tasks skips all of them, so a name outside the set is a task whatever it holds.
41
+
42
+ The handoff takes one file per session for the reason a task does. A single shared path puts two sessions closing near each other on one file that neither can watch the other write, and the loser leaves no trace on a board with no history behind it.
40
43
 
41
44
  `index.md` is generated from sibling frontmatter. The folder is gitignored, so the whole-repo index walk skips it and a hook passing the changed path regenerates it instead. Never hand-edit it.
42
45
 
46
+ The catalog is the one reader that filters nothing, so it carries a row per sibling alongside the tasks. That is what a folder catalog is for, and the handoffs are what make it worth stating: a board accumulates one row per session that ever wrote one, with nothing pruning them. Anything reading the catalog as the backlog therefore does its own filtering, and a reader that takes every row as a task reports the handoffs as queued work.
47
+
43
48
  The `claude-tasks` skill creates and archives task files. `claude-docs` marks outcomes `[x]` in an existing file and sweeps the plans those tasks cite. Neither does the other's job.
44
49
 
45
50
  ## Ordering