@erclx/aitk 0.101.0 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-docs/REQUIREMENT.md +7 -1
- package/claude/skills/claude-docs/SKILL.md +26 -3
- package/claude/skills/claude-orchestrate/SKILL.md +9 -2
- package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +1 -1
- package/claude/skills/claude-tasks/SKILL.md +10 -1
- package/claude/skills/claude-teach/REQUIREMENT.md +9 -2
- package/claude/skills/claude-teach/SKILL.md +51 -5
- package/claude/skills/claude-teach/references/promotion.md +54 -0
- package/claude/skills/session-resume/SKILL.md +1 -1
- package/docs/agents/index.md +1 -1
- package/docs/agents/markdown-audit.md +21 -11
- package/docs/agents/tasks.md +8 -4
- package/docs/agents/teach.md +1 -1
- package/docs/ai-workflow.md +2 -0
- package/docs/target-projects.md +1 -1
- package/package.json +1 -1
- package/scripts/core/verify.sh +3 -0
- package/src/commands/markdown.ts +44 -28
- package/src/commands/tasks.ts +6 -3
- package/src/markdown/bans.ts +71 -210
- package/src/markdown/structure.ts +9 -71
- package/src/tasks/archive.ts +9 -4
- package/src/tasks/validate.ts +103 -3
- package/standards/glossary.md +75 -0
- package/standards/index.md +2 -1
- package/standards/prose.md +0 -2
- package/standards/tasks.md +51 -8
- package/standards/teach.md +5 -7
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +43 -47
package/src/tasks/validate.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from '@/tasks/archive'
|
|
10
10
|
|
|
11
11
|
const ORDERING_FILE = 'priority.md'
|
|
12
|
+
const BACKLOG_FILE = 'backlog.md'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The readiness headings `.claude/standards/tasks.md` fixes. The names are the
|
|
@@ -60,6 +61,16 @@ export interface Untested {
|
|
|
60
61
|
readonly message: string
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* A backlog line, which carries a pointer and nothing else. The backlog is
|
|
66
|
+
* explicitly unordered, so a line has no position to read and no columns to
|
|
67
|
+
* resolve.
|
|
68
|
+
*/
|
|
69
|
+
export interface BacklogRow {
|
|
70
|
+
readonly label: string
|
|
71
|
+
readonly stem: string | undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
export interface BoardRow {
|
|
64
75
|
readonly group: BoardGroup
|
|
65
76
|
readonly label: string
|
|
@@ -74,6 +85,7 @@ export interface BoardRow {
|
|
|
74
85
|
export interface ValidateReport {
|
|
75
86
|
readonly ok: true
|
|
76
87
|
readonly rows: number
|
|
88
|
+
readonly backlog: number
|
|
77
89
|
readonly tasks: number
|
|
78
90
|
readonly findings: readonly Finding[]
|
|
79
91
|
readonly untested: readonly Untested[]
|
|
@@ -91,6 +103,10 @@ export function orderingPath(root: string): string {
|
|
|
91
103
|
return join(tasksDir(root), ORDERING_FILE)
|
|
92
104
|
}
|
|
93
105
|
|
|
106
|
+
export function backlogPath(root: string): string {
|
|
107
|
+
return join(tasksDir(root), BACKLOG_FILE)
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
/**
|
|
95
111
|
* Pulls the target out of a markdown link, which is how both the `Task` and the
|
|
96
112
|
* `Plan` column spell their pointer. A cell carrying prose instead of a link
|
|
@@ -225,6 +241,39 @@ export function readBoard(text: string): {
|
|
|
225
241
|
return { rows, groups }
|
|
226
242
|
}
|
|
227
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Parses the backlog into one row per bullet carrying a link. The backlog is a
|
|
246
|
+
* flat list rather than a table, so there is no header to resolve and no group
|
|
247
|
+
* to sit under, and a bullet holding prose instead of a pointer is skipped.
|
|
248
|
+
*
|
|
249
|
+
* Skipping it rather than reporting it is what keeps the intro paragraph and
|
|
250
|
+
* any explanatory bullet out of the findings. The task that bullet meant to
|
|
251
|
+
* name is still accounted for, because a stem no line reaches is reported by
|
|
252
|
+
* `checkMapping` as carrying no row on either surface.
|
|
253
|
+
*/
|
|
254
|
+
export function readBacklog(text: string): readonly BacklogRow[] {
|
|
255
|
+
const rows: BacklogRow[] = []
|
|
256
|
+
|
|
257
|
+
for (const line of text.split('\n')) {
|
|
258
|
+
if (!/^\s*[-*]\s/.test(line)) continue
|
|
259
|
+
|
|
260
|
+
const target = linkTarget(line)
|
|
261
|
+
if (!target) continue
|
|
262
|
+
|
|
263
|
+
// A pointer carrying a directory names something other than a sibling task,
|
|
264
|
+
// the way `citedStem` reads the same shape on the board.
|
|
265
|
+
const path = target.split('#')[0] ?? ''
|
|
266
|
+
if (path.includes('/')) continue
|
|
267
|
+
|
|
268
|
+
const stem = stemOf(path)
|
|
269
|
+
if (stem && isReservedStem(stem)) continue
|
|
270
|
+
|
|
271
|
+
rows.push({ label: linkText(line) || line.trim(), stem })
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return rows
|
|
275
|
+
}
|
|
276
|
+
|
|
228
277
|
async function listTaskStems(dir: string): Promise<string[]> {
|
|
229
278
|
const entries = await readdir(dir)
|
|
230
279
|
|
|
@@ -247,13 +296,44 @@ function resolves(target: string, dir: string, root: string): boolean {
|
|
|
247
296
|
return existsSync(resolve(dir, path)) || existsSync(resolve(root, path))
|
|
248
297
|
}
|
|
249
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Accounts every task file against both surfaces the board spans. A task sits
|
|
301
|
+
* on `priority.md` when it would plausibly be planned soon and on `backlog.md`
|
|
302
|
+
* otherwise, so a file reached by neither is the dropped one this reports and a
|
|
303
|
+
* file reached by both claims two contradictory things about itself.
|
|
304
|
+
*/
|
|
250
305
|
function checkMapping(
|
|
251
306
|
rows: readonly BoardRow[],
|
|
307
|
+
backlog: readonly BacklogRow[],
|
|
252
308
|
stems: readonly string[],
|
|
253
309
|
dir: string,
|
|
254
310
|
): Finding[] {
|
|
255
311
|
const findings: Finding[] = []
|
|
256
312
|
const seen = new Map<string, number>()
|
|
313
|
+
const listed = new Set<string>()
|
|
314
|
+
|
|
315
|
+
for (const row of backlog) {
|
|
316
|
+
if (!row.stem) {
|
|
317
|
+
findings.push({
|
|
318
|
+
kind: 'task-unresolved',
|
|
319
|
+
group: undefined,
|
|
320
|
+
subject: row.label,
|
|
321
|
+
message: 'is a backlog line naming no task file.',
|
|
322
|
+
})
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
listed.add(row.stem)
|
|
327
|
+
|
|
328
|
+
if (!existsSync(join(dir, `${row.stem}.md`))) {
|
|
329
|
+
findings.push({
|
|
330
|
+
kind: 'task-unresolved',
|
|
331
|
+
group: undefined,
|
|
332
|
+
subject: row.stem,
|
|
333
|
+
message: 'has a backlog line and no task file.',
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
}
|
|
257
337
|
|
|
258
338
|
for (const row of rows) {
|
|
259
339
|
if (!row.stem) {
|
|
@@ -287,15 +367,26 @@ function checkMapping(
|
|
|
287
367
|
message: `carries ${count} rows. A task belongs to exactly one group.`,
|
|
288
368
|
})
|
|
289
369
|
}
|
|
370
|
+
|
|
371
|
+
if (listed.has(stem)) {
|
|
372
|
+
findings.push({
|
|
373
|
+
kind: 'row-duplicated',
|
|
374
|
+
group: undefined,
|
|
375
|
+
subject: stem,
|
|
376
|
+
message:
|
|
377
|
+
'carries a row on the board and a line on the backlog. A task sits on one surface.',
|
|
378
|
+
})
|
|
379
|
+
}
|
|
290
380
|
}
|
|
291
381
|
|
|
292
382
|
for (const stem of stems) {
|
|
293
|
-
if (!seen.has(stem)) {
|
|
383
|
+
if (!seen.has(stem) && !listed.has(stem)) {
|
|
294
384
|
findings.push({
|
|
295
385
|
kind: 'row-missing',
|
|
296
386
|
group: undefined,
|
|
297
387
|
subject: stem,
|
|
298
|
-
message:
|
|
388
|
+
message:
|
|
389
|
+
'is a task file with no row on the board and no line on the backlog.',
|
|
299
390
|
})
|
|
300
391
|
}
|
|
301
392
|
}
|
|
@@ -547,11 +638,19 @@ export async function validateBoard(root: string): Promise<ValidateOutcome> {
|
|
|
547
638
|
)
|
|
548
639
|
}
|
|
549
640
|
|
|
641
|
+
// An absent backlog reads as empty rather than refusing. A project that has
|
|
642
|
+
// never needed the second surface keeps every task on the board, which is the
|
|
643
|
+
// one-to-one mapping this check ran before the backlog existed.
|
|
644
|
+
const backlogFile = backlogPath(root)
|
|
645
|
+
const backlog = existsSync(backlogFile)
|
|
646
|
+
? readBacklog(await readFile(backlogFile, 'utf8'))
|
|
647
|
+
: []
|
|
648
|
+
|
|
550
649
|
const stems = await listTaskStems(dir)
|
|
551
650
|
const parked = await checkParked(rows, root)
|
|
552
651
|
|
|
553
652
|
const findings = [
|
|
554
|
-
...checkMapping(rows, stems, dir),
|
|
653
|
+
...checkMapping(rows, backlog, stems, dir),
|
|
555
654
|
...checkPlans(rows, dir, root),
|
|
556
655
|
...checkCollisions(rows),
|
|
557
656
|
...parked.findings,
|
|
@@ -560,6 +659,7 @@ export async function validateBoard(root: string): Promise<ValidateOutcome> {
|
|
|
560
659
|
return {
|
|
561
660
|
ok: true,
|
|
562
661
|
rows: rows.length,
|
|
662
|
+
backlog: backlog.length,
|
|
563
663
|
tasks: stems.length,
|
|
564
664
|
findings,
|
|
565
665
|
untested: parked.untested,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Glossary reference
|
|
3
|
+
description: Frontmatter, entry shape, ordering, and the rules deciding which terms a glossary carries
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Glossary reference
|
|
7
|
+
|
|
8
|
+
Applies to a glossary, the file holding one entry per term a body of material defines. It changes whenever the material names a concept a reader cannot look up yet, and it is revised in place rather than appended to.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs a glossary at `.claude/teach/<nn>-<topic>/GLOSSARY.md` and at whatever path a surface fixes for one it holds: its frontmatter, entry shape, ordering, grouping, and the rules deciding which terms it carries.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- The folder a learning workspace lays out around its glossary, and the other files in it: `teach.md`
|
|
17
|
+
- Which surface a glossary moves to once it leaves the material that produced it, which belongs to the surface driving that move
|
|
18
|
+
- Voice and word choice: `prose.md`
|
|
19
|
+
- Headings, punctuation, and file references: `markdown.md`
|
|
20
|
+
|
|
21
|
+
## What a working glossary looks like
|
|
22
|
+
|
|
23
|
+
A glossary works when a reader who meets a term in the material settles it here without opening the page that introduced it:
|
|
24
|
+
|
|
25
|
+
- Which word does this material use for the concept, and which words does it deliberately not use?
|
|
26
|
+
- What does the term mean, stated without leaning on the term itself?
|
|
27
|
+
- Where does the term appear, so a reader can see it used rather than only defined?
|
|
28
|
+
- Does every entry carry a term the material actually uses?
|
|
29
|
+
|
|
30
|
+
A glossary failing these is non-conforming even when it satisfies every shape rule below.
|
|
31
|
+
|
|
32
|
+
## Frontmatter
|
|
33
|
+
|
|
34
|
+
- `title` (required): names the material the terms come from, in sentence case
|
|
35
|
+
- `description` (required): one line naming what a reader gets from the entries
|
|
36
|
+
|
|
37
|
+
## Entries
|
|
38
|
+
|
|
39
|
+
- Write one entry per term, as a single bullet.
|
|
40
|
+
- Lead the bullet with the term as a bolded span, then the definition in one or two sentences.
|
|
41
|
+
- Define the term without using it. A definition that spends the term explains nothing to the reader who arrived not knowing it.
|
|
42
|
+
- Name where the term first appears, so a reader can reach one use of it in context.
|
|
43
|
+
- Keep an entry to the meaning. Worked detail belongs on the page that teaches the term.
|
|
44
|
+
- Sort entries alphabetically, so a reader who knows only the word finds it without reading the file.
|
|
45
|
+
|
|
46
|
+
## Which terms it carries
|
|
47
|
+
|
|
48
|
+
- Add a term once the material has used it, never ahead of that. A glossary front-loaded with terms nothing has introduced is a syllabus rather than a reference.
|
|
49
|
+
- Pick one word per concept and use that word everywhere. A glossary carrying two words for one thing hands the reader a choice it exists to remove.
|
|
50
|
+
- List each rejected synonym as an alias to avoid inside the entry that won, so a reader arriving with the wrong word lands on the right one.
|
|
51
|
+
- Use the glossary's own terms inside other definitions. A definition reaching for a synonym of a term defined two entries down teaches the reader a word the material does not use.
|
|
52
|
+
- Revise an entry the material has moved under rather than adding a second one narrating the change.
|
|
53
|
+
|
|
54
|
+
## Grouping
|
|
55
|
+
|
|
56
|
+
- Keep a short glossary as one alphabetical list under the title. Grouping a handful of entries costs a heading per category and saves no lookup.
|
|
57
|
+
- Group a glossary long enough that one list stops helping under `##` headings by category, sorted alphabetically within each. Roughly two screens of entries is the signal.
|
|
58
|
+
- Name each category so a reader picks it from the term alone. A category a reader cannot predict makes the grouping a second thing to search.
|
|
59
|
+
- State a departure from any rule above in the file itself, naming what it departs from and why. A glossary serving no single body of material is the case that produces one, since a term drawn from everywhere has no first appearance to name.
|
|
60
|
+
|
|
61
|
+
## Template
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
---
|
|
65
|
+
title: <Material the terms come from>
|
|
66
|
+
description: <one line naming what a reader gets from these entries>
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# <Material the terms come from>
|
|
70
|
+
|
|
71
|
+
<One line on which material these terms come from and when the file changes.>
|
|
72
|
+
|
|
73
|
+
- **<Term>**: <the meaning in one or two sentences, written without using the term>. Avoid <rejected synonym>. First appears in `<page or lesson>`.
|
|
74
|
+
- **<Term>**: <the meaning in one or two sentences, written without using the term>. First appears in `<page or lesson>`.
|
|
75
|
+
```
|
package/standards/index.md
CHANGED
|
@@ -11,6 +11,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
|
|
|
11
11
|
- [Context entry reference](context.md): Shape and content rules for .claude/context/<domain>.md entries
|
|
12
12
|
- [Design reference](design.md): Shape and content rules for .claude/DESIGN.md
|
|
13
13
|
- [Diagram reference](diagrams.md): Shape and content rules for .claude/diagrams/<kind>.md files
|
|
14
|
+
- [Glossary reference](glossary.md): Frontmatter, entry shape, ordering, and the rules deciding which terms a glossary carries
|
|
14
15
|
- [Groundwork reference](groundwork.md): Folder layout, reserved numbering, frontmatter and dating, required file contents, and conventions for a measurement track
|
|
15
16
|
- [Intake reference](intake.md): Folder layout, reserved index number, frontmatter and dating, the item template, the answer contract, and retrieval
|
|
16
17
|
- [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, and file references
|
|
@@ -26,6 +27,6 @@ Reference docs for consistent authoring across the toolkit and target projects.
|
|
|
26
27
|
- [Slug reference](slug.md): Transform from a git branch name to a slug, and the three responses to an empty result
|
|
27
28
|
- [Standard reference](standard.md): Shape and content rules for authoring a standard
|
|
28
29
|
- [Tasks reference](tasks.md): Folder layout, filename convention, readiness groups, and content rules for .claude/tasks/
|
|
29
|
-
- [Teach reference](teach.md): Workspace layout, ordinal naming, frontmatter, and the mission
|
|
30
|
+
- [Teach reference](teach.md): Workspace layout, ordinal naming, frontmatter, and the mission and learning-record formats for a learning workspace
|
|
30
31
|
- [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, reviews, issues, commits, and tags
|
|
31
32
|
- [Wireframe reference](wireframes.md): Shape and content rules for .claude/wireframes/<surface>.md files
|
package/standards/prose.md
CHANGED
|
@@ -51,8 +51,6 @@ Does not govern:
|
|
|
51
51
|
|
|
52
52
|
The character bans sit in `markdown.md` under `## Punctuation` rather than here, because an em dash and a semicolon are typography and the bans here reach the words a sentence chooses and the claims it makes. A surface applying both reads both files.
|
|
53
53
|
|
|
54
|
-
Illustrate a pattern ban with a multi-word phrase. An audit reading this section harvests the single lowercase backticked words out of every `- Do not use ` bullet into a literal ban set, so a one-word example bans that word everywhere it appears rather than banning the pattern it stands for. Both the toolkit command and the audit hook a project installs parse that shape, so the constraint holds wherever this file lands.
|
|
55
|
-
|
|
56
54
|
## Frontmatter descriptions
|
|
57
55
|
|
|
58
56
|
When frontmatter carries a short `title` or `description` used for catalog display:
|
package/standards/tasks.md
CHANGED
|
@@ -13,7 +13,7 @@ The folder is gitignored. Board state changes when work ships rather than when a
|
|
|
13
13
|
|
|
14
14
|
## Scope
|
|
15
15
|
|
|
16
|
-
Governs the task board under `.claude/tasks/`: folder layout, filenames, frontmatter, file format, origin lines, execution ordering, and archiving.
|
|
16
|
+
Governs the task board under `.claude/tasks/`: folder layout, filenames, frontmatter, file format, origin lines, execution ordering, the backlog beside it, and archiving.
|
|
17
17
|
|
|
18
18
|
Does not govern:
|
|
19
19
|
|
|
@@ -30,6 +30,7 @@ Does not govern:
|
|
|
30
30
|
.claude/tasks/
|
|
31
31
|
├── index.md ← generated, never hand-edited
|
|
32
32
|
├── priority.md ← hand-maintained execution order
|
|
33
|
+
├── backlog.md ← unordered, what is not being scheduled
|
|
33
34
|
├── session-<slug>.md ← optional, what a compaction is about to destroy
|
|
34
35
|
├── v09.0-sync-paths.md
|
|
35
36
|
└── v13.0-toolkit-drift.md
|
|
@@ -37,7 +38,9 @@ Does not govern:
|
|
|
37
38
|
|
|
38
39
|
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.
|
|
39
40
|
|
|
40
|
-
Siblings sit in the folder without being tasks, and each earns its place by being governed somewhere. `index.md` and `
|
|
41
|
+
Siblings sit in the folder without being tasks, and each earns its place by being governed somewhere. `index.md`, `priority.md`, and `backlog.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.
|
|
42
|
+
|
|
43
|
+
`backlog.md` is optional too. A project small enough that every task it holds would be planned soon carries none, and the ordering rules below then describe the whole board.
|
|
41
44
|
|
|
42
45
|
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.
|
|
43
46
|
|
|
@@ -49,7 +52,7 @@ The `claude-tasks` skill creates and archives task files. `claude-docs` marks ou
|
|
|
49
52
|
|
|
50
53
|
## Ordering
|
|
51
54
|
|
|
52
|
-
`priority.md` carries execution order and what each task is waiting on. The generated index sorts by filename and says nothing about order, so without this file board state gets reconstructed by hand every session. Why
|
|
55
|
+
`priority.md` carries execution order and what each task is waiting on. The generated index sorts by filename and says nothing about order, so without this file board state gets reconstructed by hand every session. Why one version sequences against another belongs in `.claude/ROADMAP.md`, which is committed because that rationale has no substitute record. Why a row sits where it does inside its group is stated on the row itself, in the column that already carries what the task is waiting on.
|
|
53
56
|
|
|
54
57
|
Group tasks by readiness rather than by status, one row per task, under the columns each group fixes below. Keep it to links and blockers: tables, plus at most one sentence per section. A paragraph in `priority.md` is a defect whatever it says. Stating the shape this way is what lets a single diff fail, since a size cap only trips after the fact and every addition looks defensible on its own.
|
|
55
58
|
|
|
@@ -61,11 +64,15 @@ Readiness is three groups under fixed headings, `## Run now`, `## Up next`, and
|
|
|
61
64
|
|
|
62
65
|
Each group fixes its own columns, which follow from the test above it rather than from preference. Neither half of the `## Run now` test is checkable without the file set and the plan sitting beside the task.
|
|
63
66
|
|
|
67
|
+
Row position inside `## Needs a plan` is the order those tasks get planned in, top first. The three tests answer whether a task can start, which is mechanical, and none of them answers which task is worth starting, which is a judgment no column holds. Position is where that judgment is recorded, so the top row is the answer to what to plan next and a reader needs no other surface to get it. The other two groups take the same reading, and it costs them little, since a group holding what is already planned is short by construction.
|
|
68
|
+
|
|
69
|
+
Position alone carries it, and no rank column exists. A number beside each row is a second thing to keep in step with the order it duplicates, and the file is edited by one session at a time, so the order the rows are written in is already unambiguous. State on each row why it sits where it does, in the same cell that carries what it is waiting on. A position with no stated reason is re-derived from memory by the next session, which is the failure the ordering replaces rather than moves.
|
|
70
|
+
|
|
64
71
|
The `Waiting on` column under `## Up next` carries that reason in one of three forms. `## Needs a plan` states no file set at all, because a task with no plan has no bounded one to state. A group with no rows keeps its heading and its header row.
|
|
65
72
|
|
|
66
73
|
Under `## Up next` a collision names the file held by the task already running, a sibling task names that task, and an external condition names both the condition and what would satisfy it. Naming what would satisfy it is what separates a blocked row from one nobody has examined, so a cell stating a condition with no way out of it fails the test. The header text is the contract the way the group names are, because anything reading the cell resolves the column by header rather than by position.
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
Under `## Needs a plan` the cell carries two halves and each takes one clause: what the task needs before it can be planned, then why it sits at this position. A cell running past that is the paragraph this file already deletes, arriving one row at a time rather than all at once, and the group is where it costs the most, since it holds the rows nobody has read recently and is the longest group on any board that needs a backlog at all.
|
|
69
76
|
|
|
70
77
|
```markdown
|
|
71
78
|
---
|
|
@@ -88,13 +95,47 @@ description: One line on what the board covers
|
|
|
88
95
|
|
|
89
96
|
## Needs a plan
|
|
90
97
|
|
|
91
|
-
| Task | Waiting on
|
|
92
|
-
| ------------------------------- |
|
|
93
|
-
| [vXX.Y <slug>](vXX.Y-<slug>.md) | <what
|
|
98
|
+
| Task | Waiting on |
|
|
99
|
+
| ------------------------------- | -------------------------------------------------------------- |
|
|
100
|
+
| [vXX.Y <slug>](vXX.Y-<slug>.md) | <what it needs before it can be planned, and why it sits here> |
|
|
94
101
|
```
|
|
95
102
|
|
|
96
103
|
The tests live here so the board does not carry them. Writing them as a sentence under each heading produces the paragraph the rule above deletes, and a criterion with no home gets restated from memory every time the board is touched.
|
|
97
104
|
|
|
105
|
+
## The backlog
|
|
106
|
+
|
|
107
|
+
A task sits on the board when it would plausibly be planned within the next few waves, and on `backlog.md` otherwise. The call is a judgment, so restate it whenever the board is swept rather than making it once: a backlogged task rises when the work in front of it lands or the world changes under it, and a board row falls to the backlog when it stops being near-term.
|
|
108
|
+
|
|
109
|
+
A mechanical test over age or origin was the alternative and neither predicts what gets picked next, which is the judgment the ordering exists to carry. Leaving everything on the board is the other alternative, and it is what produces a group too long to rank, where the ordering means nothing because nobody can hold the whole list in one reading.
|
|
110
|
+
|
|
111
|
+
What the split buys is that the board is short enough for its order to be read, and what it costs is a second surface to keep. That trade only pays while the backlog stays honest about what it is, which is why it carries no order, no groups, and no readiness claim.
|
|
112
|
+
|
|
113
|
+
Nothing is deleted. A backlogged task keeps its file, its findings, and its frontmatter, and the backlog row is a pointer at that file. The folder is gitignored and has no history behind it, so a row dropped without landing somewhere readable is gone with nothing to recover it from.
|
|
114
|
+
|
|
115
|
+
The backlog is a flat list of links under one heading, sorted by filename. Sorting mechanically is what keeps it from reading as a queue: the order is the same order the index already sorts in, so no position on it means anything.
|
|
116
|
+
|
|
117
|
+
```markdown
|
|
118
|
+
---
|
|
119
|
+
title: Backlog
|
|
120
|
+
description: One line on what the backlog holds
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# Backlog
|
|
124
|
+
|
|
125
|
+
Unordered. Nothing here is scheduled, and a task rises to `priority.md` when it becomes near-term.
|
|
126
|
+
|
|
127
|
+
- [vXX.Y <slug>](vXX.Y-<slug>.md)
|
|
128
|
+
- [vXX.Y <slug>](vXX.Y-<slug>.md)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Add no fourth readiness group in place of this file. The three group names are the contract, and a backlog is a separate surface rather than a group because it makes no readiness claim at all: it says nobody has scheduled the task, which is a fact about attention rather than about whether the work can start.
|
|
132
|
+
|
|
133
|
+
## Validation
|
|
134
|
+
|
|
135
|
+
`aitk tasks validate` reads the columns above and reports where a row's claim and the tree disagree: a plan pointer resolving to no file, a task file reached by neither surface, a task on both surfaces or in two groups, and two `## Run now` rows touching a path in common. It also re-takes the two blocker kinds a command can settle, reporting a parked row whose cited task is archived or has closed every outcome and one whose cited file nothing under `## Run now` still holds. Both halves read a citation out of the cell rather than parsing it into fields, and a row citing neither is reported as untested, which is where the three kinds resting on a person's judgment land. Run it when the readiness claim is made rather than on a schedule, since the board is gitignored per-machine scratch and no shared moment exists to hang it on. It reports and never writes, so a session fixes the row it names.
|
|
136
|
+
|
|
137
|
+
A task file is accounted for when a row on `priority.md` or a line on `backlog.md` names it, and reported when neither does. One check across both surfaces is what lets a task move between them without the move looking like a dropped file, and a task named by both is reported for the same reason a task in two groups is: it claims two things about itself and only one of them can hold. A project carrying no `backlog.md` is read as an empty backlog rather than refused, which leaves the one-to-one mapping this check ran before the second surface existed.
|
|
138
|
+
|
|
98
139
|
## Filenames
|
|
99
140
|
|
|
100
141
|
`vXX.Y-<slug>.md`, where the version is the phase label zero-padded to two digits and the slug is kebab-case.
|
|
@@ -195,7 +236,7 @@ The line is what lets a merge close its own task. Every merge on `main` is a squ
|
|
|
195
236
|
- Architectural reasoning that outlives the task. A finding explains why this task is shaped as it is. A decision the system keeps after the task closes belongs in `.claude/ARCHITECTURE.md`.
|
|
196
237
|
- Narrative of the session that produced the task. A finding states what constrains the task, so what was probed, what it cost, and who decided belongs in the groundwork folder the `Groundwork:` line names. A task with no groundwork folder cuts the narrative rather than relocating it, since the board is not the fallback destination for it.
|
|
197
238
|
- "In progress" or "Blocked" headings. Note status inline on the outcome instead.
|
|
198
|
-
- Sequencing rationale or which version is active.
|
|
239
|
+
- Sequencing rationale or which version is active. Why this task is planned before its neighbors goes on its row in `priority.md`, in the cell that already carries what it is waiting on. Why one version sequences against another belongs in `.claude/ROADMAP.md`, which is committed because that reasoning has no substitute record.
|
|
199
240
|
|
|
200
241
|
## Archiving
|
|
201
242
|
|
|
@@ -205,6 +246,8 @@ Two callers reach that command. The `claude-tasks` skill runs it inside a sessio
|
|
|
205
246
|
|
|
206
247
|
One destination rather than a per-project choice is what lets the move happen without asking. It mirrors the plans archive at `.claude/plans-archive/` and stays gitignored, so an archived task does not start appearing in diffs. The cost is that the folder is unbacked, which is the same cost the plans archive already carries.
|
|
207
248
|
|
|
249
|
+
The archive clears a row from `priority.md` and reads no other surface, which holds because a task reaches a merge by being planned and handed out, and both steps move it onto the board first. A task archived straight off the backlog therefore leaves its line standing, and the validator reports that line as naming a file that is gone rather than the board losing it silently.
|
|
250
|
+
|
|
208
251
|
Archiving a task does not archive its plan. `claude-docs` owns the plans sweep and moves a plan only when the closing task is its last live citation. The archive clears the task's row from `priority.md` itself, since a shipped task left in the ordering reads as ready to hand a worker. It leaves prose naming the task alone for a person to resolve.
|
|
209
252
|
|
|
210
253
|
The row is matched by the link in its first cell rather than by a pattern against the whole line. A row names the task it is about in the first cell, so a link anywhere after that is a reference, such as a blocker pointing at what it waits on. Matching the line would delete the referring task's row too, on a board that is gitignored and has nothing to recover it from.
|
package/standards/teach.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Teach reference
|
|
3
|
-
description: Workspace layout, ordinal naming, frontmatter, and the mission
|
|
3
|
+
description: Workspace layout, ordinal naming, frontmatter, and the mission and learning-record formats for a learning workspace
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Teach reference
|
|
@@ -11,10 +11,11 @@ The folder is gitignored. Its markdown half is written in a format the authoring
|
|
|
11
11
|
|
|
12
12
|
## Scope
|
|
13
13
|
|
|
14
|
-
Governs a learning workspace under `.claude/teach/<nn>-<topic>/`: folder layout, ordinal naming, frontmatter, and the mission
|
|
14
|
+
Governs a learning workspace under `.claude/teach/<nn>-<topic>/`: folder layout, ordinal naming, frontmatter, and the mission and learning-record formats.
|
|
15
15
|
|
|
16
16
|
Does not govern:
|
|
17
17
|
|
|
18
|
+
- The frontmatter, entry shape, and ordering of the glossary the workspace holds: `glossary.md`
|
|
18
19
|
- What a lesson teaches, how it sequences difficulty, and what makes one worth returning to, which belong to the surface driving the workspace
|
|
19
20
|
- Where a durable page goes once it leaves the workspace, which belongs to the routing test the destination surface states
|
|
20
21
|
- One question measured in depth before anyone can plan against it: `groundwork.md`
|
|
@@ -96,12 +97,9 @@ Record the wrong answer rather than the fact of an error. A wrong answer names t
|
|
|
96
97
|
|
|
97
98
|
## GLOSSARY.md
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
Required in every workspace, holding one entry per term the subject defines. `glossary.md` fixes what an entry looks like, how the file orders and groups them, and which terms it carries, so this standard states only that the file exists and sits at the workspace root.
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
- Define the term without using it
|
|
103
|
-
- Name the lesson or reference page where the term first appears
|
|
104
|
-
- Keep an entry to the meaning. Worked detail belongs on a reference page.
|
|
102
|
+
Name the lesson or reference page a term first appears in as that standard requires. A workspace is the case it was written for, so a glossary here has a first appearance to name.
|
|
105
103
|
|
|
106
104
|
## RESOURCES.md
|
|
107
105
|
|
|
@@ -24,66 +24,62 @@ esac
|
|
|
24
24
|
|
|
25
25
|
[ -f "$file" ] || exit 0
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
27
|
+
# The audit verb owns the ban sets, so this hook carries no copy of them. The
|
|
28
|
+
# awk this replaces parsed the word bans out of the project's own prose.md,
|
|
29
|
+
# hardcoded the em-dash and semicolon, and reached none of the spellings, so a
|
|
30
|
+
# British spelling passed at edit time and a corpus check caught it later with
|
|
31
|
+
# nothing in between explaining the difference. A ban class added to the verb
|
|
32
|
+
# now reaches this hook without an edit here.
|
|
30
33
|
#
|
|
31
|
-
# The
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
# The verb resolves its own paths under the cwd, so the project root is named
|
|
35
|
+
# rather than inherited. The payload carries an absolute file path, which is
|
|
36
|
+
# what lets the runner move without taking the argument out of reach.
|
|
37
|
+
root="${CLAUDE_PROJECT_DIR:-.}"
|
|
38
|
+
|
|
39
|
+
# A machine without the binary reports that nothing ran rather than exiting
|
|
40
|
+
# clean. An edit nobody checked and an edit carrying no violation are the same
|
|
41
|
+
# silence to a reader, so the enforcement a machine lacks is reported.
|
|
42
|
+
#
|
|
43
|
+
# A completed run always writes the record, and a refusal writes nothing, so an
|
|
44
|
+
# empty one means the verb declined to measure rather than measured and found
|
|
45
|
+
# nothing. It refuses outside a git repository, which is a project this hook can
|
|
46
|
+
# be installed into, and reading the findings alone reports that as a clean file.
|
|
36
47
|
unread=""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
grep -x '[a-z][a-z]*' |
|
|
42
|
-
sort -u |
|
|
43
|
-
paste -sd '|' -)
|
|
48
|
+
record=""
|
|
49
|
+
if command -v aitk >/dev/null 2>&1; then
|
|
50
|
+
record=$(cd "$root" 2>/dev/null && aitk markdown audit "$file" --json 2>/dev/null) || true
|
|
51
|
+
[ -n "$record" ] || unread="record"
|
|
44
52
|
else
|
|
45
|
-
|
|
46
|
-
# list as nothing to look for rather than as nothing found. The path is kept
|
|
47
|
-
# so the report names what could not be read, since a file carrying no banned
|
|
48
|
-
# word and a file nobody checked produce the same silence otherwise.
|
|
49
|
-
unread="$standard"
|
|
53
|
+
unread="runner"
|
|
50
54
|
fi
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
while (match(prose, banned)) {
|
|
64
|
-
word = substr(prose, RSTART, RLENGTH)
|
|
65
|
-
gsub(/[^a-z]/, "", word)
|
|
66
|
-
if (word != "" && !(word in seen)) {
|
|
67
|
-
seen[word] = 1
|
|
68
|
-
found = found (found == "" ? "" : ", ") word
|
|
69
|
-
}
|
|
70
|
-
prose = substr(prose, RSTART + RLENGTH)
|
|
71
|
-
}
|
|
72
|
-
if (found != "") print NR ": banned word (" found "): " $0
|
|
73
|
-
}
|
|
74
|
-
' "$file")
|
|
56
|
+
# The record decides rather than the exit code, so a binary predating a ban
|
|
57
|
+
# class still reports what it does measure. A refusal and an unparseable payload
|
|
58
|
+
# both yield nothing, which is the same silence a clean file produces.
|
|
59
|
+
hits=$(printf '%s' "$record" |
|
|
60
|
+
jq -r '.entries[]?.bans[]? | ":\(.line):\(.column + 1) \(.kind) \(.term)"' 2>/dev/null)
|
|
61
|
+
|
|
62
|
+
# A set the verb shipped empty measures nothing and would report a clean file.
|
|
63
|
+
# Reading the findings alone turns that narrowed check into a pass, so the field
|
|
64
|
+
# is read beside them.
|
|
65
|
+
empty=$(printf '%s' "$record" |
|
|
66
|
+
jq -r '[.bans.emptySets[]?] | join(", ")' 2>/dev/null)
|
|
75
67
|
|
|
76
|
-
[ -z "$hits" ] && [ -z "$unread" ] && exit 0
|
|
68
|
+
[ -z "$hits" ] && [ -z "$unread" ] && [ -z "$empty" ] && exit 0
|
|
77
69
|
|
|
78
70
|
nl=$'\n'
|
|
79
71
|
msg=""
|
|
80
72
|
|
|
81
|
-
if [
|
|
82
|
-
msg=$(printf 'Standards-audit:
|
|
73
|
+
if [ "$unread" = "runner" ]; then
|
|
74
|
+
msg=$(printf 'Standards-audit: nothing checked in %s. Found no `aitk` binary on PATH. Install one with `bun add -g @erclx/aitk`.' "$file")
|
|
75
|
+
elif [ "$unread" = "record" ]; then
|
|
76
|
+
msg=$(printf 'Standards-audit: nothing checked in %s. `aitk markdown audit` returned no record, which it does when it declines to measure. It needs a git repository to build its corpus.' "$file")
|
|
77
|
+
elif [ -n "$empty" ]; then
|
|
78
|
+
msg=$(printf 'Standards-audit: the shipped ban set is empty for %s, so %s was checked against a narrowed set. Reinstall the toolkit with `bun add -g @erclx/aitk`.' "$empty" "$file")
|
|
83
79
|
fi
|
|
84
80
|
|
|
85
81
|
if [ -n "$hits" ]; then
|
|
86
|
-
found=$(printf 'Standards-audit: prose.md and markdown.md violations in %s. Rewrite
|
|
82
|
+
found=$(printf 'Standards-audit: prose.md and markdown.md violations in %s. Rewrite the sentence (do not lazy-swap). A code span is the answer only where the token is genuinely an identifier under discussion.\n%s' "$file" "$hits")
|
|
87
83
|
msg="${msg:+$msg$nl}$found"
|
|
88
84
|
fi
|
|
89
85
|
|