@mmerterden/multi-agent-pipeline 19.1.3 → 19.1.4
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 +56 -7
- package/README.md +42 -15
- package/README.tr.md +38 -12
- package/docs/ecosystem.md +3 -4
- package/docs/facts.json +3 -3
- package/install/_common.mjs +5 -5
- package/install/_mcp-register.mjs +1 -1
- package/install/_plugin-skills.mjs +3 -4
- package/install/copilot.mjs +5 -5
- package/manifest.json +42 -42
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +4 -4
- package/pipeline/commands/sim-test.md +4 -4
- package/pipeline/multi-agent-refs/analysis/locked.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +8 -8
- package/pipeline/multi-agent-refs/features/base-branch-evidence.md +2 -2
- package/pipeline/multi-agent-refs/features/scope-check.md +1 -1
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
- package/pipeline/multi-agent-refs/phases/operations.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +3 -3
- package/pipeline/multi-agent-refs/rules.md +1 -1
- package/pipeline/scripts/autopilot-runner.mjs +6 -7
- package/pipeline/scripts/build-references.mjs +3 -3
- package/pipeline/scripts/build-stack-plugins.mjs +1 -1
- package/pipeline/scripts/bulk-read.sh +6 -4
- package/pipeline/scripts/doctor.mjs +4 -4
- package/pipeline/scripts/learnings-ledger.mjs +1 -1
- package/pipeline/scripts/match-skills.mjs +4 -4
- package/pipeline/scripts/memory-load.sh +3 -3
- package/pipeline/scripts/plan-coverage-gate.mjs +3 -3
- package/pipeline/scripts/run-aggregator.mjs +2 -2
- package/pipeline/scripts/runs-index.mjs +7 -7
- package/pipeline/scripts/scope-check-gate.mjs +1 -1
- package/pipeline/scripts/smoke-schema-validation.sh +9 -12
- package/pipeline/scripts/usage-report.mjs +5 -5
- package/pipeline/scripts/validate-analysis-doc.mjs +3 -3
- package/pipeline/scripts/write-state.mjs +22 -11
- package/pipeline/skills/.skill-manifest.json +3 -3
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +4 -5
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
# scripts/_retrieval.mjs) and the most relevant are printed. Without it the
|
|
12
12
|
# index order is kept, which is what every existing caller gets.
|
|
13
13
|
#
|
|
14
|
-
# The cap
|
|
15
|
-
# thirty-first pointer
|
|
16
|
-
# the block
|
|
14
|
+
# The cap is not a bare `head -30`. That is a truncation, not a summary: the
|
|
15
|
+
# thirty-first pointer stays invisible however precisely it matches the task, and
|
|
16
|
+
# the block gets less useful the longer a repo is worked on - the opposite of
|
|
17
17
|
# what accumulated memory is for.
|
|
18
18
|
#
|
|
19
19
|
# Honors `prefs.global.perRepoMemory`. When the pref is off OR the prefs file
|
|
@@ -77,9 +77,9 @@ export function filesToAdd(markdown) {
|
|
|
77
77
|
if (!path || /^-+$/.test(path)) continue;
|
|
78
78
|
if (path.includes("<") || tag.includes("<")) continue;
|
|
79
79
|
// No header-row filter: the tag column of the header reads "Etiket / Tag",
|
|
80
|
-
// which the `Add new` test below already rejects.
|
|
81
|
-
//
|
|
82
|
-
//
|
|
80
|
+
// which the `Add new` test below already rejects. A filter here would have to
|
|
81
|
+
// match on the PATH column instead, and would skip a real row whose path
|
|
82
|
+
// begins with a `File/` directory.
|
|
83
83
|
if (!/^add new$/i.test(tag)) continue;
|
|
84
84
|
rows.push(path);
|
|
85
85
|
}
|
|
@@ -87,8 +87,8 @@ function die(msg) {
|
|
|
87
87
|
// Path resolution lives in _run-paths.mjs: tracker-state.json is written to
|
|
88
88
|
// the flat <root>/<task-id>/ while agent-state.json documents the nested
|
|
89
89
|
// <root>/<project>/<task-id>/, and both layouts are populated in practice.
|
|
90
|
-
//
|
|
91
|
-
// render-cost-summary.sh and
|
|
90
|
+
// Resolved once here rather than as a hand-rolled candidate list in each of
|
|
91
|
+
// render-cost-summary.sh and render-agent-log-cost.sh as well.
|
|
92
92
|
function resolveTrackerCandidates() {
|
|
93
93
|
const candidates = [];
|
|
94
94
|
if (flags["task-dir"]) {
|
|
@@ -44,9 +44,9 @@ import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* The phase from which a run counts as "waiting on you", read from the phase
|
|
47
|
-
* contract rather than written here.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* contract rather than written here. The threshold follows the phase count (6
|
|
48
|
+
* under an eight-phase contract, 4 under six), so a literal would survive a
|
|
49
|
+
* renumbering unchanged and silently regroup every run.
|
|
50
50
|
*/
|
|
51
51
|
const PHASE_WAITING_FROM = JSON.parse(
|
|
52
52
|
readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"),
|
|
@@ -203,10 +203,10 @@ export function buildIndex() {
|
|
|
203
203
|
salvaged: Boolean(statePath && statePath.includes(`${run.dir}/artifacts/`)),
|
|
204
204
|
// What KIND of record this is, before asking whether it is healthy.
|
|
205
205
|
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
// `stateReadable: false`
|
|
209
|
-
//
|
|
206
|
+
// A run with a tracker file and no agent state is the common case, not
|
|
207
|
+
// damage: most runs on a machine are shaped that way. Calling them
|
|
208
|
+
// `stateReadable: false` makes a panel announce "N runs could not be
|
|
209
|
+
// read" about records that are intact and never had
|
|
210
210
|
// agent state to begin with. `/multi-agent:analysis` says so in its own
|
|
211
211
|
// description ("no worktree, no commits, no dev chain"), and design-check
|
|
212
212
|
// is the same shape.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Phase 3 writes `.pipeline/scope-check.json` before the handoff: one reason
|
|
6
6
|
* per touched file, the things it deliberately did not do, and the simplifier
|
|
7
|
-
* rationales that
|
|
7
|
+
* rationales that are otherwise discarded. This gate compares that record with
|
|
8
8
|
* the real diff so an unjustified file cannot ride into review unnoticed.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
@@ -26,11 +26,10 @@ LIVE_PREFS="$HOME/.claude/multi-agent-preferences.json"
|
|
|
26
26
|
|
|
27
27
|
# The migration target, derived the way migrate-prefs.mjs derives it: the last
|
|
28
28
|
# entry of the schema's schemaVersion enum, read from the schema that sits
|
|
29
|
-
# beside the migrator being checked.
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# on it reported "no reference point". A gate that reads the source of truth
|
|
29
|
+
# beside the migrator being checked. Not grepped as a `TARGET_VERSION = "x.y.z"`
|
|
30
|
+
# literal out of the migrator: a literal drifts, and the moment it is replaced by
|
|
31
|
+
# a schema read the grep matches nothing and both checks that depend on it report
|
|
32
|
+
# "no reference point". A gate that reads the source of truth
|
|
34
33
|
# cannot go stale against it; a gate that reads a transcription of it can.
|
|
35
34
|
migration_target() {
|
|
36
35
|
local schema="$1/../schemas/prefs.schema.json"
|
|
@@ -213,15 +212,13 @@ if [ -f "$LIVE_PREFS" ]; then
|
|
|
213
212
|
|
|
214
213
|
# The target is read from migrate-prefs.mjs, never hardcoded here.
|
|
215
214
|
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
# correctly migrated to 2.4.0 hit the else arm and FAILED as "unknown". The
|
|
221
|
-
# gate was rejecting the only fully-migrated state it exists to encourage.
|
|
215
|
+
# A hardcoded version here inverts the gate the moment the migration target
|
|
216
|
+
# moves: the stale version passes as "already current" and the correctly
|
|
217
|
+
# migrated one falls through to "unknown schemaVersion", so the gate rejects
|
|
218
|
+
# the only fully-migrated state it exists to encourage.
|
|
222
219
|
#
|
|
223
220
|
# Reading the target and the accepted set from the same schema enum the
|
|
224
|
-
# migrator reads means this
|
|
221
|
+
# migrator reads means this cannot disagree with it.
|
|
225
222
|
MIGRATOR="$SMOKE_DIR/migrate-prefs.mjs"
|
|
226
223
|
TARGET=$(migration_target "$(dirname "$MIGRATOR")")
|
|
227
224
|
KNOWN=$(node -e "
|
|
@@ -612,11 +612,11 @@ if (invokedDirectly) {
|
|
|
612
612
|
// Exit 0 stays: usage telemetry must never be the reason a run fails, and a
|
|
613
613
|
// non-zero exit here would propagate into whatever called it.
|
|
614
614
|
//
|
|
615
|
-
// But the silence goes. `catch(() => process.exit(0))`
|
|
616
|
-
// every failure, so a telemetry path
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
//
|
|
615
|
+
// But the silence goes. A bare `catch(() => process.exit(0))` reports success
|
|
616
|
+
// for every failure, so a broken telemetry path looks exactly like one that
|
|
617
|
+
// works and there is no surface on which to notice it. One line on stderr
|
|
618
|
+
// costs nothing and is the difference between a degraded feature and an
|
|
619
|
+
// invisible one.
|
|
620
620
|
main().catch((err) => {
|
|
621
621
|
process.stderr.write(`usage-report: not sent - ${err?.message ?? err}\n`);
|
|
622
622
|
process.exit(0);
|
|
@@ -935,10 +935,10 @@ function main() {
|
|
|
935
935
|
|
|
936
936
|
mark("placeholder-bodies", profile === "global" ? undefined : "corporate profile");
|
|
937
937
|
|
|
938
|
-
// 4b. Locked 2's numbering half
|
|
938
|
+
// 4b. Locked 2's numbering half.
|
|
939
939
|
//
|
|
940
|
-
// It is NOT "numbering re-flows 1..N"
|
|
941
|
-
//
|
|
940
|
+
// It is NOT "numbering re-flows 1..N", which could not hold: Locked 30
|
|
941
|
+
// threads ids across the document by section
|
|
942
942
|
// NUMBER ("Section 15.1 scenario", "Section 4.4", "Section 3/5 layout cells"),
|
|
943
943
|
// so a re-flowed document sends every one of those references to the wrong
|
|
944
944
|
// section. No emitted document ever re-flowed. The rule was the wrong half.
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
* WRITE_STATE_LOCK_ABANDON_MS ceiling past which even a lock that probes as
|
|
41
41
|
* alive is reclaimed, covering PID reuse
|
|
42
42
|
* (default: 10x STALE_MS)
|
|
43
|
+
* WRITE_STATE_TEST_DELAY_MS test seam: pause between acquiring the lock
|
|
44
|
+
* and the write, so the exit-4 refusal can be
|
|
45
|
+
* driven on purpose (default 0, off)
|
|
43
46
|
* A dead holder (PID no longer alive) is reclaimed immediately regardless of
|
|
44
47
|
* age. A live holder keeps its lock: liveness outranks age. See
|
|
45
48
|
* lockIdentityIfStale() for why that order is load-bearing.
|
|
@@ -109,11 +112,11 @@ async function acquireLock(
|
|
|
109
112
|
const staleMs = Number(process.env.WRITE_STATE_LOCK_STALE_MS) || 30_000;
|
|
110
113
|
const deadline = Date.now() + timeoutMs;
|
|
111
114
|
// The lock file must never be observable without its owning PID inside it.
|
|
112
|
-
//
|
|
113
|
-
// between those two calls
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
115
|
+
// Created with openSync(wx) and filled on the next line, it exists and is
|
|
116
|
+
// empty between those two calls. A writer arriving in that window reads an
|
|
117
|
+
// unparseable PID, concludes the lock is stale, and deletes a live one -
|
|
118
|
+
// after which two writers hold it and one write is lost. The window is only
|
|
119
|
+
// wide enough to hit under CPU load, which is where it matters.
|
|
117
120
|
//
|
|
118
121
|
// Writing the PID into a private temp file and hard-linking it into place
|
|
119
122
|
// closes the window: link() fails with EEXIST if the lock is held, and the
|
|
@@ -141,8 +144,8 @@ async function acquireLock(
|
|
|
141
144
|
// deleting it, the holder can release and a THIRD writer can acquire a
|
|
142
145
|
// fresh one - and an unlink by path deletes that live lock, after which
|
|
143
146
|
// two writers hold it and one update is lost. Same failure the PID window
|
|
144
|
-
// above
|
|
145
|
-
//
|
|
147
|
+
// above describes, at a different point in the loop, and it only reproduces
|
|
148
|
+
// under load, where the gap is wide enough to lose.
|
|
146
149
|
//
|
|
147
150
|
// The inode is the identity: a newly acquired lock is a different file
|
|
148
151
|
// even at the same path. Delete only if the file we judged is still the
|
|
@@ -176,10 +179,10 @@ async function acquireLock(
|
|
|
176
179
|
* A lock is stale when its owning PID is no longer alive. Age alone is not
|
|
177
180
|
* staleness.
|
|
178
181
|
*
|
|
179
|
-
* The order here is the whole point.
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
182
|
+
* The order here is the whole point. Checking age first and returning "stale" on
|
|
183
|
+
* its own costs a holder that is demonstrably ALIVE its lock the moment the file
|
|
184
|
+
* passes 30 seconds - and the next writer then deletes a live lock and overwrites
|
|
185
|
+
* the update behind it. Liveness is direct evidence;
|
|
183
186
|
* a timestamp is a guess about it, and a guess must not overrule the evidence.
|
|
184
187
|
*
|
|
185
188
|
* Age survives for the one case liveness cannot answer: PID reuse. A dead
|
|
@@ -330,6 +333,14 @@ async function main() {
|
|
|
330
333
|
try {
|
|
331
334
|
heldLockIno = await acquireLock(path);
|
|
332
335
|
heldLockFor = path;
|
|
336
|
+
// Test seam. The refusal below is the one path that cannot be reached on
|
|
337
|
+
// demand: it needs another writer to take the lock inside the window
|
|
338
|
+
// between acquiring it and renaming, which on an idle machine is too
|
|
339
|
+
// narrow to hit and on a loaded one is a coin flip. A gate that can only
|
|
340
|
+
// observe this path by accident is not holding it. Unset in every real
|
|
341
|
+
// run; the whole seam is one sleep.
|
|
342
|
+
const testDelayMs = Number(process.env.WRITE_STATE_TEST_DELAY_MS) || 0;
|
|
343
|
+
if (testDelayMs > 0) await new Promise((r) => setTimeout(r, testDelayMs));
|
|
333
344
|
} catch (err) {
|
|
334
345
|
if (err.code === "LOCK_TIMEOUT") {
|
|
335
346
|
console.error(err.message);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-21T08:28:08Z",
|
|
4
4
|
"skillCount": 216,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "shared/core/multi-agent-channels/SKILL.md",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "e4f59e784d77018326e719632ef371e50e9fb9af61318a7d5bf713c386c0fd3f"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "shared/core/multi-agent-complaint-analysis/SKILL.md",
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
},
|
|
254
254
|
{
|
|
255
255
|
"path": "shared/core/multi-agent/SKILL.md",
|
|
256
|
-
"sha256": "
|
|
256
|
+
"sha256": "7ea676d07cd0104cc929b825463c521aff66047c6dcdfc28e9e37ca3752107da"
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
259
|
"path": "shared/external/accessibility-compliance-accessibility-audit/SKILL.md",
|
|
@@ -708,5 +708,5 @@ When `multi-agent status` is called, show:
|
|
|
708
708
|
## Help Display
|
|
709
709
|
|
|
710
710
|
Rendered by the `multi-agent-help` skill (`/multi-agent:help`), which owns
|
|
711
|
-
the full text in both languages.
|
|
712
|
-
|
|
711
|
+
the full text in both languages. Duplicating it here would mean English only,
|
|
712
|
+
on a path this skill takes for exactly one input.
|
|
@@ -152,11 +152,10 @@ h2. Bağlantılar
|
|
|
152
152
|
Reproduction scenarios first, then regressions for whatever the change could
|
|
153
153
|
have disturbed. Full contract: `channels/jira.md`.
|
|
154
154
|
|
|
155
|
-
`{{file.ext → func}}`, `*Root cause:*` and `*Changed files:*`
|
|
156
|
-
template.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
smiley.
|
|
155
|
+
`{{file.ext → func}}`, `*Root cause:*` and `*Changed files:*` are deliberately
|
|
156
|
+
NOT this template. That shape puts a hash-table mutation walk in front of a
|
|
157
|
+
tester, and `{{file → func}}` is exactly where a Swift selector like
|
|
158
|
+
`hash(into:)` ends up - whose trailing `:)` Jira renders as a smiley.
|
|
160
159
|
|
|
161
160
|
**Post through the publisher, never a hand-rolled `curl`:**
|
|
162
161
|
|