@akagilnc/pi-workflow-roles 0.1.4800 → 0.1.4870
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/dist/acp-host/production-host.js +19434 -18218
- package/dist/analyst-book-key.js +63 -0
- package/dist/analyst-cohort.js +167 -0
- package/dist/analyst-entry.js +320 -0
- package/dist/analyst-index.js +247 -0
- package/dist/analyst-ledger.js +561 -0
- package/dist/analyst-median.js +16 -0
- package/dist/analyst-metric-families/acceptance-success-rework.js +249 -0
- package/dist/analyst-metric-families/b2-frame-buckets-actions.js +189 -0
- package/dist/analyst-metric-families/gate-cycles.js +77 -0
- package/dist/analyst-metric-families/leg-wall-clock.js +56 -0
- package/dist/analyst-metric-families/round-timeline.js +126 -0
- package/dist/analyst-metric-families.js +23 -0
- package/dist/analyst-metric-family.js +11 -0
- package/dist/analyst-model-groups.js +135 -0
- package/dist/analyst-page.js +182 -0
- package/dist/atomic-write.js +23 -0
- package/dist/auditor-soul.js +1 -1
- package/dist/countersign-contracts.js +0 -2
- package/dist/diarist-contracts.js +0 -1
- package/dist/doctor-contracts.js +2 -2
- package/dist/gatekeeper-role.js +15 -16
- package/dist/gleaner-left-contracts.js +0 -2
- package/dist/headless-host/production-host.js +16967 -15749
- package/dist/host-contracts.js +0 -4
- package/dist/inspector-contracts.js +0 -2
- package/dist/institutional-resolution.js +4 -2
- package/dist/merger-contracts.js +0 -2
- package/dist/migrate-book-topology.js +3908 -3089
- package/dist/navigator-public-session.js +4 -3
- package/dist/notary-contracts.js +0 -2
- package/dist/package-contracts/auditor-output.js +0 -1
- package/dist/package-contracts/collector-output.js +0 -2
- package/dist/package-contracts/fixer-output.js +0 -2
- package/dist/package-contracts/gatekeeper-output.js +0 -2
- package/dist/package-contracts/judge-output.js +0 -2
- package/dist/package-contracts/navigator-output.js +0 -2
- package/dist/package-contracts/reviewer-output.js +0 -2
- package/dist/package-contracts/terminating-tools.js +32 -92
- package/dist/package-contracts/worker-output.js +0 -4
- package/dist/packaged-role-registry.js +518 -36
- package/dist/pi/role-turn-host.js +2 -1
- package/dist/public-cli/analyst-run.js +185 -0
- package/dist/public-cli/cli.js +885 -0
- package/dist/public-cli/config.js +12 -9
- package/dist/public-cli/countersign-run.js +4 -311
- package/dist/public-cli/instruction-seat-run.js +628 -158
- package/dist/public-cli/invocation.js +806 -1550
- package/dist/public-cli/main.js +6962 -9882
- package/dist/public-cli/option-definitions.js +51 -99
- package/dist/public-cli/post-admission.js +30 -2
- package/dist/public-cli/run-lifecycle.js +234 -353
- package/dist/public-cli/seat-ticket-binding.js +6 -0
- package/dist/public-cli/settlement.js +261 -675
- package/dist/public-cli/turn-request.js +76 -0
- package/dist/public-role-summons.js +38 -112
- package/dist/role-activation-flags.js +23 -45
- package/dist/secretariat-contracts.js +0 -2
- package/dist/session-identity.js +17 -8
- package/dist/session-opening-materials.js +3 -2
- package/extensions/role-runtime.ts +6 -80
- package/package.json +1 -1
- package/souls/coder.md +1 -2
- package/src/acp-host/role-turn-host.ts +4 -1
- package/src/analyst-metric-families/acceptance-success-rework.ts +3 -2
- package/src/auditor-role.ts +1 -2
- package/src/auditor-soul.ts +1 -1
- package/src/collector-role.ts +2 -2
- package/src/countersign-contracts.ts +0 -1
- package/src/countersign-role.ts +1 -1
- package/src/diarist-contracts.ts +0 -1
- package/src/diarist-role.ts +0 -1
- package/src/doctor-auditor.ts +1 -2
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-role.ts +3 -2
- package/src/external-host-turn-loop.ts +1 -1
- package/src/factory-board.ts +10 -7
- package/src/gatekeeper-role.ts +16 -10
- package/src/gleaner-left-contracts.ts +0 -1
- package/src/gleaner-left-role.ts +0 -1
- package/src/headless-host/role-turn-host.ts +7 -1
- package/src/host-contracts.ts +10 -6
- package/src/inspector-contracts.ts +0 -1
- package/src/inspector-role.ts +0 -2
- package/src/institutional-resolution.ts +6 -4
- package/src/judge-role.ts +3 -3
- package/src/merger-contracts.ts +0 -1
- package/src/merger-role.ts +2 -2
- package/src/navigator-public-session.ts +7 -6
- package/src/navigator-work-context.ts +6 -8
- package/src/notary-contracts.ts +0 -1
- package/src/notary-role.ts +2 -3
- package/src/package-contracts/auditor-output.ts +0 -1
- package/src/package-contracts/collector-output.ts +0 -1
- package/src/package-contracts/fixer-output.ts +0 -1
- package/src/package-contracts/gatekeeper-output.ts +0 -1
- package/src/package-contracts/judge-output.ts +0 -1
- package/src/package-contracts/navigator-output.ts +0 -1
- package/src/package-contracts/reviewer-output.ts +0 -1
- package/src/package-contracts/terminating-tools.ts +27 -99
- package/src/package-contracts/worker-output.ts +0 -2
- package/src/packaged-role-registry.ts +604 -35
- package/src/pi/role-turn-host.ts +2 -1
- package/src/public-cli/cli.ts +64 -259
- package/src/public-cli/config.ts +18 -10
- package/src/public-cli/countersign-run.ts +14 -449
- package/src/public-cli/instruction-seat-run.ts +758 -277
- package/src/public-cli/invocation.ts +1097 -2051
- package/src/public-cli/option-definitions.ts +62 -127
- package/src/public-cli/post-admission.ts +47 -2
- package/src/public-cli/run-lifecycle.ts +303 -613
- package/src/public-cli/seat-ticket-binding.ts +6 -0
- package/src/public-cli/settlement.ts +399 -1185
- package/src/public-cli/turn-request.ts +92 -1
- package/src/public-role-summons.ts +44 -128
- package/src/reviewer-role.ts +3 -2
- package/src/role-activation-flags.ts +20 -41
- package/src/role-runtime-dependencies.ts +25 -26
- package/src/role-runtime.ts +121 -232
- package/src/secretariat-contracts.ts +0 -1
- package/src/secretariat-role.ts +0 -2
- package/src/session-identity.ts +23 -8
- package/src/session-opening-materials.ts +2 -1
- package/src/worker-role.ts +3 -4
- package/dist/doctor-auditor.js +0 -25
- package/dist/public-cli/diarist-run.js +0 -130
- package/dist/public-cli/doctor-run.js +0 -87
- package/dist/public-cli/inspector-run.js +0 -151
- package/dist/public-cli/judge-run.js +0 -114
- package/dist/public-cli/notary-run.js +0 -174
- package/dist/public-cli/reviewer-run.js +0 -339
- package/src/public-cli/coder-run.ts +0 -255
- package/src/public-cli/collector-run.ts +0 -189
- package/src/public-cli/diarist-run.ts +0 -233
- package/src/public-cli/doctor-run.ts +0 -167
- package/src/public-cli/fixer-run.ts +0 -265
- package/src/public-cli/gleaner-left-run.ts +0 -167
- package/src/public-cli/inspector-run.ts +0 -261
- package/src/public-cli/judge-run.ts +0 -196
- package/src/public-cli/merger-run.ts +0 -294
- package/src/public-cli/notary-run.ts +0 -279
- package/src/public-cli/reviewer-run.ts +0 -480
- package/src/public-cli/secretariat-run.ts +0 -265
|
@@ -10,6 +10,7 @@ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
|
10
10
|
import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
11
11
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
12
12
|
import { ensureRoleRunDirectory, ensureRoleRunPlacement, roleRunArtifactsDirectory, roleRunPlacement, } from "../role-run-placement.js";
|
|
13
|
+
import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
|
|
13
14
|
import { isSafePositiveTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
|
|
14
15
|
import { rewriteRunDirectoryPathFields, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
|
|
15
16
|
import { loadDoctorCase, } from "../doctor-evidence.js";
|
|
@@ -337,63 +338,202 @@ export function parsePositiveTicketNumber(raw, flag) {
|
|
|
337
338
|
}
|
|
338
339
|
return value;
|
|
339
340
|
}
|
|
340
|
-
/**
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const positional = [];
|
|
341
|
+
/**
|
|
342
|
+
* One token scan for public argv.
|
|
343
|
+
* Callers assign values; `--` still ends flag parsing.
|
|
344
|
+
* Analyst keeps its own assignment. Public seats assign in `parsePublicSeatArgv`.
|
|
345
|
+
*/
|
|
346
|
+
function scanPublicArgv(args, options, handlers) {
|
|
347
347
|
const tokens = [...args];
|
|
348
|
-
const definitions = roleOptions(owner);
|
|
349
|
-
const options = createTypedOptionConsumer(definitions);
|
|
350
348
|
while (tokens.length > 0) {
|
|
351
349
|
if (tokens[0] === "--") {
|
|
352
350
|
tokens.shift();
|
|
353
|
-
|
|
354
|
-
|
|
351
|
+
handlers.onDoubleDash(tokens);
|
|
352
|
+
return;
|
|
355
353
|
}
|
|
356
354
|
const taken = options.takeDashed(tokens);
|
|
357
355
|
if (taken !== undefined) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
356
|
+
handlers.onDashed(taken);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
handlers.onBare(tokens.shift());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function appendBareInstruction(owner, token, positional) {
|
|
363
|
+
if (token.startsWith("-") && token !== "-") {
|
|
364
|
+
throw new CliUsageError(`unknown ${owner} option: ${token}`);
|
|
365
|
+
}
|
|
366
|
+
positional.push(token);
|
|
367
|
+
}
|
|
368
|
+
function isMergerInternalPacketFace(token) {
|
|
369
|
+
return (token === "--targetObjectId" || token.startsWith("--targetObjectId=")
|
|
370
|
+
|| token === "--sourceObjectId" || token.startsWith("--sourceObjectId=")
|
|
371
|
+
|| token === "--expectedConflictPaths" || token.startsWith("--expectedConflictPaths=")
|
|
372
|
+
|| token === "--resolutionScope" || token.startsWith("--resolutionScope="));
|
|
373
|
+
}
|
|
374
|
+
/** One assignment for every public-seat option id. Owner only selects the table. */
|
|
375
|
+
function assignPublicSeatOption(owner, taken, fields) {
|
|
376
|
+
const value = taken.value;
|
|
377
|
+
switch (taken.def.id) {
|
|
378
|
+
case "attach":
|
|
379
|
+
fields.attachmentPaths.push(requireOptionPath(taken.def.canonical, value));
|
|
380
|
+
return;
|
|
381
|
+
case "project":
|
|
382
|
+
fields.project = requireOptionPath(taken.def.canonical, value);
|
|
383
|
+
return;
|
|
384
|
+
case "subject": {
|
|
385
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
386
|
+
const subject = packagedAdmittedSubject(owner, raw);
|
|
387
|
+
if (subject === undefined) {
|
|
388
|
+
const allowed = packagedSubjectChoices(owner)?.join("|") ?? "";
|
|
389
|
+
throw new CliUsageError(`${owner} --subject must be ${allowed}, got ${value ?? "(missing)"}`);
|
|
390
|
+
}
|
|
391
|
+
fields.subject = subject;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
case "source-run": {
|
|
395
|
+
const text = typeof value === "string" ? value : "";
|
|
396
|
+
if (text.trim() === "") {
|
|
397
|
+
throw new CliUsageError(`${owner} --source-run requires a run locator`);
|
|
361
398
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
399
|
+
fields.sourceRun = packagedStoresSourceRunRaw(owner) ? text : text.trim();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
case "base":
|
|
403
|
+
fields.baseRevision = packagedBaseIsRevision(owner)
|
|
404
|
+
? requireReviewerBaseRevision(value)
|
|
405
|
+
: requireOptionPath(taken.def.canonical, value);
|
|
406
|
+
return;
|
|
407
|
+
case "lens":
|
|
408
|
+
fields.lens = requireReviewerLens(value);
|
|
409
|
+
return;
|
|
410
|
+
case "authority-ref":
|
|
411
|
+
fields.authorityRefs.push(requireAuthorityRef(value));
|
|
412
|
+
return;
|
|
413
|
+
case "prerequisites":
|
|
414
|
+
fields.prerequisitesPath = requireOptionPath(taken.def.canonical, value);
|
|
415
|
+
return;
|
|
416
|
+
case "pr":
|
|
417
|
+
fields.prNumber = parsePositivePrOption(value);
|
|
418
|
+
return;
|
|
419
|
+
case "repo":
|
|
420
|
+
fields.repo = parseRepoOption(value);
|
|
421
|
+
return;
|
|
422
|
+
case "request-manifest":
|
|
423
|
+
fields.requestManifestPath = requireOptionPath(taken.def.canonical, value);
|
|
424
|
+
return;
|
|
425
|
+
case "wait-ms": {
|
|
426
|
+
if (value === undefined || !/^[1-9]\d*$/.test(value.trim())) {
|
|
427
|
+
throw new CliUsageError("--wait-ms requires a positive safe-integer millisecond value");
|
|
365
428
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
throw new CliUsageError(`auditor --subject must be judge|doctor, got ${taken.value ?? "(missing)"}`);
|
|
370
|
-
}
|
|
371
|
-
subject = raw;
|
|
372
|
-
continue;
|
|
429
|
+
const parsed = Number(value.trim());
|
|
430
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
431
|
+
throw new CliUsageError("--wait-ms requires a positive safe-integer millisecond value");
|
|
373
432
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
continue;
|
|
433
|
+
fields.waitWindowMs = parsed;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
case "issue": {
|
|
437
|
+
if (value === undefined || value.trim() === "") {
|
|
438
|
+
throw new CliUsageError("doctor --issue requires a positive integer");
|
|
381
439
|
}
|
|
382
|
-
|
|
440
|
+
fields.issueNumber = parseDoctorIssueNumber(value);
|
|
441
|
+
return;
|
|
383
442
|
}
|
|
384
|
-
|
|
443
|
+
case "runs": {
|
|
444
|
+
if (value === undefined || value.trim() === "") {
|
|
445
|
+
throw new CliUsageError("doctor --runs requires a path");
|
|
446
|
+
}
|
|
447
|
+
fields.runs = value;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
default:
|
|
451
|
+
throw new CliUsageError(`unknown ${owner} option: ${taken.def.canonical}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function rejectSeatBareToken(owner, token) {
|
|
455
|
+
const bare = packagedBareToken(owner);
|
|
456
|
+
if (bare === "burden" && isRejectedPublicSpelling(owner, token)) {
|
|
457
|
+
throw new CliUsageError("judge does not accept a public burden selector; Judge infers its own burden");
|
|
458
|
+
}
|
|
459
|
+
if (bare === "packet"
|
|
460
|
+
&& (isRejectedPublicSpelling(owner, token) || isMergerInternalPacketFace(token))) {
|
|
461
|
+
throw new CliUsageError("merger does not accept public packet fields; the adapter reads Git merge materials");
|
|
462
|
+
}
|
|
463
|
+
if (bare === "instruction") {
|
|
385
464
|
if (token.startsWith("-") && token !== "-") {
|
|
386
|
-
throw new CliUsageError(`unknown
|
|
465
|
+
throw new CliUsageError(`unknown notary option: ${token}`);
|
|
466
|
+
}
|
|
467
|
+
throw new CliUsageError("notary rejects caller prompt/instruction; only --source-run locator is admitted");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Sole public-seat argv parse. Seat rows differ by the option table and by
|
|
472
|
+
* which result keys that table produces. Analyst does not enter here.
|
|
473
|
+
*/
|
|
474
|
+
export function parsePublicSeatArgv(owner, args) {
|
|
475
|
+
if (packagedBareToken(owner) === "burden") {
|
|
476
|
+
const dd = args.indexOf("--");
|
|
477
|
+
const preDd = dd === -1 ? args : args.slice(0, dd);
|
|
478
|
+
for (const token of preDd) {
|
|
479
|
+
if (isRejectedPublicSpelling(owner, token)) {
|
|
480
|
+
throw new CliUsageError("judge does not accept a public burden selector; Judge infers its own burden");
|
|
481
|
+
}
|
|
387
482
|
}
|
|
388
|
-
positional.push(token);
|
|
389
483
|
}
|
|
484
|
+
const fields = {
|
|
485
|
+
attachmentPaths: [],
|
|
486
|
+
positional: [],
|
|
487
|
+
authorityRefs: [],
|
|
488
|
+
};
|
|
489
|
+
const options = createTypedOptionConsumer(roleOptions(owner));
|
|
490
|
+
scanPublicArgv(args, options, {
|
|
491
|
+
onDashed(taken) {
|
|
492
|
+
assignPublicSeatOption(owner, taken, fields);
|
|
493
|
+
},
|
|
494
|
+
onBare(token) {
|
|
495
|
+
rejectSeatBareToken(owner, token);
|
|
496
|
+
appendBareInstruction(owner, token, fields.positional);
|
|
497
|
+
},
|
|
498
|
+
onDoubleDash(rest) {
|
|
499
|
+
if (packagedBareToken(owner) === "instruction" && rest.length > 0) {
|
|
500
|
+
throw new CliUsageError("notary rejects caller prompt/instruction; only --source-run locator is admitted");
|
|
501
|
+
}
|
|
502
|
+
fields.positional.push(...rest);
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
const phaseDef = optionsForOwner(owner).find((def) => def.id === "phase" && def.form === "positional");
|
|
506
|
+
const phase = phaseDef === undefined
|
|
507
|
+
? undefined
|
|
508
|
+
: options.consumeLeadingPhase(fields.positional);
|
|
390
509
|
options.assertRequired();
|
|
510
|
+
const project = fields.project === undefined ? {} : { project: fields.project };
|
|
511
|
+
if (packagedArgvResult(owner) === "source-run") {
|
|
512
|
+
const sourceRun = fields.sourceRun;
|
|
513
|
+
if (sourceRun === undefined || sourceRun.trim() === "") {
|
|
514
|
+
throw new CliUsageError(`${owner} --source-run requires a run locator`);
|
|
515
|
+
}
|
|
516
|
+
return { sourceRun, ...project };
|
|
517
|
+
}
|
|
518
|
+
const instruction = fields.positional.join(" ");
|
|
519
|
+
const material = packagedArgvResult(owner) === "instruction"
|
|
520
|
+
? { instruction, ...project }
|
|
521
|
+
: { instruction, attachmentPaths: fields.attachmentPaths, ...project };
|
|
391
522
|
return {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
...(
|
|
395
|
-
...(
|
|
396
|
-
...(
|
|
523
|
+
...material,
|
|
524
|
+
...(phase === undefined ? {} : { phase }),
|
|
525
|
+
...(fields.prerequisitesPath === undefined ? {} : { prerequisitesPath: fields.prerequisitesPath }),
|
|
526
|
+
...(fields.prNumber === undefined ? {} : { prNumber: fields.prNumber }),
|
|
527
|
+
...(fields.repo === undefined ? {} : { repo: fields.repo }),
|
|
528
|
+
...(fields.requestManifestPath === undefined ? {} : { requestManifestPath: fields.requestManifestPath }),
|
|
529
|
+
...(fields.waitWindowMs === undefined ? {} : { waitWindowMs: fields.waitWindowMs }),
|
|
530
|
+
...(fields.issueNumber === undefined ? {} : { issueNumber: fields.issueNumber }),
|
|
531
|
+
...(fields.runs === undefined ? {} : { runs: fields.runs }),
|
|
532
|
+
...(fields.sourceRun === undefined ? {} : { sourceRun: fields.sourceRun }),
|
|
533
|
+
...(fields.baseRevision === undefined ? {} : { baseRevision: fields.baseRevision }),
|
|
534
|
+
...(fields.lens === undefined ? {} : { lens: fields.lens }),
|
|
535
|
+
...(packagedEmitsAuthorityRefs(owner) ? { authorityRefs: fields.authorityRefs } : {}),
|
|
536
|
+
...(fields.subject === undefined ? {} : { subject: fields.subject }),
|
|
397
537
|
};
|
|
398
538
|
}
|
|
399
539
|
/** Reject missing/blank path values so empty overrides cannot silently degrade. */
|
|
@@ -477,139 +617,6 @@ export function requireAuthorityRef(value) {
|
|
|
477
617
|
optionLike: "--authority-ref requires a durable reference, not inline Spec prose",
|
|
478
618
|
});
|
|
479
619
|
}
|
|
480
|
-
/**
|
|
481
|
-
* Parse Judge-specific argv after the `judge` token.
|
|
482
|
-
* Spellings from PUBLIC_OPTION_TABLE.judge; rejects burden family (#342).
|
|
483
|
-
*/
|
|
484
|
-
export function parseJudgeArgv(args) {
|
|
485
|
-
// Judge owns burden inference — rejected spellings checked per-token.
|
|
486
|
-
// `--` ends flag parsing: everything after is opaque instruction.
|
|
487
|
-
const dd = args.indexOf("--");
|
|
488
|
-
const preDd = dd === -1 ? args : args.slice(0, dd);
|
|
489
|
-
for (const token of preDd) {
|
|
490
|
-
if (isRejectedPublicSpelling("judge", token)) {
|
|
491
|
-
throw new CliUsageError("judge does not accept a public burden selector; Judge infers its own burden");
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return parseInstructionArgv(args, "judge");
|
|
495
|
-
}
|
|
496
|
-
export function parseCountersignArgv(args) {
|
|
497
|
-
return parseInstructionArgv(args, "countersign");
|
|
498
|
-
}
|
|
499
|
-
export function parseInspectorArgv(args) {
|
|
500
|
-
return parseInstructionArgv(args, "inspector");
|
|
501
|
-
}
|
|
502
|
-
export function parseGatekeeperArgv(args) {
|
|
503
|
-
return parseInstructionArgv(args, "gatekeeper");
|
|
504
|
-
}
|
|
505
|
-
export function parseNavigatorArgv(args) {
|
|
506
|
-
return parseInstructionArgv(args, "navigator");
|
|
507
|
-
}
|
|
508
|
-
export function parseAuditorArgv(args) {
|
|
509
|
-
return parseInstructionArgv(args, "auditor");
|
|
510
|
-
}
|
|
511
|
-
export function parseDiaristArgv(args) {
|
|
512
|
-
return parseInstructionArgv(args, "diarist");
|
|
513
|
-
}
|
|
514
|
-
export function parseSecretariatArgv(args) {
|
|
515
|
-
return parseInstructionArgv(args, "secretariat");
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Parse Coder-specific argv after the `coder` token.
|
|
519
|
-
* Phase defaults to apply; spellings from PUBLIC_OPTION_TABLE.coder (#342).
|
|
520
|
-
*/
|
|
521
|
-
export function parseCoderArgv(args) {
|
|
522
|
-
const attachmentPaths = [];
|
|
523
|
-
let project;
|
|
524
|
-
const positional = [];
|
|
525
|
-
const tokens = [...args];
|
|
526
|
-
const definitions = roleOptions("coder");
|
|
527
|
-
const options = createTypedOptionConsumer(definitions);
|
|
528
|
-
while (tokens.length > 0) {
|
|
529
|
-
if (tokens[0] === "--") {
|
|
530
|
-
tokens.shift();
|
|
531
|
-
positional.push(...tokens);
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
const taken = options.takeDashed(tokens);
|
|
535
|
-
if (taken !== undefined) {
|
|
536
|
-
if (taken.def.id === "attach") {
|
|
537
|
-
attachmentPaths.push(requireOptionPath(taken.def.canonical, taken.value));
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
if (taken.def.id === "project") {
|
|
541
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
542
|
-
continue;
|
|
543
|
-
}
|
|
544
|
-
throw new CliUsageError(`unknown coder option: ${taken.def.canonical}`);
|
|
545
|
-
}
|
|
546
|
-
const token = tokens.shift();
|
|
547
|
-
if (token.startsWith("-") && token !== "-") {
|
|
548
|
-
throw new CliUsageError(`unknown coder option: ${token}`);
|
|
549
|
-
}
|
|
550
|
-
positional.push(token);
|
|
551
|
-
}
|
|
552
|
-
// Phase aliases + default come solely from the typed coder phase row (#342).
|
|
553
|
-
const phase = options.consumeLeadingPhase(positional);
|
|
554
|
-
options.assertRequired();
|
|
555
|
-
return {
|
|
556
|
-
phase,
|
|
557
|
-
instruction: positional.join(" "),
|
|
558
|
-
attachmentPaths,
|
|
559
|
-
...(project === undefined ? {} : { project }),
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Parse Fixer-specific argv after the `fixer` token.
|
|
564
|
-
* Phase defaults to apply; spellings from PUBLIC_OPTION_TABLE.fixer (#342).
|
|
565
|
-
*/
|
|
566
|
-
export function parseFixerArgv(args) {
|
|
567
|
-
const attachmentPaths = [];
|
|
568
|
-
let project;
|
|
569
|
-
let prerequisitesPath;
|
|
570
|
-
const positional = [];
|
|
571
|
-
const tokens = [...args];
|
|
572
|
-
const definitions = roleOptions("fixer");
|
|
573
|
-
const options = createTypedOptionConsumer(definitions);
|
|
574
|
-
while (tokens.length > 0) {
|
|
575
|
-
if (tokens[0] === "--") {
|
|
576
|
-
tokens.shift();
|
|
577
|
-
positional.push(...tokens);
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
const taken = options.takeDashed(tokens);
|
|
581
|
-
if (taken !== undefined) {
|
|
582
|
-
if (taken.def.id === "attach") {
|
|
583
|
-
attachmentPaths.push(requireOptionPath(taken.def.canonical, taken.value));
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
if (taken.def.id === "project") {
|
|
587
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
if (taken.def.id === "prerequisites") {
|
|
591
|
-
prerequisitesPath = requireOptionPath(taken.def.canonical, taken.value);
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
throw new CliUsageError(`unknown fixer option: ${taken.def.canonical}`);
|
|
595
|
-
}
|
|
596
|
-
const token = tokens.shift();
|
|
597
|
-
if (token.startsWith("-") && token !== "-") {
|
|
598
|
-
throw new CliUsageError(`unknown fixer option: ${token}`);
|
|
599
|
-
}
|
|
600
|
-
positional.push(token);
|
|
601
|
-
}
|
|
602
|
-
// Phase aliases + default come solely from the typed fixer phase row (#342).
|
|
603
|
-
const phase = options.consumeLeadingPhase(positional);
|
|
604
|
-
options.assertRequired();
|
|
605
|
-
return {
|
|
606
|
-
phase,
|
|
607
|
-
instruction: positional.join(" "),
|
|
608
|
-
attachmentPaths,
|
|
609
|
-
...(prerequisitesPath === undefined ? {} : { prerequisitesPath }),
|
|
610
|
-
...(project === undefined ? {} : { project }),
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
620
|
async function readRegularFileAttachment(sourcePath) {
|
|
614
621
|
const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
|
|
615
622
|
let st;
|
|
@@ -729,121 +736,573 @@ function ticketAdmissionFields(ticketNumber) {
|
|
|
729
736
|
ticketNumber: requireSafePositiveTicketNumber(ticketNumber, "assertedTicketNumber"),
|
|
730
737
|
};
|
|
731
738
|
}
|
|
739
|
+
/** Project an already-typed summons ticket into admit options. Absent stays absent. */
|
|
740
|
+
export function summonedTicketFields(ticketNumber) {
|
|
741
|
+
if (ticketNumber === undefined)
|
|
742
|
+
return {};
|
|
743
|
+
return { assertedTicketNumber: ticketNumber };
|
|
744
|
+
}
|
|
732
745
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
* ledger write. CorrelationId is projected only when supplied (Inspector).
|
|
736
|
-
* Ticket binding is post-admission via shared seat LLM path (#635).
|
|
746
|
+
* Fields every public admit call shares, including the summons ticket.
|
|
747
|
+
* Seat runners spread this once; they do not restate ticket placement.
|
|
737
748
|
*/
|
|
738
|
-
|
|
739
|
-
|
|
749
|
+
export function admissionCallerOptions(env) {
|
|
750
|
+
return {
|
|
751
|
+
home: env.home,
|
|
752
|
+
principalAuthority: env.principalAuthority,
|
|
753
|
+
cwd: env.cwd,
|
|
754
|
+
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
755
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
756
|
+
...(env.correlationId === undefined || env.correlationId.trim() === ""
|
|
757
|
+
? {}
|
|
758
|
+
: { correlationId: env.correlationId }),
|
|
759
|
+
...summonedTicketFields(env.boundTicketNumber),
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
export async function admitPublicRole(role, parsed, env, override) {
|
|
763
|
+
const record = packagedRoleMetadata(role);
|
|
764
|
+
if (record === undefined) {
|
|
765
|
+
throw new CliUsageError(`unknown role: ${role}`);
|
|
766
|
+
}
|
|
767
|
+
const shared = {
|
|
768
|
+
...admissionCallerOptions(env),
|
|
769
|
+
...(override?.assertedTicketNumber === undefined
|
|
770
|
+
? {}
|
|
771
|
+
: { assertedTicketNumber: override.assertedTicketNumber }),
|
|
772
|
+
};
|
|
773
|
+
const instruction = parsed.instruction ?? "";
|
|
774
|
+
const attachmentPaths = parsed.attachmentPaths ?? [];
|
|
775
|
+
const project = parsed.project === undefined ? {} : { project: parsed.project };
|
|
776
|
+
switch (record.admission) {
|
|
777
|
+
case "instruction":
|
|
778
|
+
return admitStandardMaterialInvocation(role, {
|
|
779
|
+
...shared,
|
|
780
|
+
instruction,
|
|
781
|
+
attachmentPaths,
|
|
782
|
+
...project,
|
|
783
|
+
});
|
|
784
|
+
case "court-materials":
|
|
785
|
+
return admitStandardMaterialInvocation("countersign", {
|
|
786
|
+
...shared,
|
|
787
|
+
instruction,
|
|
788
|
+
attachmentPaths,
|
|
789
|
+
...project,
|
|
790
|
+
...(override?.deferPersistence === undefined
|
|
791
|
+
? {}
|
|
792
|
+
: { deferPersistence: override.deferPersistence }),
|
|
793
|
+
});
|
|
794
|
+
case "worker-task": {
|
|
795
|
+
if (instruction.trim() === "") {
|
|
796
|
+
throw new CliUsageError("coder requires a nonblank task instruction");
|
|
797
|
+
}
|
|
798
|
+
const phase = parsed.phase ?? "apply";
|
|
799
|
+
if (!record.phases.some((item) => item === phase)) {
|
|
800
|
+
throw new CliUsageError("coder phase must be plan or apply");
|
|
801
|
+
}
|
|
802
|
+
return admitStandardMaterialInvocation("coder", {
|
|
803
|
+
...shared,
|
|
804
|
+
instruction,
|
|
805
|
+
attachmentPaths,
|
|
806
|
+
...project,
|
|
807
|
+
placedFields: async (placed) => {
|
|
808
|
+
const taskPath = join(placed.runDirectory, "task.md");
|
|
809
|
+
await writeFile(taskPath, instruction, "utf8");
|
|
810
|
+
return { phase, taskPath };
|
|
811
|
+
},
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
case "worker-packet": {
|
|
815
|
+
if (parsed.project !== undefined) {
|
|
816
|
+
requireOptionPath("--project", parsed.project);
|
|
817
|
+
}
|
|
818
|
+
if (instruction.trim() === "") {
|
|
819
|
+
throw new CliUsageError("fixer requires a nonblank repair instruction");
|
|
820
|
+
}
|
|
821
|
+
const phase = parsed.phase ?? "apply";
|
|
822
|
+
if (!record.phases.some((item) => item === phase)) {
|
|
823
|
+
throw new CliUsageError("fixer phase must be plan or apply");
|
|
824
|
+
}
|
|
825
|
+
let prerequisites = Object.freeze([]);
|
|
826
|
+
let prerequisitesSource;
|
|
827
|
+
if (parsed.prerequisitesPath !== undefined) {
|
|
828
|
+
const absolutePrereq = isAbsolute(parsed.prerequisitesPath)
|
|
829
|
+
? parsed.prerequisitesPath
|
|
830
|
+
: resolve(parsed.prerequisitesPath);
|
|
831
|
+
try {
|
|
832
|
+
prerequisitesSource = await readFile(absolutePrereq, "utf8");
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
throw new CliUsageError(`fixer prerequisites path is unreadable: ${parsed.prerequisitesPath}`, { cause: error });
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
prerequisites = parseFixerPrerequisites(prerequisitesSource);
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
if (error instanceof FixerPacketValidationError) {
|
|
842
|
+
throw new CliUsageError(error.message, { cause: error });
|
|
843
|
+
}
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return admitStandardMaterialInvocation("fixer", {
|
|
848
|
+
...shared,
|
|
849
|
+
instruction,
|
|
850
|
+
attachmentPaths,
|
|
851
|
+
...project,
|
|
852
|
+
placedFields: async (placed) => {
|
|
853
|
+
let prerequisitesPath;
|
|
854
|
+
if (prerequisitesSource !== undefined) {
|
|
855
|
+
prerequisitesPath = join(placed.runDirectory, "prerequisites.json");
|
|
856
|
+
await writeFile(prerequisitesPath, `${JSON.stringify(prerequisites, null, 2)}\n`, "utf8");
|
|
857
|
+
}
|
|
858
|
+
const packetPath = join(placed.runDirectory, "fix-packet.md");
|
|
859
|
+
await writeFile(packetPath, instruction, "utf8");
|
|
860
|
+
return {
|
|
861
|
+
phase,
|
|
862
|
+
packetPath,
|
|
863
|
+
prerequisites,
|
|
864
|
+
...(prerequisitesPath === undefined ? {} : { prerequisitesPath }),
|
|
865
|
+
};
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
case "collect-target": {
|
|
870
|
+
if (parsed.project !== undefined) {
|
|
871
|
+
requireOptionPath("--project", parsed.project);
|
|
872
|
+
}
|
|
873
|
+
let explicitPrNumber;
|
|
874
|
+
if (parsed.prNumber !== undefined) {
|
|
875
|
+
try {
|
|
876
|
+
explicitPrNumber = parseCollectorPrNumber(parsed.prNumber);
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
880
|
+
throw new CliUsageError(detail, { cause: error });
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const projectRoot = resolve(parsed.project ?? shared.cwd);
|
|
884
|
+
let repository;
|
|
885
|
+
if (parsed.repo !== undefined) {
|
|
886
|
+
try {
|
|
887
|
+
repository = parseCollectorRepository(parsed.repo);
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
891
|
+
throw new CliUsageError(detail, { cause: error });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
repository = resolveGitHubRemoteRepository(projectRoot);
|
|
896
|
+
}
|
|
897
|
+
let manifest = emptyCollectorManifest();
|
|
898
|
+
let manifestCanonicalJson;
|
|
899
|
+
if (parsed.requestManifestPath !== undefined) {
|
|
900
|
+
try {
|
|
901
|
+
manifest = await loadCollectorManifest(parsed.requestManifestPath);
|
|
902
|
+
manifestCanonicalJson = manifest.canonicalJson;
|
|
903
|
+
}
|
|
904
|
+
catch (error) {
|
|
905
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const manifestDigest = manifest.digest;
|
|
909
|
+
const admittedCollector = await admitStandardMaterialInvocation("collector", {
|
|
910
|
+
...shared,
|
|
911
|
+
instruction,
|
|
912
|
+
attachmentPaths,
|
|
913
|
+
...project,
|
|
914
|
+
placedFields: async (placed) => {
|
|
915
|
+
// Task materials are frozen before target resolution (#676 A).
|
|
916
|
+
const target = await resolveCollectorTarget({
|
|
917
|
+
projectRoot,
|
|
918
|
+
repository,
|
|
919
|
+
...(explicitPrNumber === undefined ? {} : { explicitPrNumber }),
|
|
920
|
+
});
|
|
921
|
+
const prNumber = target.kind === "bound" ? target.prNumber : undefined;
|
|
922
|
+
let requestManifestPath;
|
|
923
|
+
if (manifestCanonicalJson !== undefined) {
|
|
924
|
+
requestManifestPath = join(placed.runDirectory, "request-manifest.json");
|
|
925
|
+
await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
...(prNumber === undefined ? {} : { prNumber }),
|
|
929
|
+
repository: repository.canonical,
|
|
930
|
+
repositoryDisplay: repository.display,
|
|
931
|
+
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
932
|
+
...(parsed.waitWindowMs === undefined ? {} : { waitWindowMs: parsed.waitWindowMs }),
|
|
933
|
+
manifestDigest,
|
|
934
|
+
};
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
return { ...admittedCollector, repository };
|
|
938
|
+
}
|
|
939
|
+
case "case-identity": {
|
|
940
|
+
if (parsed.project !== undefined) {
|
|
941
|
+
requireOptionPath("--project", parsed.project);
|
|
942
|
+
}
|
|
943
|
+
const issueNumber = parsed.issueNumber ?? Number.NaN;
|
|
944
|
+
if (!Number.isInteger(issueNumber) ||
|
|
945
|
+
issueNumber < 1 ||
|
|
946
|
+
!DOCTOR_ISSUE_NUMBER_PATTERN.test(String(issueNumber))) {
|
|
947
|
+
throw new CliUsageError(`doctor --issue must be a positive integer, got ${issueNumber}`);
|
|
948
|
+
}
|
|
949
|
+
let frozenAttachments = [];
|
|
950
|
+
const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
|
|
951
|
+
...shared,
|
|
952
|
+
instruction,
|
|
953
|
+
attachmentPaths: [],
|
|
954
|
+
freezeAttachments: false,
|
|
955
|
+
...project,
|
|
956
|
+
placedFields: async (placed) => {
|
|
957
|
+
let caseRunsPath;
|
|
958
|
+
try {
|
|
959
|
+
caseRunsPath = await resolveDoctorCaseRunsPath({
|
|
960
|
+
home: shared.home,
|
|
961
|
+
projectRoot: placed.projectRoot,
|
|
962
|
+
bookKey: placed.bookKey,
|
|
963
|
+
issueNumber,
|
|
964
|
+
...(parsed.runs === undefined ? {} : { runs: parsed.runs }),
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
catch (error) {
|
|
968
|
+
if (error instanceof CliUsageError)
|
|
969
|
+
throw error;
|
|
970
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
971
|
+
throw new CliUsageError(detail, { cause: error });
|
|
972
|
+
}
|
|
973
|
+
if (parsed.runs === undefined) {
|
|
974
|
+
ensureRealDirectoryTree(placed.ledgerHome, caseRunsPath);
|
|
975
|
+
}
|
|
976
|
+
let caseIdentity;
|
|
977
|
+
try {
|
|
978
|
+
const patient = await loadDoctorCase(caseRunsPath);
|
|
979
|
+
if (patient.identity.issueNumber !== issueNumber) {
|
|
980
|
+
throw new CliUsageError(`doctor case issue ${patient.identity.issueNumber} does not match --issue ${issueNumber}`);
|
|
981
|
+
}
|
|
982
|
+
caseIdentity = patient.identity;
|
|
983
|
+
caseRunsPath = await realpath(caseRunsPath);
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
if (error instanceof CliUsageError)
|
|
987
|
+
throw error;
|
|
988
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
989
|
+
throw new CliUsageError(`doctor case could not be constructed from retained evidence: ${detail}`, { cause: error });
|
|
990
|
+
}
|
|
991
|
+
frozenAttachments = await freezeAttachments(attachmentPaths, placed.attachmentsDirectory);
|
|
992
|
+
return {
|
|
993
|
+
issueNumber,
|
|
994
|
+
caseRunsPath,
|
|
995
|
+
caseIdentity,
|
|
996
|
+
attachments: persistedAttachmentRefs(frozenAttachments),
|
|
997
|
+
};
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
return { ...admittedDoctor, attachments: frozenAttachments };
|
|
1001
|
+
}
|
|
1002
|
+
case "source-locator": {
|
|
1003
|
+
if (parsed.project !== undefined) {
|
|
1004
|
+
requireOptionPath("--project", parsed.project);
|
|
1005
|
+
}
|
|
1006
|
+
const projectRoot = resolve(parsed.project ?? shared.cwd);
|
|
1007
|
+
let sourceRun;
|
|
1008
|
+
try {
|
|
1009
|
+
sourceRun = await resolveNotarySourceRunLocator({
|
|
1010
|
+
projectRoot,
|
|
1011
|
+
sourceRun: parsed.sourceRun ?? "",
|
|
1012
|
+
home: shared.home,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
catch (error) {
|
|
1016
|
+
if (error instanceof NotarySourceRunError) {
|
|
1017
|
+
throw new CliUsageError(error.message, { cause: error });
|
|
1018
|
+
}
|
|
1019
|
+
throw error;
|
|
1020
|
+
}
|
|
1021
|
+
// Board ticket on the source run wins; otherwise the summons ticket. Code does not infer one.
|
|
1022
|
+
const inheritedTicketNumber = await readBoardTicketNumber(sourceRun.runDirectory);
|
|
1023
|
+
const knownTicketNumber = inheritedTicketNumber ?? shared.assertedTicketNumber;
|
|
1024
|
+
return admitStandardMaterialInvocation("notary", {
|
|
1025
|
+
...shared,
|
|
1026
|
+
...(knownTicketNumber === undefined
|
|
1027
|
+
? {}
|
|
1028
|
+
: { assertedTicketNumber: knownTicketNumber }),
|
|
1029
|
+
instruction: "",
|
|
1030
|
+
attachmentPaths: [],
|
|
1031
|
+
...project,
|
|
1032
|
+
freezeAttachments: false,
|
|
1033
|
+
admittedFields: {
|
|
1034
|
+
sourceRunPath: sourceRun.runDirectory,
|
|
1035
|
+
sourceRun,
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
case "gleaner": {
|
|
1040
|
+
if (parsed.project !== undefined) {
|
|
1041
|
+
requireOptionPath("--project", parsed.project);
|
|
1042
|
+
}
|
|
1043
|
+
const baseRevision = parsed.baseRevision ?? "";
|
|
1044
|
+
if (baseRevision.trim() === "") {
|
|
1045
|
+
throw new CliUsageError("--base requires a nonempty revision");
|
|
1046
|
+
}
|
|
1047
|
+
return admitStandardMaterialInvocation("gleaner-left", {
|
|
1048
|
+
...shared,
|
|
1049
|
+
instruction,
|
|
1050
|
+
attachmentPaths: [],
|
|
1051
|
+
...project,
|
|
1052
|
+
freezeAttachments: false,
|
|
1053
|
+
admittedFields: { baseRevision },
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
case "review-basis": {
|
|
1057
|
+
const lens = requireReviewerLens(parsed.lens);
|
|
1058
|
+
if (parsed.project !== undefined) {
|
|
1059
|
+
requireOptionPath("--project", parsed.project);
|
|
1060
|
+
}
|
|
1061
|
+
const baseRevision = requireReviewerBaseRevision(parsed.baseRevision);
|
|
1062
|
+
const rawRefs = parsed.authorityRefs ?? [];
|
|
1063
|
+
if (rawRefs.length === 0) {
|
|
1064
|
+
throw new CliUsageError("reviewer requires --authority-ref <ref>");
|
|
1065
|
+
}
|
|
1066
|
+
const authorityRefs = Object.freeze(rawRefs.map((ref) => requireAuthorityRef(ref)));
|
|
1067
|
+
const admittedReviewer = await admitStandardMaterialInvocation("reviewer", {
|
|
1068
|
+
...shared,
|
|
1069
|
+
instruction,
|
|
1070
|
+
attachmentPaths,
|
|
1071
|
+
...project,
|
|
1072
|
+
admittedFields: {
|
|
1073
|
+
baseRevision,
|
|
1074
|
+
lens,
|
|
1075
|
+
authorityRefs: [...authorityRefs],
|
|
1076
|
+
},
|
|
1077
|
+
});
|
|
1078
|
+
return { ...admittedReviewer, authorityRefs };
|
|
1079
|
+
}
|
|
1080
|
+
case "merge-envelope": {
|
|
1081
|
+
if (parsed.project !== undefined) {
|
|
1082
|
+
requireOptionPath("--project", parsed.project);
|
|
1083
|
+
}
|
|
1084
|
+
if (instruction.trim() === "") {
|
|
1085
|
+
throw new CliUsageError("merger requires a nonblank task instruction");
|
|
1086
|
+
}
|
|
1087
|
+
const projectRoot = resolve(parsed.project ?? shared.cwd);
|
|
1088
|
+
const derived = await deriveMergerEnvelopeFromActiveMerge(projectRoot, createProductionMergerGitState(projectRoot));
|
|
1089
|
+
return admitStandardMaterialInvocation("merger", {
|
|
1090
|
+
...shared,
|
|
1091
|
+
instruction,
|
|
1092
|
+
attachmentPaths,
|
|
1093
|
+
...project,
|
|
1094
|
+
placedFields: async (placed) => {
|
|
1095
|
+
const targetLabel = derived.targetObjectId === "" ? "(none observed)" : derived.targetObjectId;
|
|
1096
|
+
const sourceLabel = derived.sourceObjectId === "" ? "(none observed)" : derived.sourceObjectId;
|
|
1097
|
+
const mergerInput = validateMergerInput({
|
|
1098
|
+
attemptId: placed.runId,
|
|
1099
|
+
targetObjectId: derived.targetObjectId,
|
|
1100
|
+
sourceObjectId: derived.sourceObjectId,
|
|
1101
|
+
materials: {
|
|
1102
|
+
task: mergerMaterialFromUtf8(instruction),
|
|
1103
|
+
authority: mergerMaterialFromUtf8(instruction),
|
|
1104
|
+
targetIntent: mergerMaterialFromUtf8(`Investigate primary sources for target parent ${targetLabel}. Do not invent intent.`),
|
|
1105
|
+
sourceIntent: mergerMaterialFromUtf8(`Investigate primary sources for source parent ${sourceLabel}. Do not invent intent.`),
|
|
1106
|
+
},
|
|
1107
|
+
expectedConflictPaths: [...derived.expectedConflictPaths],
|
|
1108
|
+
resolutionScope: [...derived.resolutionScope],
|
|
1109
|
+
authorizedChecks: [],
|
|
1110
|
+
});
|
|
1111
|
+
const mergerInputPath = join(placed.runDirectory, "merger-input.json");
|
|
1112
|
+
await writeFile(mergerInputPath, `${JSON.stringify(mergerInput, null, 2)}\n`, "utf8");
|
|
1113
|
+
return {
|
|
1114
|
+
mergerInputPath,
|
|
1115
|
+
derived: {
|
|
1116
|
+
targetObjectId: derived.targetObjectId,
|
|
1117
|
+
sourceObjectId: derived.sourceObjectId,
|
|
1118
|
+
expectedConflictPaths: [...derived.expectedConflictPaths],
|
|
1119
|
+
resolutionScope: [...derived.resolutionScope],
|
|
1120
|
+
},
|
|
1121
|
+
};
|
|
1122
|
+
},
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/** One placement subject: a typed ticket already on the summons, otherwise unbound. */
|
|
1128
|
+
function admissionSubject(ticketNumber) {
|
|
1129
|
+
const fields = ticketAdmissionFields(ticketNumber);
|
|
1130
|
+
if (fields.ticketNumber === undefined)
|
|
1131
|
+
return { unbound: true };
|
|
1132
|
+
return { ticketNumber: fields.ticketNumber };
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* One admission placement (#505): typed ticket already on the summons, otherwise unbound.
|
|
1136
|
+
* Every public seat persists through this function. Seat-only fields are supplied by the caller.
|
|
1137
|
+
*/
|
|
1138
|
+
async function placeRoleAdmission(options) {
|
|
740
1139
|
if (options.project !== undefined) {
|
|
741
1140
|
requireOptionPath("--project", options.project);
|
|
742
1141
|
}
|
|
743
1142
|
const projectRoot = resolve(options.project ?? options.cwd);
|
|
744
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
745
|
-
// Validate asserted ticket before placement so 0/NaN never become subjects.
|
|
1143
|
+
const runId = options.runId ?? (options.createRunId ?? uuidv7)();
|
|
746
1144
|
const ticketFields = ticketAdmissionFields(options.assertedTicketNumber);
|
|
747
1145
|
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
748
1146
|
cwd: projectRoot,
|
|
749
1147
|
runId,
|
|
1148
|
+
role: options.role,
|
|
1149
|
+
subject: admissionSubject(options.assertedTicketNumber),
|
|
1150
|
+
home: options.home,
|
|
1151
|
+
...(options.materialize === undefined ? {} : { materialize: options.materialize }),
|
|
1152
|
+
});
|
|
1153
|
+
const attachments = options.freezeAttachments === false
|
|
1154
|
+
? []
|
|
1155
|
+
: await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1156
|
+
return {
|
|
1157
|
+
runId,
|
|
1158
|
+
bookKey,
|
|
1159
|
+
projectRoot,
|
|
1160
|
+
runDirectory,
|
|
1161
|
+
principal,
|
|
1162
|
+
sessionDirectory,
|
|
1163
|
+
sessionFile,
|
|
1164
|
+
attachments,
|
|
1165
|
+
attachmentsDirectory,
|
|
1166
|
+
ledgerHome,
|
|
1167
|
+
ticketFields,
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function persistedAttachmentRefs(attachments) {
|
|
1171
|
+
return attachments.map((attachment) => ({
|
|
1172
|
+
provenancePath: attachment.provenancePath,
|
|
1173
|
+
frozenPath: attachment.frozenPath,
|
|
1174
|
+
byteLength: attachment.byteLength,
|
|
1175
|
+
sha256: attachment.sha256,
|
|
1176
|
+
mediaKind: attachment.mediaKind,
|
|
1177
|
+
}));
|
|
1178
|
+
}
|
|
1179
|
+
async function persistPlacedAdmission(admitted, placed, model) {
|
|
1180
|
+
const admittedRequestPath = join(placed.runDirectory, "admitted-request.json");
|
|
1181
|
+
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1182
|
+
sessionDirectory: placed.sessionDirectory,
|
|
1183
|
+
sessionFile: placed.sessionFile,
|
|
1184
|
+
});
|
|
1185
|
+
await writeRoleInvocationLedger({
|
|
1186
|
+
...admitted,
|
|
1187
|
+
sessionDirectory: placed.sessionDirectory,
|
|
1188
|
+
sessionFile: placed.sessionFile,
|
|
1189
|
+
}, admitted.role, model);
|
|
1190
|
+
return admittedRequestPath;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Shared admission: project check, placement, attachment freeze,
|
|
1194
|
+
* admitted-request and invocation ledger write.
|
|
1195
|
+
* Countersign passes deferPersistence so same-ticket lookup can reserve coordinates
|
|
1196
|
+
* before materializeCountersignInvocation writes the page.
|
|
1197
|
+
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
1198
|
+
* Seats whose extra facts depend on the placed run pass placedFields
|
|
1199
|
+
* (coder writes task.md; fixer writes fix-packet.md; collector writes request-manifest.json;
|
|
1200
|
+
* doctor resolves the case, then freezes attachments; merger writes merger-input.json).
|
|
1201
|
+
*/
|
|
1202
|
+
async function admitStandardMaterialInvocation(role, options) {
|
|
1203
|
+
const defer = options.deferPersistence === true;
|
|
1204
|
+
const skipFreeze = defer || options.freezeAttachments === false;
|
|
1205
|
+
const placed = await placeRoleAdmission({
|
|
750
1206
|
role,
|
|
751
|
-
subject: ticketFields.ticketNumber === undefined
|
|
752
|
-
? { unbound: true }
|
|
753
|
-
: { ticketNumber: ticketFields.ticketNumber },
|
|
754
1207
|
home: options.home,
|
|
1208
|
+
principalAuthority: options.principalAuthority,
|
|
1209
|
+
cwd: options.cwd,
|
|
1210
|
+
attachmentPaths: options.attachmentPaths,
|
|
1211
|
+
...(skipFreeze ? { freezeAttachments: false } : {}),
|
|
1212
|
+
...(defer ? { materialize: false } : {}),
|
|
1213
|
+
...(options.project === undefined ? {} : { project: options.project }),
|
|
1214
|
+
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
1215
|
+
...(options.assertedTicketNumber === undefined
|
|
1216
|
+
? {}
|
|
1217
|
+
: { assertedTicketNumber: options.assertedTicketNumber }),
|
|
755
1218
|
});
|
|
756
|
-
const attachments = await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
757
1219
|
const correlationFields = options.correlationId === undefined
|
|
758
1220
|
? {}
|
|
759
1221
|
: { correlationId: options.correlationId };
|
|
760
1222
|
const instruction = options.instruction;
|
|
761
1223
|
const instructionEmpty = instruction.trim() === "";
|
|
1224
|
+
const placedExtra = options.placedFields === undefined ? undefined : await options.placedFields(placed);
|
|
1225
|
+
const admittedFields = {
|
|
1226
|
+
...(options.admittedFields ?? {}),
|
|
1227
|
+
...(placedExtra ?? {}),
|
|
1228
|
+
};
|
|
762
1229
|
const admitted = {
|
|
763
1230
|
role,
|
|
764
|
-
runId,
|
|
765
|
-
bookKey,
|
|
766
|
-
projectRoot,
|
|
767
|
-
runDirectory,
|
|
768
|
-
principal,
|
|
1231
|
+
runId: placed.runId,
|
|
1232
|
+
bookKey: placed.bookKey,
|
|
1233
|
+
projectRoot: placed.projectRoot,
|
|
1234
|
+
runDirectory: placed.runDirectory,
|
|
1235
|
+
principal: placed.principal,
|
|
769
1236
|
...correlationFields,
|
|
770
1237
|
instruction,
|
|
771
1238
|
instructionEmpty,
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
byteLength: a.byteLength,
|
|
776
|
-
sha256: a.sha256,
|
|
777
|
-
mediaKind: a.mediaKind,
|
|
778
|
-
})),
|
|
779
|
-
...ticketFields,
|
|
1239
|
+
...admittedFields,
|
|
1240
|
+
attachments: persistedAttachmentRefs(placed.attachments),
|
|
1241
|
+
...placed.ticketFields,
|
|
780
1242
|
};
|
|
781
|
-
const admittedRequestPath =
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
sessionFile,
|
|
785
|
-
});
|
|
786
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1243
|
+
const admittedRequestPath = defer
|
|
1244
|
+
? join(placed.runDirectory, "admitted-request.json")
|
|
1245
|
+
: await persistPlacedAdmission(admitted, placed, options.model);
|
|
787
1246
|
return {
|
|
788
1247
|
role,
|
|
789
|
-
runId,
|
|
790
|
-
bookKey,
|
|
791
|
-
projectRoot,
|
|
1248
|
+
runId: placed.runId,
|
|
1249
|
+
bookKey: placed.bookKey,
|
|
1250
|
+
projectRoot: placed.projectRoot,
|
|
792
1251
|
instruction,
|
|
793
1252
|
instructionEmpty,
|
|
794
|
-
attachments,
|
|
795
|
-
runDirectory,
|
|
796
|
-
principal,
|
|
1253
|
+
attachments: placed.attachments,
|
|
1254
|
+
runDirectory: placed.runDirectory,
|
|
1255
|
+
principal: placed.principal,
|
|
797
1256
|
admittedRequestPath,
|
|
798
1257
|
...correlationFields,
|
|
799
|
-
...ticketFields,
|
|
1258
|
+
...placed.ticketFields,
|
|
1259
|
+
...admittedFields,
|
|
800
1260
|
};
|
|
801
1261
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Admit a direct Inspector (台院) run: freeze attachments, persist the request,
|
|
811
|
-
* and reserve session placement. Same instruction-seat face as Judge (#568).
|
|
812
|
-
*/
|
|
813
|
-
export async function admitInspectorInvocation(options) {
|
|
814
|
-
return admitStandardMaterialInvocation("inspector", options);
|
|
815
|
-
}
|
|
816
|
-
/**
|
|
817
|
-
* Admit a direct Gatekeeper (门下省) run (#639): freeze attachments, persist
|
|
818
|
-
* the request, reserve session placement — same instruction-seat face.
|
|
819
|
-
*/
|
|
820
|
-
export async function admitGatekeeperInvocation(options) {
|
|
821
|
-
return admitStandardMaterialInvocation("gatekeeper", options);
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Admit a direct Navigator (游奕使) run (#639): freeze attachments, persist
|
|
825
|
-
* the request, reserve session placement — same instruction-seat face.
|
|
826
|
-
*/
|
|
827
|
-
export async function admitNavigatorInvocation(options) {
|
|
828
|
-
return admitStandardMaterialInvocation("navigator", options);
|
|
829
|
-
}
|
|
830
|
-
/** Admit a public 审刑院 run (#675). */
|
|
831
|
-
export async function admitAuditorInvocation(options) {
|
|
832
|
-
return admitStandardMaterialInvocation("auditor", options);
|
|
1262
|
+
function admittedTransportPromptKind(admitted) {
|
|
1263
|
+
if (admitted.role === undefined)
|
|
1264
|
+
return "instruction";
|
|
1265
|
+
const record = packagedRoleMetadata(admitted.role);
|
|
1266
|
+
if (record !== undefined && "transportPrompt" in record)
|
|
1267
|
+
return record.transportPrompt;
|
|
1268
|
+
return "instruction";
|
|
833
1269
|
}
|
|
834
1270
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
1271
|
+
* One initial prompt transport. The registry `transportPrompt` leaf selects
|
|
1272
|
+
* a fixed kickoff, a bound baseline, or frozen skill args. Absent means the
|
|
1273
|
+
* caller instruction plus frozen attachment paths.
|
|
837
1274
|
*/
|
|
838
|
-
export async function admitDiaristInvocation(options) {
|
|
839
|
-
return admitStandardMaterialInvocation("diarist", options);
|
|
840
|
-
}
|
|
841
|
-
/** Admit a public Secretariat (中书省) run (#924). */
|
|
842
|
-
export async function admitSecretariatInvocation(options) {
|
|
843
|
-
return admitStandardMaterialInvocation("secretariat", options);
|
|
844
|
-
}
|
|
845
|
-
/** Shared prompt transport for instruction-seat roles (judge/countersign/inspector). */
|
|
846
1275
|
export function buildInstructionTransportPrompt(admitted, engineMaterial) {
|
|
1276
|
+
const kind = admittedTransportPromptKind(admitted);
|
|
1277
|
+
if (kind === "fixed-kickoff") {
|
|
1278
|
+
return appendEngineSessionMaterial([NOTARY_FIXED_KICKOFF], engineMaterial).join("\n");
|
|
1279
|
+
}
|
|
1280
|
+
if (kind === "baseline") {
|
|
1281
|
+
if (admitted.baseRevision === undefined) {
|
|
1282
|
+
throw new Error("baseline transport prompt is missing the bound revision");
|
|
1283
|
+
}
|
|
1284
|
+
const lines = [`左拾遗案已受理。比较基线:${admitted.baseRevision}`];
|
|
1285
|
+
if (!admitted.instructionEmpty) {
|
|
1286
|
+
lines.push("", admitted.instruction);
|
|
1287
|
+
}
|
|
1288
|
+
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
1289
|
+
}
|
|
1290
|
+
if (kind === "skill-args") {
|
|
1291
|
+
if (admitted.baseRevision === undefined
|
|
1292
|
+
|| admitted.lens === undefined
|
|
1293
|
+
|| admitted.authorityRefs === undefined) {
|
|
1294
|
+
throw new Error("skill-args transport prompt is missing frozen skill args");
|
|
1295
|
+
}
|
|
1296
|
+
const lines = [buildReviewerSkillArgProjection({
|
|
1297
|
+
baseRevision: admitted.baseRevision,
|
|
1298
|
+
lens: admitted.lens,
|
|
1299
|
+
authorityRefs: admitted.authorityRefs,
|
|
1300
|
+
})];
|
|
1301
|
+
if (!admitted.instructionEmpty && admitted.instruction.trim() !== "") {
|
|
1302
|
+
lines.push("", admitted.instruction);
|
|
1303
|
+
}
|
|
1304
|
+
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
1305
|
+
}
|
|
847
1306
|
const lines = [admitted.instructionEmpty ? "" : admitted.instruction];
|
|
848
1307
|
if (admitted.attachments.length > 0) {
|
|
849
1308
|
lines.push("");
|
|
@@ -854,91 +1313,26 @@ export function buildInstructionTransportPrompt(admitted, engineMaterial) {
|
|
|
854
1313
|
}
|
|
855
1314
|
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
856
1315
|
}
|
|
857
|
-
/**
|
|
858
|
-
export function
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
export function buildSecretariatTransportPrompt(admitted, engineMaterial) {
|
|
862
|
-
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
863
|
-
}
|
|
864
|
-
export function buildInspectorTransportPrompt(admitted, engineMaterial) {
|
|
865
|
-
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Admit a Countersign run immediately, or reserve its coordinates for deferred
|
|
869
|
-
* materialization after identity lookup (#572 / ADR 0074 / #863).
|
|
870
|
-
*/
|
|
871
|
-
export async function admitCountersignInvocation(options) {
|
|
872
|
-
// Same shared freeze/coordinate body as judge but role: "countersign" so
|
|
873
|
-
// the ledger and session coordinates use the correct role from the start.
|
|
874
|
-
if (options.project !== undefined) {
|
|
875
|
-
requireOptionPath("--project", options.project);
|
|
876
|
-
}
|
|
877
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
878
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
879
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
880
|
-
cwd: projectRoot,
|
|
881
|
-
runId,
|
|
1316
|
+
/** Persist a deferred Countersign admission after same-ticket lookup found no retained run. */
|
|
1317
|
+
export async function materializeCountersignInvocation(admitted, options) {
|
|
1318
|
+
const placed = await placeRoleAdmission({
|
|
882
1319
|
role: "countersign",
|
|
883
|
-
subject: { unbound: true },
|
|
884
1320
|
home: options.home,
|
|
885
|
-
|
|
1321
|
+
principalAuthority: options.principalAuthority,
|
|
1322
|
+
cwd: admitted.projectRoot,
|
|
1323
|
+
attachmentPaths: [],
|
|
1324
|
+
freezeAttachments: false,
|
|
1325
|
+
runId: admitted.runId,
|
|
1326
|
+
...(options.ticketNumber === undefined ? {} : { assertedTicketNumber: options.ticketNumber }),
|
|
886
1327
|
});
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
role: "countersign",
|
|
895
|
-
runId,
|
|
896
|
-
bookKey,
|
|
897
|
-
projectRoot,
|
|
898
|
-
runDirectory,
|
|
899
|
-
principal,
|
|
900
|
-
...(options.correlationId === undefined ? {} : { correlationId: options.correlationId }),
|
|
901
|
-
instruction,
|
|
902
|
-
instructionEmpty,
|
|
903
|
-
attachments: attachments.map((a) => ({
|
|
904
|
-
provenancePath: a.provenancePath,
|
|
905
|
-
frozenPath: a.frozenPath,
|
|
906
|
-
byteLength: a.byteLength,
|
|
907
|
-
sha256: a.sha256,
|
|
908
|
-
mediaKind: a.mediaKind,
|
|
909
|
-
})),
|
|
910
|
-
};
|
|
911
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
912
|
-
if (options.deferPersistence !== true) {
|
|
913
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
914
|
-
sessionDirectory,
|
|
915
|
-
sessionFile,
|
|
916
|
-
});
|
|
917
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1328
|
+
const placement = placed;
|
|
1329
|
+
admitted.runDirectory = placement.runDirectory;
|
|
1330
|
+
admitted.admittedRequestPath = join(placement.runDirectory, "admitted-request.json");
|
|
1331
|
+
admitted.principal = placement.principal;
|
|
1332
|
+
const ticketFields = ticketAdmissionFields(options.ticketNumber);
|
|
1333
|
+
if (ticketFields.ticketNumber !== undefined) {
|
|
1334
|
+
admitted.ticketNumber = ticketFields.ticketNumber;
|
|
918
1335
|
}
|
|
919
|
-
return {
|
|
920
|
-
role: "countersign",
|
|
921
|
-
runId,
|
|
922
|
-
bookKey,
|
|
923
|
-
projectRoot,
|
|
924
|
-
instruction,
|
|
925
|
-
instructionEmpty,
|
|
926
|
-
attachments,
|
|
927
|
-
runDirectory,
|
|
928
|
-
principal,
|
|
929
|
-
admittedRequestPath,
|
|
930
|
-
...(options.correlationId === undefined ? {} : { correlationId: options.correlationId }),
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
/** Persist a deferred Countersign admission after same-ticket lookup found no retained run. */
|
|
934
|
-
export async function materializeCountersignInvocation(admitted, options) {
|
|
935
|
-
const placement = issueAdmissionPlacement(options.principalAuthority, {
|
|
936
|
-
cwd: admitted.projectRoot,
|
|
937
|
-
runId: admitted.runId,
|
|
938
|
-
role: "countersign",
|
|
939
|
-
subject: { unbound: true },
|
|
940
|
-
home: options.home,
|
|
941
|
-
});
|
|
942
1336
|
const attachments = await freezePreparedAttachmentsIntoRun(options.preparedAttachments, admitted.runDirectory);
|
|
943
1337
|
admitted.attachments = attachments;
|
|
944
1338
|
await writeAdmittedRequestPersistence(admitted.admittedRequestPath, admitted, {
|
|
@@ -947,10 +1341,6 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
947
1341
|
});
|
|
948
1342
|
await writeRoleInvocationLedger({ ...admitted, sessionDirectory: placement.sessionDirectory, sessionFile: placement.sessionFile }, admitted.role, options.model);
|
|
949
1343
|
}
|
|
950
|
-
/** Build the Pi prompt transport for an admitted Countersign request. */
|
|
951
|
-
export function buildCountersignTransportPrompt(admitted, engineMaterial) {
|
|
952
|
-
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
953
|
-
}
|
|
954
1344
|
/** Load admitted-request.json written at admission (Navigator work-context seam). */
|
|
955
1345
|
export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
956
1346
|
try {
|
|
@@ -958,7 +1348,7 @@ export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
|
958
1348
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
959
1349
|
return undefined;
|
|
960
1350
|
const record = raw;
|
|
961
|
-
if (record.role
|
|
1351
|
+
if (!packagedPublicInstructionSubject(record.role))
|
|
962
1352
|
return undefined;
|
|
963
1353
|
if (typeof record.instruction !== "string")
|
|
964
1354
|
return undefined;
|
|
@@ -980,209 +1370,6 @@ export async function ensureRunArtifactsDir(runDirectory) {
|
|
|
980
1370
|
const directory = roleRunArtifactsDirectory(runDirectory);
|
|
981
1371
|
return ensureRoleRunDirectory(resolveActivationLedgerHome(homeFromRunDirectory(runDirectory)), directory);
|
|
982
1372
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Admit a Coder Role run on the common Invocation request.
|
|
985
|
-
* Nonblank task remains authoritative: blank instruction is a structural reject.
|
|
986
|
-
* Phase (default apply / explicit plan) is frozen into the admitted request.
|
|
987
|
-
*/
|
|
988
|
-
export async function admitCoderInvocation(options) {
|
|
989
|
-
if (options.project !== undefined) {
|
|
990
|
-
requireOptionPath("--project", options.project);
|
|
991
|
-
}
|
|
992
|
-
const instruction = options.instruction;
|
|
993
|
-
if (instruction.trim() === "") {
|
|
994
|
-
throw new CliUsageError("coder requires a nonblank task instruction");
|
|
995
|
-
}
|
|
996
|
-
if (options.phase !== "plan" && options.phase !== "apply") {
|
|
997
|
-
throw new CliUsageError("coder phase must be plan or apply");
|
|
998
|
-
}
|
|
999
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
1000
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
1001
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
1002
|
-
cwd: projectRoot,
|
|
1003
|
-
runId,
|
|
1004
|
-
role: "coder",
|
|
1005
|
-
subject: { unbound: true },
|
|
1006
|
-
home: options.home,
|
|
1007
|
-
});
|
|
1008
|
-
const attachments = await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1009
|
-
const taskPath = join(runDirectory, "task.md");
|
|
1010
|
-
await writeFile(taskPath, instruction, "utf8");
|
|
1011
|
-
const admitted = {
|
|
1012
|
-
role: "coder",
|
|
1013
|
-
phase: options.phase,
|
|
1014
|
-
runId,
|
|
1015
|
-
bookKey,
|
|
1016
|
-
projectRoot,
|
|
1017
|
-
runDirectory,
|
|
1018
|
-
principal,
|
|
1019
|
-
instruction,
|
|
1020
|
-
instructionEmpty: false,
|
|
1021
|
-
taskPath,
|
|
1022
|
-
attachments: attachments.map((a) => ({
|
|
1023
|
-
provenancePath: a.provenancePath,
|
|
1024
|
-
frozenPath: a.frozenPath,
|
|
1025
|
-
byteLength: a.byteLength,
|
|
1026
|
-
sha256: a.sha256,
|
|
1027
|
-
mediaKind: a.mediaKind,
|
|
1028
|
-
})),
|
|
1029
|
-
};
|
|
1030
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1031
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1032
|
-
sessionDirectory,
|
|
1033
|
-
sessionFile,
|
|
1034
|
-
});
|
|
1035
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1036
|
-
return {
|
|
1037
|
-
role: "coder",
|
|
1038
|
-
phase: options.phase,
|
|
1039
|
-
runId,
|
|
1040
|
-
bookKey,
|
|
1041
|
-
projectRoot,
|
|
1042
|
-
instruction,
|
|
1043
|
-
instructionEmpty: false,
|
|
1044
|
-
attachments,
|
|
1045
|
-
runDirectory,
|
|
1046
|
-
principal,
|
|
1047
|
-
admittedRequestPath,
|
|
1048
|
-
taskPath,
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Build the Pi prompt transport for an admitted Coder request.
|
|
1053
|
-
* Task bytes already live at taskPath for --ak-coder-task; the prompt carries
|
|
1054
|
-
* the same instruction plus frozen Attachment paths.
|
|
1055
|
-
*/
|
|
1056
|
-
export function buildCoderTransportPrompt(admitted, engineMaterial) {
|
|
1057
|
-
const lines = [admitted.instruction];
|
|
1058
|
-
if (admitted.attachments.length > 0) {
|
|
1059
|
-
lines.push("");
|
|
1060
|
-
lines.push("已受理附件(冻结快照路径):");
|
|
1061
|
-
for (const attachment of admitted.attachments) {
|
|
1062
|
-
lines.push(`- ${attachment.frozenPath}`);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Admit a Fixer Role run on the common Invocation request plus optional prerequisites.
|
|
1069
|
-
* Nonblank instruction remains authoritative. Phase defaults to apply at parse time.
|
|
1070
|
-
* Prerequisite grammar is structural; unmet/insufficient prerequisites stay Fixer judgments.
|
|
1071
|
-
*/
|
|
1072
|
-
export async function admitFixerInvocation(options) {
|
|
1073
|
-
if (options.project !== undefined) {
|
|
1074
|
-
requireOptionPath("--project", options.project);
|
|
1075
|
-
}
|
|
1076
|
-
const instruction = options.instruction;
|
|
1077
|
-
if (instruction.trim() === "") {
|
|
1078
|
-
throw new CliUsageError("fixer requires a nonblank repair instruction");
|
|
1079
|
-
}
|
|
1080
|
-
if (options.phase !== "plan" && options.phase !== "apply") {
|
|
1081
|
-
throw new CliUsageError("fixer phase must be plan or apply");
|
|
1082
|
-
}
|
|
1083
|
-
// Validate/read prerequisites before freezing request materials.
|
|
1084
|
-
let prerequisites = Object.freeze([]);
|
|
1085
|
-
let prerequisitesSource;
|
|
1086
|
-
if (options.prerequisitesPath !== undefined) {
|
|
1087
|
-
const absolutePrereq = isAbsolute(options.prerequisitesPath)
|
|
1088
|
-
? options.prerequisitesPath
|
|
1089
|
-
: resolve(options.prerequisitesPath);
|
|
1090
|
-
try {
|
|
1091
|
-
prerequisitesSource = await readFile(absolutePrereq, "utf8");
|
|
1092
|
-
}
|
|
1093
|
-
catch (error) {
|
|
1094
|
-
throw new CliUsageError(`fixer prerequisites path is unreadable: ${options.prerequisitesPath}`, { cause: error });
|
|
1095
|
-
}
|
|
1096
|
-
try {
|
|
1097
|
-
prerequisites = parseFixerPrerequisites(prerequisitesSource);
|
|
1098
|
-
}
|
|
1099
|
-
catch (error) {
|
|
1100
|
-
if (error instanceof FixerPacketValidationError) {
|
|
1101
|
-
throw new CliUsageError(error.message, { cause: error });
|
|
1102
|
-
}
|
|
1103
|
-
throw error;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
1107
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
1108
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
1109
|
-
cwd: projectRoot,
|
|
1110
|
-
runId,
|
|
1111
|
-
role: "fixer",
|
|
1112
|
-
subject: { unbound: true },
|
|
1113
|
-
home: options.home,
|
|
1114
|
-
});
|
|
1115
|
-
const attachments = await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1116
|
-
let prerequisitesPath;
|
|
1117
|
-
if (prerequisitesSource !== undefined) {
|
|
1118
|
-
prerequisitesPath = join(runDirectory, "prerequisites.json");
|
|
1119
|
-
await writeFile(prerequisitesPath, `${JSON.stringify(prerequisites, null, 2)}\n`, "utf8");
|
|
1120
|
-
}
|
|
1121
|
-
const packetPath = join(runDirectory, "fix-packet.md");
|
|
1122
|
-
await writeFile(packetPath, instruction, "utf8");
|
|
1123
|
-
const admitted = {
|
|
1124
|
-
role: "fixer",
|
|
1125
|
-
phase: options.phase,
|
|
1126
|
-
runId,
|
|
1127
|
-
bookKey,
|
|
1128
|
-
projectRoot,
|
|
1129
|
-
runDirectory,
|
|
1130
|
-
principal,
|
|
1131
|
-
instruction,
|
|
1132
|
-
instructionEmpty: false,
|
|
1133
|
-
packetPath,
|
|
1134
|
-
...(prerequisitesPath === undefined ? {} : { prerequisitesPath }),
|
|
1135
|
-
prerequisites: prerequisites.map((entry) => ({
|
|
1136
|
-
id: entry.id,
|
|
1137
|
-
requirement: entry.requirement,
|
|
1138
|
-
})),
|
|
1139
|
-
attachments: attachments.map((a) => ({
|
|
1140
|
-
provenancePath: a.provenancePath,
|
|
1141
|
-
frozenPath: a.frozenPath,
|
|
1142
|
-
byteLength: a.byteLength,
|
|
1143
|
-
sha256: a.sha256,
|
|
1144
|
-
mediaKind: a.mediaKind,
|
|
1145
|
-
})),
|
|
1146
|
-
};
|
|
1147
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1148
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1149
|
-
sessionDirectory,
|
|
1150
|
-
sessionFile,
|
|
1151
|
-
});
|
|
1152
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1153
|
-
return {
|
|
1154
|
-
role: "fixer",
|
|
1155
|
-
phase: options.phase,
|
|
1156
|
-
runId,
|
|
1157
|
-
bookKey,
|
|
1158
|
-
projectRoot,
|
|
1159
|
-
instruction,
|
|
1160
|
-
instructionEmpty: false,
|
|
1161
|
-
attachments,
|
|
1162
|
-
runDirectory,
|
|
1163
|
-
principal,
|
|
1164
|
-
admittedRequestPath,
|
|
1165
|
-
packetPath,
|
|
1166
|
-
...(prerequisitesPath === undefined ? {} : { prerequisitesPath }),
|
|
1167
|
-
prerequisites,
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Build the Pi prompt transport for an admitted Fixer request.
|
|
1172
|
-
* Instruction bytes live at packetPath; prerequisites at optional path.
|
|
1173
|
-
* Diagnosis method is available via package --skill, not forced into this prompt.
|
|
1174
|
-
*/
|
|
1175
|
-
export function buildFixerTransportPrompt(admitted, engineMaterial) {
|
|
1176
|
-
const lines = [admitted.instruction];
|
|
1177
|
-
if (admitted.attachments.length > 0) {
|
|
1178
|
-
lines.push("");
|
|
1179
|
-
lines.push("已受理附件(冻结快照路径):");
|
|
1180
|
-
for (const attachment of admitted.attachments) {
|
|
1181
|
-
lines.push(`- ${attachment.frozenPath}`);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
1185
|
-
}
|
|
1186
1373
|
function parsePositivePrOption(raw) {
|
|
1187
1374
|
if (raw === undefined || raw.trim() === "")
|
|
1188
1375
|
throw new CliUsageError("--pr requires a positive pull request number");
|
|
@@ -1198,78 +1385,6 @@ function parseRepoOption(raw) {
|
|
|
1198
1385
|
throw new CliUsageError("--repo requires owner/repo");
|
|
1199
1386
|
return raw;
|
|
1200
1387
|
}
|
|
1201
|
-
export function parseCollectorArgv(args) {
|
|
1202
|
-
// Spellings from PUBLIC_OPTION_TABLE.collector (#342).
|
|
1203
|
-
const attachmentPaths = [];
|
|
1204
|
-
let project;
|
|
1205
|
-
let repo;
|
|
1206
|
-
let prNumber;
|
|
1207
|
-
let requestManifestPath;
|
|
1208
|
-
let waitWindowMs;
|
|
1209
|
-
const positional = [];
|
|
1210
|
-
const tokens = [...args];
|
|
1211
|
-
const definitions = roleOptions("collector");
|
|
1212
|
-
const options = createTypedOptionConsumer(definitions);
|
|
1213
|
-
while (tokens.length > 0) {
|
|
1214
|
-
if (tokens[0] === "--") {
|
|
1215
|
-
tokens.shift();
|
|
1216
|
-
positional.push(...tokens);
|
|
1217
|
-
break;
|
|
1218
|
-
}
|
|
1219
|
-
const taken = options.takeDashed(tokens);
|
|
1220
|
-
if (taken !== undefined) {
|
|
1221
|
-
if (taken.def.id === "attach") {
|
|
1222
|
-
attachmentPaths.push(requireOptionPath(taken.def.canonical, taken.value));
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
if (taken.def.id === "project") {
|
|
1226
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
1227
|
-
continue;
|
|
1228
|
-
}
|
|
1229
|
-
if (taken.def.id === "pr") {
|
|
1230
|
-
prNumber = parsePositivePrOption(taken.value);
|
|
1231
|
-
continue;
|
|
1232
|
-
}
|
|
1233
|
-
if (taken.def.id === "repo") {
|
|
1234
|
-
repo = parseRepoOption(taken.value);
|
|
1235
|
-
continue;
|
|
1236
|
-
}
|
|
1237
|
-
if (taken.def.id === "request-manifest") {
|
|
1238
|
-
requestManifestPath = requireOptionPath(taken.def.canonical, taken.value);
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
1241
|
-
if (taken.def.id === "wait-ms") {
|
|
1242
|
-
if (taken.value === undefined || !/^[1-9]\d*$/.test(taken.value.trim())) {
|
|
1243
|
-
throw new CliUsageError("--wait-ms requires a positive safe-integer millisecond value");
|
|
1244
|
-
}
|
|
1245
|
-
const parsed = Number(taken.value.trim());
|
|
1246
|
-
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
1247
|
-
throw new CliUsageError("--wait-ms requires a positive safe-integer millisecond value");
|
|
1248
|
-
}
|
|
1249
|
-
waitWindowMs = parsed;
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
throw new CliUsageError(`unknown collector option: ${taken.def.canonical}`);
|
|
1253
|
-
}
|
|
1254
|
-
const token = tokens.shift();
|
|
1255
|
-
if (token.startsWith("-") && token !== "-") {
|
|
1256
|
-
throw new CliUsageError(`unknown collector option: ${token}`);
|
|
1257
|
-
}
|
|
1258
|
-
positional.push(token);
|
|
1259
|
-
}
|
|
1260
|
-
// Unconditional required from typed table via shared consumer (#342).
|
|
1261
|
-
// #676 D1: --pr is optional; ambiguous targets reject at admission, not by guessing.
|
|
1262
|
-
options.assertRequired();
|
|
1263
|
-
return {
|
|
1264
|
-
...(prNumber === undefined ? {} : { prNumber }),
|
|
1265
|
-
instruction: positional.join(" "),
|
|
1266
|
-
attachmentPaths,
|
|
1267
|
-
...(project === undefined ? {} : { project }),
|
|
1268
|
-
...(repo === undefined ? {} : { repo }),
|
|
1269
|
-
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
1270
|
-
...(waitWindowMs === undefined ? {} : { waitWindowMs }),
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
1388
|
/**
|
|
1274
1389
|
* Resolve owner/repo from the project's `origin` remote (github.com only).
|
|
1275
1390
|
* Supports https and SSH GitHub URL shapes; never scrapes instruction prose.
|
|
@@ -1318,745 +1433,64 @@ function isGitRemoteMissing(error) {
|
|
|
1318
1433
|
const status = error.status;
|
|
1319
1434
|
return status === 2;
|
|
1320
1435
|
}
|
|
1321
|
-
/**
|
|
1322
|
-
* Admit a Collector Role run: assemble the retained leg manifest from typed
|
|
1323
|
-
* declarations, resolve repository + PR target (#676 D1), and place the session under #78.
|
|
1324
|
-
* Explicit PR is not preflighted for existence; context resolution uses online association.
|
|
1325
|
-
*/
|
|
1326
|
-
export async function admitCollectorInvocation(options) {
|
|
1327
|
-
if (options.project !== undefined) {
|
|
1328
|
-
requireOptionPath("--project", options.project);
|
|
1329
|
-
}
|
|
1330
|
-
let explicitPrNumber;
|
|
1331
|
-
if (options.prNumber !== undefined) {
|
|
1332
|
-
try {
|
|
1333
|
-
explicitPrNumber = parseCollectorPrNumber(options.prNumber);
|
|
1334
|
-
}
|
|
1335
|
-
catch (error) {
|
|
1336
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1337
|
-
throw new CliUsageError(detail, { cause: error });
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
1341
|
-
let repository;
|
|
1342
|
-
if (options.repo !== undefined) {
|
|
1343
|
-
try {
|
|
1344
|
-
repository = parseCollectorRepository(options.repo);
|
|
1345
|
-
}
|
|
1346
|
-
catch (error) {
|
|
1347
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1348
|
-
throw new CliUsageError(detail, { cause: error });
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
else {
|
|
1352
|
-
repository = resolveGitHubRemoteRepository(projectRoot);
|
|
1353
|
-
}
|
|
1354
|
-
// Validate optional request-manifest before freezing request materials.
|
|
1355
|
-
let manifest = emptyCollectorManifest();
|
|
1356
|
-
let manifestCanonicalJson;
|
|
1357
|
-
if (options.requestManifestPath !== undefined) {
|
|
1358
|
-
try {
|
|
1359
|
-
manifest = await loadCollectorManifest(options.requestManifestPath);
|
|
1360
|
-
manifestCanonicalJson = manifest.canonicalJson;
|
|
1361
|
-
}
|
|
1362
|
-
catch (error) {
|
|
1363
|
-
throw new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
const manifestDigest = manifest.digest;
|
|
1367
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
1368
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
1369
|
-
cwd: projectRoot,
|
|
1370
|
-
runId,
|
|
1371
|
-
role: "collector",
|
|
1372
|
-
subject: { unbound: true },
|
|
1373
|
-
home: options.home,
|
|
1374
|
-
});
|
|
1375
|
-
// #676 A: freeze task materials BEFORE target resolution so the role receives
|
|
1376
|
-
// real instruction + attachments. Admission binds only explicit --pr or unique
|
|
1377
|
-
// head/commit association — task-text scrape is not a target lock.
|
|
1378
|
-
const attachments = await freezeAttachments(options.attachmentPaths ?? [], attachmentsDirectory);
|
|
1379
|
-
const instruction = options.instruction ?? "";
|
|
1380
|
-
const instructionEmpty = instruction.trim() === "";
|
|
1381
|
-
const target = await resolveCollectorTarget({
|
|
1382
|
-
projectRoot,
|
|
1383
|
-
repository,
|
|
1384
|
-
...(explicitPrNumber === undefined ? {} : { explicitPrNumber }),
|
|
1385
|
-
});
|
|
1386
|
-
const prNumber = target.kind === "bound" ? target.prNumber : undefined;
|
|
1387
|
-
let requestManifestPath;
|
|
1388
|
-
if (manifestCanonicalJson !== undefined) {
|
|
1389
|
-
requestManifestPath = join(runDirectory, "request-manifest.json");
|
|
1390
|
-
await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
|
|
1391
|
-
}
|
|
1392
|
-
const admitted = {
|
|
1393
|
-
role: "collector",
|
|
1394
|
-
runId,
|
|
1395
|
-
bookKey,
|
|
1396
|
-
projectRoot,
|
|
1397
|
-
runDirectory,
|
|
1398
|
-
principal,
|
|
1399
|
-
instruction,
|
|
1400
|
-
instructionEmpty,
|
|
1401
|
-
...(prNumber === undefined ? {} : { prNumber }),
|
|
1402
|
-
repository: repository.canonical,
|
|
1403
|
-
repositoryDisplay: repository.display,
|
|
1404
|
-
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
1405
|
-
...(options.waitWindowMs === undefined ? {} : { waitWindowMs: options.waitWindowMs }),
|
|
1406
|
-
manifestDigest,
|
|
1407
|
-
attachments: attachments.map((a) => ({
|
|
1408
|
-
provenancePath: a.provenancePath,
|
|
1409
|
-
frozenPath: a.frozenPath,
|
|
1410
|
-
byteLength: a.byteLength,
|
|
1411
|
-
sha256: a.sha256,
|
|
1412
|
-
mediaKind: a.mediaKind,
|
|
1413
|
-
})),
|
|
1414
|
-
};
|
|
1415
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1416
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1417
|
-
sessionDirectory,
|
|
1418
|
-
sessionFile,
|
|
1419
|
-
});
|
|
1420
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1421
|
-
return {
|
|
1422
|
-
role: "collector",
|
|
1423
|
-
runId,
|
|
1424
|
-
bookKey,
|
|
1425
|
-
projectRoot,
|
|
1426
|
-
instruction,
|
|
1427
|
-
instructionEmpty,
|
|
1428
|
-
attachments,
|
|
1429
|
-
runDirectory,
|
|
1430
|
-
principal,
|
|
1431
|
-
admittedRequestPath,
|
|
1432
|
-
...(prNumber === undefined ? {} : { prNumber }),
|
|
1433
|
-
repository,
|
|
1434
|
-
...(requestManifestPath === undefined ? {} : { requestManifestPath }),
|
|
1435
|
-
...(options.waitWindowMs === undefined ? {} : { waitWindowMs: options.waitWindowMs }),
|
|
1436
|
-
manifestDigest,
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* #676 A: Collector consumes the real call task + frozen attachments so the role
|
|
1441
|
-
* can identify issue/PR from materials via ak_collector_bind_target. Explicit --pr
|
|
1442
|
-
* still wins at admission; unique head/commit association also binds. No fixed
|
|
1443
|
-
* kickoff rewrite of the caller task; no mechanical task-text target lock.
|
|
1444
|
-
*/
|
|
1445
|
-
export function buildCollectorTransportPrompt(admitted, engineMaterial) {
|
|
1446
|
-
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
1447
|
-
}
|
|
1448
1436
|
/** Positive Issue number grammar shared with Doctor case path identity. */
|
|
1449
1437
|
const DOCTOR_ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/;
|
|
1450
|
-
/** Match retained Doctor case runs roots (ADR 0017 / loadDoctorCase). */
|
|
1451
|
-
/** Canonical Doctor case: `<book>/<ticket>/runs`. Legacy `issues/<n>/runs` is read-only compat. */
|
|
1452
|
-
const DOCTOR_CASE_RUNS_PATH_PATTERN = /\/\.ak-roles\/books\/[^/]+\/(?:issues\/)?([1-9]\d*)\/runs$/;
|
|
1453
|
-
/**
|
|
1454
|
-
* Parse a positive Issue number for public Doctor admission.
|
|
1455
|
-
* Leading zeros and non-integers are structural rejects.
|
|
1456
|
-
*/
|
|
1457
|
-
export function parseDoctorIssueNumber(raw) {
|
|
1458
|
-
const trimmed = raw.trim();
|
|
1459
|
-
if (!DOCTOR_ISSUE_NUMBER_PATTERN.test(trimmed)) {
|
|
1460
|
-
throw new CliUsageError(`doctor --issue must be a positive integer, got ${raw}`);
|
|
1461
|
-
}
|
|
1462
|
-
return Number(trimmed);
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* Parse Doctor-specific argv after the `doctor` token.
|
|
1466
|
-
* Requires --issue; optional confined --runs override; common --attach/--project.
|
|
1467
|
-
*/
|
|
1468
|
-
export function parseDoctorArgv(args) {
|
|
1469
|
-
// Spellings from PUBLIC_OPTION_TABLE.doctor (#342).
|
|
1470
|
-
const attachmentPaths = [];
|
|
1471
|
-
let project;
|
|
1472
|
-
let issueRaw;
|
|
1473
|
-
let runs;
|
|
1474
|
-
const positional = [];
|
|
1475
|
-
const tokens = [...args];
|
|
1476
|
-
const definitions = roleOptions("doctor");
|
|
1477
|
-
const options = createTypedOptionConsumer(definitions);
|
|
1478
|
-
while (tokens.length > 0) {
|
|
1479
|
-
if (tokens[0] === "--") {
|
|
1480
|
-
tokens.shift();
|
|
1481
|
-
positional.push(...tokens);
|
|
1482
|
-
break;
|
|
1483
|
-
}
|
|
1484
|
-
const taken = options.takeDashed(tokens);
|
|
1485
|
-
if (taken !== undefined) {
|
|
1486
|
-
if (taken.def.id === "issue") {
|
|
1487
|
-
if (taken.value === undefined || taken.value.trim() === "") {
|
|
1488
|
-
throw new CliUsageError("doctor --issue requires a positive integer");
|
|
1489
|
-
}
|
|
1490
|
-
issueRaw = taken.value;
|
|
1491
|
-
continue;
|
|
1492
|
-
}
|
|
1493
|
-
if (taken.def.id === "runs") {
|
|
1494
|
-
if (taken.value === undefined || taken.value.trim() === "") {
|
|
1495
|
-
throw new CliUsageError("doctor --runs requires a path");
|
|
1496
|
-
}
|
|
1497
|
-
runs = taken.value;
|
|
1498
|
-
continue;
|
|
1499
|
-
}
|
|
1500
|
-
if (taken.def.id === "attach") {
|
|
1501
|
-
attachmentPaths.push(requireOptionPath(taken.def.canonical, taken.value));
|
|
1502
|
-
continue;
|
|
1503
|
-
}
|
|
1504
|
-
if (taken.def.id === "project") {
|
|
1505
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
1506
|
-
continue;
|
|
1507
|
-
}
|
|
1508
|
-
throw new CliUsageError(`unknown doctor option: ${taken.def.canonical}`);
|
|
1509
|
-
}
|
|
1510
|
-
const token = tokens.shift();
|
|
1511
|
-
if (token.startsWith("-") && token !== "-") {
|
|
1512
|
-
throw new CliUsageError(`unknown doctor option: ${token}`);
|
|
1513
|
-
}
|
|
1514
|
-
positional.push(token);
|
|
1515
|
-
}
|
|
1516
|
-
// Unconditional required (e.g. --issue) from typed table via shared consumer (#342).
|
|
1517
|
-
options.assertRequired();
|
|
1518
|
-
const issueNumber = parseDoctorIssueNumber(issueRaw);
|
|
1519
|
-
if (runs !== undefined && runs.trim() === "") {
|
|
1520
|
-
throw new CliUsageError("doctor --runs requires a path");
|
|
1521
|
-
}
|
|
1522
|
-
return {
|
|
1523
|
-
issueNumber,
|
|
1524
|
-
instruction: positional.join(" "),
|
|
1525
|
-
attachmentPaths,
|
|
1526
|
-
...(project === undefined ? {} : { project }),
|
|
1527
|
-
...(runs === undefined ? {} : { runs }),
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
/**
|
|
1531
|
-
* Resolve the retained Doctor case runs root from Issue identity.
|
|
1532
|
-
* Default is the #78 book locator; optional --runs must stay project-confined
|
|
1533
|
-
* and match Doctor case grammar for the same issue number.
|
|
1534
|
-
*/
|
|
1535
|
-
export async function resolveDoctorCaseRunsPath(options) {
|
|
1536
|
-
const ledgerHome = resolveActivationLedgerHome(options.home);
|
|
1537
|
-
// Canonical topology: <book>/<ticket>/runs (docs/dossier-topology.md).
|
|
1538
|
-
const defaultRuns = join(activationBookDirectory(ledgerHome, options.bookKey), String(options.issueNumber), "runs");
|
|
1539
|
-
if (options.runs === undefined) {
|
|
1540
|
-
return defaultRuns;
|
|
1541
|
-
}
|
|
1542
|
-
const raw = options.runs.trim();
|
|
1543
|
-
if (raw === "") {
|
|
1544
|
-
throw new CliUsageError("doctor --runs requires a path");
|
|
1545
|
-
}
|
|
1546
|
-
// Project-relative only — absolute overrides would bypass confinement.
|
|
1547
|
-
if (isAbsolute(raw)) {
|
|
1548
|
-
throw new CliUsageError("doctor --runs must be a project-relative path");
|
|
1549
|
-
}
|
|
1550
|
-
const resolved = resolve(options.projectRoot, raw);
|
|
1551
|
-
if (resolved !== options.projectRoot &&
|
|
1552
|
-
!pathContainedIn(options.projectRoot, resolved)) {
|
|
1553
|
-
throw new CliUsageError("doctor --runs escapes the project root");
|
|
1554
|
-
}
|
|
1555
|
-
let real;
|
|
1556
|
-
try {
|
|
1557
|
-
real = await realpath(resolved);
|
|
1558
|
-
}
|
|
1559
|
-
catch (error) {
|
|
1560
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1561
|
-
throw new CliUsageError(`doctor --runs is not a readable retained runs root: ${detail}`, { cause: error });
|
|
1562
|
-
}
|
|
1563
|
-
const normalized = real.split(sep).join("/");
|
|
1564
|
-
const match = normalized.match(DOCTOR_CASE_RUNS_PATH_PATTERN);
|
|
1565
|
-
if (!match) {
|
|
1566
|
-
throw new CliUsageError("doctor --runs must be an .ak-roles/books/<book>/<n>/runs directory");
|
|
1567
|
-
}
|
|
1568
|
-
if (Number(match[1]) !== options.issueNumber) {
|
|
1569
|
-
throw new CliUsageError(`doctor --runs issue ${match[1]} does not match --issue ${options.issueNumber}`);
|
|
1570
|
-
}
|
|
1571
|
-
return real;
|
|
1572
|
-
}
|
|
1573
|
-
/**
|
|
1574
|
-
* Admit a Doctor Role run: resolve Issue → retained runs root via #78 (or a
|
|
1575
|
-
* confined override), construct the structurally exact case identity through
|
|
1576
|
-
* loadDoctorCase, and place the Doctor session under the book runs lane.
|
|
1577
|
-
* Does not copy session content into a second store.
|
|
1578
|
-
*/
|
|
1579
|
-
export async function admitDoctorInvocation(options) {
|
|
1580
|
-
if (options.project !== undefined) {
|
|
1581
|
-
requireOptionPath("--project", options.project);
|
|
1582
|
-
}
|
|
1583
|
-
if (!Number.isInteger(options.issueNumber) ||
|
|
1584
|
-
options.issueNumber < 1 ||
|
|
1585
|
-
!DOCTOR_ISSUE_NUMBER_PATTERN.test(String(options.issueNumber))) {
|
|
1586
|
-
throw new CliUsageError(`doctor --issue must be a positive integer, got ${options.issueNumber}`);
|
|
1587
|
-
}
|
|
1588
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
1589
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
1590
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
1591
|
-
cwd: projectRoot,
|
|
1592
|
-
runId,
|
|
1593
|
-
role: "doctor",
|
|
1594
|
-
subject: { unbound: true },
|
|
1595
|
-
home: options.home,
|
|
1596
|
-
});
|
|
1597
|
-
let caseRunsPath;
|
|
1598
|
-
try {
|
|
1599
|
-
caseRunsPath = await resolveDoctorCaseRunsPath({
|
|
1600
|
-
home: options.home,
|
|
1601
|
-
projectRoot,
|
|
1602
|
-
bookKey,
|
|
1603
|
-
issueNumber: options.issueNumber,
|
|
1604
|
-
...(options.runs === undefined ? {} : { runs: options.runs }),
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
catch (error) {
|
|
1608
|
-
if (error instanceof CliUsageError)
|
|
1609
|
-
throw error;
|
|
1610
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1611
|
-
throw new CliUsageError(detail, { cause: error });
|
|
1612
|
-
}
|
|
1613
|
-
// Default #78 locator may not exist yet — ensure the empty runs root so
|
|
1614
|
-
// loadDoctorCase can form an empty case and Doctor's refusal owns insufficiency.
|
|
1615
|
-
if (options.runs === undefined) {
|
|
1616
|
-
ensureRealDirectoryTree(ledgerHome, caseRunsPath);
|
|
1617
|
-
}
|
|
1618
|
-
let caseIdentity;
|
|
1619
|
-
try {
|
|
1620
|
-
const patient = await loadDoctorCase(caseRunsPath);
|
|
1621
|
-
if (patient.identity.issueNumber !== options.issueNumber) {
|
|
1622
|
-
throw new CliUsageError(`doctor case issue ${patient.identity.issueNumber} does not match --issue ${options.issueNumber}`);
|
|
1623
|
-
}
|
|
1624
|
-
caseIdentity = patient.identity;
|
|
1625
|
-
caseRunsPath = await realpath(caseRunsPath);
|
|
1626
|
-
}
|
|
1627
|
-
catch (error) {
|
|
1628
|
-
if (error instanceof CliUsageError)
|
|
1629
|
-
throw error;
|
|
1630
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1631
|
-
throw new CliUsageError(`doctor case could not be constructed from retained evidence: ${detail}`, { cause: error });
|
|
1632
|
-
}
|
|
1633
|
-
const attachments = await freezeAttachments(options.attachmentPaths ?? [], attachmentsDirectory);
|
|
1634
|
-
const instruction = options.instruction ?? "";
|
|
1635
|
-
const instructionEmpty = instruction.trim() === "";
|
|
1636
|
-
const admitted = {
|
|
1637
|
-
role: "doctor",
|
|
1638
|
-
runId,
|
|
1639
|
-
bookKey,
|
|
1640
|
-
projectRoot,
|
|
1641
|
-
runDirectory,
|
|
1642
|
-
principal,
|
|
1643
|
-
instruction,
|
|
1644
|
-
instructionEmpty,
|
|
1645
|
-
issueNumber: options.issueNumber,
|
|
1646
|
-
caseRunsPath,
|
|
1647
|
-
caseIdentity,
|
|
1648
|
-
attachments: attachments.map((a) => ({
|
|
1649
|
-
provenancePath: a.provenancePath,
|
|
1650
|
-
frozenPath: a.frozenPath,
|
|
1651
|
-
byteLength: a.byteLength,
|
|
1652
|
-
sha256: a.sha256,
|
|
1653
|
-
mediaKind: a.mediaKind,
|
|
1654
|
-
})),
|
|
1655
|
-
};
|
|
1656
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1657
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1658
|
-
sessionDirectory,
|
|
1659
|
-
sessionFile,
|
|
1660
|
-
});
|
|
1661
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1662
|
-
return {
|
|
1663
|
-
role: "doctor",
|
|
1664
|
-
runId,
|
|
1665
|
-
bookKey,
|
|
1666
|
-
projectRoot,
|
|
1667
|
-
instruction,
|
|
1668
|
-
instructionEmpty,
|
|
1669
|
-
attachments,
|
|
1670
|
-
runDirectory,
|
|
1671
|
-
principal,
|
|
1672
|
-
admittedRequestPath,
|
|
1673
|
-
issueNumber: options.issueNumber,
|
|
1674
|
-
caseRunsPath,
|
|
1675
|
-
caseIdentity,
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
/** Build the Pi prompt transport for an admitted Doctor request. */
|
|
1679
|
-
export function buildDoctorTransportPrompt(admitted, engineMaterial) {
|
|
1680
|
-
const lines = [admitted.instructionEmpty ? "" : admitted.instruction];
|
|
1681
|
-
if (admitted.attachments.length > 0) {
|
|
1682
|
-
lines.push("");
|
|
1683
|
-
lines.push("已受理附件(冻结快照路径):");
|
|
1684
|
-
for (const attachment of admitted.attachments) {
|
|
1685
|
-
lines.push(`- ${attachment.frozenPath}`);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
1689
|
-
}
|
|
1690
|
-
/**
|
|
1691
|
-
* Parse Notary-specific argv after the `notary` token.
|
|
1692
|
-
* Input contract = zero prompt, zero attachment projection (#448 / #276).
|
|
1693
|
-
*/
|
|
1694
|
-
export function parseNotaryArgv(args) {
|
|
1695
|
-
let project;
|
|
1696
|
-
let sourceRun;
|
|
1697
|
-
const tokens = [...args];
|
|
1698
|
-
const definitions = roleOptions("notary");
|
|
1699
|
-
const options = createTypedOptionConsumer(definitions);
|
|
1700
|
-
while (tokens.length > 0) {
|
|
1701
|
-
if (tokens[0] === "--") {
|
|
1702
|
-
tokens.shift();
|
|
1703
|
-
if (tokens.length > 0) {
|
|
1704
|
-
throw new CliUsageError("notary rejects caller prompt/instruction; only --source-run locator is admitted");
|
|
1705
|
-
}
|
|
1706
|
-
break;
|
|
1707
|
-
}
|
|
1708
|
-
const taken = options.takeDashed(tokens);
|
|
1709
|
-
if (taken !== undefined) {
|
|
1710
|
-
if (taken.def.id === "project") {
|
|
1711
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
1712
|
-
continue;
|
|
1713
|
-
}
|
|
1714
|
-
if (taken.def.id === "source-run") {
|
|
1715
|
-
if (taken.value === undefined || taken.value.trim() === "") {
|
|
1716
|
-
throw new CliUsageError("notary --source-run requires a run locator");
|
|
1717
|
-
}
|
|
1718
|
-
sourceRun = taken.value;
|
|
1719
|
-
continue;
|
|
1720
|
-
}
|
|
1721
|
-
throw new CliUsageError(`unknown notary option: ${taken.def.canonical}`);
|
|
1722
|
-
}
|
|
1723
|
-
const token = tokens.shift();
|
|
1724
|
-
if (token.startsWith("-") && token !== "-") {
|
|
1725
|
-
throw new CliUsageError(`unknown notary option: ${token}`);
|
|
1726
|
-
}
|
|
1727
|
-
throw new CliUsageError("notary rejects caller prompt/instruction; only --source-run locator is admitted");
|
|
1728
|
-
}
|
|
1729
|
-
options.assertRequired();
|
|
1730
|
-
if (sourceRun === undefined || sourceRun.trim() === "") {
|
|
1731
|
-
throw new CliUsageError("notary --source-run requires a run locator");
|
|
1732
|
-
}
|
|
1733
|
-
return {
|
|
1734
|
-
sourceRun,
|
|
1735
|
-
...(project === undefined ? {} : { project }),
|
|
1736
|
-
};
|
|
1737
|
-
}
|
|
1738
|
-
export async function admitNotaryInvocation(options) {
|
|
1739
|
-
if (options.project !== undefined) {
|
|
1740
|
-
requireOptionPath("--project", options.project);
|
|
1741
|
-
}
|
|
1742
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
1743
|
-
let sourceRun;
|
|
1744
|
-
try {
|
|
1745
|
-
sourceRun = await resolveNotarySourceRunLocator({
|
|
1746
|
-
projectRoot,
|
|
1747
|
-
sourceRun: options.sourceRun,
|
|
1748
|
-
home: options.home,
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
catch (error) {
|
|
1752
|
-
if (error instanceof NotarySourceRunError) {
|
|
1753
|
-
throw new CliUsageError(error.message, { cause: error });
|
|
1754
|
-
}
|
|
1755
|
-
throw error;
|
|
1756
|
-
}
|
|
1757
|
-
// Notary inherits board identity only — migration-derived stays display/placement.
|
|
1758
|
-
const inheritedTicketNumber = await readBoardTicketNumber(sourceRun.runDirectory);
|
|
1759
|
-
const runId = options.createRunId?.() ?? uuidv7();
|
|
1760
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
1761
|
-
cwd: projectRoot,
|
|
1762
|
-
runId,
|
|
1763
|
-
role: "notary",
|
|
1764
|
-
subject: inheritedTicketNumber === undefined
|
|
1765
|
-
? { unbound: true }
|
|
1766
|
-
: { ticketNumber: inheritedTicketNumber },
|
|
1767
|
-
home: options.home,
|
|
1768
|
-
});
|
|
1769
|
-
const ticketFields = ticketAdmissionFields(inheritedTicketNumber);
|
|
1770
|
-
const admitted = {
|
|
1771
|
-
role: "notary",
|
|
1772
|
-
runId,
|
|
1773
|
-
bookKey,
|
|
1774
|
-
projectRoot,
|
|
1775
|
-
runDirectory,
|
|
1776
|
-
principal,
|
|
1777
|
-
instruction: "",
|
|
1778
|
-
instructionEmpty: true,
|
|
1779
|
-
attachments: [],
|
|
1780
|
-
sourceRunPath: sourceRun.runDirectory,
|
|
1781
|
-
sourceRun,
|
|
1782
|
-
...ticketFields,
|
|
1783
|
-
...(options.correlationId === undefined
|
|
1784
|
-
? {}
|
|
1785
|
-
: { correlationId: options.correlationId }),
|
|
1786
|
-
};
|
|
1787
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1788
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1789
|
-
sessionDirectory,
|
|
1790
|
-
sessionFile,
|
|
1791
|
-
});
|
|
1792
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1793
|
-
return {
|
|
1794
|
-
role: "notary",
|
|
1795
|
-
runId,
|
|
1796
|
-
bookKey,
|
|
1797
|
-
projectRoot,
|
|
1798
|
-
instruction: "",
|
|
1799
|
-
instructionEmpty: true,
|
|
1800
|
-
attachments: [],
|
|
1801
|
-
runDirectory,
|
|
1802
|
-
principal,
|
|
1803
|
-
admittedRequestPath,
|
|
1804
|
-
sourceRunPath: sourceRun.runDirectory,
|
|
1805
|
-
sourceRun,
|
|
1806
|
-
...ticketFields,
|
|
1807
|
-
...(options.correlationId === undefined
|
|
1808
|
-
? {}
|
|
1809
|
-
: { correlationId: options.correlationId }),
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
/** Package-owned fixed kickoff only — never caller instruction/attachments.
|
|
1813
|
-
* Ticket rides admitted → activation → agent-start typed bound (not free-text kickoff). */
|
|
1814
|
-
export function buildNotaryTransportPrompt(_admitted, engineMaterial) {
|
|
1815
|
-
return appendEngineSessionMaterial([NOTARY_FIXED_KICKOFF], engineMaterial).join("\n");
|
|
1816
|
-
}
|
|
1438
|
+
/** Match retained Doctor case runs roots (ADR 0017 / loadDoctorCase). */
|
|
1439
|
+
/** Canonical Doctor case: `<book>/<ticket>/runs`. Legacy `issues/<n>/runs` is read-only compat. */
|
|
1440
|
+
const DOCTOR_CASE_RUNS_PATH_PATTERN = /\/\.ak-roles\/books\/[^/]+\/(?:issues\/)?([1-9]\d*)\/runs$/;
|
|
1817
1441
|
/**
|
|
1818
|
-
* Parse
|
|
1819
|
-
*
|
|
1820
|
-
* Instruction may be empty; callers must not pass directional instruction.
|
|
1442
|
+
* Parse a positive Issue number for public Doctor admission.
|
|
1443
|
+
* Leading zeros and non-integers are structural rejects.
|
|
1821
1444
|
*/
|
|
1822
|
-
export function
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
const tokens = [...args];
|
|
1827
|
-
const definitions = roleOptions("gleaner-left");
|
|
1828
|
-
const options = createTypedOptionConsumer(definitions);
|
|
1829
|
-
while (tokens.length > 0) {
|
|
1830
|
-
if (tokens[0] === "--") {
|
|
1831
|
-
tokens.shift();
|
|
1832
|
-
positional.push(...tokens);
|
|
1833
|
-
break;
|
|
1834
|
-
}
|
|
1835
|
-
const taken = options.takeDashed(tokens);
|
|
1836
|
-
if (taken !== undefined) {
|
|
1837
|
-
if (taken.def.id === "project") {
|
|
1838
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
1839
|
-
continue;
|
|
1840
|
-
}
|
|
1841
|
-
if (taken.def.id === "base") {
|
|
1842
|
-
baseRevision = requireOptionPath(taken.def.canonical, taken.value);
|
|
1843
|
-
continue;
|
|
1844
|
-
}
|
|
1845
|
-
throw new CliUsageError(`unknown gleaner-left option: ${taken.def.canonical}`);
|
|
1846
|
-
}
|
|
1847
|
-
const token = tokens.shift();
|
|
1848
|
-
if (token.startsWith("-") && token !== "-") {
|
|
1849
|
-
throw new CliUsageError(`unknown gleaner-left option: ${token}`);
|
|
1850
|
-
}
|
|
1851
|
-
positional.push(token);
|
|
1445
|
+
export function parseDoctorIssueNumber(raw) {
|
|
1446
|
+
const trimmed = raw.trim();
|
|
1447
|
+
if (!DOCTOR_ISSUE_NUMBER_PATTERN.test(trimmed)) {
|
|
1448
|
+
throw new CliUsageError(`doctor --issue must be a positive integer, got ${raw}`);
|
|
1852
1449
|
}
|
|
1853
|
-
|
|
1854
|
-
return {
|
|
1855
|
-
instruction: positional.join(" "),
|
|
1856
|
-
baseRevision: baseRevision,
|
|
1857
|
-
...(project === undefined ? {} : { project }),
|
|
1858
|
-
};
|
|
1450
|
+
return Number(trimmed);
|
|
1859
1451
|
}
|
|
1860
1452
|
/**
|
|
1861
|
-
*
|
|
1862
|
-
*
|
|
1453
|
+
* Resolve the retained Doctor case runs root from Issue identity.
|
|
1454
|
+
* Default is the #78 book locator; optional --runs must stay project-confined
|
|
1455
|
+
* and match Doctor case grammar for the same issue number.
|
|
1863
1456
|
*/
|
|
1864
|
-
export async function
|
|
1865
|
-
|
|
1866
|
-
|
|
1457
|
+
export async function resolveDoctorCaseRunsPath(options) {
|
|
1458
|
+
const ledgerHome = resolveActivationLedgerHome(options.home);
|
|
1459
|
+
// Canonical topology: <book>/<ticket>/runs (docs/dossier-topology.md).
|
|
1460
|
+
const defaultRuns = join(activationBookDirectory(ledgerHome, options.bookKey), String(options.issueNumber), "runs");
|
|
1461
|
+
if (options.runs === undefined) {
|
|
1462
|
+
return defaultRuns;
|
|
1867
1463
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1464
|
+
const raw = options.runs.trim();
|
|
1465
|
+
if (raw === "") {
|
|
1466
|
+
throw new CliUsageError("doctor --runs requires a path");
|
|
1870
1467
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
cwd: projectRoot,
|
|
1875
|
-
runId,
|
|
1876
|
-
role: "gleaner-left",
|
|
1877
|
-
subject: { unbound: true },
|
|
1878
|
-
home: options.home,
|
|
1879
|
-
});
|
|
1880
|
-
const instruction = options.instruction;
|
|
1881
|
-
const instructionEmpty = instruction.trim() === "";
|
|
1882
|
-
const admitted = {
|
|
1883
|
-
role: "gleaner-left",
|
|
1884
|
-
runId,
|
|
1885
|
-
bookKey,
|
|
1886
|
-
projectRoot,
|
|
1887
|
-
runDirectory,
|
|
1888
|
-
principal,
|
|
1889
|
-
instruction,
|
|
1890
|
-
instructionEmpty,
|
|
1891
|
-
baseRevision: options.baseRevision,
|
|
1892
|
-
attachments: [],
|
|
1893
|
-
...(options.correlationId === undefined
|
|
1894
|
-
? {}
|
|
1895
|
-
: { correlationId: options.correlationId }),
|
|
1896
|
-
};
|
|
1897
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1898
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1899
|
-
sessionDirectory,
|
|
1900
|
-
sessionFile,
|
|
1901
|
-
});
|
|
1902
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1903
|
-
return {
|
|
1904
|
-
role: "gleaner-left",
|
|
1905
|
-
runId,
|
|
1906
|
-
bookKey,
|
|
1907
|
-
projectRoot,
|
|
1908
|
-
instruction,
|
|
1909
|
-
instructionEmpty,
|
|
1910
|
-
attachments: [],
|
|
1911
|
-
runDirectory,
|
|
1912
|
-
principal,
|
|
1913
|
-
admittedRequestPath,
|
|
1914
|
-
baseRevision: options.baseRevision,
|
|
1915
|
-
...(options.correlationId === undefined
|
|
1916
|
-
? {}
|
|
1917
|
-
: { correlationId: options.correlationId }),
|
|
1918
|
-
};
|
|
1919
|
-
}
|
|
1920
|
-
/** Bound comparison base is a typed fact, not a directional instruction. */
|
|
1921
|
-
export function buildGleanerLeftTransportPrompt(admitted, engineMaterial) {
|
|
1922
|
-
const lines = [
|
|
1923
|
-
`左拾遗案已受理。比较基线:${admitted.baseRevision}`,
|
|
1924
|
-
];
|
|
1925
|
-
if (!admitted.instructionEmpty) {
|
|
1926
|
-
lines.push("", admitted.instruction);
|
|
1468
|
+
// Project-relative only — absolute overrides would bypass confinement.
|
|
1469
|
+
if (isAbsolute(raw)) {
|
|
1470
|
+
throw new CliUsageError("doctor --runs must be a project-relative path");
|
|
1927
1471
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
const attachmentPaths = [];
|
|
1933
|
-
const authorityRefs = [];
|
|
1934
|
-
let project;
|
|
1935
|
-
let baseRevision;
|
|
1936
|
-
let lens;
|
|
1937
|
-
const positional = [];
|
|
1938
|
-
const tokens = [...args];
|
|
1939
|
-
const definitions = roleOptions("reviewer");
|
|
1940
|
-
const options = createTypedOptionConsumer(definitions);
|
|
1941
|
-
while (tokens.length > 0) {
|
|
1942
|
-
if (tokens[0] === "--") {
|
|
1943
|
-
tokens.shift();
|
|
1944
|
-
positional.push(...tokens);
|
|
1945
|
-
break;
|
|
1946
|
-
}
|
|
1947
|
-
const taken = options.takeDashed(tokens);
|
|
1948
|
-
if (taken !== undefined) {
|
|
1949
|
-
if (taken.def.id === "project") {
|
|
1950
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
1951
|
-
continue;
|
|
1952
|
-
}
|
|
1953
|
-
if (taken.def.id === "base") {
|
|
1954
|
-
baseRevision = requireReviewerBaseRevision(taken.value);
|
|
1955
|
-
continue;
|
|
1956
|
-
}
|
|
1957
|
-
if (taken.def.id === "lens") {
|
|
1958
|
-
// Public override is single-axis only; omission stays undefined for the parallel branch.
|
|
1959
|
-
lens = requireReviewerLens(taken.value);
|
|
1960
|
-
continue;
|
|
1961
|
-
}
|
|
1962
|
-
if (taken.def.id === "authority-ref") {
|
|
1963
|
-
authorityRefs.push(requireAuthorityRef(taken.value));
|
|
1964
|
-
continue;
|
|
1965
|
-
}
|
|
1966
|
-
throw new CliUsageError(`unknown reviewer option: ${taken.def.canonical}`);
|
|
1967
|
-
}
|
|
1968
|
-
const token = tokens.shift();
|
|
1969
|
-
if (token.startsWith("-") && token !== "-") {
|
|
1970
|
-
throw new CliUsageError(`unknown reviewer option: ${token}`);
|
|
1971
|
-
}
|
|
1972
|
-
positional.push(token);
|
|
1472
|
+
const resolved = resolve(options.projectRoot, raw);
|
|
1473
|
+
if (resolved !== options.projectRoot &&
|
|
1474
|
+
!pathContainedIn(options.projectRoot, resolved)) {
|
|
1475
|
+
throw new CliUsageError("doctor --runs escapes the project root");
|
|
1973
1476
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
instruction: positional.join(" "),
|
|
1978
|
-
attachmentPaths,
|
|
1979
|
-
baseRevision: baseRevision,
|
|
1980
|
-
...(lens === undefined ? {} : { lens }),
|
|
1981
|
-
authorityRefs,
|
|
1982
|
-
...(project === undefined ? {} : { project }),
|
|
1983
|
-
};
|
|
1984
|
-
}
|
|
1985
|
-
/**
|
|
1986
|
-
* Admit a Reviewer Role run on the fixed base + lens + authority set.
|
|
1987
|
-
* Caller instruction is optional provenance; typed fields project Skill inputs.
|
|
1988
|
-
* authorityRefs are frozen as durable references only — not Spec prose.
|
|
1989
|
-
*/
|
|
1990
|
-
export async function admitReviewerInvocation(options) {
|
|
1991
|
-
if (options.project !== undefined) {
|
|
1992
|
-
requireOptionPath("--project", options.project);
|
|
1477
|
+
let real;
|
|
1478
|
+
try {
|
|
1479
|
+
real = await realpath(resolved);
|
|
1993
1480
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
throw new CliUsageError("reviewer requires --authority-ref <ref>");
|
|
1481
|
+
catch (error) {
|
|
1482
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1483
|
+
throw new CliUsageError(`doctor --runs is not a readable retained runs root: ${detail}`, { cause: error });
|
|
1998
1484
|
}
|
|
1999
|
-
const
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
});
|
|
2009
|
-
// Public parse already rejects attachments; keep freeze loop for structural symmetry.
|
|
2010
|
-
const attachments = await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
2011
|
-
const instruction = options.instruction;
|
|
2012
|
-
const instructionEmpty = instruction.trim() === "";
|
|
2013
|
-
const admitted = {
|
|
2014
|
-
role: "reviewer",
|
|
2015
|
-
runId,
|
|
2016
|
-
bookKey,
|
|
2017
|
-
projectRoot,
|
|
2018
|
-
runDirectory,
|
|
2019
|
-
principal,
|
|
2020
|
-
instruction,
|
|
2021
|
-
instructionEmpty,
|
|
2022
|
-
baseRevision,
|
|
2023
|
-
lens,
|
|
2024
|
-
authorityRefs: [...authorityRefs],
|
|
2025
|
-
...(options.correlationId === undefined
|
|
2026
|
-
? {}
|
|
2027
|
-
: { correlationId: options.correlationId }),
|
|
2028
|
-
attachments: attachments.map((a) => ({
|
|
2029
|
-
provenancePath: a.provenancePath,
|
|
2030
|
-
frozenPath: a.frozenPath,
|
|
2031
|
-
byteLength: a.byteLength,
|
|
2032
|
-
sha256: a.sha256,
|
|
2033
|
-
mediaKind: a.mediaKind,
|
|
2034
|
-
})),
|
|
2035
|
-
};
|
|
2036
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
2037
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
2038
|
-
sessionDirectory,
|
|
2039
|
-
sessionFile,
|
|
2040
|
-
});
|
|
2041
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
2042
|
-
return {
|
|
2043
|
-
role: "reviewer",
|
|
2044
|
-
runId,
|
|
2045
|
-
bookKey,
|
|
2046
|
-
projectRoot,
|
|
2047
|
-
instruction,
|
|
2048
|
-
instructionEmpty,
|
|
2049
|
-
attachments,
|
|
2050
|
-
runDirectory,
|
|
2051
|
-
principal,
|
|
2052
|
-
admittedRequestPath,
|
|
2053
|
-
baseRevision,
|
|
2054
|
-
lens,
|
|
2055
|
-
authorityRefs,
|
|
2056
|
-
...(options.correlationId === undefined
|
|
2057
|
-
? {}
|
|
2058
|
-
: { correlationId: options.correlationId }),
|
|
2059
|
-
};
|
|
1485
|
+
const normalized = real.split(sep).join("/");
|
|
1486
|
+
const match = normalized.match(DOCTOR_CASE_RUNS_PATH_PATTERN);
|
|
1487
|
+
if (!match) {
|
|
1488
|
+
throw new CliUsageError("doctor --runs must be an .ak-roles/books/<book>/<n>/runs directory");
|
|
1489
|
+
}
|
|
1490
|
+
if (Number(match[1]) !== options.issueNumber) {
|
|
1491
|
+
throw new CliUsageError(`doctor --runs issue ${match[1]} does not match --issue ${options.issueNumber}`);
|
|
1492
|
+
}
|
|
1493
|
+
return real;
|
|
2060
1494
|
}
|
|
2061
1495
|
/** Frozen Skill arg projection shared by initial and resume (never reverse-parsed). */
|
|
2062
1496
|
export function buildReviewerSkillArgProjection(admitted) {
|
|
@@ -2066,72 +1500,6 @@ export function buildReviewerSkillArgProjection(admitted) {
|
|
|
2066
1500
|
...admitted.authorityRefs.map((ref) => `--authority ${ref}`),
|
|
2067
1501
|
].join(" ");
|
|
2068
1502
|
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Build the host-neutral prompt transport for an admitted Reviewer request.
|
|
2071
|
-
* Typed base/lens/authority project to Skill invocation args (never reverse-parsed from prose).
|
|
2072
|
-
* Optional caller words + engine material follow.
|
|
2073
|
-
*/
|
|
2074
|
-
export function buildReviewerTransportPrompt(admitted, engineMaterial) {
|
|
2075
|
-
const lines = [buildReviewerSkillArgProjection(admitted)];
|
|
2076
|
-
if (!admitted.instructionEmpty && admitted.instruction.trim() !== "") {
|
|
2077
|
-
lines.push("", admitted.instruction);
|
|
2078
|
-
}
|
|
2079
|
-
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
2080
|
-
}
|
|
2081
|
-
/**
|
|
2082
|
-
* Parse Merger-specific argv after the `merger` token.
|
|
2083
|
-
* Spellings from PUBLIC_OPTION_TABLE.merger; internal packet fields rejected (#342).
|
|
2084
|
-
*/
|
|
2085
|
-
export function parseMergerArgv(args) {
|
|
2086
|
-
const attachmentPaths = [];
|
|
2087
|
-
let project;
|
|
2088
|
-
const positional = [];
|
|
2089
|
-
const tokens = [...args];
|
|
2090
|
-
const definitions = roleOptions("merger");
|
|
2091
|
-
const options = createTypedOptionConsumer(definitions);
|
|
2092
|
-
while (tokens.length > 0) {
|
|
2093
|
-
if (tokens[0] === "--") {
|
|
2094
|
-
tokens.shift();
|
|
2095
|
-
positional.push(...tokens);
|
|
2096
|
-
break;
|
|
2097
|
-
}
|
|
2098
|
-
const taken = options.takeDashed(tokens);
|
|
2099
|
-
if (taken !== undefined) {
|
|
2100
|
-
if (taken.def.id === "attach") {
|
|
2101
|
-
attachmentPaths.push(requireOptionPath(taken.def.canonical, taken.value));
|
|
2102
|
-
continue;
|
|
2103
|
-
}
|
|
2104
|
-
if (taken.def.id === "project") {
|
|
2105
|
-
project = requireOptionPath(taken.def.canonical, taken.value);
|
|
2106
|
-
continue;
|
|
2107
|
-
}
|
|
2108
|
-
throw new CliUsageError(`unknown merger option: ${taken.def.canonical}`);
|
|
2109
|
-
}
|
|
2110
|
-
const token = tokens.shift();
|
|
2111
|
-
// Rejected public spellings (#342) plus other internal packet field faces.
|
|
2112
|
-
if (isRejectedPublicSpelling("merger", token) ||
|
|
2113
|
-
token === "--targetObjectId" ||
|
|
2114
|
-
token.startsWith("--targetObjectId=") ||
|
|
2115
|
-
token === "--sourceObjectId" ||
|
|
2116
|
-
token.startsWith("--sourceObjectId=") ||
|
|
2117
|
-
token === "--expectedConflictPaths" ||
|
|
2118
|
-
token.startsWith("--expectedConflictPaths=") ||
|
|
2119
|
-
token === "--resolutionScope" ||
|
|
2120
|
-
token.startsWith("--resolutionScope=")) {
|
|
2121
|
-
throw new CliUsageError("merger does not accept public packet fields; the adapter reads Git merge materials");
|
|
2122
|
-
}
|
|
2123
|
-
if (token.startsWith("-") && token !== "-") {
|
|
2124
|
-
throw new CliUsageError(`unknown merger option: ${token}`);
|
|
2125
|
-
}
|
|
2126
|
-
positional.push(token);
|
|
2127
|
-
}
|
|
2128
|
-
options.assertRequired();
|
|
2129
|
-
return {
|
|
2130
|
-
instruction: positional.join(" "),
|
|
2131
|
-
attachmentPaths,
|
|
2132
|
-
...(project === undefined ? {} : { project }),
|
|
2133
|
-
};
|
|
2134
|
-
}
|
|
2135
1503
|
function mergerMaterialFromUtf8(text) {
|
|
2136
1504
|
const bytes = Buffer.from(text, "utf8");
|
|
2137
1505
|
return Object.freeze({
|
|
@@ -2155,115 +1523,6 @@ export async function deriveMergerEnvelopeFromActiveMerge(projectRoot, gitState
|
|
|
2155
1523
|
resolutionScope,
|
|
2156
1524
|
});
|
|
2157
1525
|
}
|
|
2158
|
-
/**
|
|
2159
|
-
* Admit a Merger Role run on the common Invocation request.
|
|
2160
|
-
* Git materials (parents, conflicts, scope) are read from the worktree and
|
|
2161
|
-
* handed to the role as assignment materials — including when empty (#827).
|
|
2162
|
-
* Callers never supply public packet fields for those facts.
|
|
2163
|
-
*/
|
|
2164
|
-
export async function admitMergerInvocation(options) {
|
|
2165
|
-
if (options.project !== undefined) {
|
|
2166
|
-
requireOptionPath("--project", options.project);
|
|
2167
|
-
}
|
|
2168
|
-
const instruction = options.instruction;
|
|
2169
|
-
if (instruction.trim() === "") {
|
|
2170
|
-
throw new CliUsageError("merger requires a nonblank task instruction");
|
|
2171
|
-
}
|
|
2172
|
-
const projectRoot = resolve(options.project ?? options.cwd);
|
|
2173
|
-
const derived = await deriveMergerEnvelopeFromActiveMerge(projectRoot, options.gitState ?? createProductionMergerGitState(projectRoot));
|
|
2174
|
-
const runId = (options.createRunId ?? uuidv7)();
|
|
2175
|
-
const { principal, sessionDirectory, sessionFile, runDirectory, attachmentsDirectory, ledgerHome, bookKey, } = issueAdmissionPlacement(options.principalAuthority, {
|
|
2176
|
-
cwd: projectRoot,
|
|
2177
|
-
runId,
|
|
2178
|
-
role: "merger",
|
|
2179
|
-
subject: { unbound: true },
|
|
2180
|
-
home: options.home,
|
|
2181
|
-
});
|
|
2182
|
-
const attachments = await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
2183
|
-
// Intent materials seed primary-source investigation; the method owns the work.
|
|
2184
|
-
const targetLabel = derived.targetObjectId === "" ? "(none observed)" : derived.targetObjectId;
|
|
2185
|
-
const sourceLabel = derived.sourceObjectId === "" ? "(none observed)" : derived.sourceObjectId;
|
|
2186
|
-
const targetIntent = mergerMaterialFromUtf8(`Investigate primary sources for target parent ${targetLabel}. Do not invent intent.`);
|
|
2187
|
-
const sourceIntent = mergerMaterialFromUtf8(`Investigate primary sources for source parent ${sourceLabel}. Do not invent intent.`);
|
|
2188
|
-
const taskMaterial = mergerMaterialFromUtf8(instruction);
|
|
2189
|
-
const authorityMaterial = mergerMaterialFromUtf8(instruction);
|
|
2190
|
-
const mergerInput = validateMergerInput({
|
|
2191
|
-
attemptId: runId,
|
|
2192
|
-
targetObjectId: derived.targetObjectId,
|
|
2193
|
-
sourceObjectId: derived.sourceObjectId,
|
|
2194
|
-
materials: {
|
|
2195
|
-
task: taskMaterial,
|
|
2196
|
-
authority: authorityMaterial,
|
|
2197
|
-
targetIntent,
|
|
2198
|
-
sourceIntent,
|
|
2199
|
-
},
|
|
2200
|
-
expectedConflictPaths: [...derived.expectedConflictPaths],
|
|
2201
|
-
resolutionScope: [...derived.resolutionScope],
|
|
2202
|
-
authorizedChecks: [],
|
|
2203
|
-
});
|
|
2204
|
-
const mergerInputPath = join(runDirectory, "merger-input.json");
|
|
2205
|
-
await writeFile(mergerInputPath, `${JSON.stringify(mergerInput, null, 2)}\n`, "utf8");
|
|
2206
|
-
const admitted = {
|
|
2207
|
-
role: "merger",
|
|
2208
|
-
runId,
|
|
2209
|
-
bookKey,
|
|
2210
|
-
projectRoot,
|
|
2211
|
-
runDirectory,
|
|
2212
|
-
principal,
|
|
2213
|
-
instruction,
|
|
2214
|
-
instructionEmpty: false,
|
|
2215
|
-
mergerInputPath,
|
|
2216
|
-
derived: {
|
|
2217
|
-
targetObjectId: derived.targetObjectId,
|
|
2218
|
-
sourceObjectId: derived.sourceObjectId,
|
|
2219
|
-
expectedConflictPaths: [...derived.expectedConflictPaths],
|
|
2220
|
-
resolutionScope: [...derived.resolutionScope],
|
|
2221
|
-
},
|
|
2222
|
-
attachments: attachments.map((a) => ({
|
|
2223
|
-
provenancePath: a.provenancePath,
|
|
2224
|
-
frozenPath: a.frozenPath,
|
|
2225
|
-
byteLength: a.byteLength,
|
|
2226
|
-
sha256: a.sha256,
|
|
2227
|
-
mediaKind: a.mediaKind,
|
|
2228
|
-
})),
|
|
2229
|
-
};
|
|
2230
|
-
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
2231
|
-
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
2232
|
-
sessionDirectory,
|
|
2233
|
-
sessionFile,
|
|
2234
|
-
});
|
|
2235
|
-
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
2236
|
-
return {
|
|
2237
|
-
role: "merger",
|
|
2238
|
-
runId,
|
|
2239
|
-
bookKey,
|
|
2240
|
-
projectRoot,
|
|
2241
|
-
instruction,
|
|
2242
|
-
instructionEmpty: false,
|
|
2243
|
-
attachments,
|
|
2244
|
-
runDirectory,
|
|
2245
|
-
principal,
|
|
2246
|
-
admittedRequestPath,
|
|
2247
|
-
mergerInputPath,
|
|
2248
|
-
derived: admitted.derived,
|
|
2249
|
-
};
|
|
2250
|
-
}
|
|
2251
|
-
/**
|
|
2252
|
-
* Build the host-neutral prompt transport for an admitted Merger request.
|
|
2253
|
-
* Method material binds on RoleTurnRequest.methods; Pi-native `/skill:` form
|
|
2254
|
-
* is adapter-internal only (ADR 0082).
|
|
2255
|
-
*/
|
|
2256
|
-
export function buildMergerTransportPrompt(admitted, engineMaterial) {
|
|
2257
|
-
const lines = [admitted.instruction];
|
|
2258
|
-
if (admitted.attachments.length > 0) {
|
|
2259
|
-
lines.push("");
|
|
2260
|
-
lines.push("已受理附件(冻结快照路径):");
|
|
2261
|
-
for (const attachment of admitted.attachments) {
|
|
2262
|
-
lines.push(`- ${attachment.frozenPath}`);
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
2266
|
-
}
|
|
2267
1526
|
const ANALYST_TICKET_NUMBER_PATTERN = /^[1-9]\d*$/;
|
|
2268
1527
|
/**
|
|
2269
1528
|
* Parse a positive ticket / issue number for public analyst admission.
|
|
@@ -2369,7 +1628,6 @@ function requireOptionValue(flag, value, what) {
|
|
|
2369
1628
|
*/
|
|
2370
1629
|
export function parseAnalystArgv(args) {
|
|
2371
1630
|
const valueLists = new Map();
|
|
2372
|
-
const tokens = [...args];
|
|
2373
1631
|
const definitions = roleOptions("analyst");
|
|
2374
1632
|
// Shared typed consumer: dashed + positional take, repeatable, required (#342).
|
|
2375
1633
|
const options = createTypedOptionConsumer(definitions);
|
|
@@ -2380,63 +1638,61 @@ export function parseAnalystArgv(args) {
|
|
|
2380
1638
|
else
|
|
2381
1639
|
existing.push(value);
|
|
2382
1640
|
};
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
tokens.shift();
|
|
2386
|
-
if (tokens.length > 0) {
|
|
2387
|
-
throw new CliUsageError(`unexpected analyst argument: ${tokens[0]}`);
|
|
2388
|
-
}
|
|
2389
|
-
break;
|
|
2390
|
-
}
|
|
2391
|
-
const taken = options.takeDashed(tokens);
|
|
2392
|
-
if (taken !== undefined) {
|
|
1641
|
+
scanPublicArgv(args, options, {
|
|
1642
|
+
onDashed(taken) {
|
|
2393
1643
|
if (taken.def.valueMetavar === null) {
|
|
2394
1644
|
pushValue(taken.def.id, "");
|
|
2395
|
-
|
|
1645
|
+
return;
|
|
2396
1646
|
}
|
|
2397
1647
|
if (taken.def.id === "ticket") {
|
|
2398
1648
|
if (taken.value === undefined || taken.value.trim() === "") {
|
|
2399
1649
|
throw new CliUsageError("analyst --ticket requires a positive integer");
|
|
2400
1650
|
}
|
|
2401
1651
|
pushValue("ticket", taken.value);
|
|
2402
|
-
|
|
1652
|
+
return;
|
|
2403
1653
|
}
|
|
2404
1654
|
if (taken.def.id === "attach") {
|
|
2405
1655
|
pushValue("attach", requireOptionPath(taken.def.canonical, taken.value));
|
|
2406
|
-
|
|
1656
|
+
return;
|
|
2407
1657
|
}
|
|
2408
1658
|
if (taken.def.id === "group-a-label" || taken.def.id === "group-b-label") {
|
|
2409
1659
|
pushValue(taken.def.id, requireOptionValue(taken.def.canonical, taken.value, "a label"));
|
|
2410
|
-
|
|
1660
|
+
return;
|
|
2411
1661
|
}
|
|
2412
1662
|
if (taken.def.id === "group-a-issues" || taken.def.id === "group-b-issues") {
|
|
2413
1663
|
pushValue(taken.def.id, requireOptionValue(taken.def.canonical, taken.value, "a comma-separated list of N or book:N"));
|
|
2414
|
-
|
|
1664
|
+
return;
|
|
2415
1665
|
}
|
|
2416
1666
|
throw new CliUsageError(`unknown analyst option: ${taken.def.canonical}`);
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
1667
|
+
},
|
|
1668
|
+
onBare(token) {
|
|
1669
|
+
// #399: deleted --project-root; disabled --model-groups public face.
|
|
1670
|
+
if (isRejectedPublicSpelling("analyst", token)) {
|
|
1671
|
+
if (token === "--project-root" || token.startsWith("--project-root=")) {
|
|
1672
|
+
throw new CliUsageError("analyst no longer accepts --project-root (deleted); use bare call for whole book or --ticket N (cwd git common-dir selects the book)");
|
|
1673
|
+
}
|
|
1674
|
+
if (token === "--model-groups" || token.startsWith("--model-groups=")) {
|
|
1675
|
+
throw new CliUsageError("analyst --model-groups public CLI face is disabled; input face is being redesigned for multi-issue comparison (see follow-up ticket)");
|
|
1676
|
+
}
|
|
1677
|
+
throw new CliUsageError(`unknown analyst option: ${token}`);
|
|
2423
1678
|
}
|
|
2424
|
-
if (token
|
|
2425
|
-
throw new CliUsageError(
|
|
1679
|
+
if (token.startsWith("-") && token !== "-") {
|
|
1680
|
+
throw new CliUsageError(`unknown analyst option: ${token}`);
|
|
2426
1681
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
1682
|
+
// Positional selectors (e.g. sweep) via shared typed consumer — not a parallel list.
|
|
1683
|
+
const positional = options.takePositional(token);
|
|
1684
|
+
if (positional !== undefined) {
|
|
1685
|
+
pushValue(positional.id, "");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
throw new CliUsageError(`unexpected analyst argument: ${token}`);
|
|
1689
|
+
},
|
|
1690
|
+
onDoubleDash(rest) {
|
|
1691
|
+
if (rest.length > 0) {
|
|
1692
|
+
throw new CliUsageError(`unexpected analyst argument: ${rest[0]}`);
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
});
|
|
2440
1696
|
const counts = new Map();
|
|
2441
1697
|
for (const [id, values] of valueLists) {
|
|
2442
1698
|
counts.set(id, values.length);
|