@erclx/aitk 0.34.0 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/bash-script/SKILL.md +4 -0
- package/claude/skills/ci-workflow/SKILL.md +6 -0
- package/claude/skills/docs-sync/SKILL.md +7 -0
- package/claude/skills/migration-claude-md/SKILL.md +2 -0
- package/claude/skills/migration-context/SKILL.md +2 -0
- package/claude/skills/setup-gov/SKILL.md +6 -5
- package/claude/skills/setup-init/SKILL.md +2 -0
- package/docs/agents.md +5 -3
- package/package.json +1 -1
- package/src/commands/context.ts +65 -2
- package/src/context/audit.ts +83 -0
- package/standards/context.md +3 -0
|
@@ -9,6 +9,10 @@ Generate production-ready Bash scripts for DevOps and CLI workflows. Enforce str
|
|
|
9
9
|
|
|
10
10
|
Load `${CLAUDE_SKILL_DIR}/references/patterns.md` for the timeline lifecycle, logging, interactive prompt, help-screen, and full-script code templates. `${CLAUDE_SKILL_DIR}` expands to this skill's own directory, so the path resolves from any project. Copy those definitions verbatim, keeping only the colors and functions the script actually uses.
|
|
11
11
|
|
|
12
|
+
## Guards
|
|
13
|
+
|
|
14
|
+
- A request for a script with no human at the terminal stops and routes to `cli-script`. CI jobs, cron entries, agent-run tasks, and any script whose output is consumed by a pipe render no timeline, so generating one costs the frame and returns nothing.
|
|
15
|
+
|
|
12
16
|
## Script setup
|
|
13
17
|
|
|
14
18
|
- Start with `#!/usr/bin/env bash`, `set -e`, and `set -o pipefail`.
|
|
@@ -7,6 +7,11 @@ description: Generates GitHub Actions CI workflow files with parallel jobs, emoj
|
|
|
7
7
|
|
|
8
8
|
Generate GitHub Actions workflow files for CI pipelines. Enforce parallel job execution, emoji job naming, and gated deployment stages.
|
|
9
9
|
|
|
10
|
+
## Guards
|
|
11
|
+
|
|
12
|
+
- A request for what runs inside a deploy, publish, or release job stops at the gate. Credentials, environments, and deploy targets are invisible to this skill, so emit the job with its `needs` wiring and a placeholder step, then name what the caller fills in. Never guess a deploy command.
|
|
13
|
+
- The build, test, and deploy commands belong to the project. Read them from its scripts rather than asserting a second copy in the workflow.
|
|
14
|
+
|
|
10
15
|
## Workflow setup
|
|
11
16
|
|
|
12
17
|
- Include `workflow_dispatch` on every workflow alongside the primary trigger.
|
|
@@ -49,4 +54,5 @@ Before responding, verify:
|
|
|
49
54
|
- E2E uses `needs: build`. Release and deploy use `needs: e2e`.
|
|
50
55
|
- Artifacts upload on `if: failure()` only with `retention-days: 7`.
|
|
51
56
|
- Job names use emoji + title format.
|
|
57
|
+
- Deploy, publish, and release jobs carry a placeholder step and a named handoff, never a guessed deploy command.
|
|
52
58
|
- Bun projects use `oven-sh/setup-bun@v2` with `bun install --frozen-lockfile`.
|
|
@@ -55,14 +55,19 @@ Read each discovered file in parallel.
|
|
|
55
55
|
For each discovered doc, classify as one of:
|
|
56
56
|
|
|
57
57
|
- `stale`: the diff touches something the doc describes
|
|
58
|
+
- `departed`: the doc records a deliberate contract and the diff broke it
|
|
58
59
|
- `unrelated`: no overlap between diff and doc content
|
|
59
60
|
|
|
61
|
+
The split between `stale` and `departed` is whether the doc was describing or promising. A doc that trailed the code is `stale`. A doc stating a contract the code was meant to keep is `departed`, and rewriting it to match the diff would record the regression as the contract.
|
|
62
|
+
|
|
60
63
|
Classify at the section level, not the file level. A doc edited earlier in the session can still be partially stale. For each diff surface, verify the corresponding section is synced.
|
|
61
64
|
|
|
62
65
|
## Action
|
|
63
66
|
|
|
64
67
|
Rewrite only the stale sections. Do not touch sections unrelated to the diff. Write the updated file immediately after the preview. Claude Code's tool permission dialog is the confirmation gate. Do not wait for user input.
|
|
65
68
|
|
|
69
|
+
Never rewrite a `departed` section. Leave it as written and report it as a finding, since the repair belongs in the code rather than in the prose.
|
|
70
|
+
|
|
66
71
|
## Response format
|
|
67
72
|
|
|
68
73
|
### Preview
|
|
@@ -73,6 +78,7 @@ Rewrite only the stale sections. Do not touch sections unrelated to the diff. Wr
|
|
|
73
78
|
| Doc | Status | Action |
|
|
74
79
|
| ----------- | --------- | ------ |
|
|
75
80
|
| README.md | stale | update |
|
|
81
|
+
| docs/cli.md | departed | report |
|
|
76
82
|
| docs/api.md | unrelated | skip |
|
|
77
83
|
|
|
78
84
|
After outputting the preview, write all stale updates immediately.
|
|
@@ -84,4 +90,5 @@ One line per file, using the same relative path format as the preview table (e.g
|
|
|
84
90
|
```plaintext
|
|
85
91
|
✅ Updated: <relative-path>
|
|
86
92
|
⏭️ Skipped: <relative-path>
|
|
93
|
+
⚠️ Departed: <relative-path> records <the contract>, and the diff <what it did>. Left unwritten.
|
|
87
94
|
```
|
|
@@ -7,6 +7,8 @@ description: Classifies each section of a target project's bloated `CLAUDE.md` i
|
|
|
7
7
|
|
|
8
8
|
Rebalance a large `CLAUDE.md` so only always-load behavior stays in it, path-scoped behavior becomes a rule, and domain narrative becomes a context entry. Every move is a proposal the user applies by hand.
|
|
9
9
|
|
|
10
|
+
Run `migration-context` first when both apply. Its moves populate `.claude/context/`, and Step 3 has to read the folder those moves left so an existing entry resolves to an append rather than blocking the move that should have created it.
|
|
11
|
+
|
|
10
12
|
## Guards
|
|
11
13
|
|
|
12
14
|
- If no `CLAUDE.md` exists at `pwd`, stop: `❌ No CLAUDE.md found at the project root.`
|
|
@@ -5,6 +5,8 @@ description: Classifies markdown files in a target project's `docs/` folder and
|
|
|
5
5
|
|
|
6
6
|
# Claude context migrate
|
|
7
7
|
|
|
8
|
+
Run this skill before `migration-claude-md` when both apply. That skill proposes entries into the same `.claude/context/` folder, and an entry landing there first makes the corresponding move here read as a conflict and get skipped, which loses the richer `docs/` file.
|
|
9
|
+
|
|
8
10
|
## Guards
|
|
9
11
|
|
|
10
12
|
- If no `docs/` directory exists at `pwd`, stop: `❌ No docs/ directory found.`
|
|
@@ -36,13 +36,14 @@ Match the detected evidence to the catalog:
|
|
|
36
36
|
|
|
37
37
|
If a detected technology has no matching rule, stop and surface the gap. Do not guess.
|
|
38
38
|
|
|
39
|
-
Present
|
|
39
|
+
Present four options:
|
|
40
40
|
|
|
41
|
-
1. Author a new rule in the toolkit at `governance/rules/<domain>/<num>-<name>.md` following `.claude/standards/rule.md`, or `${CLAUDE_SKILL_DIR}/../../standards/rule.md` when the project does not have it, commit, then re-run install.
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
41
|
+
1. Author a new rule in the toolkit at `governance/rules/<domain>/<num>-<name>.md` following `.claude/standards/rule.md`, or `${CLAUDE_SKILL_DIR}/../../standards/rule.md` when the project does not have it, commit, then re-run install. Take this route when the toolkit should ship the rule to every project.
|
|
42
|
+
2. Invoke `create-rule`, which scaffolds the file into the target project at a non-colliding number. Take this route when the rule is specific to this project and no sync should ever overwrite it.
|
|
43
|
+
3. Install the matching non-<tech> rules and skip the tech-specific layer.
|
|
44
|
+
4. Abort.
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
This skill authors no rule itself. Option 1 writes in the toolkit repository, and option 2 hands the project-local file to `create-rule`.
|
|
46
47
|
|
|
47
48
|
## Preview
|
|
48
49
|
|
|
@@ -10,6 +10,7 @@ Orchestrates the onboarding chain. Detects project type, resolves per-domain arg
|
|
|
10
10
|
## Scope
|
|
11
11
|
|
|
12
12
|
- This skill and `aitk init` run once on a fresh scaffold, never on an existing project. They do not guard against clobbering existing configs. When tempted to add guards, mode switches, or an existing-project branch, stop. Extend the per-domain `aitk <domain> install` or `aitk sync` paths instead.
|
|
13
|
+
- The chain does not bootstrap the `index.md` system and does not provision Claude Code plugins. `setup-indexes` owns the first. `setup-plugins` owns the second, which installs once per machine rather than into a project, so no project-scoped chain can carry it. Name both in the report so a clean result does not read as onboarding complete.
|
|
13
14
|
|
|
14
15
|
## Read catalogs
|
|
15
16
|
|
|
@@ -112,3 +113,4 @@ After the chain, report:
|
|
|
112
113
|
- `setup-verify` outcome
|
|
113
114
|
- Any domains or scripts that failed
|
|
114
115
|
- Any detection gaps surfaced during resolve
|
|
116
|
+
- Onboarding steps left to the caller: `setup-indexes` for the `index.md` system, `setup-plugins` for Claude Code plugins
|
package/docs/agents.md
CHANGED
|
@@ -80,7 +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, provenance, and index drift
|
|
83
|
+
| `aitk context audit` | Report entry length, depth, bullet weight, cited-path resolution, provenance, and index drift |
|
|
84
84
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
85
85
|
|
|
86
86
|
### Domain commands
|
|
@@ -387,7 +387,7 @@ aitk context audit --folder context,diagrams
|
|
|
387
387
|
|
|
388
388
|
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.
|
|
389
389
|
|
|
390
|
-
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, provenance, and index findings print and return `0`, because each is a judgment and failing a push on one would make the check something to route around.
|
|
390
|
+
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, bullet weight, table, provenance, and index findings print and return `0`, because each is a judgment and failing a push on one would make the check something to route around.
|
|
391
391
|
|
|
392
392
|
### What each check reports
|
|
393
393
|
|
|
@@ -395,11 +395,13 @@ Length and depth quote their checkpoints from `.claude/standards/context.md`: ro
|
|
|
395
395
|
|
|
396
396
|
Both checks count rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights. Entries here are authored one line per bullet, so a block of fifteen paragraph-bullets occupies fifteen source lines and renders past sixty, which source counting cannot see. Measuring one checkpoint in each unit would put an entry length beside a run length that mean different things. Their exclusions still differ: the file measure counts fenced blocks and frontmatter, while the run measure skips a fence so an example cannot break the run around it. A reference-heavy entry therefore ranks by its examples, which the length legend states on every run. Runs count blank lines, which the standard leaves open, so a hand reader who drops them lands a line or two lower. Both sections state the width on every run, since a number in rendered lines cannot be reproduced without it.
|
|
397
397
|
|
|
398
|
+
The bullet check reports a top-level bullet past roughly 400 characters, which is where a bullet stops carrying a decision alone and starts carrying the incident that motivated it beside the decision. Continuation lines fold into the bullet they belong to, so a heavy bullet cannot fall under the checkpoint by wrapping across two source lines, while a nested item is left out because the parent's own text is what the checkpoint asks about. Findings group by entry and narrow to `.claude/context/` for the reasons the provenance ones do both. Unlike the peer-list threshold above it, this corpus has no gap behind the number: bullet weight decays smoothly from a median near 170, so the number is a judgment where that one was a measurement, and a bullet reading well past it means the number is wrong rather than the rule.
|
|
399
|
+
|
|
398
400
|
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.
|
|
399
401
|
|
|
400
402
|
The provenance check reports the markers narrating how a domain reached its shape rather than describing what it is: a date, a change number, or a release label. The standard admits a rejected alternative and the reasoning that killed it while refusing the provenance attached to it, so a marker names a line to read rather than a line to delete. Findings group by entry and sort left to right within a line, since what a reader acts on is which file to open. Fenced blocks are excluded, which keeps a pinned version in an install command from reading as a claim the entry makes. Frontmatter is excluded with them, since the content checks read the body alone, and that is what keeps a diagram entry's dated `verified` stamp a record of its last check rather than a marker to settle. Length is the exception, counting the whole file, so a reader applying the 150-rendered-line checkpoint against the body alone lands a few lines under what the tool reports.
|
|
401
403
|
|
|
402
|
-
|
|
404
|
+
The provenance and bullet-weight checks cover `.claude/context/` alone, while length, depth, and the table finding reach every audited folder. The rule is stated in `.claude/standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is whether the remedy is actionable rather than what the check measures. Subdividing a run and splitting a file mean something in any entry, so length and depth generalize. Moving an incident out of a bullet and keeping the decision means nothing in a folder whose entries declare no decisions, which is why bullet weight narrows despite measuring a distance like the two that do not. The scoping key is the folder an entry was audited under, so `--folder` still reaches a folder the default list does not carry, and a domain split into `context/<sub-area>/` is governed as `context`. Every run states the reach, including a run where no audited folder is the governed one. The JSON record carries it as `checkpoints.provenanceFolder` and a per-folder `governsContent`.
|
|
403
405
|
|
|
404
406
|
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.
|
|
405
407
|
|
package/package.json
CHANGED
package/src/commands/context.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import type { Command } from 'commander'
|
|
3
3
|
import {
|
|
4
|
+
BULLET_CHECKPOINT,
|
|
4
5
|
type EntryReport,
|
|
5
6
|
governsContent,
|
|
6
7
|
LENGTH_CHECKPOINT,
|
|
@@ -50,7 +51,7 @@ export function register(program: Command): void {
|
|
|
50
51
|
context
|
|
51
52
|
.command('audit')
|
|
52
53
|
.description(
|
|
53
|
-
'Report entry length, depth, citations, provenance, and index drift',
|
|
54
|
+
'Report entry length, depth, bullet weight, citations, provenance, and index drift',
|
|
54
55
|
)
|
|
55
56
|
.argument('[path]', 'Project root, defaulting to the current directory')
|
|
56
57
|
.helpOption('-h, --help', 'Show this help message')
|
|
@@ -67,7 +68,7 @@ export function register(program: Command): void {
|
|
|
67
68
|
' 2 a cited path did not resolve',
|
|
68
69
|
'',
|
|
69
70
|
'Only unresolved citations set a failing exit code. Length, depth,',
|
|
70
|
-
'table, provenance, and index findings are advisory.',
|
|
71
|
+
'bullet, table, provenance, and index findings are advisory.',
|
|
71
72
|
'',
|
|
72
73
|
'Examples:',
|
|
73
74
|
' aitk context audit',
|
|
@@ -139,6 +140,7 @@ async function runAudit(
|
|
|
139
140
|
reportCitations(citations)
|
|
140
141
|
reportLength(entries)
|
|
141
142
|
reportDepth(entries)
|
|
143
|
+
reportBullets(entries, folders)
|
|
142
144
|
reportTables(entries)
|
|
143
145
|
reportProvenance(entries, folders)
|
|
144
146
|
reportDrift(drift)
|
|
@@ -167,6 +169,7 @@ async function runAudit(
|
|
|
167
169
|
runCountsBlankLines: true,
|
|
168
170
|
renderWidth: RENDER_WIDTH,
|
|
169
171
|
peerBullet: PEER_BULLET_CHECKPOINT,
|
|
172
|
+
bullet: BULLET_CHECKPOINT,
|
|
170
173
|
provenanceFolder: PROVENANCE_FOLDER,
|
|
171
174
|
},
|
|
172
175
|
})}\n`,
|
|
@@ -311,6 +314,66 @@ function reportDepth(entries: readonly EntryReport[]): void {
|
|
|
311
314
|
)
|
|
312
315
|
}
|
|
313
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Groups by entry, and states its reach on every run, for the reasons the
|
|
319
|
+
* provenance report does both.
|
|
320
|
+
*
|
|
321
|
+
* Bullets past the checkpoint cluster in a handful of entries and the heaviest
|
|
322
|
+
* entries carry them a dozen at a time, so a flat list of bullets buries the
|
|
323
|
+
* entry holding one. What a reader acts on is which file to open.
|
|
324
|
+
*/
|
|
325
|
+
function reportBullets(
|
|
326
|
+
entries: readonly EntryReport[],
|
|
327
|
+
folders: readonly AuditedFolder[],
|
|
328
|
+
): void {
|
|
329
|
+
logStep('Bullets')
|
|
330
|
+
|
|
331
|
+
const governed = folders.filter(governsContent)
|
|
332
|
+
if (governed.length === 0) {
|
|
333
|
+
logInfo(
|
|
334
|
+
`Out of scope. The rule is stated in the standard governing .claude/${PROVENANCE_FOLDER}/, and no audited folder is that one.`,
|
|
335
|
+
)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
logInfo(
|
|
340
|
+
`Covers .claude/${PROVENANCE_FOLDER}/ alone, since moving an incident out of a bullet needs a decision to keep.`,
|
|
341
|
+
)
|
|
342
|
+
logInfo(
|
|
343
|
+
'Top-level bullets measure characters, folding in continuation lines.',
|
|
344
|
+
)
|
|
345
|
+
logInfo(
|
|
346
|
+
'Nested items and fenced blocks are excluded. Weight is a judgment, never a defect.',
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
const carrying = entries
|
|
350
|
+
.filter((entry) => entry.heavyBullets.length > 0)
|
|
351
|
+
.sort((a, b) => b.heavyBullets.length - a.heavyBullets.length)
|
|
352
|
+
|
|
353
|
+
if (carrying.length === 0) {
|
|
354
|
+
logInfo(`No bullet past the ${BULLET_CHECKPOINT}-character checkpoint.`)
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const total = carrying.reduce(
|
|
359
|
+
(sum, entry) => sum + entry.heavyBullets.length,
|
|
360
|
+
0,
|
|
361
|
+
)
|
|
362
|
+
logWarn(
|
|
363
|
+
`${plural(total, 'bullet')} past the ${BULLET_CHECKPOINT}-character checkpoint across ${carrying.length} ${carrying.length === 1 ? 'entry' : 'entries'}`,
|
|
364
|
+
)
|
|
365
|
+
pipeOutput(
|
|
366
|
+
carrying
|
|
367
|
+
.map(
|
|
368
|
+
(entry) =>
|
|
369
|
+
`${entry.rel} ${plural(entry.heavyBullets.length, 'bullet')}\n${entry.heavyBullets
|
|
370
|
+
.map((found) => ` :${found.line} ${found.characters} characters`)
|
|
371
|
+
.join('\n')}`,
|
|
372
|
+
)
|
|
373
|
+
.join('\n'),
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
314
377
|
function reportTables(entries: readonly EntryReport[]): void {
|
|
315
378
|
logStep('Tables')
|
|
316
379
|
|
package/src/context/audit.ts
CHANGED
|
@@ -27,6 +27,18 @@ export const RENDER_WIDTH = 80
|
|
|
27
27
|
*/
|
|
28
28
|
export const PEER_BULLET_CHECKPOINT = 130
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Characters a bullet carries before it holds more than the decision itself.
|
|
32
|
+
*
|
|
33
|
+
* Unlike the peer-list checkpoint above, this corpus has no gap behind the
|
|
34
|
+
* number. Bullet weight decays smoothly from a median near 170 with the
|
|
35
|
+
* steepest relative fall across this boundary and nothing resembling two
|
|
36
|
+
* populations, so the number is a judgment where that one was a measurement.
|
|
37
|
+
* A bullet that reads well past it means the number is wrong rather than the
|
|
38
|
+
* rule, which is why this reports and never gates.
|
|
39
|
+
*/
|
|
40
|
+
export const BULLET_CHECKPOINT = 400
|
|
41
|
+
|
|
30
42
|
/**
|
|
31
43
|
* A table this size or larger whose first column mostly names artifacts reads
|
|
32
44
|
* as a catalog that grows a row per shipped thing, which is the shape the
|
|
@@ -71,6 +83,14 @@ const PROVENANCE: readonly { kind: ProvenanceKind; pattern: RegExp }[] = [
|
|
|
71
83
|
* audited folder, because a threshold on how far a reader travels generalizes
|
|
72
84
|
* across entry types while a rule about what an entry may say does not.
|
|
73
85
|
*
|
|
86
|
+
* Bullet weight sits on this side of the split with provenance, which the
|
|
87
|
+
* measure alone would not predict. What decides it is whether the remedy is
|
|
88
|
+
* actionable: subdividing a run and splitting a file mean something in any
|
|
89
|
+
* entry, while moving an incident out of a bullet and keeping the decision
|
|
90
|
+
* means nothing in a folder whose entries carry no decisions to keep. No
|
|
91
|
+
* diagram entry declares `## Decisions` or `## Gotchas`, so the scope of the
|
|
92
|
+
* rule is what narrows the finding rather than the shape of the number.
|
|
93
|
+
*
|
|
74
94
|
* Restating the exclusion in the sibling standards was the alternative. It
|
|
75
95
|
* duplicates one knowledge item across three surfaces, which the root
|
|
76
96
|
* instruction file forbids, and pointing is not available because the surface
|
|
@@ -87,6 +107,12 @@ export interface TableFinding {
|
|
|
87
107
|
readonly rows: number
|
|
88
108
|
}
|
|
89
109
|
|
|
110
|
+
export interface BulletFinding {
|
|
111
|
+
readonly line: number
|
|
112
|
+
/** Weight as folded, so a report says how far past the checkpoint it sits. */
|
|
113
|
+
readonly characters: number
|
|
114
|
+
}
|
|
115
|
+
|
|
90
116
|
export interface ProvenanceFinding {
|
|
91
117
|
readonly line: number
|
|
92
118
|
readonly kind: ProvenanceKind
|
|
@@ -116,6 +142,8 @@ export interface EntryReport {
|
|
|
116
142
|
readonly catalogTables: readonly TableFinding[]
|
|
117
143
|
/** Empty for an entry no standard bans a change narrative in. */
|
|
118
144
|
readonly provenance: readonly ProvenanceFinding[]
|
|
145
|
+
/** Empty for the same reason `provenance` is, and under the same folder. */
|
|
146
|
+
readonly heavyBullets: readonly BulletFinding[]
|
|
119
147
|
}
|
|
120
148
|
|
|
121
149
|
interface BodyLine {
|
|
@@ -302,6 +330,60 @@ function catalogTables(lines: readonly BodyLine[]): TableFinding[] {
|
|
|
302
330
|
return findings
|
|
303
331
|
}
|
|
304
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Finds the top-level bullets carrying more than a decision.
|
|
335
|
+
*
|
|
336
|
+
* A nested item is left out rather than folded into its parent, since the
|
|
337
|
+
* checkpoint asks what one bullet carries and a child carries its own. Lines
|
|
338
|
+
* continuing a bullet do fold in, so a heavy bullet cannot fall under the
|
|
339
|
+
* checkpoint by being wrapped across two source lines. Fenced blocks are
|
|
340
|
+
* skipped for the reason the scans above skip them: a sample an entry displays
|
|
341
|
+
* is not a claim it makes.
|
|
342
|
+
*/
|
|
343
|
+
function heavyBullets(lines: readonly BodyLine[]): BulletFinding[] {
|
|
344
|
+
const findings: BulletFinding[] = []
|
|
345
|
+
let open: BulletFinding | null = null
|
|
346
|
+
let fenced = false
|
|
347
|
+
|
|
348
|
+
const close = (): void => {
|
|
349
|
+
if (open && open.characters > BULLET_CHECKPOINT) findings.push(open)
|
|
350
|
+
open = null
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const line of lines) {
|
|
354
|
+
if (FENCE.test(line.text)) {
|
|
355
|
+
fenced = !fenced
|
|
356
|
+
close()
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
if (fenced) continue
|
|
360
|
+
|
|
361
|
+
const item = line.text.match(LIST_ITEM)
|
|
362
|
+
const text = line.text.trim()
|
|
363
|
+
|
|
364
|
+
if (item) {
|
|
365
|
+
close()
|
|
366
|
+
if (item[1].length === 0) {
|
|
367
|
+
open = { line: line.number, characters: text.length }
|
|
368
|
+
}
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (text === '' || HEADING.test(line.text) || TABLE_ROW.test(line.text)) {
|
|
373
|
+
close()
|
|
374
|
+
continue
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// The joining space a wrapped line would have carried, so folding two
|
|
378
|
+
// source lines measures what one unwrapped line would have.
|
|
379
|
+
if (open) open = { ...open, characters: open.characters + text.length + 1 }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
close()
|
|
383
|
+
|
|
384
|
+
return findings
|
|
385
|
+
}
|
|
386
|
+
|
|
305
387
|
/**
|
|
306
388
|
* Finds the markers narrating a change rather than describing the domain.
|
|
307
389
|
*
|
|
@@ -365,6 +447,7 @@ export function measureEntry(
|
|
|
365
447
|
longestRunLine: run.line,
|
|
366
448
|
catalogTables: catalogTables(lines),
|
|
367
449
|
provenance: governsContent ? provenance(lines) : [],
|
|
450
|
+
heavyBullets: governsContent ? heavyBullets(lines) : [],
|
|
368
451
|
}
|
|
369
452
|
}
|
|
370
453
|
|
package/standards/context.md
CHANGED
|
@@ -104,8 +104,11 @@ Only the `development` entry carries this section. It is not a general-purpose h
|
|
|
104
104
|
- Past roughly 40 rendered lines with no heading of any level breaking them, add a subheading at the seam. Measure the longest such run rather than everything under one `##`, and exclude fenced code blocks. The number is a checkpoint like the 150 above, not a cap.
|
|
105
105
|
- Both checkpoints count rendered lines, so wrap each source line at 80 columns and sum the heights. Source lines undercount an entry authored one line per bullet, where a block of fifteen paragraph-bullets occupies fifteen lines and renders past sixty. Counting the two checkpoints in different units would put a file measured one way beside a run measured another.
|
|
106
106
|
- Exempt a block whose lines are all list items at one level averaging under roughly 130 characters. A flat list of short peers is already navigable, and a subheading dropped into it splits a set that belongs together. Bullet count says nothing on its own, since a catalog of one-liners and a stack of paragraphs reach the same count and read nothing alike, so weight is what decides. Mixing prose with the list, or nesting levels inside it, ends the exemption at any weight.
|
|
107
|
+
- Past roughly 400 characters in one top-level bullet, counting the lines that continue it and excluding any bullet nested under it, check whether the incident that motivated the decision sits beside the decision itself. Keep the current design and the alternative that lost, and move the incident to the change that introduced it, the issue that tracked it, or the research record behind it. The number is a checkpoint like the two above, and a bullet reading well past it means the number is wrong rather than the rule.
|
|
108
|
+
- Collapse a stack of bullets narrating one subsystem into a single `###` subsection carrying one narrative. Splitting a heavy bullet into three light ones satisfies the checkpoint above and leaves the reader no better off, and subdividing a block does not lighten the bullets inside it, so the two rules answer different defects.
|
|
107
109
|
- Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
|
|
108
110
|
- Retire a decision or gotcha once its subject is gone, rewriting the bullet to state the current design rather than leaving the narration of what it replaced beside it. A rejected alternative is not a retired one, so what was tried and why it lost stays whatever its age. The rule above protects content whose subject is live, and this one releases content whose subject is not.
|
|
111
|
+
- Rewrite a decision a later one replaced rather than appending the replacement beside it. The subject is still live, so the rule above does not reach it, and two bullets on one subject leave a reader to work out which of them is current. State the design that stands and keep the superseded reasoning only where it is the alternative that lost.
|
|
109
112
|
- Split into a folder (`.claude/context/<domain>/<sub-area>.md`) when a domain has three or more sub-areas that do not fit cleanly in one file. That split is the natural ceiling.
|
|
110
113
|
- Keep a split domain's `index.md` generated. The catalog body is rewritten on every regen, so the domain's own overview and layout belong in a sibling file rather than in it, and the `subtitle` is what names the file to start with.
|
|
111
114
|
- Author a catalog that grows a row per shipped thing as a bullet list, never a markdown table. A table pads its columns to a shared width, so one row outgrowing its column reflows every row and turns a one-line edit into a whole-file rewrite that conflicts with any sibling branch. This is what forced the split above.
|