@erclx/aitk 3.45.0 → 3.47.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-orchestrate/REQUIREMENT.md +3 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +18 -2
- package/claude/skills/claude-teach/REQUIREMENT.md +3 -0
- package/claude/skills/claude-teach/SKILL.md +18 -1
- package/claude/skills/claude-worker/SKILL.md +41 -4
- package/docs/agents/commands.md +9 -0
- package/docs/agents/counts.md +1 -1
- package/docs/agents/index.md +1 -1
- package/docs/agents/sessions.md +27 -2
- package/docs/agents/teach.md +12 -0
- package/governance/rules/claude/561-teach.md +1 -1
- package/governance/rules/ui/400-ui.md +1 -0
- package/governance/rules/ui/410-a11y.md +2 -0
- package/governance/rules/ui/420-forms.md +1 -0
- package/governance/rules/ui/430-ux-completeness.md +2 -0
- package/governance/rules/ui/440-surface-capture.md +13 -5
- package/package.json +1 -1
- package/src/cli.ts +4 -0
- package/src/commands/serve.ts +159 -0
- package/src/commands/sessions.ts +83 -6
- package/src/process/harness.ts +167 -0
- package/src/serve/static.ts +322 -0
- package/src/sessions/resolve.ts +103 -0
|
@@ -82,7 +82,9 @@ The session also records nothing of what it learns. Both other callers of memory
|
|
|
82
82
|
- Check a candidate branch is unclaimed by an existing worktree or a live session before dispatching a background worker for it, since the measured failure this closes is a worker colliding with someone else's work already sitting in the row
|
|
83
83
|
- Dispatch only a candidate whose file set is disjoint from every track in flight, compared at the file path rather than a folder above it, since a count knows nothing about what two workers write
|
|
84
84
|
- Hold a candidate whose sets are disjoint when a stated reason serializes it, and write that reason on the hold, since disjointness is necessary and not sufficient
|
|
85
|
-
- Name each self-dispatched worker with the `
|
|
85
|
+
- Name each self-dispatched worker with the `worker-` prefix, since that is the role the name marks and no controlling session has ever carried it
|
|
86
|
+
- Pass `-n` explicitly on every self-dispatch, since a launch that omits it leaves the client to derive a name and the fallback is a fragment of the session's own identifier
|
|
87
|
+
- Carry the dispatcher's `sessionId` in the launch prompt, since a worker owed two messages has no property on the roster that resolves a controller and a name goes stale inside the window it builds in
|
|
86
88
|
- Report each self-dispatch and the row it fired against loudly enough to follow, since a person no longer watches the launch step happen
|
|
87
89
|
- Stop dispatching once `## Run now` is empty or every row in it reads claimed, rather than waking again to fire on a board nobody is clearing
|
|
88
90
|
|
|
@@ -54,15 +54,31 @@ Name `<model>` on the launch, and pick it against the task rather than copying w
|
|
|
54
54
|
## Dispatch
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
claude --bg --model <model> -n "
|
|
57
|
+
claude --bg --model <model> -n "worker-<slug>" "Run /aitk:claude-worktree <type>/<slug>, then /aitk:claude-autoship <plan>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
`--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `aitk sessions list`, and `--model` overrides the inheritance the section above measured.
|
|
60
|
+
`--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `aitk sessions list`, and `--model` overrides the inheritance the section above measured. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
|
|
61
|
+
|
|
62
|
+
The prefix reads `worker-` because that is the role it marks. It read `orchestrator-` until 2026-08-31, and no controlling session ever carried it, so a worker filtering the roster for that string found a sibling or itself on every row. Nothing matches the prefix programmatically, which is what kept the rename down to three strings.
|
|
63
|
+
|
|
64
|
+
Read `<dispatcher-id>` with `aitk sessions list --self --json` and interpolate the `sessionId` that row carries. Carry the id rather than the name. A name is derived from whatever the session turned out to be doing, and across the 181 records stamping both fields, nine were renamed after launch at a median of 5.4 minutes and a maximum of 509. Three landed more than ten minutes in, which is inside the window a worker announces its pull request in, so a name written into the prompt is aimed at a send that happens after it goes stale.
|
|
65
|
+
|
|
66
|
+
Where the installed CLI answers `--self` with an unknown option, that flag is newer than the release the target holds. Read the `sessionId` from the record the client writes for this session under its configuration directory, and say which route answered so the reader knows whether the id was read or inferred.
|
|
61
67
|
|
|
62
68
|
The worktree call comes first and carries the branch as its argument, which is tier 0 of `claude-worktree` Step 2 and the only tier a caller can reach. `claude-autoship` Step 0 then finds the session already in a linked worktree and continues, which is a path it already documents. The autoship call carries `<plan>`, the same file this runbook already read to derive the branch, so its Step 1 takes it as the caller-supplied plan rather than re-deriving one from the slug the worker's branch happens to carry.
|
|
63
69
|
|
|
64
70
|
Naming the branch in prose instead was tried and closes nothing, because no tier of that ladder reads the prompt. A worker launched onto `main` cannot match tier 1, a board carrying more than one plan puts tier 2 out of reach, and tier 3 tells it to ask a person who is not there. Four workers took the right branch that way, by inference rather than by contract, which is the same judgment both live disagreements came from.
|
|
65
71
|
|
|
72
|
+
### What the brief may carry
|
|
73
|
+
|
|
74
|
+
The prompt carries pointers and standing context, and stops there. The branch and the plan stay arguments, because a skill resolves an argument through a documented ladder and reads no prose at all. What the prose reaches is the worker's judgment, so it holds only what a session has to weigh:
|
|
75
|
+
|
|
76
|
+
- Name the addressee and what it is owed, which the two message clauses above already do.
|
|
77
|
+
- Carry standing context this session holds that a cold one cannot derive, such as a constraint settled in conversation that never reached the plan.
|
|
78
|
+
- Leave out anything scope-shaped. A file list, a naming convention, or a check to run belongs in the plan, where the review reads it back against the diff.
|
|
79
|
+
|
|
80
|
+
The last bullet is the one under pressure, since the dispatch that first proved unattended work possible sent a prompt naming the task file, the likely files, the conventions, and the check to run. It shipped in 874 seconds and touched four files its task never named. Scope that arrives as prose is scope nothing verifies afterward.
|
|
81
|
+
|
|
66
82
|
Report the dispatch as loudly as the human-launch line it replaces: name the branch, the model, the task, and the session name, so a person reading the transcript can follow what fired without watching it happen.
|
|
67
83
|
|
|
68
84
|
## Fall back to the human
|
|
@@ -13,6 +13,8 @@ It also produces one output where two are needed. A page written to be worked th
|
|
|
13
13
|
|
|
14
14
|
A session that does record something records the wrong thing. It writes what was taught rather than what the learner retrieved, and a tally of errors carries none of the misconception a later session would work against.
|
|
15
15
|
|
|
16
|
+
The lesson then reaches nobody. It is a page carrying a stylesheet and a script, and the only thing a session hands over is a file path, which an editor preview opens with neither. The learner reads unstyled markup and takes it for the lesson, or opens nothing at all, and either way the session reports the lesson as delivered. A path is also the wrong unit once a workspace holds several pages, since the learner wants the one they are on rather than the folder it sits in.
|
|
17
|
+
|
|
16
18
|
The durable half then has nowhere to go. A reference page and a glossary carry no learner and are ordinary reference prose, so they belong wherever the project already keeps prose on that subject, and a workspace holding them is a gitignored folder one person reads. A session moving them by hand picks a destination from the reader's activity rather than from who owns the subject, drops a page into a corpus without the source line that corpus requires, and has nothing stopping it from carrying a lesson across.
|
|
17
19
|
|
|
18
20
|
Two failures land specifically on where the folder sits. A workspace resolved against the current directory forks into a copy per linked worktree, so the learning records split and no session sees the whole history. A body naming only the destination path reports success and loses the write, because the file-editing tools refuse a main-root path from a linked worktree and offer a worktree copy instead.
|
|
@@ -28,6 +30,7 @@ Two failures land specifically on where the folder sits. A workspace resolved ag
|
|
|
28
30
|
- Split the output by lifetime, sending the worked-through half to lessons and the looked-up half to reference pages in the format the authoring gates read
|
|
29
31
|
- Record the wrong answer itself rather than the count, since that is what the next session places the learner from
|
|
30
32
|
- Report progress against the mission's success lines, so a mission can be called finished
|
|
33
|
+
- Hand the learner a link that opens the workspace in a real browser on every run, reading it back from the verb that served it rather than composing one, since the port a preview lands on is not the port it asked for
|
|
31
34
|
- Propose a destination for each durable page by who owns its subject, and wait for the operator rather than routing on the session's own reading
|
|
32
35
|
- Hand each confirmed page off through a named file of its own, so one skill owns the durable writes and no other producer's unread work is discarded with it
|
|
33
36
|
- Name what the destination expects that the page does not carry yet, since the workspace runs none of the gates the destination does
|
|
@@ -113,6 +113,18 @@ aitk teach glossary <topic> --json \
|
|
|
113
113
|
|
|
114
114
|
Follow `${CLAUDE_SKILL_DIR}/references/lesson-craft.md` for what makes a lesson worth returning to. Keep every quiz answer the same length, so formatting leaks no clue about which one is correct.
|
|
115
115
|
|
|
116
|
+
### Hand over a link, never a path
|
|
117
|
+
|
|
118
|
+
A lesson is a page carrying a stylesheet and a script, and an editor preview opens it with neither, so a path alone delivers unstyled markup that reads as the lesson. Serve the teach root and give the learner a link they can click:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
aitk serve .claude/teach --entry <nn>-<topic>/index.html --json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Start it in the background so the session keeps going, and read `url` off the record rather than composing one. The verb walks past a port already in use, so the port it took is exactly the half a guessed URL gets wrong. Report the refusal and its `reason` when `ok` is false, and report it rather than proceeding silently when the verb does not resolve at all, which is an installed CLI predating it.
|
|
125
|
+
|
|
126
|
+
Do this on every run that opens or resumes a workspace, including one that writes no lesson, since the learner's route into what is already there is the same link.
|
|
127
|
+
|
|
116
128
|
## Step 5: record what happened
|
|
117
129
|
|
|
118
130
|
Write `learning-records/<nnnn>-<slug>.md` before the session ends, carrying the lessons covered, what the learner retrieved unaided, what they got wrong with the wrong answer itself, and what to revisit.
|
|
@@ -165,9 +177,14 @@ Lesson: .claude/teach/<nn>-<topic>/lessons/<nnnn>-<slug>.html
|
|
|
165
177
|
Reference: .claude/teach/<nn>-<topic>/reference/<slug>.md
|
|
166
178
|
Record: .claude/teach/<nn>-<topic>/learning-records/<nnnn>-<slug>.md
|
|
167
179
|
Progress: <n> of <m> success lines met
|
|
180
|
+
Open: [<the url the serve verb reported>](<the same url>)
|
|
168
181
|
```
|
|
169
182
|
|
|
170
|
-
Omit the reference line where the lesson produced no durable page.
|
|
183
|
+
Omit the reference line where the lesson produced no durable page. The open line is the one line that is never omitted, since it is the only route the learner has into the page, and it carries what `aitk serve` reported rather than a URL composed here. Where the verb refused, that line names the refusal instead of a link.
|
|
184
|
+
|
|
185
|
+
Write that line as a markdown link carrying the URL as both its text and its target, rather than as a bare URL and never inside backticks. A code span renders as text the reader has to select and copy, which is the one thing the line exists to save them, and the path rule the project states governs a file path rather than a URL.
|
|
186
|
+
|
|
187
|
+
Emit every path from the project root, in the form the project's instruction file sets.
|
|
171
188
|
|
|
172
189
|
A promotion pass reports its own shape instead, one line per page the operator confirmed and one naming the handoff:
|
|
173
190
|
|
|
@@ -41,10 +41,47 @@ nothing else.
|
|
|
41
41
|
- Send a block out as a message before it becomes an interactive prompt. A session already waiting on input never reaches the tool round that drains an inbound message, so a relayed answer arrives under the open question and changes nothing.
|
|
42
42
|
- Send nothing on progress. A worker reporting progress rebuilds, on this side of the channel, the poll the announcement retired on the other.
|
|
43
43
|
|
|
44
|
-
Address the session the launch named.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
Address the session the launch named. It names a `sessionId` rather than a name,
|
|
45
|
+
so read `aitk sessions list --json`, find the row carrying that id, and send to
|
|
46
|
+
the `name` on it. Resolve that name at the moment of sending rather than at
|
|
47
|
+
launch, since a name is derived from what a session turned out to be doing and
|
|
48
|
+
has gone stale inside the hour that a build takes.
|
|
49
|
+
|
|
50
|
+
Check that name against the agent listing before sending it. A name is not
|
|
51
|
+
unique, and the roster carries no field that separates two live sessions holding
|
|
52
|
+
one, so the resolution can end on a string that reaches the wrong session. Send
|
|
53
|
+
the name bare where the listing shows one row under it. Where it shows more than
|
|
54
|
+
one, complete the address with the `[ref]` that listing prints beside each row
|
|
55
|
+
rather than sending to the name alone, which lands on whichever row the channel
|
|
56
|
+
resolves first. Two sessions differing only by a trailing ` (3)` is the shape
|
|
57
|
+
this meets in practice, and one of them being a controller is what makes the
|
|
58
|
+
wrong pick silent.
|
|
59
|
+
|
|
60
|
+
Ask the operator when the launch named nobody and a person is there to answer.
|
|
61
|
+
Put the candidate rows to them through the structured question surface, so they
|
|
62
|
+
pick a row rather than recalling a name. The ask halts the build, and the halt
|
|
63
|
+
is the cheaper error: a worker that cannot reach its controller has nothing
|
|
64
|
+
useful to do with the message it owes, where sending to the wrong session
|
|
65
|
+
reports success and loses it. A halt is only as visible as whatever watches for
|
|
66
|
+
one, so say what you are waiting on in the same turn you stop, and expect a
|
|
67
|
+
controller running no stall detector to find the question only when it next
|
|
68
|
+
looks.
|
|
69
|
+
|
|
70
|
+
Infer only where no operator is present. Read `aitk sessions list --json` and
|
|
71
|
+
take the sessions holding no feature branch as the candidates, since a
|
|
72
|
+
controlling session holds none. Say the addressee was inferred so the reader can
|
|
73
|
+
correct it. Never filter that roster by name prefix: every self-dispatched
|
|
74
|
+
worker is named `worker-<slug>`, so a prefix scan returns a sibling or this
|
|
75
|
+
session itself, which is the defect that sent messages owed to a controller
|
|
76
|
+
somewhere else.
|
|
77
|
+
|
|
78
|
+
Report a resolution that returns nothing rather than falling back to a guess.
|
|
79
|
+
The roster and the send channel enumerate different populations in both
|
|
80
|
+
directions, measured at one moment: a live background session sat on the roster
|
|
81
|
+
that the agent listing did not carry, and nine sessions were addressable there
|
|
82
|
+
with no roster row at all. Those nine were driving through Remote Control, which
|
|
83
|
+
writes no local process record, so a controller working from a phone is exactly
|
|
84
|
+
the case this read answers nothing for.
|
|
48
85
|
|
|
49
86
|
## Refusing is part of the job
|
|
50
87
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -63,8 +63,17 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
|
|
|
63
63
|
| `aitk audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
|
|
64
64
|
| `aitk inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
|
|
65
65
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
66
|
+
| `aitk serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
|
|
66
67
|
| `aitk upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
|
|
67
68
|
|
|
69
|
+
`aitk serve` ships and drives no browser, which is what separates it from the two that do. A generated page loses its stylesheet and its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
|
|
70
|
+
|
|
71
|
+
A port already in use is the ordinary case rather than a refusal, so it walks forward to the next free one and reports which it took. That is why a caller reads `url` off the `--json` record instead of composing one from the port it asked for. Only contention is walked past. Any other bind failure refuses as `bind-failed` carrying the error's code, rather than being retried twenty times and reported as a range being full, which names a cause nothing checked.
|
|
72
|
+
|
|
73
|
+
A request naming a directory is redirected to its trailing-slash form rather than answered in place. A browser resolves a relative asset against the last slash of the URL it is on, so answering `/lesson` directly leaves the page asking for `/course.css` instead of `/lesson/course.css`, and it renders unstyled through the server that exists to prevent exactly that.
|
|
74
|
+
|
|
75
|
+
Containment is tested after symlinks are followed rather than on the path as written, and the test sits immediately before the read rather than beside the request that produced it. Resolving a request lexically clears a link pointing outside the served root, and this repository is a live instance of that shape, since `claude/standards` and `claude/snippets` are links out of `claude/`. Position is what makes the property hold: a directory request appends its index after the request path has been checked, so a check placed earlier leaves that index untested. An `--entry` that escapes the root refuses with `no-entry` before a port is taken, because `url` is the field a caller hands to a reader.
|
|
76
|
+
|
|
68
77
|
`aitk demo` is the second browser command and the one that ships, since its purpose is running in a target rather than regenerating what this repository commits. It needs a browser binary the package does not carry, installed once with `bunx playwright install chromium`.
|
|
69
78
|
|
|
70
79
|
`aitk inventory` is the third and takes the same answer for the same reason. It reads `inventory.toml` at the project root for its base URL, its routes, and the element query each subject runs over, so what it walks comes from the project rather than from the toolkit. It reports how many different answers a site gives for one property and never gates, because whether five focus rings across four routes is a defect is a judgment. A missing server and an unmatched query are both refusals rather than empty listings, since a listing with no rows reads as one consistent answer.
|
package/docs/agents/counts.md
CHANGED
|
@@ -59,7 +59,7 @@ The article gate is looser than the verb list and carries a real cost. Re-runnin
|
|
|
59
59
|
|
|
60
60
|
A delta phrased as a transition (`from fourteen to fifteen`), a fraction (`thirteen of sixteen`), and a total reached through an indirect noun (`denominator of sixty-one shipped skills`) are all catalog-size claims this corpus carries, and none matches the trigger shape this reads. Each stays a known gap. The false-positive rate that gated closing them has a measurement behind it now, and what it showed is that the widening these three need is the one that costs a false positive rather than the one that does not.
|
|
61
61
|
|
|
62
|
-
A second figure in a sentence whose first figure already matched is a fourth gap and a structural one, since one match is taken per catalog per sentence. `authors 70 rules under governance/rules/ and consumes
|
|
62
|
+
A second figure in a sentence whose first figure already matched is a fourth gap and a structural one, since one match is taken per catalog per sentence. `authors 70 rules under governance/rules/ and consumes 62 into .claude/rules/` is read for its 70 alone, which is correct here because the two figures name different populations, and a sentence stating one catalog twice would go unread the same way.
|
|
63
63
|
|
|
64
64
|
A calendar date (`2026-08-21`) or a backticked commit reference in the same sentence reads the whole sentence past, since that is how this corpus already marks a figure as a historical record rather than a live claim. `.claude/ARCHITECTURE.md` and the context entries carry a figure this way deliberately, and every one of them stays correct forever.
|
|
65
65
|
|
package/docs/agents/index.md
CHANGED
|
@@ -30,7 +30,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
30
30
|
- [Routing report](routing.md): Reading per CLAUDE.md section how many bullets name a path, what counts as naming one, when a rule counts as covering it, the two refusals, and why the verb reports rather than gates
|
|
31
31
|
- [Sandbox](sandbox.md): Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
|
|
32
32
|
- [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
|
|
33
|
-
- [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds,
|
|
33
|
+
- [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, and what each session surface can see
|
|
34
34
|
- [Skill audit](skills-audit.md): Measuring both skill corpora against standards/skill.md, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
|
|
35
35
|
- [Citation reach](skills-reach.md): Reporting the skill bodies that cite a path no target project receives, which corpus the verb reads, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
|
|
36
36
|
- [Standard success criteria](standards-audit.md): Reading the corpus against the Success criterion gate, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
|
package/docs/agents/sessions.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Sessions
|
|
3
|
-
description: Resolving live peer sessions to the worktree and branch each holds,
|
|
3
|
+
description: Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, and what each session surface can see
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sessions
|
|
@@ -14,6 +14,7 @@ aitk sessions list
|
|
|
14
14
|
aitk sessions list --json
|
|
15
15
|
aitk sessions list --branch feat/parser --json
|
|
16
16
|
aitk sessions list --branch chore/agents --repository ../caret --json
|
|
17
|
+
aitk sessions list --self --json
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
| Option | Behavior |
|
|
@@ -21,10 +22,11 @@ aitk sessions list --branch chore/agents --repository ../caret --json
|
|
|
21
22
|
| `--json` | Add a machine-readable record on stdout |
|
|
22
23
|
| `--branch <name>` | Report the sessions holding this branch |
|
|
23
24
|
| `--repository <path>` | Answer about this project rather than the working one |
|
|
25
|
+
| `--self` | Report the caller's own row |
|
|
24
26
|
|
|
25
27
|
It reads and never writes. The question it answers is which session to address when work has to reach the one holding a given branch, which a session listing cannot answer on its own.
|
|
26
28
|
|
|
27
|
-
Exit codes: `0` the roster was read, `1` refused. The refusal carries a `reason` of `no-registry` or `no-
|
|
29
|
+
Exit codes: `0` the roster was read, `1` refused. The refusal carries a `reason` of `no-registry`, `no-repository`, `no-self-identity`, or `no-self-row`.
|
|
28
30
|
|
|
29
31
|
An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `reason` rather than the exit when a skill consumes this.
|
|
30
32
|
|
|
@@ -52,6 +54,29 @@ The ref read covers the local head and the remote-tracking ref, which means it s
|
|
|
52
54
|
|
|
53
55
|
Two flags say which reading came up short. `sessionsReadable` is `false` when the session roster could not be read, and `refsReadable` is `false` when the ref read failed. Either one leaves `claimed` covering the readings around it alone, so a `false` there is a report that ran short of evidence rather than a report that the branch is clear. They stay separate fields because a caller told the roster failed goes and looks at the roster, and folding both into one flag would send it to the wrong place.
|
|
54
56
|
|
|
57
|
+
## Which row is the caller
|
|
58
|
+
|
|
59
|
+
`--self` narrows the report to the row belonging to the session making the call. A dispatcher reads it to learn the `sessionId` it carries into a launch, because the roster returns every field on every row and marks none of them as the caller.
|
|
60
|
+
|
|
61
|
+
Three identifier namespaces reach a session and two of them join to a row. Four variables spell them:
|
|
62
|
+
|
|
63
|
+
- `CLAUDE_CODE_SESSION_ID` holds the roster's own `sessionId`, and is read first because it survives a rename.
|
|
64
|
+
- `CLAUDE_PID` holds the caller's process id, which every row already carries as `pid`.
|
|
65
|
+
- `CLAUDE_CODE_MESSAGING_SOCKET` spells that same pid in its basename. It is read last, since the spelling is a client convention rather than a published interface and a client that moves the socket drops this rung alone.
|
|
66
|
+
- `CLAUDE_CODE_HOST_SESSION_ID` is never read. It carries a `local_`-prefixed value from the harness namespace that matches no row, and it is the variable a reader searching the environment for a session id finds first.
|
|
67
|
+
|
|
68
|
+
The read refuses rather than returning an empty roster, on the same ground as `no-registry`. A `no-self-identity` refusal means the environment stated none of the three, which is a client identifying its sessions some other way. A `no-self-row` refusal means it stated one and no live row carries it, which is what a session running outside a local process gets.
|
|
69
|
+
|
|
70
|
+
## What each surface can see
|
|
71
|
+
|
|
72
|
+
The roster this verb reads and the channel a session sends messages on enumerate different populations, and neither contains the other. Read at one moment, `aitk sessions list` returned 6 rows against the 13 peers plus caller the agent listing reported. The roster held a background session at confirmed confidence on two reads a minute apart that the listing never carried, so a live session sat here with no way to reach it. Nine Remote Control sessions ran the other way, reachable there with no row here at all, since they leave no local process for the registry to record.
|
|
73
|
+
|
|
74
|
+
That bounds what a carried identifier buys. Resolving a `sessionId` to a name here can produce a name the send channel rejects, and `--self` answers nothing for a controller driving from Remote Control, which is the operator working from their phone rather than an edge case. A caller that cannot resolve an addressee reports the failed resolution instead of falling back to a guess.
|
|
75
|
+
|
|
76
|
+
The `name` field is what both surfaces agree on, and that agreement is what makes the resolution work. `SendMessage` takes a name and carries no identifier parameter, so the carry runs id to name to send rather than sending an id anywhere.
|
|
77
|
+
|
|
78
|
+
Agreement is not uniqueness, and the last hop is where that bites. Two live sessions can hold one name, the roster carries no field that separates them, and a send to the bare name reaches whichever the channel resolves first. The agent listing prints a ref beside each row and the roster does not, so a name resolving to more than one live row is completed from that listing rather than from anything this verb returns.
|
|
79
|
+
|
|
55
80
|
## Why the verb exists
|
|
56
81
|
|
|
57
82
|
A session listing reports a name, a kind, a status, and how long each session has been running. None of those names a branch. Resolving a branch to a session therefore meant ordering the roster by start time and matching it against the order the worktrees were created, which is an inference that fails whenever two sessions start inside the same minute.
|
package/docs/agents/teach.md
CHANGED
|
@@ -129,6 +129,18 @@ Each `quiz` entry carries `order`, the authored option indices in presentation o
|
|
|
129
129
|
|
|
130
130
|
The order is drawn here rather than instructed, and that is the point of the verb. An author told to vary the position still varies it by judgment, and the judgment settles on the first slot, which is the defect this design departs from. The draw is uniform over the options, so the position carries no information about which answer is correct.
|
|
131
131
|
|
|
132
|
+
## Opening a workspace
|
|
133
|
+
|
|
134
|
+
No `aitk teach` verb serves the workspace. `aitk serve` does, taking the teach root as its directory and the workspace contents page as its entry:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
aitk serve .claude/teach --entry 03-fde-system-design/index.html --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
It stays general rather than becoming `aitk teach serve`, because nothing about serving a directory is specific to a learning workspace, and the same verb carries a slide render and a design preview.
|
|
141
|
+
|
|
142
|
+
Read `url` off the record rather than building one from the port that was asked for. The verb walks forward past a port already in use, which is routine when a second workspace is already open, and the port it took is the one thing a composed URL gets wrong.
|
|
143
|
+
|
|
132
144
|
## Refusal reasons
|
|
133
145
|
|
|
134
146
|
| Reason | Raised when |
|
|
@@ -9,5 +9,5 @@ paths:
|
|
|
9
9
|
## Authority
|
|
10
10
|
|
|
11
11
|
- 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`.
|
|
12
|
-
-
|
|
12
|
+
- Follow the glossary standard 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 with `aitk standards glossary`.
|
|
13
13
|
- 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.
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Require a
|
|
2
|
+
description: Require a capture against a running preview after a rendered surface changes
|
|
3
3
|
paths:
|
|
4
4
|
- '**/routes/**/*.{tsx,jsx,vue,svelte,astro}'
|
|
5
5
|
- '**/pages/**/*.{tsx,jsx,vue,svelte,astro}'
|
|
6
6
|
- '**/app/**/page.{tsx,jsx}'
|
|
7
|
+
- '**/*.html'
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Surface capture standards
|
|
10
11
|
|
|
12
|
+
## What a surface is
|
|
13
|
+
|
|
14
|
+
- A surface is anything the project renders for a person to look at. A framework route is one. A page the project generates is another, whoever generates it.
|
|
15
|
+
- Judge a generated page by the same rule as a route. Reading its markup reports nothing about how it composes.
|
|
16
|
+
|
|
11
17
|
## When to capture
|
|
12
18
|
|
|
13
|
-
- Run
|
|
19
|
+
- Run the project's capture command after changing what a surface renders. Report it rather than proceeding silently when the project has none.
|
|
14
20
|
- Capture against a running preview server. Do not capture against a dev server.
|
|
15
|
-
- Capture every theme the
|
|
21
|
+
- Capture every theme the surface ships. Do not capture the default theme alone.
|
|
22
|
+
- Capture again after fixing a defect a capture found. A repair inside a shared stylesheet can cancel a rule written earlier in the same file.
|
|
16
23
|
|
|
17
24
|
## What a capture covers
|
|
18
25
|
|
|
19
26
|
- Capture the full page at the viewport its case declares. Do not capture a component in isolation.
|
|
20
|
-
-
|
|
21
|
-
-
|
|
27
|
+
- Drive a state a screenshot cannot reach. An initial render reports nothing about a menu that opens, an answer that is chosen, or a rail that tracks scrolling.
|
|
28
|
+
- Add a case to the capture record when adding a surface.
|
|
29
|
+
- Remove a surface's case in the change that removes the surface.
|
|
22
30
|
|
|
23
31
|
## Sharing a capture
|
|
24
32
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { register as docs } from '@/commands/docs'
|
|
|
15
15
|
import { register as design } from '@/commands/design'
|
|
16
16
|
import { register as slides } from '@/commands/slides'
|
|
17
17
|
import { register as capture } from '@/commands/capture'
|
|
18
|
+
import { register as serve } from '@/commands/serve'
|
|
18
19
|
import { register as demo } from '@/commands/demo'
|
|
19
20
|
import { register as inventory } from '@/commands/inventory'
|
|
20
21
|
import { register as feedback } from '@/commands/feedback'
|
|
@@ -61,6 +62,7 @@ function showHelp(): void {
|
|
|
61
62
|
`${GREY}│${NC} design [cmd] ${GREY}# Design system commands (render)${NC}`,
|
|
62
63
|
`${GREY}│${NC} slides [cmd] ${GREY}# Slide deck commands (render, list)${NC}`,
|
|
63
64
|
`${GREY}│${NC} capture [source] ${GREY}# Render HTML capture sources to PNG${NC}`,
|
|
65
|
+
`${GREY}│${NC} serve [dir] ${GREY}# Serve a directory over localhost and print the preview link${NC}`,
|
|
64
66
|
`${GREY}│${NC} demo [cmd] ${GREY}# Record a running app (compile, run)${NC}`,
|
|
65
67
|
`${GREY}│${NC} inventory [subj] ${GREY}# Report one computed property across every route${NC}`,
|
|
66
68
|
`${GREY}│${NC} feedback ${GREY}# Write toolkit feedback from stdin to .claude/review/feedback/${NC}`,
|
|
@@ -108,6 +110,7 @@ function showHelp(): void {
|
|
|
108
110
|
`${GREY}│${NC} aitk slides render`,
|
|
109
111
|
`${GREY}│${NC} aitk slides list --json`,
|
|
110
112
|
`${GREY}│${NC} aitk capture assets/install.html`,
|
|
113
|
+
`${GREY}│${NC} aitk serve .claude/teach`,
|
|
111
114
|
`${GREY}│${NC} aitk inventory focus --json`,
|
|
112
115
|
`${GREY}│${NC} pbpaste | aitk feedback`,
|
|
113
116
|
`${GREY}│${NC} aitk transcripts https://youtu.be/VIDEO_ID`,
|
|
@@ -160,6 +163,7 @@ docs(program)
|
|
|
160
163
|
design(program)
|
|
161
164
|
slides(program)
|
|
162
165
|
capture(program)
|
|
166
|
+
serve(program)
|
|
163
167
|
demo(program)
|
|
164
168
|
inventory(program)
|
|
165
169
|
feedback(program)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { relative } from 'node:path'
|
|
3
|
+
import { DEFAULT_PORT, type ServeOutcome, startServer } from '@/serve/static'
|
|
4
|
+
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIR = '.'
|
|
7
|
+
|
|
8
|
+
interface ServeCommandOptions {
|
|
9
|
+
readonly entry?: string
|
|
10
|
+
readonly json?: boolean
|
|
11
|
+
readonly port?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function register(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('serve')
|
|
17
|
+
.description('Serve a directory over localhost and print the preview link')
|
|
18
|
+
.argument('[dir]', 'Directory to serve', DEFAULT_DIR)
|
|
19
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
20
|
+
.option('--port <number>', `Port to try first, default ${DEFAULT_PORT}`)
|
|
21
|
+
.option('--entry <path>', 'Page the printed link opens, default index.html')
|
|
22
|
+
.option('--json', 'Emit a machine-readable record on stdout')
|
|
23
|
+
.addHelpText(
|
|
24
|
+
'after',
|
|
25
|
+
[
|
|
26
|
+
'',
|
|
27
|
+
'Exit codes:',
|
|
28
|
+
' 0 the server stopped after running',
|
|
29
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
30
|
+
'',
|
|
31
|
+
'The server binds 127.0.0.1 and nothing else, and it sends no cache',
|
|
32
|
+
'headers, so an edited stylesheet is never served stale. A port in use',
|
|
33
|
+
'is not a failure: the next free one is taken and the link says which.',
|
|
34
|
+
'',
|
|
35
|
+
'It runs until interrupted. A session wanting the link without waiting',
|
|
36
|
+
'starts it in the background and reads the record off stdout.',
|
|
37
|
+
'',
|
|
38
|
+
'Examples:',
|
|
39
|
+
' aitk serve .claude/teach',
|
|
40
|
+
' aitk serve .claude/teach --entry 03-fde-system-design/index.html',
|
|
41
|
+
' aitk serve dist --port 4000 --json',
|
|
42
|
+
'',
|
|
43
|
+
].join('\n'),
|
|
44
|
+
)
|
|
45
|
+
.action(async (dir: string, opts: ServeCommandOptions) => {
|
|
46
|
+
process.exitCode = await runServe(dir, opts)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runServe(
|
|
51
|
+
dir: string,
|
|
52
|
+
opts: ServeCommandOptions,
|
|
53
|
+
): Promise<number> {
|
|
54
|
+
const emitJson = opts.json ?? false
|
|
55
|
+
|
|
56
|
+
const port = parsePort(opts.port)
|
|
57
|
+
if (port === undefined) {
|
|
58
|
+
return report(
|
|
59
|
+
{ ok: false, reason: 'no-port', detail: `${opts.port} is not a port` },
|
|
60
|
+
emitJson,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const outcome = startServer(dir, { port, entry: opts.entry })
|
|
65
|
+
const code = report(outcome, emitJson)
|
|
66
|
+
if (!outcome.ok) return code
|
|
67
|
+
|
|
68
|
+
await waitForInterrupt(outcome.stop)
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The relative path where it stays inside the working directory, and the
|
|
74
|
+
* absolute one where it climbs out. A run from a linked worktree serving the
|
|
75
|
+
* main root reports `../../teach`, which names the directory without giving a
|
|
76
|
+
* reader anything they can open.
|
|
77
|
+
*/
|
|
78
|
+
function displayPath(root: string): string {
|
|
79
|
+
const near = relative(process.cwd(), root)
|
|
80
|
+
if (near === '') return '.'
|
|
81
|
+
return near.startsWith('..') ? root : near
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Undefined for a value that is not a port, distinct from an absent flag,
|
|
86
|
+
* which takes the default. A `Number` of a typo is `NaN`, and passing that on
|
|
87
|
+
* asks the runtime to bind a port nobody named.
|
|
88
|
+
*/
|
|
89
|
+
function parsePort(raw: string | undefined): number | undefined {
|
|
90
|
+
if (raw === undefined) return DEFAULT_PORT
|
|
91
|
+
const value = Number(raw)
|
|
92
|
+
if (!Number.isInteger(value) || value < 0 || value > 65535) return undefined
|
|
93
|
+
return value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function report(outcome: ServeOutcome, emitJson: boolean): number {
|
|
97
|
+
if (!outcome.ok) {
|
|
98
|
+
if (emitJson) {
|
|
99
|
+
process.stdout.write(
|
|
100
|
+
`${JSON.stringify({ ok: false, reason: outcome.reason, detail: outcome.detail })}\n`,
|
|
101
|
+
)
|
|
102
|
+
return 1
|
|
103
|
+
}
|
|
104
|
+
intro('aitk serve')
|
|
105
|
+
logError(outcome.detail)
|
|
106
|
+
outro()
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (emitJson) {
|
|
111
|
+
process.stdout.write(
|
|
112
|
+
`${JSON.stringify({
|
|
113
|
+
ok: true,
|
|
114
|
+
root: outcome.root,
|
|
115
|
+
host: outcome.host,
|
|
116
|
+
port: outcome.port,
|
|
117
|
+
entry: outcome.entry,
|
|
118
|
+
url: outcome.url,
|
|
119
|
+
entryExists: outcome.entryExists,
|
|
120
|
+
})}\n`,
|
|
121
|
+
)
|
|
122
|
+
return 0
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
intro('aitk serve')
|
|
126
|
+
logStep('Serving')
|
|
127
|
+
logInfo(displayPath(outcome.root))
|
|
128
|
+
logStep('Open')
|
|
129
|
+
logInfo(outcome.url)
|
|
130
|
+
|
|
131
|
+
if (!outcome.entryExists) {
|
|
132
|
+
logStep('No entry page')
|
|
133
|
+
logWarn(
|
|
134
|
+
`${outcome.entry} is not in that directory, so the link opens a 404`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
logStep('Stop')
|
|
139
|
+
logInfo('Ctrl-C')
|
|
140
|
+
return 0
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolves when the process is asked to stop. The frame is closed here rather
|
|
145
|
+
* than in the reporter, because the run is the serving rather than the start,
|
|
146
|
+
* and closing at start would print the frame's end while the server ran on.
|
|
147
|
+
*/
|
|
148
|
+
function waitForInterrupt(stop: () => Promise<void>): Promise<void> {
|
|
149
|
+
return new Promise((settle) => {
|
|
150
|
+
const finish = () => {
|
|
151
|
+
void stop().then(() => {
|
|
152
|
+
outro()
|
|
153
|
+
settle()
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
process.once('SIGINT', finish)
|
|
157
|
+
process.once('SIGTERM', finish)
|
|
158
|
+
})
|
|
159
|
+
}
|