@mmerterden/multi-agent-pipeline 17.5.0 → 17.6.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 +177 -0
- package/README.md +24 -0
- package/README.tr.md +24 -0
- package/docs/features.md +44 -3
- package/docs/token-budget-history.md +1 -1
- package/install/templates/claude-hooks.json +13 -1
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/feedback/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/issue/SKILL.md +13 -1
- package/pipeline/commands/multi-agent/jira/SKILL.md +13 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +16 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +14 -16
- package/pipeline/commands/multi-agent/update/SKILL.md +13 -56
- package/pipeline/multi-agent-refs/features/code-graph.md +20 -0
- package/pipeline/multi-agent-refs/features/doctor.md +23 -0
- package/pipeline/multi-agent-refs/features/maturity-followup.md +166 -0
- package/pipeline/multi-agent-refs/features/package-manager.md +80 -0
- package/pipeline/multi-agent-refs/features/usage-reporting.md +79 -0
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +5 -2
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +8 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +1 -1
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/preferences-template.json +1 -1
- package/pipeline/schemas/agent-state.schema.json +122 -11
- package/pipeline/schemas/prefs.schema.json +35 -0
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/doctor.mjs +65 -0
- package/pipeline/scripts/feedback-send.mjs +1 -1
- package/pipeline/scripts/graph-report.mjs +155 -1
- package/pipeline/scripts/maturity-followup.mjs +294 -0
- package/pipeline/scripts/package-manager.mjs +310 -0
- package/pipeline/scripts/usage-register.mjs +271 -0
- package/pipeline/scripts/usage-report.mjs +2 -2
- package/pipeline/skills/.skill-manifest.json +5 -5
- package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +14 -0
- package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +14 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +13 -0
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +6 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* usage-register.mjs - ask the reporting endpoint for this machine's token.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
*
|
|
7
|
+
* Operational reporting needs two things to work: `usageLog.enabled` true AND a
|
|
8
|
+
* token that resolves. Since v15.8.0 both are arranged automatically - but only
|
|
9
|
+
* inside `/multi-agent:update`, as forty lines of shell embedded in a skill. A
|
|
10
|
+
* new user installs the package, runs `/multi-agent:setup` (which explicitly
|
|
11
|
+
* says registration happens in update), works for weeks, and never registers.
|
|
12
|
+
* Their runs emit nothing, and the panel cannot tell that apart from nobody
|
|
13
|
+
* using the pipeline at all - which is exactly what it looked like.
|
|
14
|
+
*
|
|
15
|
+
* So the registration becomes one deterministic call that three places make:
|
|
16
|
+
* setup (onboarding), update (existing path), and the first run of a machine
|
|
17
|
+
* that reached neither.
|
|
18
|
+
*
|
|
19
|
+
* WHAT IT NEVER DOES
|
|
20
|
+
*
|
|
21
|
+
* - It never ships a secret: the token is REQUESTED, and it is write-only.
|
|
22
|
+
* - It never writes the token to a file. The credential store holds it; prefs
|
|
23
|
+
* hold only the name of the entry.
|
|
24
|
+
* - It never registers when `usageLog.optOut` is true. That is permanent and
|
|
25
|
+
* checked before anything else.
|
|
26
|
+
* - It never fails a caller. Offline, endpoint down, ingest disabled by the
|
|
27
|
+
* admin, no credential store: one status line, exit 0.
|
|
28
|
+
* - It never reports run CONTENT. That is usage-report.mjs's contract, and it
|
|
29
|
+
* is coarse metadata only.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* node usage-register.mjs [--prefs <path>] [--json] [--quiet] [--dry-run] [--feedback]
|
|
33
|
+
*
|
|
34
|
+
* Exit: always 0. `--json` says what happened; the shell does not need to care.
|
|
35
|
+
*
|
|
36
|
+
* @module pipeline/scripts/usage-register
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
40
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
41
|
+
import { join } from "node:path";
|
|
42
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
43
|
+
import { pathToFileURL } from "node:url";
|
|
44
|
+
|
|
45
|
+
const DEFAULT_ENDPOINT = "https://mmerterden.com/api/usage/ingest";
|
|
46
|
+
const TIMEOUT_MS = 10000;
|
|
47
|
+
|
|
48
|
+
function readJson(file) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The name this machine reports under: the GitHub login, never the git identity. */
|
|
57
|
+
export function reportingUser(prefs, { env = process.env } = {}) {
|
|
58
|
+
const declared = prefs?.global?.identities?.[0]?.username;
|
|
59
|
+
if (typeof declared === "string" && declared.trim()) return declared.trim();
|
|
60
|
+
try {
|
|
61
|
+
const out = execFileSync("gh", ["api", "user", "--jq", ".login"], {
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
timeout: 5000,
|
|
64
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
65
|
+
}).trim();
|
|
66
|
+
if (out) return out;
|
|
67
|
+
} catch {
|
|
68
|
+
/* gh is optional */
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return env.USER || userInfo().username || "unknown";
|
|
72
|
+
} catch {
|
|
73
|
+
return "unknown";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Should this machine register at all?
|
|
79
|
+
*
|
|
80
|
+
* The three "no" answers are deliberately distinguishable: the caller prints
|
|
81
|
+
* them, and "you opted out" and "we could not reach the endpoint" are different
|
|
82
|
+
* facts about the same silence.
|
|
83
|
+
*/
|
|
84
|
+
export function decideRegistration(
|
|
85
|
+
prefs,
|
|
86
|
+
{ env = process.env, hasStoredToken = false, forFeedback = false } = {},
|
|
87
|
+
) {
|
|
88
|
+
const g = prefs?.global || {};
|
|
89
|
+
const u = g.usageLog || {};
|
|
90
|
+
// `optOut` is a choice about PASSIVE collection. /multi-agent:feedback is the
|
|
91
|
+
// opposite: somebody typed a message meant to be read, and it rides the same
|
|
92
|
+
// token. So a feedback run may mint one - and it must NOT switch telemetry on
|
|
93
|
+
// when it does, which is what `enableTelemetry` below is for.
|
|
94
|
+
if (u.optOut === true && !forFeedback) return { act: false, reason: "opted out" };
|
|
95
|
+
if (env.MULTI_AGENT_USAGE_TOKEN) return { act: false, reason: "token in environment" };
|
|
96
|
+
if (typeof u.token === "string" && u.token.trim())
|
|
97
|
+
return { act: false, reason: "token in prefs" };
|
|
98
|
+
if (hasStoredToken) return { act: false, reason: "token already onboarded" };
|
|
99
|
+
return { act: true, reason: "no token resolves" };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The /register URL derived from whatever endpoint the prefs name. */
|
|
103
|
+
export function registerUrl(endpoint) {
|
|
104
|
+
const ep = (endpoint || DEFAULT_ENDPOINT).trim().replace(/\/+$/, "");
|
|
105
|
+
return ep.endsWith("/ingest") ? `${ep.slice(0, -"/ingest".length)}/register` : `${ep}/register`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function storedToken(prefs, credStore) {
|
|
109
|
+
const name = prefs?.global?.keychainMapping?.usage_ingest;
|
|
110
|
+
if (!name || !existsSync(credStore)) return null;
|
|
111
|
+
const r = spawnSync("bash", [credStore, "get", name], { encoding: "utf8", timeout: 8000 });
|
|
112
|
+
const out = (r.stdout || "").trim();
|
|
113
|
+
return out || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function requestToken(url, body) {
|
|
117
|
+
const ctrl = new AbortController();
|
|
118
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch(url, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: { "Content-Type": "application/json" },
|
|
123
|
+
body: JSON.stringify(body),
|
|
124
|
+
signal: ctrl.signal,
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) return { token: null, why: `endpoint answered ${res.status}` };
|
|
127
|
+
const json = await res.json().catch(() => null);
|
|
128
|
+
const token = typeof json?.token === "string" ? json.token.trim() : "";
|
|
129
|
+
return token ? { token, why: null } : { token: null, why: "endpoint returned no token" };
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return {
|
|
132
|
+
token: null,
|
|
133
|
+
why: e?.name === "AbortError" ? "endpoint timed out" : "endpoint unreachable",
|
|
134
|
+
};
|
|
135
|
+
} finally {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Write the switch, and the credential-store ENTRY NAME when there is one.
|
|
142
|
+
*
|
|
143
|
+
* `entryName` is optional on purpose: a token can resolve from the environment
|
|
144
|
+
* or from `usageLog.token`, and those machines still need the switch flipped.
|
|
145
|
+
* Claiming "enabled" while writing nothing is the failure this whole feature is
|
|
146
|
+
* about, one level smaller.
|
|
147
|
+
*
|
|
148
|
+
* The token itself is never written here, whatever the caller passes.
|
|
149
|
+
*/
|
|
150
|
+
export function persistPrefs(prefsPath, entryName = null, { enableTelemetry = true } = {}) {
|
|
151
|
+
const j = readJson(prefsPath);
|
|
152
|
+
if (!j) return false;
|
|
153
|
+
j.global = j.global || {};
|
|
154
|
+
if (entryName) {
|
|
155
|
+
j.global.keychainMapping = j.global.keychainMapping || {};
|
|
156
|
+
j.global.keychainMapping.usage_ingest = entryName;
|
|
157
|
+
}
|
|
158
|
+
j.global.usageLog = j.global.usageLog || {};
|
|
159
|
+
if (enableTelemetry) j.global.usageLog.enabled = true;
|
|
160
|
+
writeFileSync(prefsPath, `${JSON.stringify(j, null, 2)}\n`);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main(argv) {
|
|
165
|
+
const opts = { quiet: false, json: false, dryRun: false, prefs: null, feedback: false };
|
|
166
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
167
|
+
const a = argv[i];
|
|
168
|
+
if (a === "--quiet") opts.quiet = true;
|
|
169
|
+
else if (a === "--json") opts.json = true;
|
|
170
|
+
else if (a === "--dry-run") opts.dryRun = true;
|
|
171
|
+
else if (a === "--feedback") opts.feedback = true;
|
|
172
|
+
else if (a === "--prefs") opts.prefs = argv[++i];
|
|
173
|
+
}
|
|
174
|
+
const HOME = homedir();
|
|
175
|
+
const prefsPath = opts.prefs || join(HOME, ".claude", "multi-agent-preferences.json");
|
|
176
|
+
const credStore = join(HOME, ".claude", "lib", "credential-store.sh");
|
|
177
|
+
|
|
178
|
+
const say = (line) => {
|
|
179
|
+
if (!opts.quiet) process.stdout.write(`${line}\n`);
|
|
180
|
+
};
|
|
181
|
+
const done = (status, detail) => {
|
|
182
|
+
if (opts.json) process.stdout.write(`${JSON.stringify({ status, detail })}\n`);
|
|
183
|
+
return 0;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const prefs = readJson(prefsPath);
|
|
187
|
+
if (!prefs) {
|
|
188
|
+
// Setup writes this file; nothing here creates it. An installer that seeds
|
|
189
|
+
// state is the one thing the install contract forbids.
|
|
190
|
+
say(" -> operational reporting: no preferences file yet (run /multi-agent:setup)");
|
|
191
|
+
return done("skipped", "no prefs");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const has = Boolean(storedToken(prefs, credStore));
|
|
195
|
+
const decision = decideRegistration(prefs, { hasStoredToken: has, forFeedback: opts.feedback });
|
|
196
|
+
if (!decision.act) {
|
|
197
|
+
if (decision.reason !== "opted out") {
|
|
198
|
+
// Already has a token: make sure the switch is actually on. A token with
|
|
199
|
+
// enabled:false is the other half of the same silence.
|
|
200
|
+
if (prefs.global?.usageLog?.enabled !== true && !opts.dryRun) {
|
|
201
|
+
const name = prefs.global?.keychainMapping?.usage_ingest || null;
|
|
202
|
+
if (persistPrefs(prefsPath, name)) {
|
|
203
|
+
say(" -> operational reporting: enabled (a token was already onboarded)");
|
|
204
|
+
return done("enabled", decision.reason);
|
|
205
|
+
}
|
|
206
|
+
say(" -> operational reporting: could not write preferences");
|
|
207
|
+
return done("unavailable", "prefs write failed");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
say(` -> operational reporting: unchanged (${decision.reason})`);
|
|
211
|
+
return done("skipped", decision.reason);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const url = registerUrl(prefs.global?.usageLog?.endpoint);
|
|
215
|
+
const user = reportingUser(prefs);
|
|
216
|
+
let host = "unknown";
|
|
217
|
+
try {
|
|
218
|
+
host = hostname().split(".")[0] || "unknown";
|
|
219
|
+
} catch {
|
|
220
|
+
/* a nameless host is still a host */
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (opts.dryRun) {
|
|
224
|
+
say(` -> operational reporting: would register ${user} with ${url}`);
|
|
225
|
+
return done("dry-run", url);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const { token, why } = await requestToken(url, { u: user, c: host });
|
|
229
|
+
if (!token) {
|
|
230
|
+
say(` -> operational reporting left off (${why})`);
|
|
231
|
+
return done("unavailable", why);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const optedOut = prefs.global?.usageLog?.optOut === true;
|
|
235
|
+
const entry = `${process.env.USER || user}_Usage_Ingest_Token`;
|
|
236
|
+
const set = spawnSync("bash", [credStore, "set", entry], {
|
|
237
|
+
input: token,
|
|
238
|
+
encoding: "utf8",
|
|
239
|
+
timeout: 10000,
|
|
240
|
+
});
|
|
241
|
+
if (set.status !== 0) {
|
|
242
|
+
say(" -> operational reporting left off (the credential store refused the token)");
|
|
243
|
+
return done("unavailable", "credential store write failed");
|
|
244
|
+
}
|
|
245
|
+
persistPrefs(prefsPath, entry, { enableTelemetry: !optedOut });
|
|
246
|
+
if (optedOut) {
|
|
247
|
+
say(" -> feedback token registered; telemetry stays off (usageLog.optOut is set)");
|
|
248
|
+
return done("registered-feedback-only", entry);
|
|
249
|
+
}
|
|
250
|
+
say(
|
|
251
|
+
" -> operational reporting: registered this machine (write-only token in the credential store)",
|
|
252
|
+
);
|
|
253
|
+
say(" opt out any time: set global.usageLog.optOut=true in multi-agent-preferences.json");
|
|
254
|
+
return done("registered", entry);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isEntrypoint() {
|
|
258
|
+
const arg = process.argv[1];
|
|
259
|
+
if (!arg) return false;
|
|
260
|
+
try {
|
|
261
|
+
return import.meta.url === pathToFileURL(arg).href;
|
|
262
|
+
} catch {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (isEntrypoint()) {
|
|
268
|
+
main(process.argv.slice(2)).then((code) => {
|
|
269
|
+
process.exitCode = code;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Enable + configure via prefs.global.usageLog:
|
|
11
11
|
* { "enabled": true,
|
|
12
|
-
* "endpoint": "https://mmerterden.
|
|
12
|
+
* "endpoint": "https://mmerterden.com/api/usage/ingest",
|
|
13
13
|
* "token": "<shared ingest token>" } // token may instead come from
|
|
14
14
|
* // env MULTI_AGENT_USAGE_TOKEN
|
|
15
15
|
* Nothing is sent when enabled is not true or no token resolves.
|
|
@@ -30,7 +30,7 @@ import { createHash } from "crypto";
|
|
|
30
30
|
import { costUsd } from "./_cost.mjs";
|
|
31
31
|
|
|
32
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
const ENDPOINT_DEFAULT = "https://mmerterden.
|
|
33
|
+
const ENDPOINT_DEFAULT = "https://mmerterden.com/api/usage/ingest";
|
|
34
34
|
const TIMEOUT_MS = 2500;
|
|
35
35
|
|
|
36
36
|
// The emitter ships verbatim into ~/.claude, ~/.copilot and ~/.codex, so it must
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-15T20:06:53Z",
|
|
4
4
|
"skillCount": 212,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -93,11 +93,11 @@
|
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "shared/core/multi-agent-issue/SKILL.md",
|
|
96
|
-
"sha256": "
|
|
96
|
+
"sha256": "8f8918c8a0f5d6431892e65737c685bb47a62379d023ac7c760a8a605d3eb0a4"
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"path": "shared/core/multi-agent-jira/SKILL.md",
|
|
100
|
-
"sha256": "
|
|
100
|
+
"sha256": "1ceb984239217060ab9fa3e7964270f9c6245bef0a3cac429bc329548d91e617"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"path": "shared/core/multi-agent-kill/SKILL.md",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"path": "shared/core/multi-agent-setup/SKILL.md",
|
|
184
|
-
"sha256": "
|
|
184
|
+
"sha256": "d475f14de5db9ffe0c8ae5b3b83374d5f82a41c29da5271c7399d151e6bd4c1d"
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
"path": "shared/core/multi-agent-stack/SKILL.md",
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
},
|
|
234
234
|
{
|
|
235
235
|
"path": "shared/core/multi-agent-update/SKILL.md",
|
|
236
|
-
"sha256": "
|
|
236
|
+
"sha256": "8a0b089ea5c0d81813dc936d8b9d012f956e6381f71e025ca8878e538a8e9303"
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
239
|
"path": "shared/core/multi-agent/SKILL.md",
|
|
@@ -34,3 +34,17 @@ Interactively list unassigned GitHub issues, let the user pick one, and send it
|
|
|
34
34
|
- If the user passed the `autopilot` flag, start in autopilot mode
|
|
35
35
|
|
|
36
36
|
6. **gh auth restore** - return to `{owner}` once done
|
|
37
|
+
|
|
38
|
+
## Maturity: the picked item may not be ready
|
|
39
|
+
|
|
40
|
+
The pipeline scores the item before anything is built. A blocker does not end the
|
|
41
|
+
run any more: an interactive run asks at that step (open the item and fix it,
|
|
42
|
+
continue without it - recording which gap was waved through - or abort), and an
|
|
43
|
+
autopilot run with `prefs.global.maturityFollowup.autopilotCommentsOnIssue` on
|
|
44
|
+
posts ONE comment on the item asking for what is missing, then stops and waits for
|
|
45
|
+
`resume`. Never a status change, never an assignee, never a close; `Ref:`, never a
|
|
46
|
+
closing keyword. Read the item's existing comments first and take the prior ask
|
|
47
|
+
from the newest one of ours - a scan is a new run with a fresh state file, so
|
|
48
|
+
state alone would make every scan a first ask. Full contract:
|
|
49
|
+
`$HOME/.claude/multi-agent-refs/features/maturity-followup.md`.
|
|
50
|
+
|
|
@@ -36,3 +36,17 @@ Interactively list the user's open Jira issues, let the user pick one, and send
|
|
|
36
36
|
5. **Start the pipeline** - run the `/multi-agent` command:
|
|
37
37
|
- Input: `"PROJ-{id}" "branch-name"`
|
|
38
38
|
- If the `autopilot` flag is present, in autopilot mode
|
|
39
|
+
|
|
40
|
+
## Maturity: the picked item may not be ready
|
|
41
|
+
|
|
42
|
+
The pipeline scores the item before anything is built. A blocker does not end the
|
|
43
|
+
run any more: an interactive run asks at that step (open the item and fix it,
|
|
44
|
+
continue without it - recording which gap was waved through - or abort), and an
|
|
45
|
+
autopilot run with `prefs.global.maturityFollowup.autopilotCommentsOnIssue` on
|
|
46
|
+
posts ONE comment on the item asking for what is missing, then stops and waits for
|
|
47
|
+
`resume`. Never a status change, never an assignee, never a close; `Ref:`, never a
|
|
48
|
+
closing keyword. Read the item's existing comments first and take the prior ask
|
|
49
|
+
from the newest one of ours - a scan is a new run with a fresh state file, so
|
|
50
|
+
state alone would make every scan a first ask. Full contract:
|
|
51
|
+
`$HOME/.claude/multi-agent-refs/features/maturity-followup.md`.
|
|
52
|
+
|
|
@@ -141,6 +141,19 @@ Save the resolved mapping to preferences:
|
|
|
141
141
|
|
|
142
142
|
`null` = not mapped (missing or skipped). Pipeline phases read this mapping to retrieve tokens dynamically - never hardcoded key names.
|
|
143
143
|
|
|
144
|
+
### Step 2.7 - Operational reporting
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
node "$HOME/.claude/scripts/usage-register.mjs"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Requests a per-machine **write-only** token when none resolves, stores it in the
|
|
151
|
+
credential store only, and switches reporting on. Registration used to happen
|
|
152
|
+
only in update, so a machine that ran setup and nothing else never reported at
|
|
153
|
+
all. `usageLog.optOut: true` blocks it permanently; an unreachable endpoint
|
|
154
|
+
leaves reporting off with one line. Contract:
|
|
155
|
+
`$HOME/.claude/multi-agent-refs/features/usage-reporting.md`.
|
|
156
|
+
|
|
144
157
|
### Step 3 - Jira Project Key (auto-learned)
|
|
145
158
|
|
|
146
159
|
Jira project keys are NOT asked during setup. They are auto-learned from pipeline usage:
|
|
@@ -37,6 +37,12 @@ Update the pipeline with a single command. Preferences are preserved; only skill
|
|
|
37
37
|
4b. Refresh the stack-plugin marketplace so the latest plugin versions are picked up:
|
|
38
38
|
`claude marketplace update multi-agent-plugins` (or `claude marketplace add {owner}/multi-agent-plugins` if not yet added)
|
|
39
39
|
5. `node migrate-prefs.mjs` (if there is a schema upgrade)
|
|
40
|
+
5b. `node "$HOME/.claude/scripts/usage-register.mjs"` - operational reporting.
|
|
41
|
+
It requests a per-machine **write-only** token when none resolves, stores it in
|
|
42
|
+
the credential store only, and switches reporting on. A no-op when a token is
|
|
43
|
+
already onboarded, and permanently blocked by `usageLog.optOut: true`. Offline
|
|
44
|
+
or endpoint down leaves reporting off with one line, never an error. Contract:
|
|
45
|
+
`$HOME/.claude/multi-agent-refs/features/usage-reporting.md`.
|
|
40
46
|
6. Show the new version + changelog
|
|
41
47
|
7. Smoke test (schema + cross-cli)
|
|
42
48
|
|