@mmerterden/multi-agent-pipeline 20.22.0 → 20.23.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 +29 -0
- package/docs/adr/0010-own-code-graph.md +3 -2
- package/docs/facts.json +3 -3
- package/docs/features.md +2 -2
- package/install/_usage-lifecycle.mjs +44 -0
- package/install/catalog-history.json +1 -1
- package/install/index.mjs +2 -0
- package/manifest.json +52 -49
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/update/SKILL.md +3 -3
- package/pipeline/lib/json-file-lock.mjs +24 -2
- package/pipeline/multi-agent-refs/analysis/evidence.md +1 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +1 -0
- package/pipeline/multi-agent-refs/features/analysis-jira.md +5 -0
- package/pipeline/multi-agent-refs/features/code-graph.md +40 -3
- package/pipeline/multi-agent-refs/features/usage-reporting.md +40 -23
- package/pipeline/multi-agent-refs/generate-issue.md +11 -1
- package/pipeline/multi-agent-refs/knowledge.md +18 -0
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +2 -2
- package/pipeline/schemas/code-graph.schema.json +3 -3
- package/pipeline/schemas/prefs.schema.json +26 -4
- package/pipeline/scripts/_code-graph.mjs +121 -12
- package/pipeline/scripts/_diff-impact.mjs +12 -5
- package/pipeline/scripts/_supertypes.mjs +133 -0
- package/pipeline/scripts/analysis-story-body.mjs +24 -8
- package/pipeline/scripts/capture-flush.sh +4 -1
- package/pipeline/scripts/code-graph-rules/android.json +1 -0
- package/pipeline/scripts/code-graph-rules/ios.json +5 -0
- package/pipeline/scripts/code-graph-rules/node.json +1 -0
- package/pipeline/scripts/code-graph-rules/python.json +1 -0
- package/pipeline/scripts/git-path.mjs +21 -0
- package/pipeline/scripts/graph-affected.mjs +2 -1
- package/pipeline/scripts/graph-confirm.mjs +140 -0
- package/pipeline/scripts/learnings-ledger.mjs +3 -2
- package/pipeline/scripts/product-context.mjs +30 -19
- package/pipeline/scripts/symbol-existence-gate.mjs +21 -9
- package/pipeline/scripts/test-gap-scan.mjs +4 -1
- package/pipeline/scripts/test-strength.mjs +7 -8
- package/pipeline/scripts/usage-identity.mjs +1 -1
- package/pipeline/scripts/usage-register.mjs +181 -15
- package/pipeline/scripts/validate-code-graph.mjs +3 -3
- package/pipeline/scripts/valven-gate.mjs +9 -1
- package/pipeline/scripts/valven-port.mjs +16 -5
- package/pipeline/scripts/valven-split.mjs +6 -4
- package/pipeline/skills/.skill-manifest.json +3 -3
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +3 -3
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
* - It never registers when `usageLog.optOut` is true. That is permanent and
|
|
19
19
|
* checked before anything else, including any credential read (the
|
|
20
20
|
* credential store and gh).
|
|
21
|
-
* - It
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* - It registers the GitHub account on this machine that can read the
|
|
22
|
+
* pipeline repository (usage-identity.mjs: gh, the git credential helper, or
|
|
23
|
+
* an SSH key). With no such account it registers only a name the person
|
|
24
|
+
* typed (`--login`, kept as `usageLog.declaredLogin`), as a declared claim
|
|
25
|
+
* the server accepts for a collaborator and marks `claimed`. Otherwise the
|
|
26
|
+
* request proves the caller owns the login:
|
|
25
27
|
* a gh or credential-helper token as `Authorization: Bearer`, or for an SSH
|
|
26
28
|
* key a signature over a one-time server challenge
|
|
27
29
|
* (`ssh-keygen -Y sign`), sent with no Authorization header at all. The
|
|
@@ -37,11 +39,14 @@
|
|
|
37
39
|
* is coarse metadata only.
|
|
38
40
|
*
|
|
39
41
|
* Usage:
|
|
40
|
-
* node usage-register.mjs [--prefs <path>] [--json] [--quiet] [--dry-run] [--feedback] [--refresh]
|
|
42
|
+
* node usage-register.mjs [--prefs <path>] [--json] [--quiet] [--dry-run] [--feedback] [--refresh] [--login <name>]
|
|
41
43
|
*
|
|
42
44
|
* `--refresh` re-checks which GitHub account on this machine can read the
|
|
43
45
|
* pipeline repository instead of using the cached `usageLog.login`.
|
|
44
46
|
*
|
|
47
|
+
* `--login <name>` records the person's GitHub username, for a machine that
|
|
48
|
+
* proves no account (a work machine), and implies `--refresh`.
|
|
49
|
+
*
|
|
45
50
|
* Exit: always 0. `--json` says what happened; the shell does not need to care.
|
|
46
51
|
*
|
|
47
52
|
* @module pipeline/scripts/usage-register
|
|
@@ -49,12 +54,14 @@
|
|
|
49
54
|
|
|
50
55
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
51
56
|
import { spawn, spawnSync } from "node:child_process";
|
|
57
|
+
import { randomBytes } from "node:crypto";
|
|
52
58
|
import { join } from "node:path";
|
|
53
59
|
import { homedir, hostname, tmpdir } from "node:os";
|
|
54
60
|
import { pathToFileURL } from "node:url";
|
|
55
61
|
import { hostCommand } from "../lib/user-facing.mjs";
|
|
56
62
|
import { canonicalEndpoint } from "../lib/usage-endpoint.mjs";
|
|
57
63
|
import {
|
|
64
|
+
LOGIN_RE,
|
|
58
65
|
SSH_TIMEOUT_MS,
|
|
59
66
|
credentialToken,
|
|
60
67
|
displayPath,
|
|
@@ -82,8 +89,11 @@ function readJson(file) {
|
|
|
82
89
|
* it was found by, or login null when none can. Never the git identity, never
|
|
83
90
|
* the OS user, never the account gh merely has active.
|
|
84
91
|
*/
|
|
85
|
-
export function reportingUser(
|
|
86
|
-
|
|
92
|
+
export function reportingUser(
|
|
93
|
+
prefsPath,
|
|
94
|
+
{ env = process.env, refresh = false, write = true } = {},
|
|
95
|
+
) {
|
|
96
|
+
return resolveReportingLogin({ prefsPath, env, refresh, write });
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
/**
|
|
@@ -107,6 +117,7 @@ export function decideRegistration(
|
|
|
107
117
|
forFeedback = false,
|
|
108
118
|
login = undefined,
|
|
109
119
|
loginReason = null,
|
|
120
|
+
proven = true,
|
|
110
121
|
} = {},
|
|
111
122
|
) {
|
|
112
123
|
const g = prefs?.global || {};
|
|
@@ -129,13 +140,21 @@ export function decideRegistration(
|
|
|
129
140
|
if (login === null) {
|
|
130
141
|
return { act: false, reason: loginReason || "no GitHub account on this machine has access" };
|
|
131
142
|
}
|
|
143
|
+
if (hasStoredToken && login && proven && u.registeredClaim === true) {
|
|
144
|
+
return { act: true, reason: `replacing a declared registration with a proof for ${login}` };
|
|
145
|
+
}
|
|
132
146
|
if (hasStoredToken && login && u.registeredFallback === true) {
|
|
133
147
|
return { act: true, reason: `replacing a fallback registration with ${login}` };
|
|
134
148
|
}
|
|
135
149
|
// The server pins a token to the login it was issued for. A token issued to a
|
|
136
150
|
// different login, or before the login was verified, reports under the wrong
|
|
137
151
|
// name, so it is replaced.
|
|
138
|
-
if (
|
|
152
|
+
if (
|
|
153
|
+
hasStoredToken &&
|
|
154
|
+
login &&
|
|
155
|
+
u.registeredAs &&
|
|
156
|
+
u.registeredAs.toLowerCase() !== login.toLowerCase()
|
|
157
|
+
) {
|
|
139
158
|
return { act: true, reason: `reporting account changed to ${login}` };
|
|
140
159
|
}
|
|
141
160
|
if (hasStoredToken && login && !u.registeredAs) {
|
|
@@ -228,6 +247,14 @@ function refusal(status, json, body, method) {
|
|
|
228
247
|
: "retry with --refresh";
|
|
229
248
|
return `endpoint answered ${status}${detail} - GitHub did not accept the proof for ${body.u}; ${fix}`;
|
|
230
249
|
}
|
|
250
|
+
if (code === "proof_required") {
|
|
251
|
+
return `${body.u} is the repository owner, which registers with a proof: log in with \`gh auth login\` or add its SSH key, then retry with --refresh`;
|
|
252
|
+
}
|
|
253
|
+
if (code === "not_collaborator") {
|
|
254
|
+
const rerun =
|
|
255
|
+
method === "claim" ? "rerun setup and enter the name again" : "rerun with --refresh";
|
|
256
|
+
return `${body.u} is not a collaborator of the pipeline repository; the repository owner adds it, then ${rerun}`;
|
|
257
|
+
}
|
|
231
258
|
if (status === 403) {
|
|
232
259
|
return (
|
|
233
260
|
`endpoint answered 403${detail} - ${body.u} needs collaborator access to the pipeline repository; ` +
|
|
@@ -240,7 +267,10 @@ function refusal(status, json, body, method) {
|
|
|
240
267
|
async function requestToken(url, body, proof, method) {
|
|
241
268
|
const { res, json, failure } = await postJson(url, body, proof);
|
|
242
269
|
if (failure) return { token: null, why: failure };
|
|
243
|
-
if (!res.ok)
|
|
270
|
+
if (!res.ok) {
|
|
271
|
+
const code = typeof json?.code === "string" ? json.code : null;
|
|
272
|
+
return { token: null, code, why: refusal(res.status, json, body, method) };
|
|
273
|
+
}
|
|
244
274
|
if (json?.accepted === false)
|
|
245
275
|
return { token: null, declined: true, why: "the endpoint declined this login" };
|
|
246
276
|
const token = typeof json?.token === "string" ? json.token.trim() : "";
|
|
@@ -319,6 +349,94 @@ async function requestTokenWithSsh(url, body, key) {
|
|
|
319
349
|
return requestToken(url, { ...body, ssh: { challenge, signature } }, null, "ssh");
|
|
320
350
|
}
|
|
321
351
|
|
|
352
|
+
/** The account name the person typed at setup or update, when it is a GitHub login. */
|
|
353
|
+
export function declaredLoginOf(prefs) {
|
|
354
|
+
const d = prefs?.global?.usageLog?.declaredLogin;
|
|
355
|
+
return typeof d === "string" && LOGIN_RE.test(d.trim()) ? d.trim() : null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Keep a typed account name and prefer it as the account to report under (the
|
|
360
|
+
* reportAs it replaces is kept as `usageLog.reportAsBefore`), or
|
|
361
|
+
* forget it (name null) once the server says it is not a collaborator, so the
|
|
362
|
+
* next setup asks again instead of retrying a name that cannot register.
|
|
363
|
+
*/
|
|
364
|
+
function declareLogin(prefsPath, name) {
|
|
365
|
+
try {
|
|
366
|
+
const written = updateJsonFileSync(
|
|
367
|
+
prefsPath,
|
|
368
|
+
(j) => {
|
|
369
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return undefined;
|
|
370
|
+
j.global = j.global || {};
|
|
371
|
+
const u = { ...(j.global.usageLog || {}) };
|
|
372
|
+
if (name) {
|
|
373
|
+
if (u.reportAs && u.reportAs !== u.declaredLogin && u.reportAsBefore === undefined)
|
|
374
|
+
u.reportAsBefore = u.reportAs;
|
|
375
|
+
Object.assign(u, { declaredLogin: name, reportAs: name });
|
|
376
|
+
delete u.claimFailedAt;
|
|
377
|
+
} else if (u.declaredLogin) {
|
|
378
|
+
if (u.reportAs === u.declaredLogin) {
|
|
379
|
+
if (u.reportAsBefore) u.reportAs = u.reportAsBefore;
|
|
380
|
+
else delete u.reportAs;
|
|
381
|
+
}
|
|
382
|
+
delete u.reportAsBefore;
|
|
383
|
+
delete u.declaredLogin;
|
|
384
|
+
}
|
|
385
|
+
j.global.usageLog = u;
|
|
386
|
+
return j;
|
|
387
|
+
},
|
|
388
|
+
{ mode: 0o600, followSymlink: true, timeoutMs: lockTimeoutFrom(process.env) },
|
|
389
|
+
);
|
|
390
|
+
return written !== undefined;
|
|
391
|
+
} catch {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Merge `fields` into `usageLog` (undefined removes a key). True when written. */
|
|
397
|
+
function patchUsageLog(prefsPath, fields) {
|
|
398
|
+
try {
|
|
399
|
+
const written = updateJsonFileSync(
|
|
400
|
+
prefsPath,
|
|
401
|
+
(j) => {
|
|
402
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return undefined;
|
|
403
|
+
j.global = j.global || {};
|
|
404
|
+
const u = { ...(j.global.usageLog || {}) };
|
|
405
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
406
|
+
if (v === undefined) delete u[k];
|
|
407
|
+
else u[k] = v;
|
|
408
|
+
}
|
|
409
|
+
j.global.usageLog = u;
|
|
410
|
+
return j;
|
|
411
|
+
},
|
|
412
|
+
{ mode: 0o600, followSymlink: true, timeoutMs: lockTimeoutFrom(process.env) },
|
|
413
|
+
);
|
|
414
|
+
return written !== undefined;
|
|
415
|
+
} catch {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* A declared claim that failed (`usageLog.claimFailedAt`) is not sent again for
|
|
422
|
+
* a day unless the person types the name again.
|
|
423
|
+
*/
|
|
424
|
+
const CLAIM_RETRY_MS = 24 * 3600 * 1000;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* The machine label a declared registration is sent with: the host name and a
|
|
428
|
+
* random suffix kept in prefs (`usageLog.claimNonce`). The server replaces a login's token for the same
|
|
429
|
+
* label, so a label nobody else can guess keeps a stranger from replacing it.
|
|
430
|
+
*/
|
|
431
|
+
function claimLabel(prefsPath, usageLog, host) {
|
|
432
|
+
let nonce = usageLog?.claimNonce;
|
|
433
|
+
if (typeof nonce !== "string" || !/^[0-9a-f]{8,32}$/.test(nonce)) {
|
|
434
|
+
nonce = randomBytes(6).toString("hex");
|
|
435
|
+
patchUsageLog(prefsPath, { claimNonce: nonce });
|
|
436
|
+
}
|
|
437
|
+
return `${host.slice(0, 30)}-${nonce}`;
|
|
438
|
+
}
|
|
439
|
+
|
|
322
440
|
/**
|
|
323
441
|
* Write the switch, and the credential-store ENTRY NAME when there is one.
|
|
324
442
|
*
|
|
@@ -332,7 +450,7 @@ async function requestTokenWithSsh(url, body, key) {
|
|
|
332
450
|
export function persistPrefs(
|
|
333
451
|
prefsPath,
|
|
334
452
|
entryName = null,
|
|
335
|
-
{ enableTelemetry = true, registeredAs = null } = {},
|
|
453
|
+
{ enableTelemetry = true, registeredAs = null, claimed = false } = {},
|
|
336
454
|
) {
|
|
337
455
|
try {
|
|
338
456
|
const written = updateJsonFileSync(
|
|
@@ -349,6 +467,8 @@ export function persistPrefs(
|
|
|
349
467
|
if (registeredAs) {
|
|
350
468
|
j.global.usageLog.registeredAs = registeredAs;
|
|
351
469
|
delete j.global.usageLog.registeredFallback;
|
|
470
|
+
if (claimed) j.global.usageLog.registeredClaim = true;
|
|
471
|
+
else delete j.global.usageLog.registeredClaim;
|
|
352
472
|
j.global.usageLog.registeredAt = new Date().toISOString();
|
|
353
473
|
}
|
|
354
474
|
return j;
|
|
@@ -371,6 +491,7 @@ export function persistPrefs(
|
|
|
371
491
|
export function personalAccountMissing(prefs, login = undefined, env = process.env) {
|
|
372
492
|
const u = prefs?.global?.usageLog || {};
|
|
373
493
|
if (u.optOut === true) return false;
|
|
494
|
+
if (declaredLoginOf(prefs)) return false;
|
|
374
495
|
if (env.MULTI_AGENT_USAGE_TOKEN) return false;
|
|
375
496
|
if (typeof u.token === "string" && u.token.trim()) return false;
|
|
376
497
|
if (pinned(u)) return false;
|
|
@@ -425,6 +546,7 @@ async function main(argv) {
|
|
|
425
546
|
prefs: null,
|
|
426
547
|
feedback: false,
|
|
427
548
|
refresh: false,
|
|
549
|
+
login: null,
|
|
428
550
|
};
|
|
429
551
|
for (let i = 0; i < argv.length; i += 1) {
|
|
430
552
|
const a = argv[i];
|
|
@@ -434,6 +556,7 @@ async function main(argv) {
|
|
|
434
556
|
else if (a === "--feedback") opts.feedback = true;
|
|
435
557
|
else if (a === "--refresh") opts.refresh = true;
|
|
436
558
|
else if (a === "--prefs") opts.prefs = argv[++i];
|
|
559
|
+
else if (a === "--login") opts.login = argv[++i] ?? "";
|
|
437
560
|
}
|
|
438
561
|
const HOME = homedir();
|
|
439
562
|
const prefsPath = opts.prefs || join(HOME, ".claude", "multi-agent-preferences.json");
|
|
@@ -464,6 +587,26 @@ async function main(argv) {
|
|
|
464
587
|
return done("skipped", "no prefs");
|
|
465
588
|
}
|
|
466
589
|
|
|
590
|
+
// A name the person typed: kept as `declaredLogin` and preferred as the
|
|
591
|
+
// account to report under. A proof for it is still used when this machine
|
|
592
|
+
// has one; without one it is registered as declared (see below).
|
|
593
|
+
if (opts.login !== null) {
|
|
594
|
+
if (prefs.global?.usageLog?.optOut === true && !opts.feedback) {
|
|
595
|
+
say(" -> operational reporting: unchanged (opted out)");
|
|
596
|
+
return done("skipped", "opted out");
|
|
597
|
+
}
|
|
598
|
+
const name = String(opts.login).trim();
|
|
599
|
+
if (!LOGIN_RE.test(name)) {
|
|
600
|
+
say(` -> operational reporting: "${name.slice(0, 40)}" is not a GitHub login`);
|
|
601
|
+
return done("skipped", `"${name.slice(0, 40)}" is not a GitHub login`);
|
|
602
|
+
}
|
|
603
|
+
if (!opts.dryRun && !declareLogin(prefsPath, name)) {
|
|
604
|
+
say(" -> operational reporting: could not write preferences");
|
|
605
|
+
return done("unavailable", "prefs write failed");
|
|
606
|
+
}
|
|
607
|
+
opts.refresh = true;
|
|
608
|
+
}
|
|
609
|
+
|
|
467
610
|
// Decided before any credential is read: an opted-out machine, or one whose
|
|
468
611
|
// token is already in the environment or prefs, never touches the credential
|
|
469
612
|
// store or gh.
|
|
@@ -472,16 +615,26 @@ async function main(argv) {
|
|
|
472
615
|
pre.reason,
|
|
473
616
|
);
|
|
474
617
|
const has = identityMatters ? Boolean(storedToken(prefs, credStore)) : false;
|
|
475
|
-
|
|
476
|
-
? reportingUser(prefsPath, { refresh: opts.refresh })
|
|
618
|
+
let who = identityMatters
|
|
619
|
+
? reportingUser(prefsPath, { refresh: opts.refresh, write: !opts.dryRun })
|
|
477
620
|
: { login: undefined, reason: null };
|
|
478
|
-
identity.login = who.login;
|
|
479
621
|
const current = readJson(prefsPath) || prefs;
|
|
622
|
+
const declared =
|
|
623
|
+
opts.dryRun && opts.login !== null ? String(opts.login).trim() : declaredLoginOf(current);
|
|
624
|
+
// A typed name is used only when this machine proves no account at all; a
|
|
625
|
+
// proof, for that name or another, is evidence and a declaration is not.
|
|
626
|
+
const cu = current.global?.usageLog || {};
|
|
627
|
+
const provenPin = has && pinned(cu) && cu.registeredClaim !== true;
|
|
628
|
+
if (identityMatters && declared && !who.login && !provenPin && !opts.feedback) {
|
|
629
|
+
who = { login: declared, method: "claim", key: null, reason: "declared by the person" };
|
|
630
|
+
}
|
|
631
|
+
identity.login = who.login;
|
|
480
632
|
const decision = decideRegistration(current, {
|
|
481
633
|
hasStoredToken: has,
|
|
482
634
|
forFeedback: opts.feedback,
|
|
483
635
|
login: who.login,
|
|
484
636
|
loginReason: who.reason,
|
|
637
|
+
proven: who.method !== "claim",
|
|
485
638
|
});
|
|
486
639
|
if (!decision.act) {
|
|
487
640
|
const repairable = ["token in environment", "token in prefs", "token already onboarded"];
|
|
@@ -531,7 +684,15 @@ async function main(argv) {
|
|
|
531
684
|
}
|
|
532
685
|
|
|
533
686
|
let result;
|
|
534
|
-
if (who.method === "
|
|
687
|
+
if (who.method === "claim") {
|
|
688
|
+
const failedAt = Date.parse(current.global?.usageLog?.claimFailedAt ?? "");
|
|
689
|
+
if (opts.login === null && !Number.isNaN(failedAt) && Date.now() - failedAt < CLAIM_RETRY_MS) {
|
|
690
|
+
say(" -> operational reporting: unchanged (the declared name was refused in the last day)");
|
|
691
|
+
return done("skipped", "declared registration refused recently");
|
|
692
|
+
}
|
|
693
|
+
const label = claimLabel(prefsPath, current.global?.usageLog, host);
|
|
694
|
+
result = await requestToken(url, { u: user, c: label, claim: true }, null, "claim");
|
|
695
|
+
} else if (who.method === "ssh") {
|
|
535
696
|
result = await requestTokenWithSsh(url, { u: user, c: host }, who.key);
|
|
536
697
|
} else {
|
|
537
698
|
const proof = who.method === "credential" ? credentialToken() : ghTokenFor(user);
|
|
@@ -545,7 +706,11 @@ async function main(argv) {
|
|
|
545
706
|
}
|
|
546
707
|
result = await requestToken(url, { u: user, c: host }, proof, who.method || "gh");
|
|
547
708
|
}
|
|
548
|
-
const { token, why, declined, login: pinnedLogin } = result;
|
|
709
|
+
const { token, why, declined, code, login: pinnedLogin } = result;
|
|
710
|
+
if (who.method === "claim" && ["not_collaborator", "proof_required"].includes(code))
|
|
711
|
+
declareLogin(prefsPath, null);
|
|
712
|
+
else if (who.method === "claim" && !token && !declined)
|
|
713
|
+
patchUsageLog(prefsPath, { claimFailedAt: new Date().toISOString() });
|
|
549
714
|
if (declined) {
|
|
550
715
|
say(` -> operational reporting left off (${why})`);
|
|
551
716
|
return done("declined", why);
|
|
@@ -569,6 +734,7 @@ async function main(argv) {
|
|
|
569
734
|
persistPrefs(prefsPath, entry, {
|
|
570
735
|
enableTelemetry: !optedOut,
|
|
571
736
|
registeredAs: pinnedLogin || user,
|
|
737
|
+
claimed: who.method === "claim",
|
|
572
738
|
});
|
|
573
739
|
if (optedOut) {
|
|
574
740
|
say(" -> feedback token registered; telemetry stays off (usageLog.optOut is set)");
|
|
@@ -17,7 +17,7 @@ import { readFileSync } from "node:fs";
|
|
|
17
17
|
|
|
18
18
|
const ALLOWED_STACK = new Set(["ios", "android", "node", "python", "go"]);
|
|
19
19
|
const ALLOWED_NODE_KIND = new Set(["file", "symbol", "module"]);
|
|
20
|
-
const ALLOWED_EDGE_KIND = new Set(["defines", "imports", "references"]);
|
|
20
|
+
const ALLOWED_EDGE_KIND = new Set(["defines", "imports", "references", "inherits"]);
|
|
21
21
|
const MAX_REPORTED_DANGLING = 5;
|
|
22
22
|
|
|
23
23
|
function readInput() {
|
|
@@ -137,8 +137,8 @@ export function validate(obj) {
|
|
|
137
137
|
if (typeof obj !== "object" || obj === null || Array.isArray(obj))
|
|
138
138
|
return ["root must be an object"];
|
|
139
139
|
|
|
140
|
-
if (!["1.0.0", "1.1.0"].includes(obj.schemaVersion))
|
|
141
|
-
errors.push(`schemaVersion must be "1.0.0" or "1.
|
|
140
|
+
if (!["1.0.0", "1.1.0", "1.2.0"].includes(obj.schemaVersion))
|
|
141
|
+
errors.push(`schemaVersion must be "1.0.0", "1.1.0" or "1.2.0"`);
|
|
142
142
|
if (!ALLOWED_STACK.has(obj.stack))
|
|
143
143
|
errors.push(`stack must be one of ${[...ALLOWED_STACK].join(",")}`);
|
|
144
144
|
if (typeof obj.root !== "string" || obj.root.length === 0)
|
|
@@ -837,7 +837,15 @@ function measureRework(repo, mergeBase, range, human, windowDays, now) {
|
|
|
837
837
|
if (f.deleted === 0 || f.binary) continue;
|
|
838
838
|
const old = f.oldPath ?? f.path;
|
|
839
839
|
if (f.untracked) continue;
|
|
840
|
-
const diff = git(repo, [
|
|
840
|
+
const diff = git(repo, [
|
|
841
|
+
"diff",
|
|
842
|
+
"-U0",
|
|
843
|
+
"-M",
|
|
844
|
+
...range,
|
|
845
|
+
"--",
|
|
846
|
+
`:(literal)${old}`,
|
|
847
|
+
`:(literal)${f.path}`,
|
|
848
|
+
]);
|
|
841
849
|
for (const [start, count] of removedRanges(diff)) {
|
|
842
850
|
const porcelain = git(repo, [
|
|
843
851
|
"blame",
|
|
@@ -75,6 +75,7 @@ function fail(msg) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const lines = (out) => out.split("\n").filter(Boolean);
|
|
78
|
+
const paths = (out) => out.split("\0").filter(Boolean);
|
|
78
79
|
|
|
79
80
|
async function main() {
|
|
80
81
|
let args;
|
|
@@ -93,9 +94,11 @@ async function main() {
|
|
|
93
94
|
const headSha = git(repo, ["rev-parse", head]).trim();
|
|
94
95
|
const baseSha = git(repo, ["rev-parse", args.base]).trim();
|
|
95
96
|
const branch = args.branch ?? git(repo, ["rev-parse", "--abbrev-ref", head]).trim();
|
|
96
|
-
const changed =
|
|
97
|
+
const changed = paths(git(repo, ["diff", "-z", "--name-only", "-M", mergeBase, headSha]));
|
|
97
98
|
if (!changed.length) return fail(`no change between ${args.base} and ${head}`);
|
|
98
|
-
const baseTouched = new Set(
|
|
99
|
+
const baseTouched = new Set(
|
|
100
|
+
paths(git(repo, ["diff", "-z", "--name-only", mergeBase, baseSha])),
|
|
101
|
+
);
|
|
99
102
|
const exists = (name) => git(repo, ["branch", "--list", name]).trim() !== "";
|
|
100
103
|
const to = args.to ?? nextName(branch, exists);
|
|
101
104
|
const subject =
|
|
@@ -139,7 +142,7 @@ async function main() {
|
|
|
139
142
|
encoding: "utf-8",
|
|
140
143
|
});
|
|
141
144
|
if (r.status !== 0) {
|
|
142
|
-
const unmerged =
|
|
145
|
+
const unmerged = paths(git(repo, ["diff", "-z", "--name-only", "--diff-filter=U"]));
|
|
143
146
|
plan.conflicts = unmerged.length ? unmerged : lines(r.stderr).slice(0, 20);
|
|
144
147
|
git(repo, ["reset", "-q", "--hard"]);
|
|
145
148
|
git(repo, ["checkout", "-q", back]);
|
|
@@ -149,8 +152,16 @@ async function main() {
|
|
|
149
152
|
git(repo, ["commit", "-q", "-m", plan.subject]);
|
|
150
153
|
plan.applied = true;
|
|
151
154
|
plan.portedSha = git(repo, ["rev-parse", "HEAD"]).trim();
|
|
152
|
-
plan.differsFromHead =
|
|
153
|
-
git(repo, [
|
|
155
|
+
plan.differsFromHead = paths(
|
|
156
|
+
git(repo, [
|
|
157
|
+
"diff",
|
|
158
|
+
"-z",
|
|
159
|
+
"--name-only",
|
|
160
|
+
plan.head,
|
|
161
|
+
"HEAD",
|
|
162
|
+
"--",
|
|
163
|
+
...plan.changedPaths.map((p) => `:(literal)${p}`),
|
|
164
|
+
]),
|
|
154
165
|
);
|
|
155
166
|
git(repo, ["checkout", "-q", back]);
|
|
156
167
|
}
|
|
@@ -320,12 +320,14 @@ async function main() {
|
|
|
320
320
|
git(repo, ["commit", "-q", "--allow-empty", "-m", plan.slices[i].subject]);
|
|
321
321
|
parent = name;
|
|
322
322
|
}
|
|
323
|
-
const drift = git(repo, ["diff", "--name-only", parent, headSha])
|
|
323
|
+
const drift = git(repo, ["diff", "-z", "--name-only", parent, headSha])
|
|
324
|
+
.split("\0")
|
|
325
|
+
.filter(Boolean);
|
|
324
326
|
git(repo, ["checkout", "-q", original === "HEAD" ? headSha : original]);
|
|
325
327
|
plan.applied = true;
|
|
326
|
-
plan.topMatchesBranch = drift ===
|
|
327
|
-
if (drift) {
|
|
328
|
-
plan.drift = drift
|
|
328
|
+
plan.topMatchesBranch = drift.length === 0;
|
|
329
|
+
if (drift.length) {
|
|
330
|
+
plan.drift = drift;
|
|
329
331
|
process.exitCode = 1;
|
|
330
332
|
}
|
|
331
333
|
} catch (err) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-13T08:10:09Z",
|
|
4
4
|
"skillCount": 228,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"path": "shared/core/multi-agent-setup/SKILL.md",
|
|
216
|
-
"sha256": "
|
|
216
|
+
"sha256": "73a1d1d8484d32b6d83107632a84aeced2f230f8581b7d47cad5e2d14e13860a"
|
|
217
217
|
},
|
|
218
218
|
{
|
|
219
219
|
"path": "shared/core/multi-agent-stack/SKILL.md",
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"path": "shared/core/multi-agent-update/SKILL.md",
|
|
268
|
-
"sha256": "
|
|
268
|
+
"sha256": "2f9e224e240e1c9053a59066678f869ae36f18c088482488f3bda8fd207148ea"
|
|
269
269
|
},
|
|
270
270
|
{
|
|
271
271
|
"path": "shared/core/multi-agent/SKILL.md",
|
|
@@ -140,9 +140,9 @@ Then, **whether they pasted or skipped**, run:
|
|
|
140
140
|
node "$HOME/.copilot/scripts/usage-register.mjs" --json
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
If its JSON line says `"needsPersonalAccount":true`,
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
If its JSON line says `"needsPersonalAccount":true`, ask once for the person's
|
|
144
|
+
GitHub username (free text; Skip leaves reporting off) and run
|
|
145
|
+
`node "$HOME/.copilot/scripts/usage-register.mjs" --login <name> --json`.
|
|
146
146
|
|
|
147
147
|
It maps whatever token exists and switches reporting on, and requests a
|
|
148
148
|
per-machine write-only one when none resolves. Setup is a registration entry point
|
|
@@ -44,9 +44,9 @@ Update the pipeline with a single command. Preferences are preserved; only skill
|
|
|
44
44
|
already onboarded, and permanently blocked by `usageLog.optOut: true`. Offline
|
|
45
45
|
or endpoint down leaves reporting off with one line, never an error. Contract:
|
|
46
46
|
`$HOME/.claude/multi-agent-refs/features/usage-reporting.md`.
|
|
47
|
-
If its JSON line says `"needsPersonalAccount":true`,
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
If its JSON line says `"needsPersonalAccount":true`, ask once for the person's
|
|
48
|
+
GitHub username (free text; Skip leaves reporting off) and run
|
|
49
|
+
`node "$HOME/.copilot/scripts/usage-register.mjs" --login <name> --json`.
|
|
50
50
|
6. Show the new version + changelog
|
|
51
51
|
7. Smoke test (schema + cross-cli)
|
|
52
52
|
|