@erclx/aitk 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/claude-address-review/SKILL.md +5 -6
  3. package/claude/skills/claude-autoship/SKILL.md +2 -2
  4. package/claude/skills/claude-design-extract/SKILL.md +3 -5
  5. package/claude/skills/claude-diagram/SKILL.md +2 -2
  6. package/claude/skills/claude-docs/SKILL.md +11 -11
  7. package/claude/skills/claude-feature/SKILL.md +3 -3
  8. package/claude/skills/claude-groundwork/SKILL.md +2 -2
  9. package/claude/skills/claude-intake/SKILL.md +1 -1
  10. package/claude/skills/claude-intake-answer/SKILL.md +1 -1
  11. package/claude/skills/claude-memory-capture/SKILL.md +7 -9
  12. package/claude/skills/claude-memory-review/SKILL.md +6 -8
  13. package/claude/skills/claude-memory-review/references/receipt-format.md +1 -1
  14. package/claude/skills/claude-orchestrate/SKILL.md +3 -3
  15. package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +1 -1
  16. package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +1 -1
  17. package/claude/skills/claude-pr-review/SKILL.md +2 -2
  18. package/claude/skills/claude-review/SKILL.md +1 -1
  19. package/claude/skills/claude-seed-sync/SKILL.md +1 -1
  20. package/claude/skills/claude-standards-audit/SKILL.md +4 -4
  21. package/claude/skills/claude-tasks/SKILL.md +34 -9
  22. package/claude/skills/claude-teach/SKILL.md +2 -2
  23. package/claude/skills/claude-ui-test/SKILL.md +1 -1
  24. package/claude/skills/claude-ux-audit/SKILL.md +2 -4
  25. package/claude/skills/claude-ux-measure/SKILL.md +1 -1
  26. package/claude/skills/claude-worktree/SKILL.md +3 -3
  27. package/claude/skills/create-rule/SKILL.md +2 -2
  28. package/claude/skills/create-skill/SKILL.md +4 -6
  29. package/claude/skills/create-snippet/SKILL.md +1 -3
  30. package/claude/skills/create-standard/SKILL.md +2 -4
  31. package/claude/skills/decision-escalate/SKILL.md +1 -1
  32. package/claude/skills/docs-sync/SKILL.md +3 -5
  33. package/claude/skills/git-commit/SKILL.md +1 -3
  34. package/claude/skills/git-followup/SKILL.md +1 -1
  35. package/claude/skills/git-issue/SKILL.md +2 -4
  36. package/claude/skills/git-pr/SKILL.md +4 -6
  37. package/claude/skills/git-split/SKILL.md +1 -1
  38. package/claude/skills/git-stage/SKILL.md +1 -3
  39. package/claude/skills/migration-claude-md/SKILL.md +2 -2
  40. package/claude/skills/session-resume/SKILL.md +1 -1
  41. package/claude/skills/setup-gov/SKILL.md +1 -1
  42. package/claude/skills/setup-indexes/SKILL.md +1 -1
  43. package/claude/skills/write-human/SKILL.md +2 -2
  44. package/docs/agents/commands.md +2 -1
  45. package/docs/agents/index.md +1 -1
  46. package/docs/agents/markdown-audit.md +3 -1
  47. package/docs/agents/records.md +32 -4
  48. package/docs/ai-workflow.md +3 -1
  49. package/docs/target-projects.md +3 -3
  50. package/governance/rules/claude/501-markdown.md +1 -1
  51. package/governance/rules/claude/510-context.md +1 -1
  52. package/governance/rules/claude/520-wireframes.md +1 -1
  53. package/governance/rules/claude/530-requirements.md +1 -1
  54. package/governance/rules/claude/540-architecture.md +1 -1
  55. package/governance/rules/claude/550-design.md +1 -1
  56. package/governance/rules/claude/555-tasks.md +1 -1
  57. package/governance/rules/claude/556-groundwork.md +1 -1
  58. package/governance/rules/claude/557-intake.md +1 -1
  59. package/governance/rules/claude/558-plan.md +1 -1
  60. package/governance/rules/claude/559-memory.md +1 -1
  61. package/governance/rules/claude/560-diagrams.md +1 -1
  62. package/governance/rules/claude/561-teach.md +1 -1
  63. package/governance/rules/claude/562-session.md +1 -1
  64. package/governance/rules/claude/570-skill.md +1 -1
  65. package/governance/rules/claude/580-readme.md +1 -1
  66. package/governance/rules/claude/590-rule-authoring.md +1 -1
  67. package/governance/rules/claude/591-standard-authoring.md +1 -1
  68. package/package.json +1 -1
  69. package/src/cli.ts +2 -1
  70. package/src/commands/records.ts +145 -7
  71. package/src/markdown/structure.ts +39 -4
  72. package/src/records/size.ts +260 -0
  73. package/standards/tasks.md +3 -1
@@ -93,10 +93,12 @@ An execution that picks other than the suggestion rewrites the `- Suggested:` li
93
93
 
94
94
  `aitk records validate plans` reports where a plan and that standard disagree: a filename that is not `feature-<slug>.md`, a missing required section, a files-to-touch entry naming no file, and a question carrying a suggestion with no answer slot. The same verb takes `groundwork`, `intake`, `memory`, and `teach`, which are governed the same way and were unreachable for the same reason. Nothing fires it automatically, because all five folders are gitignored and every check the repository runs reads changed files from git. It reports and never writes, since the folders are per-machine scratch with no history to recover a wrong repair from.
95
95
 
96
- `aitk records push` carries these folders off the disk they live on, and `aitk records pull` brings them back. Eight of them are backed: `groundwork`, `intake`, `memory`, `plans`, `plans-archive`, `review`, `task-archive`, and `tasks`. The history lives in a second git directory at `.claude/.records.git` with `.claude/` as its work tree, so every path a task file cites stays where it is.
96
+ `aitk records push` carries these folders off the disk they live on, and `aitk records pull` brings them back. Nine of them are backed: `groundwork`, `intake`, `memory`, `plans`, `plans-archive`, `review`, `task-archive`, `tasks`, and `teach`. The history lives in a second git directory at `.claude/.records.git` with `.claude/` as its work tree, so every path a task file cites stays where it is.
97
97
 
98
98
  A person points it at a private repository once and both verbs refuse until they have, and `push` refuses when that origin is also a remote of the project, since the payload is the memory pen and the groundwork trails. `.husky/post-merge` runs the push after its archive loop, on every merge rather than only on one that closed a task. See [records](agents/records.md) for the refusal table.
99
99
 
100
+ `aitk records size` reports what each of these folders holds, heaviest first, along with `.claude/.tmp`. Each row carries the file count, the bytes, how many files were written in the last 7 and 30 days, and the dates of the least and most recently written one. Nothing fails on a number, because a record folder has no correct size. What the verb replaces is a reading somebody had to remember to take: the memory pen went from 44 entries to 236 between two counts made by hand a fortnight apart, and nothing reported the rate in between.
101
+
100
102
  A plan that ships is archived, never deleted. `aitk:claude-docs` moves it to `.claude/plans-archive/` and retargets the task file's `Plan:` line at the new location, so a completed task still leads to the reasoning behind it. Both folders are gitignored, which is why a deleted plan had no recovery path. A plan cited by more than one task stays put until the last of them closes, since moving it early would strand every other pointer.
101
103
 
102
104
  The sweep reads the whole board rather than the tasks the session touched. It is the one place the skill reaches past its own rule against editing a task file the session did not change, because a task that closed while an earlier run missed its archive is exactly what the sweep exists to clear. Reaching it is safe: the archive moves the plan and retargets the pointer in the same pass, so an untouched task ends up with a working link rather than a broken one.
@@ -88,11 +88,11 @@ Run `aitk tooling list --json` and `aitk gov list --json` to see the current cat
88
88
 
89
89
  `governance`, `standards`, and `wiki` are skippable:
90
90
 
91
- - `--skip governance`: leave `.claude/rules/` empty. Standards still install, so `.claude/standards/markdown.md` lands with nothing pointing at it and no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
92
- - `--skip standards`: leave standards out. The governance rules still reference `.claude/standards/`, so their authority lines resolve to nothing for a reader following the path. Toolkit skills are unaffected, since each falls back to the copy in its own plugin root. `aitk standards <name>` searches the corpus inside the CLI's own package behind both project roots, so it prints a standard in a project that skipped the install, and `aitk markdown audit` needs no standard at all, its ban sets and checkpoints shipping with the package as data. What the skip costs is the editable copy rather than the enforcement.
91
+ - `--skip governance`: leave `.claude/rules/` empty, so no coding standard loads on a file match. Standards still install, and the installed copy is the first root `aitk standards <name>` resolves, so it stays the editable seed a project owns. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
92
+ - `--skip standards`: leave standards out. The governance rules are unaffected, since each authority line names `aitk standards <name>` and that command answers from the corpus inside the CLI's own package. Toolkit skills are unaffected too, each naming the copy in its own plugin root. `aitk standards <name>` searches the corpus inside the CLI's own package behind both project roots, so it prints a standard in a project that skipped the install, and `aitk markdown audit` needs no standard at all, its ban sets and checkpoints shipping with the package as data. What the skip costs is the editable copy rather than the enforcement.
93
93
  - `--skip wiki`: skip the `.claude/wiki/` scaffold. A target that already carries a root `wiki/` keeps it, since the verb reports that folder rather than migrating it.
94
94
 
95
- That standards fallback carries runtime behavior rather than reference prose alone, because the pre-publish scan and the branch-slug transform each have a standard of their own, `publish.md` and `slug.md`, cited by the skills that run them.
95
+ The plugin corpus carries runtime behavior rather than reference prose alone, because the pre-publish scan and the branch-slug transform each have a standard of their own, `publish.md` and `slug.md`, cited by the skills that run them.
96
96
 
97
97
  ## Add a domain later
98
98
 
@@ -8,7 +8,7 @@ paths:
8
8
 
9
9
  ## Authority
10
10
 
11
- - Follow `.claude/standards/markdown.md` for headings, paragraph and list structure, code spans, punctuation, emphasis, file references, the banned words and spellings, and the wording of a `title` or `description`. It is the single source.
11
+ - Follow the markdown standard for headings, paragraph and list structure, code spans, punctuation, emphasis, file references, the banned words and spellings, and the wording of a `title` or `description`. It is the single source. Read it with `aitk standards markdown`.
12
12
  - Read it before a substantial markdown edit. Do not work the banned words or characters from memory.
13
13
  - Run `aitk markdown audit <path>` after the edit, and rewrite the sentence carrying a hit rather than swapping the token for a near-synonym.
14
14
  - Voice, rhythm, and sentence construction are a separate topic. `500-prose` routes them.
@@ -26,4 +26,4 @@ Do not put a sibling bullet's design in the past with one of these.
26
26
 
27
27
  ## Authority
28
28
 
29
- - Follow `.claude/standards/context.md` for entry shape: frontmatter, sections, length, and what goes in. It is the single source.
29
+ - Follow the context standard for entry shape: frontmatter, sections, length, and what goes in. It is the single source. Read it with `aitk standards context`.
@@ -16,5 +16,5 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/wireframes.md` for layout and interaction intent: ASCII layout, region labels, variants, copy, and what moves to `.claude/context/`. It is the single source.
19
+ - Follow the wireframes standard for layout and interaction intent: ASCII layout, region labels, variants, copy, and what moves to `.claude/context/`. It is the single source. Read it with `aitk standards wireframes`.
20
20
  - Read it before adding or revising a surface.
@@ -16,4 +16,4 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/requirements.md` for what belongs in the problem, goals, non-goals, MVP features, tech stack, and constraints. It is the single source.
19
+ - Follow the requirements standard for what belongs in the problem, goals, non-goals, MVP features, tech stack, and constraints. It is the single source. Read it with `aitk standards requirements`.
@@ -18,4 +18,4 @@ paths:
18
18
 
19
19
  ## Authority
20
20
 
21
- - Follow `.claude/standards/architecture.md` for the overview, named decision entries, and risks. It is the single source.
21
+ - Follow the architecture standard for the overview, named decision entries, and risks. It is the single source. Read it with `aitk standards architecture`.
@@ -16,4 +16,4 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/design.md` for visual intent, the token tables, and the render contract. It is the single source.
19
+ - Follow the design standard for visual intent, the token tables, and the render contract. It is the single source. Read it with `aitk standards design`.
@@ -16,5 +16,5 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/tasks.md` for filenames, frontmatter, what belongs, and the task file format. It is the single source.
19
+ - Follow the tasks standard for filenames, frontmatter, what belongs, and the task file format. It is the single source. Read it with `aitk standards tasks`.
20
20
  - Never hand-edit `.claude/tasks/index.md`. A hook regenerates it from sibling frontmatter.
@@ -13,4 +13,4 @@ paths:
13
13
 
14
14
  ## Authority
15
15
 
16
- - Follow `.claude/standards/groundwork.md` for reserved numbering, frontmatter and dating, what each required file holds, and the conventions a track keeps. It is the single source.
16
+ - Follow the groundwork standard for reserved numbering, frontmatter and dating, what each required file holds, and the conventions a track keeps. It is the single source. Read it with `aitk standards groundwork`.
@@ -13,4 +13,4 @@ paths:
13
13
 
14
14
  ## Authority
15
15
 
16
- - Follow `.claude/standards/intake.md` for the folder layout, frontmatter and dating, the item format, and retrieval. It is the single source.
16
+ - Follow the intake standard for the folder layout, frontmatter and dating, the item format, and retrieval. It is the single source. Read it with `aitk standards intake`.
@@ -20,4 +20,4 @@ paths:
20
20
 
21
21
  ## Authority
22
22
 
23
- - Follow `.claude/standards/plan.md` for the filename and slug, the required sections, the suggested-and-answer contract, and the lifecycle. It is the single source.
23
+ - Follow the plan standard for the filename and slug, the required sections, the suggested-and-answer contract, and the lifecycle. It is the single source. Read it with `aitk standards plan`.
@@ -17,4 +17,4 @@ paths:
17
17
 
18
18
  ## Authority
19
19
 
20
- - Follow `.claude/standards/memory.md` for the filename and type prefix, the frontmatter, the body shape per type, links between entries, and the lifecycle. It is the single source.
20
+ - Follow the memory standard for the filename and type prefix, the frontmatter, the body shape per type, links between entries, and the lifecycle. It is the single source. Read it with `aitk standards memory`.
@@ -9,7 +9,7 @@ paths:
9
9
 
10
10
  ## Authority
11
11
 
12
- - Follow `.claude/standards/diagrams.md` for Mermaid diagram layout, budgets, accessibility, verification, and explanation prose. It is the single source.
12
+ - Follow the diagrams standard for Mermaid diagram layout, budgets, accessibility, verification, and explanation prose. It is the single source. Read it with `aitk standards diagrams`.
13
13
  - A diagram entry carries structure and flow, not implementation. Read the standard before adding or revising a kind.
14
14
 
15
15
  ## Scope
@@ -16,6 +16,6 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/teach.md` for the folder layout, ordinal naming, frontmatter, and the mission and learning-record formats. It is the single source.
19
+ - Follow the teach standard for the folder layout, ordinal naming, frontmatter, and the mission and learning-record formats. It is the single source. Read it with `aitk standards teach`.
20
20
  - Read `references/glossary.md` inside the `claude-teach` skill for the glossary every workspace carries at its root. It is the single source for the entry shape, the ordering, and which terms the file carries. Read it rather than invoking the skill, which resumes a workspace and runs lessons.
21
21
  - Report it rather than proceeding silently when that file does not resolve. It ships with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have it.
@@ -16,4 +16,4 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/session.md` for the filename and location, frontmatter, the core sections, and the write and read procedures. It is the single source.
19
+ - Follow the session standard for the filename and location, frontmatter, the core sections, and the write and read procedures. It is the single source. Read it with `aitk standards session`.
@@ -20,4 +20,4 @@ paths:
20
20
 
21
21
  ## Authority
22
22
 
23
- - Follow `.claude/standards/skill.md` for skill structure, frontmatter fields, invocation rules, and the shape a `REQUIREMENT.md` states. It is the single source.
23
+ - Follow the skill standard for skill structure, frontmatter fields, invocation rules, and the shape a `REQUIREMENT.md` states. It is the single source. Read it with `aitk standards skill`.
@@ -16,4 +16,4 @@ paths:
16
16
 
17
17
  ## Authority
18
18
 
19
- - Follow `.claude/standards/readme.md` for voice, structure, required and optional sections, heading hierarchy, badge classes, per-project-type content, and what to link out rather than inline. It is the single source.
19
+ - Follow the readme standard for voice, structure, required and optional sections, heading hierarchy, badge classes, per-project-type content, and what to link out rather than inline. It is the single source. Read it with `aitk standards readme`.
@@ -19,5 +19,5 @@ paths:
19
19
 
20
20
  ## Authority
21
21
 
22
- - Follow `.claude/standards/rule.md` for rule frontmatter, body shape, and voice. It is the single source.
22
+ - Follow the rule-authoring standard for rule frontmatter, body shape, and voice. It is the single source. Read it with `aitk standards rule`.
23
23
  - Read it before writing or editing a rule. Do not work the shape from memory.
@@ -17,5 +17,5 @@ paths:
17
17
 
18
18
  ## Authority
19
19
 
20
- - Follow `.claude/standards/standard.md` for a standard's frontmatter, shape, scope, and success criterion. It is the single source.
20
+ - Follow the standard-authoring standard for a standard's frontmatter, shape, scope, and success criterion. It is the single source. Read it with `aitk standards standard`.
21
21
  - Read it before writing or editing a standard. Do not work the shape from memory.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "1.1.0",
4
+ "version": "1.3.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
package/src/cli.ts CHANGED
@@ -61,7 +61,7 @@ function showHelp(): void {
61
61
  `${GREY}│${NC} comments [cmd] ${GREY}# Measure comment density and trend (scan)${NC}`,
62
62
  `${GREY}│${NC} context [cmd] ${GREY}# Report context folder health (audit)${NC}`,
63
63
  `${GREY}│${NC} markdown [cmd] ${GREY}# Report markdown against the attribute standards (audit)${NC}`,
64
- `${GREY}│${NC} records [cmd] ${GREY}# Session records under .claude/ (validate, push, pull)${NC}`,
64
+ `${GREY}│${NC} records [cmd] ${GREY}# Session records under .claude/ (validate, size, push, pull)${NC}`,
65
65
  `${GREY}│${NC} sessions [cmd] ${GREY}# Resolve live sessions to worktree and branch (list)${NC}`,
66
66
  `${GREY}│${NC} audits [cmd] ${GREY}# Run every health check as one set (run, list)${NC}`,
67
67
  `${GREY}│${NC} upgrade ${GREY}# Reinstall the CLI globally with the manager that installed it${NC}`,
@@ -101,6 +101,7 @@ function showHelp(): void {
101
101
  `${GREY}│${NC} aitk context audit --json`,
102
102
  `${GREY}│${NC} aitk markdown audit .claude/rules --json`,
103
103
  `${GREY}│${NC} aitk records validate plans`,
104
+ `${GREY}│${NC} aitk records size --json`,
104
105
  `${GREY}│${NC} aitk records push --json`,
105
106
  `${GREY}│${NC} aitk sessions list --json`,
106
107
  `${GREY}│${NC} aitk audits run --json`,
@@ -1,11 +1,13 @@
1
1
  import type { Command } from 'commander'
2
+ import { BACKED_FOLDERS, pullRecords, pushRecords } from '@/records/backup'
2
3
  import {
3
- BACKED_FOLDERS,
4
- type PullOutcome,
5
- pullRecords,
6
- type PushOutcome,
7
- pushRecords,
8
- } from '@/records/backup'
4
+ type FolderSize,
5
+ formatBytes,
6
+ GROWTH_WINDOWS,
7
+ SIZED_FOLDERS,
8
+ type SizeOutcome,
9
+ sizeRecords,
10
+ } from '@/records/size'
9
11
  import {
10
12
  type Finding,
11
13
  isRecordKind,
@@ -23,6 +25,7 @@ import {
23
25
  logWarn,
24
26
  outro,
25
27
  pipeOutput,
28
+ plural,
26
29
  } from '@/ui'
27
30
  import { currentWorktreeRoot, mainWorktreeRoot } from '@/worktree'
28
31
 
@@ -87,6 +90,41 @@ export function register(program: Command): void {
87
90
  process.exitCode = await runValidate(kind, opts)
88
91
  })
89
92
 
93
+ records
94
+ .command('size')
95
+ .description('Report what each record folder holds and how much is recent')
96
+ .helpOption('-h, --help', 'Show this help message')
97
+ .option('--json', 'Add a machine-readable record on stdout')
98
+ .option('--root <path>', 'Project root, defaulting to the main worktree')
99
+ .addHelpText(
100
+ 'after',
101
+ [
102
+ '',
103
+ 'Folders read under .claude/:',
104
+ ` ${SIZED_FOLDERS.join(', ')}`,
105
+ '',
106
+ 'Exit codes:',
107
+ ' 0 the reading completed',
108
+ ' 1 refused, with the reason on stderr or in the JSON record',
109
+ '',
110
+ 'It gates nothing. A record folder has no correct size, so the reading is',
111
+ 'a number to notice rather than a threshold to fail, and a session takes it',
112
+ `by running this rather than by counting the folder. The ${GROWTH_WINDOWS.join(
113
+ ' and ',
114
+ )} day`,
115
+ 'counts read mtime, so a file rewritten long after it landed reads as recent,',
116
+ 'and a machine restored by records pull reads its whole tree as one week old.',
117
+ '',
118
+ 'Examples:',
119
+ ' aitk records size',
120
+ ' aitk records size --json',
121
+ '',
122
+ ].join('\n'),
123
+ )
124
+ .action(async (opts: BackupCommandOptions) => {
125
+ process.exitCode = await runSize(opts)
126
+ })
127
+
90
128
  records
91
129
  .command('push')
92
130
  .description(
@@ -149,6 +187,99 @@ function backupHelp(verb: 'push' | 'pull'): string {
149
187
  ].join('\n')
150
188
  }
151
189
 
190
+ async function runSize(opts: BackupCommandOptions): Promise<number> {
191
+ const root = opts.root ?? (await mainWorktreeRoot())
192
+ const outcome = await sizeRecords(root)
193
+
194
+ if (!outcome.ok)
195
+ return reportRefusal('aitk records size', outcome, opts.json ?? false)
196
+
197
+ if (opts.json ?? false) {
198
+ process.stdout.write(`${JSON.stringify(outcome)}\n`)
199
+ return 0
200
+ }
201
+
202
+ reportSize(outcome)
203
+ return 0
204
+ }
205
+
206
+ /** Widest cell in the column, so a row lines up against the header as well. */
207
+ function columnWidth(header: string, cells: readonly string[]): number {
208
+ return Math.max(header.length, ...cells.map((cell) => cell.length))
209
+ }
210
+
211
+ function sizeRow(entry: FolderSize): string[] {
212
+ return [
213
+ entry.folder,
214
+ String(entry.files),
215
+ formatBytes(entry.bytes),
216
+ ...entry.touched.map((window) => String(window.files)),
217
+ entry.oldest ?? '',
218
+ entry.newest ?? '',
219
+ ]
220
+ }
221
+
222
+ /**
223
+ * Renders the present folders as a table, heaviest first.
224
+ *
225
+ * The order is what makes the reading worth taking. A folder listed
226
+ * alphabetically hides behind its neighbors, and the one that grew is the row
227
+ * a reader came for, so it leads.
228
+ */
229
+ function reportSize(outcome: Extract<SizeOutcome, { ok: true }>): void {
230
+ const present = outcome.folders
231
+ .filter((entry) => entry.present)
232
+ .toSorted((left, right) => right.files - left.files)
233
+ const absent = outcome.folders
234
+ .filter((entry) => !entry.present)
235
+ .map((entry) => entry.folder)
236
+
237
+ intro('aitk records size')
238
+ logStep('Folders')
239
+
240
+ if (present.length === 0) {
241
+ logInfo('none of the record folders exist yet')
242
+ } else {
243
+ const headers = [
244
+ 'folder',
245
+ 'files',
246
+ 'size',
247
+ ...GROWTH_WINDOWS.map((days) => `${days}d`),
248
+ 'oldest',
249
+ 'newest',
250
+ ]
251
+ const rows = present.map(sizeRow)
252
+ const widths = headers.map((header, column) =>
253
+ columnWidth(
254
+ header,
255
+ rows.map((row) => row[column]),
256
+ ),
257
+ )
258
+
259
+ // The name column reads as a list and the rest as numbers, so one is
260
+ // left-aligned and the others are not.
261
+ const render = (cells: readonly string[]): string =>
262
+ cells
263
+ .map((cell, column) =>
264
+ column === 0
265
+ ? cell.padEnd(widths[column])
266
+ : cell.padStart(widths[column]),
267
+ )
268
+ .join(' ')
269
+ .trimEnd()
270
+
271
+ pipeOutput([render(headers), ...rows.map(render)].join('\n'))
272
+ }
273
+
274
+ if (absent.length > 0) logInfo(`absent: ${absent.join(', ')}`)
275
+
276
+ logStep('Total')
277
+ logInfo(
278
+ `${plural(outcome.files, 'file')}, ${formatBytes(outcome.bytes)} across ${plural(present.length, 'folder')}`,
279
+ )
280
+ outro()
281
+ }
282
+
152
283
  async function runPush(opts: BackupCommandOptions): Promise<number> {
153
284
  const root = opts.root ?? (await mainWorktreeRoot())
154
285
  const outcome = await pushRecords(root)
@@ -197,9 +328,16 @@ async function runPull(opts: BackupCommandOptions): Promise<number> {
197
328
  return 0
198
329
  }
199
330
 
331
+ /**
332
+ * Reports a refusal from any of the three verbs that carry one.
333
+ *
334
+ * The parameter is structural rather than the union of their outcome types,
335
+ * because the three refusal vocabularies are separate lists and naming them all
336
+ * here would grow with every verb added.
337
+ */
200
338
  function reportRefusal(
201
339
  banner: string,
202
- outcome: Extract<PushOutcome | PullOutcome, { ok: false }>,
340
+ outcome: { readonly reason: string; readonly message: string },
203
341
  emitJson: boolean,
204
342
  ): number {
205
343
  if (emitJson) {
@@ -536,10 +536,45 @@ function measureParagraph(block: readonly BodyLine[]): CadenceFinding {
536
536
  * absence of something, so no addition to that set catches a paragraph whose
537
537
  * sentences are all one length. The measure stops at what is countable. A
538
538
  * sentence's grammatical shape and whether it carries a finite verb are the two
539
- * rules `write-human` states that this does not implement, because identifying
540
- * either needs a parse rather than a match, and an imperative or a heading
541
- * fragment would read as a defect. Reporting them wrong is worse than not
542
- * reporting them, since they name the exact failure this exists to measure.
539
+ * rules `write-human` states that this does not implement. Reporting them wrong
540
+ * is worse than not reporting them, since they name the exact failure this
541
+ * exists to measure.
542
+ *
543
+ * That was a prediction and it has now been measured, against two parsers
544
+ * rather than against the idea of one. Both ran over 11,389 paragraph sentences
545
+ * across 503 markdown files at `c7e92612` on 2026-08-20, and they disagree by
546
+ * a factor of four on a number one of them has to be wrong about.
547
+ *
548
+ * `compromise` reported 2 percent. It tags a fronted past participle as a
549
+ * finite past-tense verb, so all twelve of those sentences in
550
+ * `.claude/ARCHITECTURE.md` read as carrying one, eleven opening `Measured at`
551
+ * and one `Overturned by`, which is the exact shape the measure exists to
552
+ * catch. It is not even consistent with itself there: `Measured at` tags a verb
553
+ * and `Measured against` tags an adjective.
554
+ *
555
+ * `wink-pos-tagger` reported 9 percent and fixes that class, tagging the
556
+ * participle `VBN`. Roughly three quarters of what it flags is still wrong:
557
+ * hand-classifying every 21st of the 1,071 flagged sentences put 12 of 51
558
+ * genuinely verbless. Two classes split the other 39 almost evenly and neither
559
+ * is a tuning problem. Nineteen are imperatives, which this instructional
560
+ * corpus is full of and whose verb tags as a proper noun as often as a verb.
561
+ * Twenty are ordinary declaratives whose predicate is noun-ambiguous and tags
562
+ * as a noun outright, so `Each maps to a skill.` and `Nothing checks either
563
+ * one.` both report verbless. This corpus is built from exactly those words:
564
+ * reports, answers, checks, maps, holds, names, carries, records, measures.
565
+ *
566
+ * Separating them needs to know which token is the predicate, which is syntax
567
+ * rather than a tag. Neither tagger carries one and nothing was found worth
568
+ * taking for a number nothing gates on, so the rule the deferral set is met and
569
+ * still returns no: a parse was tried, twice, and neither produced a number
570
+ * worth printing beside two that hold.
571
+ *
572
+ * Where a shape sits is a different question and is already answered. A heading
573
+ * fragment, a list item, and a table cell are all legitimately verbless and
574
+ * none of them reaches this measure, because `paragraphBlocks` ends a paragraph
575
+ * on each. That exclusion is structural and needs no grammar. The imperative is
576
+ * the one that sits in paragraph prose, and it is what neither parser could
577
+ * separate out.
543
578
  *
544
579
  * A paragraph under the floor is skipped rather than scored. A two-sentence
545
580
  * configuration note has no spread worth reading, and the opener rule is