@akagilnc/pi-workflow-roles 0.1.1918 → 0.1.1941
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/evidence-child-executor.js +12 -6
- package/dist/package-contracts/reviewer-output.js +16 -0
- package/dist/public-cli/main.js +79 -4
- package/dist/reviewer-construction.js +46 -6
- package/dist/reviewer-dispatch.js +64 -1
- package/dist/reviewer-execution-ledger.js +2 -1
- package/dist/reviewer-pinned-git.js +33 -0
- package/package.json +1 -1
- package/src/evidence-child-executor.ts +13 -6
- package/src/package-contracts/reviewer-output.ts +19 -0
- package/src/public-cli/invocation.ts +46 -1
- package/src/public-cli/reviewer-run.ts +12 -0
- package/src/public-cli/run-lifecycle.ts +22 -1
- package/src/public-cli/settlement.ts +8 -0
- package/src/reviewer-construction.ts +64 -7
- package/src/reviewer-dispatch.ts +81 -2
- package/src/reviewer-execution-ledger.ts +2 -1
- package/src/reviewer-pinned-git.ts +43 -0
- package/src/reviewer-role.ts +143 -132
- package/src/reviewer-settlement.ts +4 -0
- package/src/role-runtime.ts +186 -3
|
@@ -9,9 +9,19 @@ import { join } from "node:path";
|
|
|
9
9
|
import { createAssistantMessageEventStream, InMemoryCredentialStore, } from "@earendil-works/pi-ai";
|
|
10
10
|
import { AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE, prepareComplianceDispatch, } from "./compliance-transport.js";
|
|
11
11
|
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
|
|
12
|
-
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.js";
|
|
13
12
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
|
|
13
|
+
import { REVIEWER_VERIFICATION_BOUNDARY } from "./reviewer-construction.js";
|
|
14
|
+
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.js";
|
|
14
15
|
import { hasUpstreamErrorTestimony, isNonSuccessHttpStatus, projectConfirmedRemotePayload, } from "./upstream-error-testimony.js";
|
|
16
|
+
/** Package-owned system prompt for Reviewer Standards/Spec evidence children (private carrier). */
|
|
17
|
+
function buildEvidenceChildSystemPrompt() {
|
|
18
|
+
return [
|
|
19
|
+
"Work only in the supplied workspace.",
|
|
20
|
+
"Use the available evidence tools to investigate. Do not commit, push, or mutate remotes.",
|
|
21
|
+
REVIEWER_VERIFICATION_BOUNDARY,
|
|
22
|
+
"Return one substantive non-blank report.",
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
15
25
|
// ── shared constants / types ──────────────────────────────────────────────
|
|
16
26
|
export const AUDITOR_TURN_LIMIT = 32;
|
|
17
27
|
export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
|
|
@@ -450,11 +460,7 @@ export async function executeEvidenceChild(workspace, prompt, context, options =
|
|
|
450
460
|
model: inherited.model,
|
|
451
461
|
thinkingLevel: context.thinkingLevel ?? "off",
|
|
452
462
|
modelRuntime: inherited.runtime,
|
|
453
|
-
systemPrompt:
|
|
454
|
-
"Work only in the supplied workspace.",
|
|
455
|
-
"Use the available evidence tools to investigate. Do not commit, push, or mutate remotes.",
|
|
456
|
-
"Return one substantive non-blank report.",
|
|
457
|
-
].join("\n"),
|
|
463
|
+
systemPrompt: buildEvidenceChildSystemPrompt(),
|
|
458
464
|
sessionManager: createRecordSession({
|
|
459
465
|
cwd: workspace,
|
|
460
466
|
kind: "evidence-children",
|
|
@@ -68,6 +68,22 @@ export function validateRuntimeReviewerReceipt(output) {
|
|
|
68
68
|
if (status === "failed" && report !== undefined)
|
|
69
69
|
throw new Error("Failed Reviewer outcome cannot bind a report");
|
|
70
70
|
}
|
|
71
|
+
// One read inside the existing accepted-leg consistency cycle — not a second validator.
|
|
72
|
+
// launched ⇔ Standards+Spec; skipped-missing ⇔ Standards only.
|
|
73
|
+
const specDisposition = read(output, "specDisposition");
|
|
74
|
+
if (specDisposition === "launched") {
|
|
75
|
+
if (expectedAxes.length !== 2 || expectedAxes[0] !== "standards" || expectedAxes[1] !== "spec") {
|
|
76
|
+
throw new Error("Reviewer specDisposition launched requires Standards+Spec accepted legs");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (specDisposition === "skipped-missing") {
|
|
80
|
+
if (expectedAxes.length !== 1 || expectedAxes[0] !== "standards") {
|
|
81
|
+
throw new Error("Reviewer specDisposition skipped-missing requires Standards-only accepted legs");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (specDisposition !== undefined) {
|
|
85
|
+
throw new Error("Invalid Reviewer specDisposition");
|
|
86
|
+
}
|
|
71
87
|
}
|
|
72
88
|
return output;
|
|
73
89
|
}
|
package/dist/public-cli/main.js
CHANGED
|
@@ -147,6 +147,18 @@ function validateRuntimeReviewerReceipt(output) {
|
|
|
147
147
|
throw new Error("Successful Reviewer outcome lacks report");
|
|
148
148
|
if (status === "failed" && report !== void 0) throw new Error("Failed Reviewer outcome cannot bind a report");
|
|
149
149
|
}
|
|
150
|
+
const specDisposition = read(output, "specDisposition");
|
|
151
|
+
if (specDisposition === "launched") {
|
|
152
|
+
if (expectedAxes.length !== 2 || expectedAxes[0] !== "standards" || expectedAxes[1] !== "spec") {
|
|
153
|
+
throw new Error("Reviewer specDisposition launched requires Standards+Spec accepted legs");
|
|
154
|
+
}
|
|
155
|
+
} else if (specDisposition === "skipped-missing") {
|
|
156
|
+
if (expectedAxes.length !== 1 || expectedAxes[0] !== "standards") {
|
|
157
|
+
throw new Error("Reviewer specDisposition skipped-missing requires Standards-only accepted legs");
|
|
158
|
+
}
|
|
159
|
+
} else if (specDisposition !== void 0) {
|
|
160
|
+
throw new Error("Invalid Reviewer specDisposition");
|
|
161
|
+
}
|
|
150
162
|
}
|
|
151
163
|
return output;
|
|
152
164
|
}
|
|
@@ -15464,6 +15476,17 @@ function requireOptionPath(flag, value) {
|
|
|
15464
15476
|
}
|
|
15465
15477
|
return value;
|
|
15466
15478
|
}
|
|
15479
|
+
function requireAuthorityRef(value) {
|
|
15480
|
+
if (value === void 0 || value.trim() === "") {
|
|
15481
|
+
throw new CliUsageError("--authority-ref requires a nonempty durable reference");
|
|
15482
|
+
}
|
|
15483
|
+
if (/\s/.test(value)) {
|
|
15484
|
+
throw new CliUsageError(
|
|
15485
|
+
"--authority-ref requires a durable reference, not inline Spec prose"
|
|
15486
|
+
);
|
|
15487
|
+
}
|
|
15488
|
+
return value;
|
|
15489
|
+
}
|
|
15467
15490
|
function parseJudgeArgv(args) {
|
|
15468
15491
|
const attachmentPaths = [];
|
|
15469
15492
|
let project;
|
|
@@ -16426,6 +16449,7 @@ function buildDoctorTransportPrompt(admitted) {
|
|
|
16426
16449
|
}
|
|
16427
16450
|
function parseReviewerArgv(args) {
|
|
16428
16451
|
const attachmentPaths = [];
|
|
16452
|
+
const authorityRefs = [];
|
|
16429
16453
|
let project;
|
|
16430
16454
|
let baseRevision;
|
|
16431
16455
|
const positional = [];
|
|
@@ -16452,6 +16476,14 @@ function parseReviewerArgv(args) {
|
|
|
16452
16476
|
baseRevision = requireOptionPath("--base", token.slice("--base=".length));
|
|
16453
16477
|
continue;
|
|
16454
16478
|
}
|
|
16479
|
+
if (token === "--authority-ref") {
|
|
16480
|
+
authorityRefs.push(requireAuthorityRef(tokens.shift()));
|
|
16481
|
+
continue;
|
|
16482
|
+
}
|
|
16483
|
+
if (token.startsWith("--authority-ref=")) {
|
|
16484
|
+
authorityRefs.push(requireAuthorityRef(token.slice("--authority-ref=".length)));
|
|
16485
|
+
continue;
|
|
16486
|
+
}
|
|
16455
16487
|
if (token.startsWith("-") && token !== "-") {
|
|
16456
16488
|
throw new CliUsageError(`unknown reviewer option: ${token}`);
|
|
16457
16489
|
}
|
|
@@ -16464,6 +16496,7 @@ function parseReviewerArgv(args) {
|
|
|
16464
16496
|
instruction: positional.join(" "),
|
|
16465
16497
|
attachmentPaths,
|
|
16466
16498
|
baseRevision,
|
|
16499
|
+
authorityRefs,
|
|
16467
16500
|
...project === void 0 ? {} : { project }
|
|
16468
16501
|
};
|
|
16469
16502
|
}
|
|
@@ -16474,6 +16507,9 @@ async function admitReviewerInvocation(options) {
|
|
|
16474
16507
|
if (options.baseRevision.trim() === "") {
|
|
16475
16508
|
throw new CliUsageError("--base requires a nonempty revision");
|
|
16476
16509
|
}
|
|
16510
|
+
const authorityRefs = Object.freeze(
|
|
16511
|
+
(options.authorityRefs ?? []).map((ref) => requireAuthorityRef(ref))
|
|
16512
|
+
);
|
|
16477
16513
|
const projectRoot = resolve4(options.project ?? options.cwd);
|
|
16478
16514
|
const runId = (options.createRunId ?? uuidv7)();
|
|
16479
16515
|
const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } = roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "reviewer", home: options.home });
|
|
@@ -16499,6 +16535,7 @@ async function admitReviewerInvocation(options) {
|
|
|
16499
16535
|
instruction,
|
|
16500
16536
|
instructionEmpty,
|
|
16501
16537
|
baseRevision: options.baseRevision,
|
|
16538
|
+
authorityRefs: [...authorityRefs],
|
|
16502
16539
|
attachments: attachments.map((a) => ({
|
|
16503
16540
|
provenancePath: a.provenancePath,
|
|
16504
16541
|
frozenPath: a.frozenPath,
|
|
@@ -16528,6 +16565,7 @@ async function admitReviewerInvocation(options) {
|
|
|
16528
16565
|
sessionFile,
|
|
16529
16566
|
admittedRequestPath,
|
|
16530
16567
|
baseRevision: options.baseRevision,
|
|
16568
|
+
authorityRefs,
|
|
16531
16569
|
...ticketFields
|
|
16532
16570
|
};
|
|
16533
16571
|
}
|
|
@@ -16804,7 +16842,7 @@ var init_reviewer_scope_prompt = __esm({
|
|
|
16804
16842
|
});
|
|
16805
16843
|
|
|
16806
16844
|
// src/reviewer-construction.ts
|
|
16807
|
-
var REVIEWER_CONSTRUCTION_RECIPE, REVIEWER_AXIS_OUTPUT_ADAPTER, REVIEWER_STANDARDS_CONCLUSION_KEYS, REVIEWER_STANDARDS_CONCLUSION_LABELS;
|
|
16845
|
+
var REVIEWER_CONSTRUCTION_RECIPE, REVIEWER_AXIS_OUTPUT_ADAPTER, REVIEWER_VERIFICATION_BOUNDARY, REVIEWER_STANDARDS_CONCLUSION_KEYS, REVIEWER_STANDARDS_CONCLUSION_LABELS;
|
|
16808
16846
|
var init_reviewer_construction = __esm({
|
|
16809
16847
|
"src/reviewer-construction.ts"() {
|
|
16810
16848
|
"use strict";
|
|
@@ -16821,6 +16859,14 @@ var init_reviewer_construction = __esm({
|
|
|
16821
16859
|
version: 1,
|
|
16822
16860
|
implementationSha256: sha256Hex("reviewer-axis-output:v1:single-axis-verbatim-report+standards-three-priorities")
|
|
16823
16861
|
});
|
|
16862
|
+
REVIEWER_VERIFICATION_BOUNDARY = [
|
|
16863
|
+
"Verification-Boundary: you may run focused product tests during this review turn when independent verification needs them.",
|
|
16864
|
+
"A full repository test suite is not forbidden, but do not re-run it every review round;",
|
|
16865
|
+
"prefer once at family wrap-up unless this review specifically requires a broader run.",
|
|
16866
|
+
"Slice and review work should not trigger frequent full-suite reruns.",
|
|
16867
|
+
"Independently discover test facts (including existing coder/fixer receipts and any tests you run);",
|
|
16868
|
+
"do not treat caller prose as the source of those facts."
|
|
16869
|
+
].join(" ");
|
|
16824
16870
|
REVIEWER_STANDARDS_CONCLUSION_KEYS = Object.freeze([
|
|
16825
16871
|
"constitutionality",
|
|
16826
16872
|
"minimum-necessary-test-cost",
|
|
@@ -16845,6 +16891,7 @@ var init_reviewer_dispatch = __esm({
|
|
|
16845
16891
|
init_reviewer_prompt_identity();
|
|
16846
16892
|
init_sha256();
|
|
16847
16893
|
init_reviewer_construction();
|
|
16894
|
+
init_reviewer_construction();
|
|
16848
16895
|
init_reviewer_preflight_error();
|
|
16849
16896
|
init_sha256();
|
|
16850
16897
|
init_reviewer_prompt_identity();
|
|
@@ -17640,6 +17687,7 @@ async function loadResumableRunRecord(home, runId) {
|
|
|
17640
17687
|
let prerequisitesPath;
|
|
17641
17688
|
let prerequisites;
|
|
17642
17689
|
let baseRevision;
|
|
17690
|
+
let authorityRefs;
|
|
17643
17691
|
let mergerInputPath;
|
|
17644
17692
|
let derived;
|
|
17645
17693
|
let correlationId;
|
|
@@ -17677,6 +17725,18 @@ async function loadResumableRunRecord(home, runId) {
|
|
|
17677
17725
|
if (typeof record4.baseRevision === "string" && record4.baseRevision.trim() !== "") {
|
|
17678
17726
|
baseRevision = record4.baseRevision;
|
|
17679
17727
|
}
|
|
17728
|
+
if (Array.isArray(record4.authorityRefs)) {
|
|
17729
|
+
authorityRefs = Object.freeze(
|
|
17730
|
+
record4.authorityRefs.map((ref) => {
|
|
17731
|
+
if (typeof ref !== "string") {
|
|
17732
|
+
throw new CliUsageError(
|
|
17733
|
+
"role run admitted authority refs must be durable reference strings"
|
|
17734
|
+
);
|
|
17735
|
+
}
|
|
17736
|
+
return requireAuthorityRef(ref);
|
|
17737
|
+
})
|
|
17738
|
+
);
|
|
17739
|
+
}
|
|
17680
17740
|
if (typeof record4.mergerInputPath === "string" && record4.mergerInputPath.trim() !== "") {
|
|
17681
17741
|
mergerInputPath = record4.mergerInputPath;
|
|
17682
17742
|
}
|
|
@@ -17696,9 +17756,11 @@ async function loadResumableRunRecord(home, runId) {
|
|
|
17696
17756
|
correlationId = fromAdmitted.correlationId;
|
|
17697
17757
|
ticketNumber = fromAdmitted.ticketNumber;
|
|
17698
17758
|
}
|
|
17699
|
-
} catch {
|
|
17759
|
+
} catch (error) {
|
|
17760
|
+
if (error instanceof CliUsageError) throw error;
|
|
17700
17761
|
throw new CliUsageError(
|
|
17701
|
-
`role run admitted request is unreadable: ${runId}
|
|
17762
|
+
`role run admitted request is unreadable: ${runId}`,
|
|
17763
|
+
{ cause: error }
|
|
17702
17764
|
);
|
|
17703
17765
|
}
|
|
17704
17766
|
if (correlationId === void 0 || ticketNumber === void 0) {
|
|
@@ -17735,6 +17797,7 @@ async function loadResumableRunRecord(home, runId) {
|
|
|
17735
17797
|
...prerequisitesPath === void 0 ? {} : { prerequisitesPath },
|
|
17736
17798
|
...prerequisites === void 0 ? {} : { prerequisites },
|
|
17737
17799
|
...baseRevision === void 0 ? {} : { baseRevision },
|
|
17800
|
+
...authorityRefs === void 0 ? {} : { authorityRefs },
|
|
17738
17801
|
...mergerInputPath === void 0 ? {} : { mergerInputPath },
|
|
17739
17802
|
...derived === void 0 ? {} : { derived },
|
|
17740
17803
|
...correlationId === void 0 ? {} : { correlationId },
|
|
@@ -17890,6 +17953,7 @@ async function loadResumableReviewerRun(home, runId) {
|
|
|
17890
17953
|
sessionFile: loaded.run.sessionFile,
|
|
17891
17954
|
admittedRequestPath: loaded.run.admittedRequestPath,
|
|
17892
17955
|
baseRevision,
|
|
17956
|
+
authorityRefs: Object.freeze([...loaded.admittedFields.authorityRefs ?? []]),
|
|
17893
17957
|
...restoredTicketFields(loaded.admittedFields)
|
|
17894
17958
|
};
|
|
17895
17959
|
return {
|
|
@@ -18093,8 +18157,9 @@ var init_evidence_child_executor = __esm({
|
|
|
18093
18157
|
"use strict";
|
|
18094
18158
|
init_compliance_transport();
|
|
18095
18159
|
init_package_owned_tool_idle();
|
|
18096
|
-
init_stream_idle_guard();
|
|
18097
18160
|
init_receipt_delivery_policy();
|
|
18161
|
+
init_reviewer_construction();
|
|
18162
|
+
init_stream_idle_guard();
|
|
18098
18163
|
init_upstream_error_testimony();
|
|
18099
18164
|
}
|
|
18100
18165
|
});
|
|
@@ -19352,6 +19417,7 @@ function reviewerDecisiveFacts(output) {
|
|
|
19352
19417
|
const axes = reviewerAxes(outcomes.readable ? outcomes.value : void 0);
|
|
19353
19418
|
const reportAxes = reviewerAxes(reports.readable ? reports.value : void 0);
|
|
19354
19419
|
const acceptedBatch = safelyRead(candidate, "acceptedBatch");
|
|
19420
|
+
const specDisposition = safelyRead(candidate, "specDisposition");
|
|
19355
19421
|
const facts = {
|
|
19356
19422
|
axes,
|
|
19357
19423
|
reportAxes,
|
|
@@ -19359,6 +19425,9 @@ function reviewerDecisiveFacts(output) {
|
|
|
19359
19425
|
...auditNoReceiptDecisiveFact(candidate)
|
|
19360
19426
|
};
|
|
19361
19427
|
if (status.readable && typeof status.value === "string") facts.reviewerStatus = status.value;
|
|
19428
|
+
if (specDisposition.readable && (specDisposition.value === "launched" || specDisposition.value === "skipped-missing")) {
|
|
19429
|
+
facts.specDisposition = specDisposition.value;
|
|
19430
|
+
}
|
|
19362
19431
|
const diagnostic = safelyRead(candidate, "diagnostic");
|
|
19363
19432
|
if (status.readable && status.value === "refused" && diagnostic.readable) {
|
|
19364
19433
|
facts.diagnosticPresent = typeof diagnostic.value === "string" && diagnostic.value.trim().length > 0;
|
|
@@ -20649,6 +20718,7 @@ async function publishReviewerArtifacts(admitted, roleOutcome, sessionDirectory,
|
|
|
20649
20718
|
sessionFile: admitted.sessionFile,
|
|
20650
20719
|
admittedRequestPath: admitted.admittedRequestPath,
|
|
20651
20720
|
baseRevision: admitted.baseRevision,
|
|
20721
|
+
authorityRefs: [...admitted.authorityRefs],
|
|
20652
20722
|
...admitted.instructionEmpty ? {} : { callerProvenance: admitted.instruction },
|
|
20653
20723
|
attachments: admitted.attachments.map((a) => ({
|
|
20654
20724
|
provenancePath: a.provenancePath,
|
|
@@ -23335,6 +23405,7 @@ function buildReviewerActivationExtraArgs(admitted, options) {
|
|
|
23335
23405
|
options.packageRoot,
|
|
23336
23406
|
"code-review"
|
|
23337
23407
|
);
|
|
23408
|
+
const authorityRefArgs = admitted.authorityRefs.length === 0 ? [] : ["--ak-review-authority-refs", JSON.stringify([...admitted.authorityRefs])];
|
|
23338
23409
|
return [
|
|
23339
23410
|
"--no-skills",
|
|
23340
23411
|
"--skill",
|
|
@@ -23351,6 +23422,7 @@ function buildReviewerActivationExtraArgs(admitted, options) {
|
|
|
23351
23422
|
"reviewer",
|
|
23352
23423
|
"--ak-review-base",
|
|
23353
23424
|
admitted.baseRevision,
|
|
23425
|
+
...authorityRefArgs,
|
|
23354
23426
|
"--mode",
|
|
23355
23427
|
"json",
|
|
23356
23428
|
...buildModelArgs7(options.model),
|
|
@@ -23362,6 +23434,7 @@ function buildReviewerResumeActivationExtraArgs(admitted, options) {
|
|
|
23362
23434
|
options.packageRoot,
|
|
23363
23435
|
"code-review"
|
|
23364
23436
|
);
|
|
23437
|
+
const authorityRefArgs = admitted.authorityRefs.length === 0 ? [] : ["--ak-review-authority-refs", JSON.stringify([...admitted.authorityRefs])];
|
|
23365
23438
|
return [
|
|
23366
23439
|
"--no-skills",
|
|
23367
23440
|
"--skill",
|
|
@@ -23378,6 +23451,7 @@ function buildReviewerResumeActivationExtraArgs(admitted, options) {
|
|
|
23378
23451
|
"reviewer",
|
|
23379
23452
|
"--ak-review-base",
|
|
23380
23453
|
admitted.baseRevision,
|
|
23454
|
+
...authorityRefArgs,
|
|
23381
23455
|
"--mode",
|
|
23382
23456
|
"json",
|
|
23383
23457
|
...buildModelArgs7(options.model),
|
|
@@ -23571,6 +23645,7 @@ async function runPublicReviewer(argv, env, io, parseReviewerArgv2) {
|
|
|
23571
23645
|
instruction: parsed.instruction,
|
|
23572
23646
|
attachmentPaths: parsed.attachmentPaths,
|
|
23573
23647
|
baseRevision: parsed.baseRevision,
|
|
23648
|
+
authorityRefs: parsed.authorityRefs,
|
|
23574
23649
|
...parsed.project === void 0 ? {} : { project: parsed.project },
|
|
23575
23650
|
...env.createRunId === void 0 ? {} : { createRunId: env.createRunId }
|
|
23576
23651
|
});
|
|
@@ -11,6 +11,21 @@ export const REVIEWER_AXIS_OUTPUT_ADAPTER = Object.freeze({
|
|
|
11
11
|
version: 1,
|
|
12
12
|
implementationSha256: sha256Hex("reviewer-axis-output:v1:single-axis-verbatim-report+standards-three-priorities"),
|
|
13
13
|
});
|
|
14
|
+
/**
|
|
15
|
+
* Package-owned #1185 review verification cadence.
|
|
16
|
+
* Single true source consumed by two real actor carriers: parent Reviewer system-prompt injection
|
|
17
|
+
* and evidence-child system prompt. Not part of axis-adapter identity or axis leg prompts.
|
|
18
|
+
* Graded guidance only: focused tests allowed; full suite not forbidden but avoid frequent every-round reruns.
|
|
19
|
+
* Does not narrow ADR 0064 tools and adds no command ban, allowlist, or runtime block.
|
|
20
|
+
*/
|
|
21
|
+
export const REVIEWER_VERIFICATION_BOUNDARY = [
|
|
22
|
+
"Verification-Boundary: you may run focused product tests during this review turn when independent verification needs them.",
|
|
23
|
+
"A full repository test suite is not forbidden, but do not re-run it every review round;",
|
|
24
|
+
"prefer once at family wrap-up unless this review specifically requires a broader run.",
|
|
25
|
+
"Slice and review work should not trigger frequent full-suite reruns.",
|
|
26
|
+
"Independently discover test facts (including existing coder/fixer receipts and any tests you run);",
|
|
27
|
+
"do not treat caller prose as the source of those facts.",
|
|
28
|
+
].join(" ");
|
|
14
29
|
/** Typed Standards conclusion keys owned by reviewer construction (presentation labels are not the contract). */
|
|
15
30
|
export const REVIEWER_STANDARDS_CONCLUSION_KEYS = Object.freeze([
|
|
16
31
|
"constitutionality",
|
|
@@ -63,8 +78,22 @@ export function reviewerAxisMethodAdapter(axis) {
|
|
|
63
78
|
"The returned report is the complete output envelope and its UTF-8 bytes are preserved verbatim; no heading parser, sanitizer, section splitter, rewrite, aggregation, or replacement leg follows.",
|
|
64
79
|
].join("\n");
|
|
65
80
|
}
|
|
66
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Spec-only evidence-child material carrier for durable authority references.
|
|
83
|
+
* Exact values preserved; no prose extraction and no Standards/parent injection.
|
|
84
|
+
*/
|
|
85
|
+
export function reviewerAuthorityRefsMaterial(authorityRefs) {
|
|
86
|
+
return [
|
|
87
|
+
"Authority-Refs:",
|
|
88
|
+
JSON.stringify(Object.freeze([...authorityRefs])),
|
|
89
|
+
"These are durable authority references only. Read them as Spec grounding materials; do not invent Spec prose from caller instruction.",
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
92
|
+
/** Deterministic compiler: fixed target/range plus discovery product in, dispatch text out. */
|
|
67
93
|
export function constructReviewerDispatch(input) {
|
|
94
|
+
const launchSpec = input.specAuthority.status === "available";
|
|
95
|
+
const authorityRefs = Object.freeze(input.specAuthority.status === "available" ? [...input.specAuthority.refs] : []);
|
|
96
|
+
const specDisposition = launchSpec ? "launched" : "skipped-missing";
|
|
68
97
|
const common = [
|
|
69
98
|
`Target: ${input.range.target}`,
|
|
70
99
|
`Base: ${input.range.base}`,
|
|
@@ -76,11 +105,20 @@ export function constructReviewerDispatch(input) {
|
|
|
76
105
|
"Fixed-Range:",
|
|
77
106
|
JSON.stringify(input.range, null, 2),
|
|
78
107
|
].join("\n");
|
|
79
|
-
const axes =
|
|
80
|
-
|
|
81
|
-
axis:
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
const axes = launchSpec
|
|
109
|
+
? [{ axis: "standards" }, { axis: "spec" }]
|
|
110
|
+
: [{ axis: "standards" }];
|
|
111
|
+
const legs = axes.map((x) => {
|
|
112
|
+
const parts = [common, reviewerAxisMethodAdapter(x.axis)];
|
|
113
|
+
// Spec evidence-child only — never Standards or a parent replacement Spec leg.
|
|
114
|
+
if (x.axis === "spec" && authorityRefs.length > 0) {
|
|
115
|
+
parts.push(reviewerAuthorityRefsMaterial(authorityRefs));
|
|
116
|
+
}
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
axis: x.axis,
|
|
119
|
+
prompt: `${parts.join("\n")}\n`,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
84
122
|
return Object.freeze({
|
|
85
123
|
identity: input.identity,
|
|
86
124
|
recipe: "reviewer-common-bundle-v1",
|
|
@@ -90,6 +128,8 @@ export function constructReviewerDispatch(input) {
|
|
|
90
128
|
}),
|
|
91
129
|
targetSnapshot: input.target,
|
|
92
130
|
range: input.range,
|
|
131
|
+
authorityRefs,
|
|
132
|
+
specDisposition,
|
|
93
133
|
legs: Object.freeze(legs),
|
|
94
134
|
});
|
|
95
135
|
}
|
|
@@ -3,10 +3,67 @@ import { immutableReviewerPin } from "./reviewer-pinned-git.js";
|
|
|
3
3
|
export { createReviewerPinnedGitReader, immutableReviewerPin } from "./reviewer-pinned-git.js";
|
|
4
4
|
import { isReviewerPromptText, sameReviewerPromptText } from "./reviewer-prompt-identity.js";
|
|
5
5
|
import { sha256Hex } from "./sha256.js";
|
|
6
|
-
import { constructReviewerDispatch } from "./reviewer-construction.js";
|
|
6
|
+
import { constructReviewerDispatch, } from "./reviewer-construction.js";
|
|
7
|
+
export {} from "./reviewer-construction.js";
|
|
7
8
|
import { ReviewerCorrectablePreflightError } from "./reviewer-preflight-error.js";
|
|
8
9
|
export { sha256Hex } from "./sha256.js";
|
|
9
10
|
export { isReviewerPromptText as isReviewerPromptIdentity, sameReviewerPromptText as sameReviewerPromptIdentity } from "./reviewer-prompt-identity.js";
|
|
11
|
+
const GENERIC_FEATURE_TOKENS = new Set(["", "head", "main", "master", "trunk", "develop", "development"]);
|
|
12
|
+
/** Conventional branch shells that must not hide the feature token (feat/login → login). */
|
|
13
|
+
const BRANCH_SHELL_PREFIX = /^(?:feat|feature|fix|bugfix|hotfix|chore|docs|refactor)-/;
|
|
14
|
+
function normalizeFeatureToken(value) {
|
|
15
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16
|
+
}
|
|
17
|
+
/** Expand one branch/ref name into matchable tokens, stripping conventional shells. */
|
|
18
|
+
function expandFeatureTokens(raw) {
|
|
19
|
+
const normalized = normalizeFeatureToken(raw);
|
|
20
|
+
if (normalized.length === 0)
|
|
21
|
+
return Object.freeze([]);
|
|
22
|
+
const tokens = new Set([normalized]);
|
|
23
|
+
const stripped = normalized.replace(BRANCH_SHELL_PREFIX, "");
|
|
24
|
+
if (stripped.length > 0 && stripped !== normalized)
|
|
25
|
+
tokens.add(stripped);
|
|
26
|
+
return Object.freeze([...tokens]);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Unique production owner of code-review Skill step 2 Spec discovery.
|
|
30
|
+
* Directly yields durable refs Spec child can read, or confirmed missing.
|
|
31
|
+
* - Supplied authorityRefs ⇒ available with those refs as material.
|
|
32
|
+
* - Matching pinned-target docs/specs/.scratch paths ⇒ available with those paths as material.
|
|
33
|
+
* - Commit message bare #N without durable source ⇒ missing (not available).
|
|
34
|
+
* Only confirmed absence yields missing; other Git/I-O failures keep true cause for preflight.
|
|
35
|
+
* Construction builds Standards/Spec solely from this product.
|
|
36
|
+
*/
|
|
37
|
+
export async function discoverReviewerSpecAuthority(input) {
|
|
38
|
+
if (input.authorityRefs.length > 0) {
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
status: "available",
|
|
41
|
+
refs: Object.freeze([...input.authorityRefs]),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const featureTokens = await input.reader.featureTokens();
|
|
45
|
+
const tokens = [
|
|
46
|
+
...new Set(featureTokens
|
|
47
|
+
.flatMap((raw) => expandFeatureTokens(raw))
|
|
48
|
+
.filter((token) => token.length >= 3 && !GENERIC_FEATURE_TOKENS.has(token))),
|
|
49
|
+
];
|
|
50
|
+
if (tokens.length === 0) {
|
|
51
|
+
return Object.freeze({ status: "missing" });
|
|
52
|
+
}
|
|
53
|
+
// Pinned target tree only — Spec child cannot read live-worktree or gitignored paths.
|
|
54
|
+
const candidates = await input.reader.listSpecCandidatePaths();
|
|
55
|
+
const matched = candidates.filter((relativePath) => {
|
|
56
|
+
const normalizedPath = normalizeFeatureToken(relativePath);
|
|
57
|
+
return tokens.some((token) => normalizedPath.includes(token));
|
|
58
|
+
});
|
|
59
|
+
if (matched.length === 0) {
|
|
60
|
+
return Object.freeze({ status: "missing" });
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
status: "available",
|
|
64
|
+
refs: Object.freeze(matched),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
10
67
|
export const REVIEWER_PREFLIGHT_VIOLATIONS = ["base-invalid", "range-invalid", "prompt-identity-invalid", "target-drift"];
|
|
11
68
|
export class ReviewerPreflightError extends Error {
|
|
12
69
|
code;
|
|
@@ -49,12 +106,18 @@ export function createReviewerDispatcher(d) {
|
|
|
49
106
|
try {
|
|
50
107
|
const base = await d.reader.resolve(baseRevision);
|
|
51
108
|
const range = await d.reader.range(base);
|
|
109
|
+
const authorityRefs = Object.freeze([...(d.authorityRefs ?? [])]);
|
|
110
|
+
const specAuthority = await discoverReviewerSpecAuthority({
|
|
111
|
+
authorityRefs,
|
|
112
|
+
reader: d.reader,
|
|
113
|
+
});
|
|
52
114
|
dispatch = constructReviewerDispatch({
|
|
53
115
|
identity,
|
|
54
116
|
canonicalSkill: d.canonicalSkill,
|
|
55
117
|
target,
|
|
56
118
|
range,
|
|
57
119
|
...(d.reviewScopeKeys === undefined ? {} : { reviewScopeKeys: d.reviewScopeKeys }),
|
|
120
|
+
specAuthority,
|
|
58
121
|
});
|
|
59
122
|
if (!sameReviewerPinnedTarget(await d.reader.snapshot(), target)) {
|
|
60
123
|
throw new ReviewerPreflightError("target-drift", "pinned target snapshot changed before child execution");
|
|
@@ -5,7 +5,8 @@ export function projectAcceptedDispatch(dispatch) {
|
|
|
5
5
|
return {
|
|
6
6
|
source: "reviewer-dispatch", type: "accepted", identity: dispatch.identity,
|
|
7
7
|
recipe: dispatch.recipe, input: dispatch.input, target: dispatch.targetSnapshot,
|
|
8
|
-
range: dispatch.range,
|
|
8
|
+
range: dispatch.range, authorityRefs: dispatch.authorityRefs,
|
|
9
|
+
specDisposition: dispatch.specDisposition, legs: dispatch.legs,
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
export function projectReviewerDispatchOutcome(ledger, dispatch, result) {
|
|
@@ -142,5 +142,38 @@ export async function createReviewerPinnedGitReader(root = process.cwd()) {
|
|
|
142
142
|
invalid("range-invalid", "review range must contain a non-empty diff between base and pinned target");
|
|
143
143
|
return Object.freeze({ base: mergeBase, target: targetHead, diffCommand, diffSha256: sha256Hex(Uint8Array.from(diff)), commits: Object.freeze(commitsText ? commitsText.split("\n") : []) });
|
|
144
144
|
},
|
|
145
|
+
async featureTokens() {
|
|
146
|
+
// Pinned ref snapshot is the target-tree fact — no live branch/symbolic-ref walk,
|
|
147
|
+
// no catch-to-empty. Detached/remote-only tips surface via refs/remotes/* entries.
|
|
148
|
+
const names = new Set();
|
|
149
|
+
for (const [refName, entry] of Object.entries(pin.refs)) {
|
|
150
|
+
if (entry.peeledCommitId !== targetHead)
|
|
151
|
+
continue;
|
|
152
|
+
const short = refName.startsWith("refs/heads/")
|
|
153
|
+
? refName.slice("refs/heads/".length)
|
|
154
|
+
: refName.startsWith("refs/tags/")
|
|
155
|
+
? refName.slice("refs/tags/".length)
|
|
156
|
+
: refName.startsWith("refs/remotes/")
|
|
157
|
+
? refName.slice("refs/remotes/".length).replace(/^[^/]+\//, "")
|
|
158
|
+
: refName;
|
|
159
|
+
if (short.trim() !== "")
|
|
160
|
+
names.add(short.trim());
|
|
161
|
+
}
|
|
162
|
+
return Object.freeze([...names]);
|
|
163
|
+
},
|
|
164
|
+
async listSpecCandidatePaths() {
|
|
165
|
+
const roots = ["docs", "specs", ".scratch"];
|
|
166
|
+
// git ls-tree exits 0 with empty stdout when none of the roots exist at targetHead.
|
|
167
|
+
// Other Git/I-O failures keep their true cause for the dispatch preflight path.
|
|
168
|
+
const text = await gitText(repositoryRoot, [
|
|
169
|
+
"ls-tree",
|
|
170
|
+
"-r",
|
|
171
|
+
"--name-only",
|
|
172
|
+
targetHead,
|
|
173
|
+
"--",
|
|
174
|
+
...roots,
|
|
175
|
+
]);
|
|
176
|
+
return Object.freeze(text === "" ? [] : text.split("\n").filter((line) => line.length > 0));
|
|
177
|
+
},
|
|
145
178
|
});
|
|
146
179
|
}
|
package/package.json
CHANGED
|
@@ -30,15 +30,26 @@ import {
|
|
|
30
30
|
type AuditorParentAttemptBinding,
|
|
31
31
|
} from "./compliance-transport.ts";
|
|
32
32
|
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.ts";
|
|
33
|
+
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
34
|
+
import { REVIEWER_VERIFICATION_BOUNDARY } from "./reviewer-construction.ts";
|
|
33
35
|
import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
|
|
34
36
|
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.ts";
|
|
35
|
-
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
36
37
|
import {
|
|
37
38
|
hasUpstreamErrorTestimony,
|
|
38
39
|
isNonSuccessHttpStatus,
|
|
39
40
|
projectConfirmedRemotePayload,
|
|
40
41
|
} from "./upstream-error-testimony.ts";
|
|
41
42
|
|
|
43
|
+
/** Package-owned system prompt for Reviewer Standards/Spec evidence children (private carrier). */
|
|
44
|
+
function buildEvidenceChildSystemPrompt(): string {
|
|
45
|
+
return [
|
|
46
|
+
"Work only in the supplied workspace.",
|
|
47
|
+
"Use the available evidence tools to investigate. Do not commit, push, or mutate remotes.",
|
|
48
|
+
REVIEWER_VERIFICATION_BOUNDARY,
|
|
49
|
+
"Return one substantive non-blank report.",
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
// ── shared constants / types ──────────────────────────────────────────────
|
|
43
54
|
|
|
44
55
|
export const AUDITOR_TURN_LIMIT = 32;
|
|
@@ -561,11 +572,7 @@ export async function executeEvidenceChild(
|
|
|
561
572
|
model: inherited.model,
|
|
562
573
|
thinkingLevel: context.thinkingLevel ?? "off",
|
|
563
574
|
modelRuntime: inherited.runtime,
|
|
564
|
-
systemPrompt:
|
|
565
|
-
"Work only in the supplied workspace.",
|
|
566
|
-
"Use the available evidence tools to investigate. Do not commit, push, or mutate remotes.",
|
|
567
|
-
"Return one substantive non-blank report.",
|
|
568
|
-
].join("\n"),
|
|
575
|
+
systemPrompt: buildEvidenceChildSystemPrompt(),
|
|
569
576
|
sessionManager: createRecordSession({
|
|
570
577
|
cwd: workspace,
|
|
571
578
|
kind: "evidence-children",
|
|
@@ -26,11 +26,15 @@ export type RuntimeReviewerAcceptedBatch = Readonly<{
|
|
|
26
26
|
identity: string;
|
|
27
27
|
legs: readonly Readonly<{ axis: "standards" | "spec"; prompt: ReviewerReceiptPrompt }>[];
|
|
28
28
|
}>;
|
|
29
|
+
/** Honest Spec-child disposition on the receipt face. */
|
|
30
|
+
export type RuntimeReviewerSpecDisposition = "launched" | "skipped-missing";
|
|
29
31
|
export type RuntimeReviewerReceiptV2 = Readonly<{
|
|
30
32
|
version: 2;
|
|
31
33
|
status: "completed" | "refused";
|
|
32
34
|
diagnostic?: string;
|
|
33
35
|
acceptedBatch?: RuntimeReviewerAcceptedBatch;
|
|
36
|
+
/** Present on accepted batches: launched Spec child, or skipped after confirmed missing Spec. */
|
|
37
|
+
specDisposition?: RuntimeReviewerSpecDisposition;
|
|
34
38
|
reports: Readonly<Partial<Record<"standards" | "spec", VerbatimChildReport>>>;
|
|
35
39
|
outcomes: Readonly<Partial<Record<"standards" | "spec", RuntimeReviewerOutcome>>>;
|
|
36
40
|
identities: Readonly<{
|
|
@@ -100,6 +104,21 @@ export function validateRuntimeReviewerReceipt(output: unknown): RuntimeReviewer
|
|
|
100
104
|
throw new Error("Successful Reviewer outcome lacks report");
|
|
101
105
|
if (status === "failed" && report !== undefined) throw new Error("Failed Reviewer outcome cannot bind a report");
|
|
102
106
|
}
|
|
107
|
+
|
|
108
|
+
// One read inside the existing accepted-leg consistency cycle — not a second validator.
|
|
109
|
+
// launched ⇔ Standards+Spec; skipped-missing ⇔ Standards only.
|
|
110
|
+
const specDisposition = read(output, "specDisposition");
|
|
111
|
+
if (specDisposition === "launched") {
|
|
112
|
+
if (expectedAxes.length !== 2 || expectedAxes[0] !== "standards" || expectedAxes[1] !== "spec") {
|
|
113
|
+
throw new Error("Reviewer specDisposition launched requires Standards+Spec accepted legs");
|
|
114
|
+
}
|
|
115
|
+
} else if (specDisposition === "skipped-missing") {
|
|
116
|
+
if (expectedAxes.length !== 1 || expectedAxes[0] !== "standards") {
|
|
117
|
+
throw new Error("Reviewer specDisposition skipped-missing requires Standards-only accepted legs");
|
|
118
|
+
}
|
|
119
|
+
} else if (specDisposition !== undefined) {
|
|
120
|
+
throw new Error("Invalid Reviewer specDisposition");
|
|
121
|
+
}
|
|
103
122
|
}
|
|
104
123
|
return output as RuntimeReviewerReceiptV2;
|
|
105
124
|
}
|