@mmerterden/multi-agent-pipeline 16.14.0 → 16.16.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/CHANGELOG.md +32 -0
- package/README.md +5 -4
- package/README.tr.md +5 -4
- package/docs/adr/0010-own-code-graph.md +6 -0
- package/docs/adr/0011-dormant-ci.md +87 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +2 -2
- package/docs/ecosystem.md +5 -5
- package/install/_common.mjs +0 -1
- package/install/_dev-only-files.mjs +1 -0
- package/install/claude.mjs +14 -4
- package/install/index.mjs +8 -1
- package/package.json +6 -5
- package/pipeline/commands/multi-agent/help/SKILL.md +44 -39
- package/pipeline/commands/multi-agent/steer/SKILL.md +109 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +5 -7
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/phases/operations.md +23 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +4 -0
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +1 -1
- package/pipeline/multi-agent-refs/phases.md +38 -0
- package/pipeline/preferences-template.json +2 -10
- package/pipeline/rules/outside-the-pipeline.md +1 -1
- package/pipeline/schemas/agent-state.schema.json +35 -0
- package/pipeline/schemas/analysis-spec.schema.json +8 -2
- package/pipeline/schemas/conventions-output.schema.json +2 -13
- package/pipeline/scripts/build-references.mjs +22 -6
- package/pipeline/scripts/code-graph-rules/android.json +4 -21
- package/pipeline/scripts/code-graph-rules/go.json +4 -19
- package/pipeline/scripts/code-graph-rules/node.json +4 -21
- package/pipeline/scripts/feedback-send.mjs +3 -1
- package/pipeline/scripts/gate-linux.sh +62 -0
- package/pipeline/scripts/localize-commands.mjs +1 -2
- package/pipeline/scripts/pre-push-check.sh +6 -4
- package/pipeline/scripts/usage-report.mjs +16 -14
- package/pipeline/scripts/validate-analysis-doc.mjs +46 -27
- package/pipeline/scripts/validate-analysis.mjs +7 -1
- package/pipeline/scripts/validate-complaint-doc.mjs +24 -8
- package/pipeline/scripts/write-state.mjs +71 -12
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +111 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Queue an instruction for a task that is already running. It is applied at the next phase boundary, not mid-phase. Use when a run is going the wrong way and killing it would throw away good work."
|
|
3
|
+
description-tr: "Zaten koşmakta olan bir göreve talimat bırakır. Talimat faz ortasında değil, bir sonraki faz sınırında uygulanır. Koşu yanlış yöne gidiyorsa ve kill etmek iyi işi de çöpe atacaksa kullanılır."
|
|
4
|
+
argument-hint: "#id \"<instruction>\" - e.g. #3 \"the field is called web, not frontend\". With no instruction, you are asked for it."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# multi-agent steer - correct a run without stopping it
|
|
8
|
+
|
|
9
|
+
**Input**: $ARGUMENTS
|
|
10
|
+
|
|
11
|
+
Leave one instruction for a running task. The next phase reads it before it
|
|
12
|
+
starts work, applies it, and marks it consumed.
|
|
13
|
+
|
|
14
|
+
This exists because the alternative was losing the run. `kill` stops a task and
|
|
15
|
+
deletes its worktree; `resume` picks a stopped one back up. Neither helps while
|
|
16
|
+
a phase is in flight, so a correction that arrived mid-run - "that field is
|
|
17
|
+
called `web`, not `frontend`", "keep the analysis, drop the rest" - had nowhere
|
|
18
|
+
to go, and the run carried on in the wrong direction until it finished.
|
|
19
|
+
|
|
20
|
+
**Not a second prompt.** One instruction is queued at a time. Steering a task
|
|
21
|
+
that already has an unconsumed instruction replaces it, after showing you what
|
|
22
|
+
is being replaced.
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
1. **Find the task** - parse `#N` or `{JIRA-KEY}-XXXXX` from the argument,
|
|
27
|
+
the same way `kill` and `resume` do. Locate its `agent-state.json`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
find {repo}/.worktrees/ -name "agent-state.json" -maxdepth 2
|
|
31
|
+
find $HOME/.claude/logs/multi-agent -maxdepth 4 -name agent-state.json -path '*/artifacts/*'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Not found → `ERR: no task #N. '/multi-agent:status' lists what is running.`
|
|
35
|
+
|
|
36
|
+
2. **Check it can still be steered** - read `status` and `currentPhase`:
|
|
37
|
+
|
|
38
|
+
| State | What to do |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `in_progress` | Queue it. This is the case the command is for. |
|
|
41
|
+
| `paused` / `failed` | Say the task is not running, and that `/multi-agent:resume #N` will re-enter with the instruction applied at that phase's entry. Queue it. |
|
|
42
|
+
| `complete` | Refuse. Nothing will read it. Point at `/multi-agent` for a follow-up run. |
|
|
43
|
+
|
|
44
|
+
`currentPhase` is 7 and status is `in_progress` → warn that Phase 7 is the
|
|
45
|
+
last one, so an instruction queued now may never be consumed.
|
|
46
|
+
|
|
47
|
+
3. **Read the instruction** - from the argument, or ask for it when the
|
|
48
|
+
argument carries only an id. Verbatim, up to 4000 characters. Do not
|
|
49
|
+
summarize or rewrite it: the phase that consumes it needs the user's own
|
|
50
|
+
words, and a paraphrase is where the meaning goes.
|
|
51
|
+
|
|
52
|
+
4. **Show what will be queued, and ask**:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Steer #3 ({JIRA-KEY}-12345, Phase 3 Dev, in_progress)
|
|
56
|
+
|
|
57
|
+
"the field is called web, not frontend"
|
|
58
|
+
|
|
59
|
+
Applied at the entry to Phase 4. The current phase finishes as it is.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Already carrying an unconsumed `pendingSteer` → print the old text above the
|
|
63
|
+
new one and ask whether to replace it.
|
|
64
|
+
|
|
65
|
+
5. **Write it** - through the state writer, never by editing the file, because
|
|
66
|
+
the running task is writing to it too. `$STATE_FILE` is the path from step 1
|
|
67
|
+
and `$INSTRUCTION` the text from step 3:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
printf '{"pendingSteer":{"text":%s,"at":"%s","appliedAt":null,"appliedPhase":null}}' \
|
|
71
|
+
"$(node -e 'process.stdout.write(JSON.stringify(process.argv[1]))' "$INSTRUCTION")" \
|
|
72
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
73
|
+
| node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The instruction is JSON-encoded by `node -e`, not by hand: it is arbitrary
|
|
77
|
+
user text, and a quote or newline in it would otherwise produce invalid JSON
|
|
78
|
+
or, worse, a payload that merges into fields nobody meant to touch.
|
|
79
|
+
|
|
80
|
+
Exit 2 (lock timeout) → the task is mid-write. Retry once, then report it
|
|
81
|
+
rather than forcing the write.
|
|
82
|
+
|
|
83
|
+
6. **Confirm**: `🧭 Steer queued for #N - applies at the entry to Phase {N+1}`
|
|
84
|
+
|
|
85
|
+
## What the phase does with it
|
|
86
|
+
|
|
87
|
+
Phase entry, before any work: read a `pendingSteer` that has no `appliedAt`.
|
|
88
|
+
When present, apply it to that phase's context, set `appliedAt` and
|
|
89
|
+
`appliedPhase` - which is what stops it being read a second time - and write a
|
|
90
|
+
`Steer applied` line into `agent-log.md`. The record itself stays, so the run
|
|
91
|
+
keeps the trace of what was asked and when. The contract lives in
|
|
92
|
+
`$HOME/.claude/multi-agent-refs/phases.md` under "Phase entry - pending
|
|
93
|
+
steer" (the file has a second, unrelated "Phase entry" line inside the tracker
|
|
94
|
+
block).
|
|
95
|
+
|
|
96
|
+
Applied at entry rather than the moment it arrives, on purpose: a phase that
|
|
97
|
+
changes target halfway through throws away the work it already did, which is
|
|
98
|
+
the outcome this command exists to avoid.
|
|
99
|
+
|
|
100
|
+
An instruction that contradicts the plan is not silently obeyed. The phase says
|
|
101
|
+
what it is changing, and a contradiction that would invalidate an approved plan
|
|
102
|
+
halts for the user instead of quietly rewriting it.
|
|
103
|
+
|
|
104
|
+
**One limit worth knowing.** The reader is the phase contract, so a task started
|
|
105
|
+
by an install that predates it will not consume the field: the instruction is
|
|
106
|
+
written and nothing picks it up. There is no version stamp on a state file to
|
|
107
|
+
detect this from, so the honest advice is that steer applies to runs started
|
|
108
|
+
after the install carrying it. A run already in flight from an older install is
|
|
109
|
+
still a `kill` or a wait.
|
|
@@ -59,7 +59,7 @@ Run every step automatically:
|
|
|
59
59
|
```
|
|
60
60
|
Step 1: PLATFORM Detect macOS / Linux / Windows (Git Bash / WSL); export PLATFORM env
|
|
61
61
|
Step 1.5: DETECT Compare timestamps, find stale targets
|
|
62
|
-
Step 2: COPILOT Claude Code -> Copilot CLI (instructions +
|
|
62
|
+
Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 55 sub-command skills)
|
|
63
63
|
Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 51 specs as refs + 8 agent TOML)
|
|
64
64
|
Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub, bash -n on all sh)
|
|
65
65
|
Step 3c: PLUGINS pipeline shared/external -> multi-agent-plugins marketplace (rebuild knowledge/,
|
|
@@ -166,7 +166,7 @@ If nothing is stale → report "All targets up to date" and stop.
|
|
|
166
166
|
Unlike the Copilot step, this one does **not** hand-copy files. The Codex tree is a
|
|
167
167
|
*transform* of the Claude tree, not a mirror of it, and the transform is real work:
|
|
168
168
|
|
|
169
|
-
- the
|
|
169
|
+
- the 55 sub-command specs become reference files, because Codex silently truncates
|
|
170
170
|
its skills block (see `cross-cli-contract.md` 2.6 for the measurement)
|
|
171
171
|
- every `$HOME/.claude/...` reference to a CLI-owned tree is retargeted, with
|
|
172
172
|
`agents/<persona>.md` becoming `.toml` and the dispatcher becoming the router skill
|
|
@@ -479,17 +479,15 @@ When invoked with the `release` argument:
|
|
|
479
479
|
|
|
480
480
|
## Sub-Command Sync (Claude Code <-> Copilot CLI Skills)
|
|
481
481
|
|
|
482
|
-
> Codex takes the Step 2b path instead; see that section.
|
|
483
|
-
|
|
484
482
|
This runs on the Claude <-> Copilot axis. Codex is NOT synced here: it receives the
|
|
485
|
-
same
|
|
483
|
+
same 55 specs as reference files rather than as peer skills, via Step 2b - see
|
|
486
484
|
`cross-cli-contract.md` 2.6 for why the parity axis differs per host.
|
|
487
485
|
|
|
488
486
|
| Claude Code | Copilot CLI |
|
|
489
487
|
|-------------|-------------|
|
|
490
488
|
| `~/.claude/commands/multi-agent/{cmd}/SKILL.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
|
|
491
489
|
|
|
492
|
-
**
|
|
490
|
+
**55 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
|
|
493
491
|
|
|
494
492
|
```
|
|
495
493
|
analysis, analysis-resolve, autopilot, build-optimize, channels,
|
|
@@ -498,7 +496,7 @@ dev-local-autopilot, diff-explain, feedback, forget, garbage-collect,
|
|
|
498
496
|
graph, help, ios-coding-standard, issue, jira, kill, language, local,
|
|
499
497
|
local-autopilot, log, manual-test, prune-logs, prune-prompts, purge,
|
|
500
498
|
refactor, resume, resume-local, review, review-analysis, review-issue,
|
|
501
|
-
review-jira, routines, save, scan, search, setup, stack, status,
|
|
499
|
+
review-jira, routines, save, scan, search, setup, stack, status, steer,
|
|
502
500
|
store-ready, sync, test, test-accessibility, test-dark-mode,
|
|
503
501
|
test-dynamic-type, test-screenshots, testflight-validation, uninstall, update
|
|
504
502
|
```
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## 1. Command Inventory (
|
|
9
|
+
## 1. Command Inventory (55 files, 51 live commands)
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
analysis, analysis-resolve, autopilot, build-optimize, channels,
|
|
@@ -15,7 +15,7 @@ dev-local-autopilot, diff-explain, feedback, forget, garbage-collect,
|
|
|
15
15
|
graph, help, ios-coding-standard, issue, jira, kill, language, local,
|
|
16
16
|
local-autopilot, log, manual-test, prune-logs, prune-prompts, purge,
|
|
17
17
|
refactor, resume, resume-local, review, review-analysis, review-issue,
|
|
18
|
-
review-jira, routines, save, scan, search, setup, stack, status,
|
|
18
|
+
review-jira, routines, save, scan, search, setup, stack, status, steer,
|
|
19
19
|
store-ready, sync, test, test-accessibility, test-dark-mode,
|
|
20
20
|
test-dynamic-type, test-screenshots, testflight-validation, uninstall, update
|
|
21
21
|
```
|
|
@@ -27,12 +27,12 @@ Categories:
|
|
|
27
27
|
- **Pipeline entries**: `autopilot`, `local`, `local-autopilot` (plus the bare `/multi-agent` in the dispatcher). Depth is not a command: `/multi-agent` and `local` ask Full or Short at Phase 0 Step 7.5; the two autopilot entries never ask and always run Full.
|
|
28
28
|
- **Retired stubs** (v16.0.0, deleted next minor - they print a redirect and run no phase): `dev`, `dev-local` redirect to the picker entries with Short; `dev-autopilot`, `dev-local-autopilot` have no equivalent, because fast-plus-unattended no longer exists
|
|
29
29
|
- **Tail modes** (run the pipeline tail over already-done local work): `resume-local`
|
|
30
|
-
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `complaint-analysis`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `graph`, `prune-logs`, `prune-prompts`
|
|
30
|
+
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `steer`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `complaint-analysis`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `graph`, `prune-logs`, `prune-prompts`
|
|
31
31
|
- **Local audits** (worktree only to build; no commit, push, PR or channels): `design-check`, `testflight-validation`, `ios-coding-standard`. `testflight-validation` additionally never invokes `altool --upload-app` - a validation run must not be able to ship a build by accident.
|
|
32
32
|
- **Meta-ops**: `setup`, `sync`, `update`, `help`, `refactor`, `test`, `stack`, `manual-test`, `language`
|
|
33
33
|
- **Routines** (user-defined routine registry; the routines they create are local-only and never synced): `save`, `routines`, `forget`
|
|
34
34
|
|
|
35
|
-
The count is
|
|
35
|
+
The count is 55 files and 51 live commands until the four stubs are deleted, at which point both numbers become 51. A stub is still installed and still invocable, so counting it as absent would be wrong; counting it as a command would be worse.
|
|
36
36
|
|
|
37
37
|
> **Inventory drift is a contract violation.** Adding a slash command under `pipeline/commands/multi-agent/` without updating this list + its counterpart Copilot dir (`pipeline/skills/shared/core/multi-agent-<cmd>/`) is a merge blocker. `smoke-commands-skills-parity.sh` enforces command ↔ skill directory parity; `smoke-cross-cli-behavior.sh` enforces behavior parity. This doc is the authoritative command list - bump the count + table together.
|
|
38
38
|
|
|
@@ -95,6 +95,29 @@ halt per the halt-visibility rule), `3` I/O error.
|
|
|
95
95
|
Reads need no wrapper; the rename makes any read see either the old or the new
|
|
96
96
|
document, never a truncated one.
|
|
97
97
|
|
|
98
|
+
**`rev`, and reading before writing back.** Every successful write bumps
|
|
99
|
+
`state.rev`, a counter that only increases. It exists because a read and the
|
|
100
|
+
write that follows it are not one operation. The lock above makes a single write
|
|
101
|
+
atomic and nothing more; the concurrent worktrees named there, and
|
|
102
|
+
`/multi-agent:steer`, which writes into the state file of a task that is still
|
|
103
|
+
running, both land inside the gap between a phase's read and its write-back.
|
|
104
|
+
|
|
105
|
+
A phase that will write back what it read keeps the `rev` it read, re-reads
|
|
106
|
+
immediately before writing, and re-derives its patch when the number has moved.
|
|
107
|
+
`write-state.mjs` takes the higher of the on-disk `rev` and the one in the
|
|
108
|
+
incoming patch, so a stale patch can never walk the counter backwards - but that
|
|
109
|
+
only protects the counter, not the fields. Deciding what to do about a
|
|
110
|
+
concurrent change is the caller's job.
|
|
111
|
+
|
|
112
|
+
A record written before `rev` existed has none: treat a missing `rev` as 0.
|
|
113
|
+
|
|
114
|
+
**Stale locks: liveness outranks age.** A lock is reclaimed when its holder PID
|
|
115
|
+
is no longer alive. A holder that is alive keeps its lock however old the file
|
|
116
|
+
is - the reverse order (age first) is what let a writer delete a live lock and
|
|
117
|
+
lose the update behind it. `WRITE_STATE_LOCK_STALE_MS` now applies only to a
|
|
118
|
+
lock with no readable PID, and `WRITE_STATE_LOCK_ABANDON_MS` (default ten times
|
|
119
|
+
that) is the last-resort ceiling for a PID that has been recycled.
|
|
120
|
+
|
|
98
121
|
**Halt visibility (required, autopilot included).** A halt is never silent. Whenever a phase halts on a hard error (validator failed twice, no subagent returned, dispatch error past fallback, lock irrecoverable), in addition to the `agent-log.md` line: (a) write `state.status = "paused"` and `state.haltReason = "<phase>:<cause>"`; (b) record the cause on the tracker via `phase-tracker.sh meta <phase> halt "<cause>"` and `phase-tracker.sh update <phase> failed`; (c) emit one `>&2` alert line `HALT phase <N>: <cause> - resume with /multi-agent:resume #<id>`; (d) if `prefs.global.usageLog.enabled` is true, emit the end-of-run report so a run that never reaches Phase 7 is still recorded with the phase it stopped at (`state.currentPhase` + `haltReason`) - the emitter no-ops when it is off or unconfigured:
|
|
99
122
|
|
|
100
123
|
```bash
|
|
@@ -631,8 +631,12 @@ Phase 0 owns `agent-state.json`. Do not call
|
|
|
631
631
|
|
|
632
632
|
```bash
|
|
633
633
|
node "$HOME/.claude/scripts/phase0-exit-gate.mjs" "$TASK_ID" --input "$ORIGINAL_INPUT"
|
|
634
|
+
node "$HOME/.claude/scripts/usage-report.mjs" --task-id "$TASK_ID" >/dev/null 2>&1 || true
|
|
634
635
|
```
|
|
635
636
|
|
|
637
|
+
The second line reports the run as started: reporting only from Phase 7 reported
|
|
638
|
+
only runs that finish, and few do. Phase 7 upserts the same key over it.
|
|
639
|
+
|
|
636
640
|
It asserts three things, each of which has failed silently in a real run:
|
|
637
641
|
|
|
638
642
|
1. **`agent-state.json` exists.** A run once reported Phase 0 `completed` with only
|
|
@@ -196,7 +196,7 @@ $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 7 task.completed \
|
|
|
196
196
|
duration_ms=$TOTAL_DURATION
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
-
**Operational reporting.** On by default since v16.8.0; `enabled: false` or `optOut: true` silences it.
|
|
199
|
+
**Operational reporting.** On by default since v16.8.0; `enabled: false` or `optOut: true` silences it. Coarse run metadata only, never prompts, code, diffs, repo names or paths. Fire-and-forget: it no-ops without a token and never blocks the run, so the call is unconditional. Phase 0 already reported this run as started; this upserts the final state over it.
|
|
200
200
|
|
|
201
201
|
```bash
|
|
202
202
|
node $HOME/.claude/scripts/usage-report.mjs --state "$STATE_FILE" >/dev/null 2>&1 || true
|
|
@@ -26,6 +26,44 @@ Short: 0-Init -> (1,2 skipped) --------------> 3-Dev -> 4-Review -> 5-Test -
|
|
|
26
26
|
Full or Short is the Phase 0 Step 7.5 question, not a command name. Autopilot never asks and always runs Full.
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Phase entry - pending steer (every phase, every mode)
|
|
30
|
+
|
|
31
|
+
Before a phase does any work, read `pendingSteer` from `agent-state.json`. It
|
|
32
|
+
is how a correction reaches a run that is already going: `/multi-agent:steer #N
|
|
33
|
+
"<instruction>"` queues one, and the next phase to start consumes it.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
STEER=$(node -e 'try{const s=require(process.argv[1]);const p=s.pendingSteer;if(p&&p.text&&!p.appliedAt)process.stdout.write(p.text)}catch{}' "$STATE_FILE")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `catch` is not decoration: a state file that does not exist yet (Phase 0
|
|
40
|
+
before its first write) makes `require` throw, and an unguarded read prints a
|
|
41
|
+
stack trace at every phase entry that reads like a failed phase.
|
|
42
|
+
|
|
43
|
+
Empty → carry on, nothing to do. Non-empty:
|
|
44
|
+
|
|
45
|
+
1. Apply it to this phase's context before starting. It is the user's own
|
|
46
|
+
words; treat it as an instruction from them, arriving now.
|
|
47
|
+
2. Say what changed, in one line, so the correction is visible in the run and
|
|
48
|
+
not only in the state file.
|
|
49
|
+
3. Mark it consumed. The record stays as a trace; `appliedAt` is what stops it
|
|
50
|
+
being read again, and the next steer overwrites all four keys:
|
|
51
|
+
```bash
|
|
52
|
+
printf '{"pendingSteer":{"appliedAt":"%s","appliedPhase":<N>}}' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
53
|
+
| node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`<N>` is this phase's number, substituted literally and unquoted - it is a
|
|
57
|
+
JSON number. Leaving it as an unset shell variable produces
|
|
58
|
+
`"appliedPhase":}`, which is invalid JSON and exits 1.
|
|
59
|
+
4. Record it on the tracker and in the log: `bash $HOME/.claude/scripts/phase-tracker.sh meta <N> steer "applied"`, plus a `Steer applied: <text>` line in `agent-log.md`.
|
|
60
|
+
|
|
61
|
+
Two limits, both deliberate. It is read at phase **entry** only, never mid-phase:
|
|
62
|
+
a phase that changes target halfway discards the work it already did, which is
|
|
63
|
+
the outcome steer exists to avoid. And an instruction that contradicts an
|
|
64
|
+
approved plan halts for the user rather than silently rewriting the plan -
|
|
65
|
+
steering corrects a run, it does not re-authorize one.
|
|
66
|
+
|
|
29
67
|
## Visual Phase Tracker
|
|
30
68
|
|
|
31
69
|
Two channels run in parallel at every phase boundary. Both are required in their target CLIs - skipping either is the #1 source of "I see no progress" complaints.
|
|
@@ -48,12 +48,7 @@
|
|
|
48
48
|
"autoDiff": false,
|
|
49
49
|
"manualNote": false
|
|
50
50
|
},
|
|
51
|
-
"wikiScope": [
|
|
52
|
-
"main",
|
|
53
|
-
"ios",
|
|
54
|
-
"screenshots",
|
|
55
|
-
"index"
|
|
56
|
-
],
|
|
51
|
+
"wikiScope": ["main", "ios", "screenshots", "index"],
|
|
57
52
|
"autopilotReportTimeoutSeconds": 1800,
|
|
58
53
|
"promptLanguage": "en",
|
|
59
54
|
"outputLanguage": "en",
|
|
@@ -185,10 +180,7 @@
|
|
|
185
180
|
"localPath": "plugins/<my-plugin>/skills/workflow",
|
|
186
181
|
"upstreamMarketplace": "<installed-marketplace-name>",
|
|
187
182
|
"upstreamPlugin": "<upstream-plugin-name>",
|
|
188
|
-
"upstreamSkills": [
|
|
189
|
-
"<skill-a>",
|
|
190
|
-
"<skill-b>"
|
|
191
|
-
],
|
|
183
|
+
"upstreamSkills": ["<skill-a>", "<skill-b>"],
|
|
192
184
|
"derivedFromVersion": "0.0.0",
|
|
193
185
|
"upstreamVersionSource": "marketplace.json",
|
|
194
186
|
"upstreamLocalClone": "$HOME/<upstream-repo-working-copy>",
|
|
@@ -21,6 +21,6 @@ available. Read the effective `enabledPlugins` and load each enabled toolkit's
|
|
|
21
21
|
`ai-common-toolkit` and `ai-analyst-toolkit` are on everywhere. Nothing enabled
|
|
22
22
|
is a normal state.
|
|
23
23
|
|
|
24
|
-
**multi-agent-toolkit MCP.**
|
|
24
|
+
**multi-agent-toolkit MCP.** 80+ tools for a running app: `ui-inspect`,
|
|
25
25
|
`crash-logs`, `design-check`, `ios-app-store-audit`, `ios-testflight`. Use them
|
|
26
26
|
instead of guessing about on-screen state. Not registered is a silent no-op.
|
|
@@ -104,6 +104,41 @@
|
|
|
104
104
|
"type": ["string", "null"],
|
|
105
105
|
"description": "Set when a phase halts on a hard error (validator failed twice, no subagent returned, dispatch error past fallback, lock irrecoverable). Format '<phase>:<cause>'. Surfaced to the user and cleared on successful resume. See operations.md 'Halt visibility'."
|
|
106
106
|
},
|
|
107
|
+
"rev": {
|
|
108
|
+
"type": "integer",
|
|
109
|
+
"minimum": 0,
|
|
110
|
+
"description": "Monotonic revision, bumped by write-state.mjs on every successful write. A reader keeps the rev it read and compares before writing back; a changed rev means the record moved underneath it. Optional, so a record written by an earlier version stays valid: treat a missing rev as 0. No schemaVersion bump - the field is additive and nothing needs migrating."
|
|
111
|
+
},
|
|
112
|
+
"pendingSteer": {
|
|
113
|
+
"type": ["object", "null"],
|
|
114
|
+
"additionalProperties": false,
|
|
115
|
+
"required": ["text", "at"],
|
|
116
|
+
"description": "A mid-run instruction from the user, waiting for the next phase boundary to consume it. Written by /multi-agent:steer; a phase consumes it at entry by setting appliedAt, which is what stops it being read again. The record is kept as a trace and overwritten wholesale by the next steer. Never applied mid-phase: changing the target of work already in flight throws that work away.",
|
|
117
|
+
"properties": {
|
|
118
|
+
"text": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"minLength": 1,
|
|
121
|
+
"maxLength": 4000,
|
|
122
|
+
"description": "The instruction, verbatim as the user typed it."
|
|
123
|
+
},
|
|
124
|
+
"at": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"format": "date-time",
|
|
127
|
+
"description": "When it was queued."
|
|
128
|
+
},
|
|
129
|
+
"appliedAt": {
|
|
130
|
+
"type": ["string", "null"],
|
|
131
|
+
"format": "date-time",
|
|
132
|
+
"description": "When a phase consumed it. Set alongside clearing the record, so the log keeps the trace."
|
|
133
|
+
},
|
|
134
|
+
"appliedPhase": {
|
|
135
|
+
"type": ["integer", "null"],
|
|
136
|
+
"minimum": 0,
|
|
137
|
+
"maximum": 7,
|
|
138
|
+
"description": "The phase that consumed it."
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
107
142
|
"telemetry": {
|
|
108
143
|
"type": "object",
|
|
109
144
|
"additionalProperties": true,
|
|
@@ -104,7 +104,10 @@
|
|
|
104
104
|
"platforms": {
|
|
105
105
|
"type": "array",
|
|
106
106
|
"minItems": 1,
|
|
107
|
-
"items": {
|
|
107
|
+
"items": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"enum": ["ios", "android", "web", "backend", "mobile", "frontend"]
|
|
110
|
+
},
|
|
108
111
|
"description": "Platforms selected in Phase 0 step 3 (multi-select)."
|
|
109
112
|
},
|
|
110
113
|
"repos": {
|
|
@@ -114,7 +117,10 @@
|
|
|
114
117
|
"additionalProperties": false,
|
|
115
118
|
"required": ["platform", "name", "canPush"],
|
|
116
119
|
"properties": {
|
|
117
|
-
"platform": {
|
|
120
|
+
"platform": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"enum": ["ios", "android", "web", "backend", "mobile", "frontend"]
|
|
123
|
+
},
|
|
118
124
|
"name": { "type": "string", "minLength": 1 },
|
|
119
125
|
"cloneUrl": { "type": ["string", "null"] },
|
|
120
126
|
"canPush": { "type": "boolean" }
|
|
@@ -66,13 +66,7 @@
|
|
|
66
66
|
"conventionBucket": {
|
|
67
67
|
"type": "object",
|
|
68
68
|
"additionalProperties": false,
|
|
69
|
-
"required": [
|
|
70
|
-
"pattern",
|
|
71
|
-
"example",
|
|
72
|
-
"confidence",
|
|
73
|
-
"evidenceFiles",
|
|
74
|
-
"alternativeCandidates"
|
|
75
|
-
],
|
|
69
|
+
"required": ["pattern", "example", "confidence", "evidenceFiles", "alternativeCandidates"],
|
|
76
70
|
"properties": {
|
|
77
71
|
"pattern": {
|
|
78
72
|
"type": "string",
|
|
@@ -84,12 +78,7 @@
|
|
|
84
78
|
},
|
|
85
79
|
"confidence": {
|
|
86
80
|
"type": "string",
|
|
87
|
-
"enum": [
|
|
88
|
-
"high",
|
|
89
|
-
"medium",
|
|
90
|
-
"low",
|
|
91
|
-
"none"
|
|
92
|
-
],
|
|
81
|
+
"enum": ["high", "medium", "low", "none"],
|
|
93
82
|
"description": "high: 5+ examples; medium: 3-4; low: 1-2 or mixed; none: no evidence."
|
|
94
83
|
},
|
|
95
84
|
"evidenceFiles": {
|
|
@@ -34,7 +34,7 @@ const NONE = "-";
|
|
|
34
34
|
|
|
35
35
|
function argOf(argv, flag) {
|
|
36
36
|
const i = argv.indexOf(flag);
|
|
37
|
-
return i === -1 ? null : argv[i + 1] ?? null;
|
|
37
|
+
return i === -1 ? null : (argv[i + 1] ?? null);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function readState(path) {
|
|
@@ -102,15 +102,27 @@ function rowsFrom(spec, lang) {
|
|
|
102
102
|
c.url,
|
|
103
103
|
ref || NONE,
|
|
104
104
|
c.embeddedApiTable
|
|
105
|
-
? lang === "tr"
|
|
106
|
-
|
|
105
|
+
? lang === "tr"
|
|
106
|
+
? "API kontratı"
|
|
107
|
+
: "API contract"
|
|
108
|
+
: lang === "tr"
|
|
109
|
+
? "özellik spesifikasyonu"
|
|
110
|
+
: "feature spec",
|
|
107
111
|
OK[lang],
|
|
108
112
|
NONE,
|
|
109
113
|
);
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
for (const j of ev.jira ?? []) {
|
|
113
|
-
push(
|
|
117
|
+
push(
|
|
118
|
+
"Jira",
|
|
119
|
+
j.id,
|
|
120
|
+
j.url ?? NONE,
|
|
121
|
+
NONE,
|
|
122
|
+
lang === "tr" ? "ticket" : "ticket",
|
|
123
|
+
OK[lang],
|
|
124
|
+
j.summary,
|
|
125
|
+
);
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
for (const s of ev.swagger ?? []) {
|
|
@@ -266,13 +278,17 @@ function tableLocators(block) {
|
|
|
266
278
|
const found = [];
|
|
267
279
|
for (const line of block.split(/\r?\n/)) {
|
|
268
280
|
if (!line.trim().startsWith("|")) continue;
|
|
269
|
-
const cells = line
|
|
281
|
+
const cells = line
|
|
282
|
+
.split("|")
|
|
283
|
+
.slice(1, -1)
|
|
284
|
+
.map((c) => c.trim());
|
|
270
285
|
if (cells.length < 3) continue;
|
|
271
286
|
if (/^:?-{3,}:?$/.test(cells[0])) continue;
|
|
272
287
|
const locator = cells[2];
|
|
273
288
|
const source = cells[1];
|
|
274
289
|
if (!locator) continue;
|
|
275
|
-
if (HEADER_CELLS.has(locator.toLowerCase()) && HEADER_CELLS.has(cells[0].toLowerCase()))
|
|
290
|
+
if (HEADER_CELLS.has(locator.toLowerCase()) && HEADER_CELLS.has(cells[0].toLowerCase()))
|
|
291
|
+
continue;
|
|
276
292
|
found.push({ locator, source, raw: line });
|
|
277
293
|
}
|
|
278
294
|
return found;
|
|
@@ -3,19 +3,9 @@
|
|
|
3
3
|
"stack": "android",
|
|
4
4
|
"description": "Kotlin + Java code-graph rules. Adds what the graph needs on top of test-gap-rules/android.json, which already owns sourceExtensions, excludePathGlobs and the test-path predicates. Definition patterns here are deliberately broader than the test-gap publicApiPatterns: the graph wants internal types too, because Phase 1 narrows scope on the whole module, not just its public surface.",
|
|
5
5
|
"comments": {
|
|
6
|
-
"line": [
|
|
7
|
-
|
|
8
|
-
]
|
|
9
|
-
"block": [
|
|
10
|
-
[
|
|
11
|
-
"/*",
|
|
12
|
-
"*/"
|
|
13
|
-
]
|
|
14
|
-
],
|
|
15
|
-
"string": [
|
|
16
|
-
"\"\"\"",
|
|
17
|
-
"\""
|
|
18
|
-
]
|
|
6
|
+
"line": ["//"],
|
|
7
|
+
"block": [["/*", "*/"]],
|
|
8
|
+
"string": ["\"\"\"", "\""]
|
|
19
9
|
},
|
|
20
10
|
"definitionPatterns": [
|
|
21
11
|
{
|
|
@@ -60,14 +50,7 @@
|
|
|
60
50
|
"regex": "^\\s*import\\s+(?:static\\s+)?(?:[A-Za-z_][A-Za-z0-9_]*\\.)+([A-Z][A-Za-z0-9_]*)"
|
|
61
51
|
}
|
|
62
52
|
],
|
|
63
|
-
"referenceKinds": [
|
|
64
|
-
"class",
|
|
65
|
-
"interface",
|
|
66
|
-
"object",
|
|
67
|
-
"enum",
|
|
68
|
-
"record",
|
|
69
|
-
"typealias"
|
|
70
|
-
],
|
|
53
|
+
"referenceKinds": ["class", "interface", "object", "enum", "record", "typealias"],
|
|
71
54
|
"referenceKindsNote": "fun is deliberately absent, for the reason Swift's rules record for func: a bare lowercase name matched across files is almost never a call to that exact declaration, and on a large app the generic ones (invoke, build, create, get) take the top god-node slots purely because each happened to be declared once. Composables are the tempting exception - they are capitalised and behave like types - but they are declared with fun, so including them means including every other fun with them.",
|
|
72
55
|
"ignoredIdentifiers": [
|
|
73
56
|
"this",
|
|
@@ -3,20 +3,9 @@
|
|
|
3
3
|
"stack": "go",
|
|
4
4
|
"description": "Go code-graph rules. Adds what the graph needs on top of test-gap-rules/go.json, which already owns sourceExtensions, excludePathGlobs and the test-path predicates. Definition patterns here are deliberately broader than the test-gap publicApiPatterns: the graph wants unexported declarations too, because Phase 1 narrows scope on the whole package, not just its exported surface.",
|
|
5
5
|
"comments": {
|
|
6
|
-
"line": [
|
|
7
|
-
|
|
8
|
-
]
|
|
9
|
-
"block": [
|
|
10
|
-
[
|
|
11
|
-
"/*",
|
|
12
|
-
"*/"
|
|
13
|
-
]
|
|
14
|
-
],
|
|
15
|
-
"string": [
|
|
16
|
-
"`",
|
|
17
|
-
"\"",
|
|
18
|
-
"'"
|
|
19
|
-
]
|
|
6
|
+
"line": ["//"],
|
|
7
|
+
"block": [["/*", "*/"]],
|
|
8
|
+
"string": ["`", "\"", "'"]
|
|
20
9
|
},
|
|
21
10
|
"importSpecifiersAreStrings": true,
|
|
22
11
|
"definitionPatterns": [
|
|
@@ -47,11 +36,7 @@
|
|
|
47
36
|
"regex": "^[ \\t]*(?:import[ \\t]+)?(?:[A-Za-z_.][A-Za-z0-9_]*[ \\t]+)?\"(?:[^\"]*/)?([A-Za-z_][A-Za-z0-9_.-]*)\"[ \\t]*$"
|
|
48
37
|
}
|
|
49
38
|
],
|
|
50
|
-
"referenceKinds": [
|
|
51
|
-
"struct",
|
|
52
|
-
"interface",
|
|
53
|
-
"type"
|
|
54
|
-
],
|
|
39
|
+
"referenceKinds": ["struct", "interface", "type"],
|
|
55
40
|
"referenceKindsNote": "func is deliberately absent, for the reason the Swift, Kotlin, Node and Python rules all record: a bare name matched across files is almost never a call to that exact declaration, and Go's short-name convention (New, Run, Get, Close, Error) makes it worse than most. Functions still reach the graph through their defines edge, so they stay findable by name.",
|
|
56
41
|
"ignoredIdentifiers": [
|
|
57
42
|
"package",
|
|
@@ -3,20 +3,9 @@
|
|
|
3
3
|
"stack": "node",
|
|
4
4
|
"description": "TypeScript + JavaScript code-graph rules. Adds what the graph needs on top of test-gap-rules/node.json, which already owns sourceExtensions, excludePathGlobs and the test-path predicates. Definition patterns here are deliberately broader than the test-gap publicApiPatterns: the graph wants unexported declarations too, because Phase 1 narrows scope on the whole module, not just its public surface.",
|
|
5
5
|
"comments": {
|
|
6
|
-
"line": [
|
|
7
|
-
|
|
8
|
-
]
|
|
9
|
-
"block": [
|
|
10
|
-
[
|
|
11
|
-
"/*",
|
|
12
|
-
"*/"
|
|
13
|
-
]
|
|
14
|
-
],
|
|
15
|
-
"string": [
|
|
16
|
-
"`",
|
|
17
|
-
"\"",
|
|
18
|
-
"'"
|
|
19
|
-
]
|
|
6
|
+
"line": ["//"],
|
|
7
|
+
"block": [["/*", "*/"]],
|
|
8
|
+
"string": ["`", "\"", "'"]
|
|
20
9
|
},
|
|
21
10
|
"definitionPatterns": [
|
|
22
11
|
{
|
|
@@ -56,13 +45,7 @@
|
|
|
56
45
|
"regex": "(?:\\bfrom|\\brequire\\s*\\(|\\bimport)\\s*\\(?\\s*[\"'](?:[^\"']*[\\/:])?([A-Za-z_$][A-Za-z0-9_$-]*)(?:\\.[A-Za-z0-9]+)*[\"']"
|
|
57
46
|
}
|
|
58
47
|
],
|
|
59
|
-
"referenceKinds": [
|
|
60
|
-
"class",
|
|
61
|
-
"interface",
|
|
62
|
-
"type",
|
|
63
|
-
"enum",
|
|
64
|
-
"const"
|
|
65
|
-
],
|
|
48
|
+
"referenceKinds": ["class", "interface", "type", "enum", "const"],
|
|
66
49
|
"referenceKindsNote": "function is deliberately absent, for the reason Swift's rules record for func: a bare name matched across files is almost never a call to that exact declaration, and the generic ones (run, main, get, parse, handler) would take the top god-node slots purely because each happened to be declared once. Functions still reach the graph through their defines edge, so they stay findable by name. const is present but narrowed to an uppercase-initial declaration at column 0, which is what a component, a singleton or an exported table looks like; a lowercase const is a local and is not a declaration anyone references across files. Measured on this repo (126 sources): including function put `ok` at degree 52, `f` at 38, `tokens` at 15 and `arg` at 12 - local helper names that happen to be declared exactly once, so the ambiguity rule does not catch them either. Excluding it leaves a thin symbol layer, and that is the honest shape of this stack: a JavaScript module's exported unit is usually a function, so the useful graph here is the import graph between files. `graph-affected \"<file>.mjs\"` answers who imports a module, which is the question this stack actually gets asked.",
|
|
67
50
|
"ignoredIdentifiers": [
|
|
68
51
|
"this",
|
|
@@ -115,7 +115,9 @@ function endpointAllowed(endpoint) {
|
|
|
115
115
|
async function main() {
|
|
116
116
|
const text = String(arg("--text", "") ?? "").trim();
|
|
117
117
|
if (!text) {
|
|
118
|
-
process.stderr.write(
|
|
118
|
+
process.stderr.write(
|
|
119
|
+
'usage: feedback-send.mjs --text "<message>" [--kind bug|idea|question]\n',
|
|
120
|
+
);
|
|
119
121
|
process.exit(2);
|
|
120
122
|
}
|
|
121
123
|
if (text.length > TEXT_MAX) {
|