@erclx/aitk 0.20.0 → 0.22.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.20.0",
4
+ "version": "0.22.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -17,6 +17,7 @@ Without this skill, review findings are worked in whatever order the author read
17
17
  - Push before replying, so the comment never runs ahead of the code it describes
18
18
  - Map every finding to what changed, or to a one-line reason when it is a question or a conscious accept
19
19
  - Post the terminal comment only when the findings are addressed and every check passes
20
+ - Scan the reply for banned characters and internal phase labels before posting, since the comment leaves for the remote unchecked
20
21
 
21
22
  ## Must not
22
23
 
@@ -73,9 +73,10 @@ bullet per finding, each opening with the bolded finding identifier.
73
73
  Close the body with `🤖 Addressed by Claude Code` on its own line so the reply
74
74
  reads as an independent machine pass, not a human sign-off.
75
75
 
76
- Before posting, run the banned-character scan in `.claude/standards/prose.md`
77
- against the reply. The hook skips `.claude/.tmp/`, so this scan is the only gate
78
- on the published reply. Post it to the PR:
76
+ Before posting, run the pre-publish scan in `.claude/standards/prose.md`
77
+ against the reply, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the
78
+ project does not have it. The hook skips `.claude/.tmp/`, so this scan is the
79
+ only gate on the published reply. Post it to the PR:
79
80
 
80
81
  ```bash
81
82
  gh pr comment <number> --body-file .claude/.tmp/address-review/reply-<number>.md
@@ -80,7 +80,7 @@ The code paths the explanation cites are what the `claude-docs` sweep watches fo
80
80
 
81
81
  Quote node labels containing spaces or special characters with double quotes (`A["Web shell"]`). Avoid parentheses inside labels, they break some renderers. Use `<br/>` for line breaks inside labels.
82
82
 
83
- Run the banned-character scan in `.claude/standards/prose.md` against the whole file, including inside mermaid `subgraph` labels and node text, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. A colon also works as a replacement inside a label. The pedagogical voice the diagram standard asks for is a yield on voice alone and buys no exemption from these bans.
83
+ Run the pre-publish scan in `.claude/standards/prose.md` against the whole file, including inside mermaid `subgraph` labels and node text, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. The file lands in the repository rather than on a remote, so the scan's destination scoping leaves the character checks and not the phase-label one. A colon also works as a replacement inside a label. The pedagogical voice the diagram standard asks for is a yield on voice alone and buys no exemption from these bans.
84
84
 
85
85
  Nothing enforces the ban inside the diagram. The standards-audit hook toggles on a fence and skips every line within it, so a label carrying an em dash passes silently while the same character in the explanation paragraph below is caught. Check the labels by reading them. This is the one place in the file where the author is the only gate.
86
86
 
@@ -17,6 +17,7 @@ Without this skill, a pull request is reviewed only by the session that wrote it
17
17
  - Apply the integration, contract, and consumer lenses a self-review structurally cannot
18
18
  - Post a close-out even with nothing to report, since a first pass left unanswered reads as a review nobody closed
19
19
  - Key the body file on both the pull request number and the head commit, so no two passes overwrite each other
20
+ - Scan the comment for banned characters and internal phase labels before posting, since a finding phrased against a phase label reaches a reader with no task board
20
21
 
21
22
  ## Must not
22
23
 
@@ -131,7 +131,7 @@ The `What is right` section is optional, capped at three bullets, and included o
131
131
 
132
132
  Close the body with `🤖 Reviewed by Claude Code` on its own line so the review reads as an independent machine pass, not a human sign-off.
133
133
 
134
- Before posting, run the banned-character scan in `.claude/standards/prose.md` against the body. The hook skips `.claude/.tmp/`, so this scan is the only gate on the published comment.
134
+ Before posting, run the pre-publish scan in `.claude/standards/prose.md` against the body, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. The hook skips `.claude/.tmp/`, so this scan is the only gate on the published comment. A finding phrased against an internal phase label is what the label half of the scan catches here.
135
135
 
136
136
  ```bash
137
137
  gh pr review <number> --comment --body-file .claude/.tmp/pr-review/body-<number>-<short-sha>.md
@@ -14,7 +14,7 @@ Without this skill, an edit made after a pull request is already open ships as a
14
14
  - Refuse unless the branch has an upstream and an open pull request, since every later step addresses one of the two
15
15
  - Delegate the message to `git-commit` rather than composing one, so a follow-up commit reads like every other commit
16
16
  - Sync the body when the new commit moves the scope, and the title when the shift makes it inaccurate
17
- - Reply on the pull request when it carries review comments, and scan that reply for the banned characters before posting. The hook watches files and never sees a comment body on its way to the remote.
17
+ - Reply on the pull request when it carries review comments, and scan that reply for the banned characters and internal phase labels before posting. The hook watches files and never sees a comment body on its way to the remote.
18
18
  - Suppress the reply when the caller owns it, and still run the push and the body sync
19
19
 
20
20
  ## Must not
@@ -24,7 +24,7 @@ its own reply, so skip the comment in step 6. The push and body sync still run.
24
24
  3. Invoke `aitk:git-commit` to generate one conventional commit from the staged diff
25
25
  4. Run `git push` to the tracking branch
26
26
  5. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'`, resolving `<number>` from `gh pr view --json number`.
27
- 6. When invoked with `reply-owned`, skip this step's comment: the caller posts the reply. Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh pr comment --body`, first running the banned-character scan in `.claude/standards/prose.md` against it, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it, since the hook does not see an inline comment body. If it is zero, run `gh pr view --json url,title,body` and update the body with `gh pr edit --body` when the new commit changes scope, and the title with `gh pr edit --title` when the scope shifted enough to make it inaccurate.
27
+ 6. When invoked with `reply-owned`, skip this step's comment: the caller posts the reply. Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh pr comment --body`, first running the pre-publish scan in `.claude/standards/prose.md` against it, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it, since the hook does not see an inline comment body. If it is zero, run `gh pr view --json url,title,body` and update the body with `gh pr edit --body` when the new commit changes scope, and the title with `gh pr edit --title` when the scope shifted enough to make it inaccurate.
28
28
 
29
29
  ## After completion
30
30
 
@@ -13,7 +13,7 @@ Without this skill, an issue takes the shape of whatever the session was saying.
13
13
 
14
14
  - Read the issue reference and the prose standard before formatting
15
15
  - Confirm a remote and an authenticated `gh` before composing, so the failure lands before a body is written rather than after
16
- - Scan the title and body for banned characters as an explicit step. Reading the standard does not catch them, because the text is generated after the read.
16
+ - Scan the title and body for banned characters and internal phase labels as an explicit step. Reading the standard does not catch them, because the text is generated after the read.
17
17
  - Map the type to exactly one label, so filing and filtering agree
18
18
  - Ask once when the type is ambiguous between a bug and a task, since the label follows from it and a wrong label buries the issue
19
19
  - Pass the body through a file and remove it afterward, so shell quoting cannot mangle it
@@ -40,7 +40,7 @@ After outputting the preview, execute the final command immediately. Claude Code
40
40
 
41
41
  ### Pre-publish scan
42
42
 
43
- Before running the final command, run the banned-character scan in `.claude/standards/prose.md` against the title and body. The title and body go straight to the remote with nothing checking them on the way, so this scan is the only gate. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/issue.md`.
43
+ Before running the final command, run the pre-publish scan in `.claude/standards/prose.md` against the title and body, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. The title and body go straight to the remote with nothing checking them on the way, so this scan is the only gate. It covers the phase-label check as well as the characters, since both go to a reader who has no task board. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/issue.md`.
44
44
 
45
45
  ### Final command
46
46
 
@@ -15,7 +15,7 @@ Without this skill, a pull request body is written from memory of the branch rat
15
15
  - Derive the title and body from the commits and the diff against main, with lockfiles excluded
16
16
  - Run each check before writing its line, then state the result the run reported
17
17
  - Leave a box unchecked only for a human-only case, naming which human and why on the same line
18
- - Scan the title and body for banned characters as an explicit step, on top of reading the prose standard
18
+ - Scan the title and body for banned characters and internal phase labels as an explicit step, on top of reading the prose standard
19
19
  - Detect an open pull request and edit it in place, so a follow-up push keeps the body in sync instead of failing
20
20
 
21
21
  ## Must not
@@ -69,7 +69,7 @@ Leave a box unchecked only for the human-only cases the reference defines, and n
69
69
 
70
70
  ### Pre-publish scan
71
71
 
72
- Before running the final command, run the banned-character scan in `.claude/standards/prose.md` against the PR title and body. The title and body go straight to the remote with nothing checking them on the way, so this scan is the only gate. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/pr.md`.
72
+ Before running the final command, run the pre-publish scan in `.claude/standards/prose.md` against the PR title and body, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. The title and body go straight to the remote with nothing checking them on the way, so this scan is the only gate. It covers the phase-label check as well as the characters, since both go to a reader who has no task board. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/references/pr.md`.
73
73
 
74
74
  ### Final command
75
75
 
@@ -17,7 +17,7 @@ Without this skill, a branch carrying unrelated commits ships as one pull reques
17
17
  - Account for every commit ahead of main in the preview
18
18
  - State the merge order and the reason behind it, since the order is not recoverable from reading the pull requests afterward
19
19
  - Carry the restack loop for stacked mode, where each branch is rebased onto main once the one below it merges
20
- - Scan every generated title and body for banned characters as an explicit step before opening a pull request
20
+ - Scan every generated title and body for banned characters and internal phase labels as an explicit step before opening a pull request
21
21
 
22
22
  ## Must not
23
23
 
@@ -69,7 +69,7 @@ If Mode is Independent, append this line to the preview:
69
69
 
70
70
  After outputting the preview, execute the final commands immediately. Claude Code's tool permission dialog is the confirmation gate. Do not wait for user input.
71
71
 
72
- Before running them, run the banned-character scan in `.claude/standards/prose.md` against every title and body, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. Each body stages under `.claude/.tmp/` and leaves through `gh`, so the hook sees neither and this scan is the only gate.
72
+ Before running them, run the pre-publish scan in `.claude/standards/prose.md` against every title and body, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. Each body stages under `.claude/.tmp/` and leaves through `gh`, so the hook sees neither and this scan is the only gate.
73
73
 
74
74
  ### Final commands
75
75
 
package/docs/agents.md CHANGED
@@ -80,6 +80,7 @@ Full help: `aitk <command> --help`.
80
80
  | `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
81
81
  | `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
82
82
  | `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
83
+ | `aitk context audit` | Report entry length, depth, cited-path resolution, and index drift in the context folders |
83
84
  | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
84
85
 
85
86
  ### Domain commands
@@ -98,6 +99,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
98
99
  | `slides` | `render`, `list` |
99
100
  | `tasks` | `archive` |
100
101
  | `comments` | `scan` |
102
+ | `context` | `audit` |
101
103
 
102
104
  Common patterns:
103
105
 
@@ -351,6 +353,43 @@ The degradation sweep reads its vocabulary from whichever rule publishes a `## D
351
353
 
352
354
  `090-code-comments` is the rule that publishes the list, and it ships on the `base` stack. A project that installs or syncs governance for the first time after that rule landed gets a sweep that previously reported skipped, so hits appear where the command used to stay quiet. Edit the backticked terms in the installed copy to change what that project sweeps for. The sweep matches comment text, so a comment naming a term as an example is a hit, and a hit is a prompt to read the line rather than a verdict on it.
353
355
 
356
+ ## Context audit
357
+
358
+ `aitk context audit [path]` reports the structural state of the folders following the index-plus-entry contract, meaning a generated `index.md` beside entries carrying frontmatter. It reads and reports. Fixing what it finds is separate work.
359
+
360
+ ```bash
361
+ aitk context audit
362
+ aitk context audit --json
363
+ aitk context audit --citations-only
364
+ aitk context audit --folder context,diagrams
365
+ ```
366
+
367
+ | Option | Behavior |
368
+ | ------------------ | ------------------------------------------------------------------------ |
369
+ | `--json` | Add a machine-readable record on stdout, keeping the frame |
370
+ | `--folder <list>` | Comma-separated folder names under `.claude/` (default: the three below) |
371
+ | `--citations-only` | Run the gating citation check alone, printing nothing when it passes |
372
+
373
+ Scope defaults to `context`, `diagrams`, and `wireframes`, and a folder the project does not carry is skipped rather than reported. A domain that outgrew one file and split into `<domain>/` is audited as its own folder, so a split entry measures at the same grain as a flat one.
374
+
375
+ Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code. Length, depth, table, and index findings print and return `0`, because each is a judgment threshold and failing a push on one would make the check something to route around.
376
+
377
+ ### What each check reports
378
+
379
+ Length and depth quote their checkpoints from `.claude/standards/context.md`: roughly 150 lines for an entry, roughly 40 for a run of lines no heading breaks. Depth measures the longest such run rather than everything under one `##`, skips fenced blocks so a markdown example does not read as three headings, and exempts a run whose lines are all list items at one indent. Runs count blank lines, which the standard leaves open, so a hand reader who drops them lands a line or two lower. The report states the convention on every run.
380
+
381
+ The table check reports a catalog that grows a row per shipped thing, not a table count. A fixed comparison table never reflows, so its size costs nothing. A table qualifies at six or more body rows whose first column mostly carries a path, command, or link, which is what separates a catalog from a comparison without reading the prose.
382
+
383
+ Index drift compares an index against its siblings in both directions. An entry the index does not link is invisible to a session choosing what to open, and a linked name resolving to nothing sends one to a path that opens nothing.
384
+
385
+ ### The citation gate
386
+
387
+ The citation check resolves every path into an audited folder that appears anywhere in the repository, and it is the half wired into `bun run check`. A stale reference has a silent failure mode: the session opens nothing and carries on.
388
+
389
+ Three exclusions keep it from firing on prose about paths. Fenced blocks are skipped in markdown, which covers a standard displaying a path as an example. Fixture and harness trees are skipped by location, covering sandbox scenarios that describe their own scratch tree, the eval harness naming its target project, and `*.test.ts`. A path into a folder the project does not carry is skipped, so a skill directing a reader to `.claude/wireframes/index.md` stays valid in a project that has wireframes and silent in one that does not.
390
+
391
+ What remains is a sentence naming a hypothetical entry to show the shape of a name, which no syntax separates from a real reference. Append `<!-- audit-ignore-citations -->` to that source line. The marker suppresses citation checking for its own line only.
392
+
354
393
  ## Runtime catalogs
355
394
 
356
395
  Use these to discover what's available instead of hardcoding names.
@@ -77,7 +77,7 @@ Run `aitk tooling list --json` and `aitk gov list --json` to see the current cat
77
77
  `governance`, `standards`, and `wiki` are skippable:
78
78
 
79
79
  - `--skip governance`: leave `.claude/rules/` empty. Standards still install, so `.claude/standards/prose.md` lands with nothing pointing at it and no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
80
- - `--skip standards`: leave standards out. The governance rules still reference `.claude/standards/`, so their authority lines resolve to nothing. Toolkit skills are unaffected, since each falls back to the copy in its own plugin root. That fallback now carries runtime behavior rather than reference prose alone, because the banned-character scan and the branch-slug transform are defined in the standards and cited by the skills that run them.
80
+ - `--skip standards`: leave standards out. The governance rules still reference `.claude/standards/`, so their authority lines resolve to nothing. Toolkit skills are unaffected, since each falls back to the copy in its own plugin root. That fallback now carries runtime behavior rather than reference prose alone, because the pre-publish scan and the branch-slug transform are defined in the standards and cited by the skills that run them.
81
81
  - `--skip wiki`: skip the `.claude/wiki/` scaffold. A target that already carries a root `wiki/` keeps it, since the verb reports that folder rather than migrating it.
82
82
 
83
83
  ## Add a domain later
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.20.0",
4
+ "version": "0.22.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -148,6 +148,15 @@ main() {
148
148
  run_check "bash $PROJECT_ROOT/scripts/core/check-plugin-boundary.sh" "Plugin ships toolkit-internal content."
149
149
  log_info "Plugin boundary clean"
150
150
 
151
+ # Only the citation half of the audit gates. Length, depth, table, and index
152
+ # findings are judgment thresholds, and failing a push on one would make the
153
+ # stage something to route around. `bun src/cli.ts` rather than `aitk`, since a
154
+ # globally installed binary resolves to the main checkout no matter which
155
+ # worktree is running.
156
+ log_step "Context citations"
157
+ run_check "cd $PROJECT_ROOT && bun src/cli.ts context audit --citations-only" "A cited context path does not resolve. Run bun src/cli.ts context audit."
158
+ log_info "Context citations resolve"
159
+
151
160
  log_step "Plugin manifests"
152
161
  if ! command -v claude >/dev/null 2>&1; then
153
162
  log_info "Skipped, claude is not installed"
@@ -21,16 +21,70 @@ resolve_sandbox_dir() {
21
21
  printf '%s/aitk/sandbox\n' "${XDG_STATE_HOME:-$HOME/.local/state}"
22
22
  }
23
23
 
24
- # Collapses repeated separators and strips every trailing one, leaving a bare
25
- # root as `/`. Every comparison below is a string test, so `//` and `$HOME//`
26
- # would otherwise read as paths no rule names.
24
+ # Collapses repeated separators, folds `.` and `..` segments, and strips every
25
+ # trailing separator, leaving a bare root as `/`. Every comparison below is a
26
+ # string test, so `//`, `$HOME//`, and `$HOME/../../usr` each read as a path no
27
+ # rule names until this runs, and an unresolved `..` defeats the allowlist and
28
+ # both directions of the repository test at once.
29
+ #
30
+ # The fold is lexical because the guard runs before provisioning creates the
31
+ # tree, which rules out `cd` with `pwd -P` and any resolution needing the path
32
+ # to exist. Nothing here follows a symlink, so a `..` below one resolves against
33
+ # the link's own path rather than its target. A `..` climbing past the root
34
+ # clamps to `/`, matching the kernel, which leaves the allowlist to refuse it
35
+ # under the rule that already covers every root path.
27
36
  normalize_sandbox_path() {
28
37
  local path="$1"
29
38
 
39
+ [ -n "$path" ] || return 0
40
+
30
41
  while [ "$path" != "${path//\/\//\/}" ]; do
31
42
  path="${path//\/\//\/}"
32
43
  done
33
44
 
45
+ local root=""
46
+ if [ "${path#/}" != "$path" ]; then
47
+ root="/"
48
+ path="${path#/}"
49
+ fi
50
+
51
+ # `resolved` carries each kept segment behind its own separator, so a pop is
52
+ # one suffix removal and popping an empty stack is the no-op that clamps at
53
+ # the root. `climbed` holds the leading `..` a relative path has no segment to
54
+ # pop against, which dropping would change the directory it names.
55
+ local resolved="" climbed="" segment
56
+ local remaining=4096
57
+
58
+ while [ -n "$path" ] && [ "$remaining" -gt 0 ]; do
59
+ remaining=$((remaining - 1))
60
+
61
+ segment="${path%%/*}"
62
+ if [ "$segment" = "$path" ]; then
63
+ path=""
64
+ else
65
+ path="${path#*/}"
66
+ fi
67
+
68
+ case "$segment" in
69
+ "" | .) ;;
70
+ ..)
71
+ if [ -n "$resolved" ]; then
72
+ resolved="${resolved%/*}"
73
+ elif [ -z "$root" ]; then
74
+ climbed="$climbed../"
75
+ fi
76
+ ;;
77
+ *) resolved="$resolved/$segment" ;;
78
+ esac
79
+ done
80
+
81
+ if [ -n "$root" ]; then
82
+ path="${resolved:-/}"
83
+ else
84
+ path="${climbed}${resolved#/}"
85
+ path="${path:-.}"
86
+ fi
87
+
34
88
  while [ "${#path}" -gt 1 ] && [ "${path%/}" != "$path" ]; do
35
89
  path="${path%/}"
36
90
  done
@@ -72,13 +126,28 @@ assert_sandbox_dir_safe() {
72
126
  return 1
73
127
  fi
74
128
 
129
+ # `normalize_sandbox_path` stops at 4096 segments and drops the remainder,
130
+ # which would hand the tests below an ancestor of the path provisioning
131
+ # removes. Refusing past `PATH_MAX` keeps that bound out of reach, since a
132
+ # segment costs at least two bytes and no syscall accepts the string anyway.
133
+ if [ "${#raw}" -gt 4096 ]; then
134
+ printf 'Refusing the sandbox path. It is %s characters, past the longest path any filesystem here accepts.\n' "${#raw}"
135
+ return 1
136
+ fi
137
+
75
138
  local dir home temp
76
139
  dir="$(normalize_sandbox_path "$raw")"
77
140
  home="$(normalize_sandbox_path "${HOME:-/root}")"
78
141
  temp="$(normalize_sandbox_path "${TMPDIR:-/tmp}")"
79
142
 
143
+ # Every message below names `$raw`, which is what the operator set. The tests
144
+ # compare `$dir`, so a path carrying `..` is refused for a location its own
145
+ # spelling does not show.
146
+ local resolution=""
147
+ [ "$dir" = "$raw" ] || resolution=" It resolves to $dir."
148
+
80
149
  if ! is_at_or_above "$home" "$dir" && ! is_at_or_above "$temp" "$dir"; then
81
- printf 'Refusing %s as the sandbox. Provisioning removes the tree first, so the path has to sit under %s or %s.\n' "$raw" "$home" "$temp"
150
+ printf 'Refusing %s as the sandbox. Provisioning removes the tree first, so the path has to sit under %s or %s.%s\n' "$raw" "$home" "$temp" "$resolution"
82
151
  return 1
83
152
  fi
84
153
 
@@ -93,13 +162,13 @@ assert_sandbox_dir_safe() {
93
162
  main_root="$(normalize_sandbox_path "${main_root:-$root}")"
94
163
 
95
164
  if is_at_or_above "$dir" "$main_root"; then
96
- printf 'Refusing %s as the sandbox. Provisioning removes the tree first, and that path contains %s.\n' "$raw" "$main_root"
165
+ printf 'Refusing %s as the sandbox. Provisioning removes the tree first, and that path contains %s.%s\n' "$raw" "$main_root" "$resolution"
97
166
  return 1
98
167
  fi
99
168
 
100
169
  case "$dir" in
101
170
  "$main_root"/*)
102
- printf 'Sandbox at %s sits inside %s, which puts the toolkit CLAUDE.md back on the session ancestor chain. Point AITK_SANDBOX_DIR outside the repository.\n' "$raw" "$main_root"
171
+ printf 'Sandbox at %s sits inside %s, which puts the toolkit CLAUDE.md back on the session ancestor chain.%s Point AITK_SANDBOX_DIR outside the repository.\n' "$raw" "$main_root" "$resolution"
103
172
  return 1
104
173
  ;;
105
174
  esac
package/src/cli.ts CHANGED
@@ -19,6 +19,7 @@ import { register as feedback } from '@/commands/feedback'
19
19
  import { register as transcripts } from '@/commands/transcripts'
20
20
  import { register as tasks } from '@/commands/tasks'
21
21
  import { register as comments } from '@/commands/comments'
22
+ import { register as context } from '@/commands/context'
22
23
 
23
24
  const GREY = '\x1b[0;90m'
24
25
  const WHITE = '\x1b[1;37m'
@@ -48,6 +49,7 @@ function showHelp(): void {
48
49
  `${GREY}│${NC} transcripts <url> ${GREY}# Fetch a YouTube transcript with metadata frontmatter${NC}`,
49
50
  `${GREY}│${NC} tasks [cmd] ${GREY}# Task board commands (archive)${NC}`,
50
51
  `${GREY}│${NC} comments [cmd] ${GREY}# Measure comment density and trend (scan)${NC}`,
52
+ `${GREY}│${NC} context [cmd] ${GREY}# Report context folder health (audit)${NC}`,
51
53
  `${GREY}│${NC}`,
52
54
  `${GREY}│${NC} ${WHITE}Sandbox:${NC}`,
53
55
  `${GREY}│${NC} aitk sandbox ${GREY}# Interactive scenario picker${NC}`,
@@ -79,6 +81,7 @@ function showHelp(): void {
79
81
  `${GREY}│${NC} aitk transcripts https://youtu.be/VIDEO_ID`,
80
82
  `${GREY}│${NC} aitk tasks archive --pull-request 673 --json`,
81
83
  `${GREY}│${NC} aitk comments scan src --json`,
84
+ `${GREY}│${NC} aitk context audit --json`,
82
85
  `${GREY}└${NC}`,
83
86
  ]
84
87
  console.log(lines.join('\n'))
@@ -115,5 +118,6 @@ feedback(program)
115
118
  transcripts(program)
116
119
  tasks(program)
117
120
  comments(program)
121
+ context(program)
118
122
 
119
123
  program.parse()