@akagilnc/pi-workflow-roles 0.1.1893 → 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 +447 -173
- 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/factory-board.ts +60 -3
- package/src/package-contracts/reviewer-output.ts +19 -0
- package/src/public-cli/cli.ts +96 -39
- package/src/public-cli/coder-run.ts +11 -4
- package/src/public-cli/collector-run.ts +7 -3
- package/src/public-cli/fixer-run.ts +11 -4
- package/src/public-cli/invocation.ts +183 -102
- package/src/public-cli/judge-run.ts +11 -4
- package/src/public-cli/merger-run.ts +11 -4
- package/src/public-cli/reviewer-run.ts +23 -4
- package/src/public-cli/run-lifecycle.ts +97 -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
- package/src/ticket-frontmatter.ts +94 -0
- package/src/ticket-trajectory.ts +145 -5
|
@@ -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",
|
package/src/factory-board.ts
CHANGED
|
@@ -28,14 +28,19 @@ import {
|
|
|
28
28
|
} from "./human-format.ts";
|
|
29
29
|
import {
|
|
30
30
|
DEFAULT_REFRESH_BOUNDARY_SECONDS,
|
|
31
|
+
buildTicketTrajectoryBookIndex,
|
|
31
32
|
loadTicketTrajectoryRuns,
|
|
33
|
+
loadUnboundTrajectoryRuns,
|
|
32
34
|
renderTicketTrajectoryStationHtml,
|
|
35
|
+
type TicketTrajectoryBookIndex,
|
|
33
36
|
type TicketTrajectoryRun,
|
|
34
37
|
type TrajectoryClock,
|
|
35
38
|
type TrajectoryScheduler,
|
|
39
|
+
type UnboundTrajectoryRun,
|
|
36
40
|
} from "./ticket-trajectory.ts";
|
|
37
41
|
import type { BoardSnapshot, SnapshotTicket, TicketIssueState } from "./ticket-snapshot.ts";
|
|
38
42
|
|
|
43
|
+
|
|
39
44
|
/** Unaccepted latest-run mtime bands (page-visible thresholds). */
|
|
40
45
|
export const UNACCEPTED_FLYING_MS = 2 * 60 * 1000;
|
|
41
46
|
export const UNACCEPTED_WATCH_MS = 15 * 60 * 1000;
|
|
@@ -403,6 +408,40 @@ function latestKnownStation(runs: readonly TicketTrajectoryRun[]): string | unde
|
|
|
403
408
|
return latest.station;
|
|
404
409
|
}
|
|
405
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Unbound activation card for the existing unknown-set presentation seam.
|
|
413
|
+
* Not a ticket: no issue number, never placed in a yamen column.
|
|
414
|
+
*/
|
|
415
|
+
function renderUnboundRunCard(input: {
|
|
416
|
+
bookKey: string;
|
|
417
|
+
unbound: UnboundTrajectoryRun;
|
|
418
|
+
}): string {
|
|
419
|
+
const { run, role, observedAt } = input.unbound;
|
|
420
|
+
const correlationId = input.unbound.correlationId;
|
|
421
|
+
const stationLabel =
|
|
422
|
+
run.station === "unknown" ? "未知站" : (YAMEN_LABELS[run.station] ?? run.station);
|
|
423
|
+
const corrAttr = correlationId === undefined ? "" : ` data-correlation="${attr(correlationId)}"`;
|
|
424
|
+
const corrMeta =
|
|
425
|
+
correlationId === undefined ? "" : ` · corr ${escapeHtml(correlationId)}`;
|
|
426
|
+
return [
|
|
427
|
+
`<article class="ticket unbound-run"`,
|
|
428
|
+
` data-unbound-run="true"`,
|
|
429
|
+
` data-book="${attr(input.bookKey)}"`,
|
|
430
|
+
` data-run-id="${attr(run.runId)}"`,
|
|
431
|
+
corrAttr,
|
|
432
|
+
` data-placement="unknown"`,
|
|
433
|
+
`>`,
|
|
434
|
+
`<header class="ticket-header">`,
|
|
435
|
+
`<span class="ticket-title">未绑定 · ${escapeHtml(run.runId)}</span>`,
|
|
436
|
+
`<span class="yamen-tag yamen-unknown" data-yamen="unknown">${escapeHtml(stationLabel)}</span>`,
|
|
437
|
+
`</header>`,
|
|
438
|
+
`<p class="unbound-meta" data-unbound-meta="true">`,
|
|
439
|
+
`role ${escapeHtml(role)}${corrMeta} · ${escapeHtml(observedAt)}`,
|
|
440
|
+
`</p>`,
|
|
441
|
+
`</article>`,
|
|
442
|
+
].join("");
|
|
443
|
+
}
|
|
444
|
+
|
|
406
445
|
function renderTicketArticle(input: {
|
|
407
446
|
bookKey: string;
|
|
408
447
|
prepared: PreparedTicket;
|
|
@@ -681,8 +720,9 @@ async function prepareTicket(
|
|
|
681
720
|
ledgerDir: string,
|
|
682
721
|
ticket: SnapshotTicket,
|
|
683
722
|
now: Date,
|
|
723
|
+
bookIndex?: TicketTrajectoryBookIndex,
|
|
684
724
|
): Promise<PreparedTicket> {
|
|
685
|
-
const runs = await loadTicketTrajectoryRuns(ledgerDir, ticket.issueNumber);
|
|
725
|
+
const runs = await loadTicketTrajectoryRuns(ledgerDir, ticket.issueNumber, bookIndex);
|
|
686
726
|
const pending = ticket.state !== "closed" && runs.length === 0;
|
|
687
727
|
const currentState = decideTicketCurrentState({
|
|
688
728
|
ticketState: ticket.state,
|
|
@@ -862,9 +902,11 @@ async function renderLaneHtml(
|
|
|
862
902
|
now: Date,
|
|
863
903
|
options?: { hidden?: boolean },
|
|
864
904
|
): Promise<RenderedLane> {
|
|
905
|
+
// One book-level flat-run index shared by every ticket in this lane.
|
|
906
|
+
const bookIndex = await buildTicketTrajectoryBookIndex(ledgerDir);
|
|
865
907
|
const prepared = new Map<number, PreparedTicket>();
|
|
866
908
|
for (const ticket of tickets) {
|
|
867
|
-
prepared.set(ticket.issueNumber, await prepareTicket(ledgerDir, ticket, now));
|
|
909
|
+
prepared.set(ticket.issueNumber, await prepareTicket(ledgerDir, ticket, now, bookIndex));
|
|
868
910
|
}
|
|
869
911
|
|
|
870
912
|
const childrenByParent = buildDirectChildrenByParent(prepared);
|
|
@@ -980,6 +1022,20 @@ async function renderLaneHtml(
|
|
|
980
1022
|
list.push(entry);
|
|
981
1023
|
byPlacement.set(entry.placement, list);
|
|
982
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
// Unbound flat runs: isolated from every ticket, honest on the unknown seam.
|
|
1027
|
+
const unboundRuns = await loadUnboundTrajectoryRuns(ledgerDir, bookIndex);
|
|
1028
|
+
for (const unbound of unboundRuns) {
|
|
1029
|
+
unknownEntries.push({
|
|
1030
|
+
placement: "unknown",
|
|
1031
|
+
band: 0,
|
|
1032
|
+
activityMs: unbound.run.mtimeMs,
|
|
1033
|
+
// Sort after real tickets; no issue identity (unbound is not a ticket).
|
|
1034
|
+
issueNumber: Number.MAX_SAFE_INTEGER,
|
|
1035
|
+
html: renderUnboundRunCard({ bookKey, unbound }),
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
|
|
983
1039
|
unknownEntries.sort(entryCompare);
|
|
984
1040
|
unknownTickets.sort((a, b) => a.issueNumber - b.issueNumber);
|
|
985
1041
|
|
|
@@ -1469,7 +1525,8 @@ export async function renderFactoryBoardHtml(
|
|
|
1469
1525
|
generatedAt,
|
|
1470
1526
|
);
|
|
1471
1527
|
}
|
|
1472
|
-
|
|
1528
|
+
let lane: RenderedLane;
|
|
1529
|
+
lane = await renderLaneHtml(
|
|
1473
1530
|
book.bookKey,
|
|
1474
1531
|
resolve(book.ledgerDir),
|
|
1475
1532
|
bookSnap.tickets,
|
|
@@ -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
|
}
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -61,6 +61,69 @@ export {
|
|
|
61
61
|
export { CliUsageError } from "./cli-errors.ts";
|
|
62
62
|
export type { CliIo } from "./cli-io.ts";
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Sole production map: public role command → argv parser.
|
|
66
|
+
* cli handlers consume this table; no parallel set.
|
|
67
|
+
*/
|
|
68
|
+
export const PUBLIC_ROLE_ARGV = {
|
|
69
|
+
judge: { parse: parseJudgeArgv },
|
|
70
|
+
coder: { parse: parseCoderArgv },
|
|
71
|
+
fixer: { parse: parseFixerArgv },
|
|
72
|
+
collector: { parse: parseCollectorArgv },
|
|
73
|
+
doctor: { parse: parseDoctorArgv },
|
|
74
|
+
merger: { parse: parseMergerArgv },
|
|
75
|
+
reviewer: { parse: parseReviewerArgv },
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
type TakenPublicGlobalFlag =
|
|
79
|
+
| { flag: "help"; consume: 1 }
|
|
80
|
+
| { flag: "model"; consume: 1 | 2; value: string | undefined }
|
|
81
|
+
| { flag: "thinking"; consume: 1 | 2; raw: string | undefined };
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* If `argv[index]` is a public global flag, describe its span and payload.
|
|
85
|
+
* Sole global-flag grammar for parseArgv.
|
|
86
|
+
*/
|
|
87
|
+
function takePublicGlobalFlag(
|
|
88
|
+
argv: readonly string[],
|
|
89
|
+
index: number,
|
|
90
|
+
): TakenPublicGlobalFlag | undefined {
|
|
91
|
+
const token = argv[index];
|
|
92
|
+
if (token === undefined) return undefined;
|
|
93
|
+
if (token === "--help" || token === "-h") {
|
|
94
|
+
return { flag: "help", consume: 1 };
|
|
95
|
+
}
|
|
96
|
+
if (token === "--model") {
|
|
97
|
+
const value = argv[index + 1];
|
|
98
|
+
if (value === undefined) {
|
|
99
|
+
return { flag: "model", consume: 1, value: undefined };
|
|
100
|
+
}
|
|
101
|
+
return { flag: "model", consume: 2, value };
|
|
102
|
+
}
|
|
103
|
+
if (token.startsWith("--model=")) {
|
|
104
|
+
return {
|
|
105
|
+
flag: "model",
|
|
106
|
+
consume: 1,
|
|
107
|
+
value: token.slice("--model=".length),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (token === "--thinking") {
|
|
111
|
+
const raw = argv[index + 1];
|
|
112
|
+
if (raw === undefined) {
|
|
113
|
+
return { flag: "thinking", consume: 1, raw: undefined };
|
|
114
|
+
}
|
|
115
|
+
return { flag: "thinking", consume: 2, raw };
|
|
116
|
+
}
|
|
117
|
+
if (token.startsWith("--thinking=")) {
|
|
118
|
+
return {
|
|
119
|
+
flag: "thinking",
|
|
120
|
+
consume: 1,
|
|
121
|
+
raw: token.slice("--thinking=".length),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
64
127
|
export type CliEnv = {
|
|
65
128
|
home?: string;
|
|
66
129
|
agentDir?: string;
|
|
@@ -167,44 +230,38 @@ function parseArgv(argv: readonly string[]): ParsedGlobal {
|
|
|
167
230
|
|
|
168
231
|
// Global flags may appear before or after the subcommand
|
|
169
232
|
// (`ak-role --model x roles` and `ak-role roles --model x`).
|
|
233
|
+
// Grammar authority: takePublicGlobalFlag above.
|
|
170
234
|
while (args.length > 0) {
|
|
171
|
-
|
|
172
|
-
|
|
235
|
+
if (args[0] === "--") {
|
|
236
|
+
args.shift();
|
|
173
237
|
positional.push(...args);
|
|
174
238
|
break;
|
|
175
239
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
model
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (token.startsWith("--thinking=")) {
|
|
197
|
-
thinking = parseThinking(token.slice("--thinking=".length));
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
if (token.startsWith("-") && token !== "-") {
|
|
201
|
-
// Subcommands may own additional flags later; only reject unknowns at the
|
|
202
|
-
// top level when they appear before any positional command is collected
|
|
203
|
-
// and the command is one that does not accept free flags (enforced below).
|
|
204
|
-
positional.push(token);
|
|
240
|
+
const taken = takePublicGlobalFlag(args, 0);
|
|
241
|
+
if (taken !== undefined) {
|
|
242
|
+
if (taken.flag === "help") {
|
|
243
|
+
help = true;
|
|
244
|
+
args.splice(0, taken.consume);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (taken.flag === "model") {
|
|
248
|
+
if (taken.value === undefined) {
|
|
249
|
+
throw new CliUsageError("--model requires a value");
|
|
250
|
+
}
|
|
251
|
+
model = taken.value;
|
|
252
|
+
args.splice(0, taken.consume);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (taken.raw === undefined) {
|
|
256
|
+
throw new CliUsageError("--thinking requires a value");
|
|
257
|
+
}
|
|
258
|
+
thinking = parseThinking(taken.raw);
|
|
259
|
+
args.splice(0, taken.consume);
|
|
205
260
|
continue;
|
|
206
261
|
}
|
|
207
|
-
|
|
262
|
+
// Subcommands may own additional flags later; unknown dashed tokens stay
|
|
263
|
+
// positional here (same as pre-unification parseArgv).
|
|
264
|
+
positional.push(args.shift()!);
|
|
208
265
|
}
|
|
209
266
|
|
|
210
267
|
const [command, ...rest] = positional;
|
|
@@ -626,7 +683,7 @@ export async function runAkRole(
|
|
|
626
683
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
627
684
|
},
|
|
628
685
|
io,
|
|
629
|
-
|
|
686
|
+
PUBLIC_ROLE_ARGV.judge.parse,
|
|
630
687
|
);
|
|
631
688
|
return {
|
|
632
689
|
exitCode: result.exitCode,
|
|
@@ -669,7 +726,7 @@ export async function runAkRole(
|
|
|
669
726
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
670
727
|
},
|
|
671
728
|
io,
|
|
672
|
-
|
|
729
|
+
PUBLIC_ROLE_ARGV.coder.parse,
|
|
673
730
|
);
|
|
674
731
|
return {
|
|
675
732
|
exitCode: result.exitCode,
|
|
@@ -712,7 +769,7 @@ export async function runAkRole(
|
|
|
712
769
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
713
770
|
},
|
|
714
771
|
io,
|
|
715
|
-
|
|
772
|
+
PUBLIC_ROLE_ARGV.fixer.parse,
|
|
716
773
|
);
|
|
717
774
|
return {
|
|
718
775
|
exitCode: result.exitCode,
|
|
@@ -755,7 +812,7 @@ export async function runAkRole(
|
|
|
755
812
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
756
813
|
},
|
|
757
814
|
io,
|
|
758
|
-
|
|
815
|
+
PUBLIC_ROLE_ARGV.collector.parse,
|
|
759
816
|
);
|
|
760
817
|
return {
|
|
761
818
|
exitCode: result.exitCode,
|
|
@@ -798,7 +855,7 @@ export async function runAkRole(
|
|
|
798
855
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
799
856
|
},
|
|
800
857
|
io,
|
|
801
|
-
|
|
858
|
+
PUBLIC_ROLE_ARGV.reviewer.parse,
|
|
802
859
|
);
|
|
803
860
|
return {
|
|
804
861
|
exitCode: result.exitCode,
|
|
@@ -841,7 +898,7 @@ export async function runAkRole(
|
|
|
841
898
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
842
899
|
},
|
|
843
900
|
io,
|
|
844
|
-
|
|
901
|
+
PUBLIC_ROLE_ARGV.doctor.parse,
|
|
845
902
|
);
|
|
846
903
|
return {
|
|
847
904
|
exitCode: result.exitCode,
|
|
@@ -884,7 +941,7 @@ export async function runAkRole(
|
|
|
884
941
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
885
942
|
},
|
|
886
943
|
io,
|
|
887
|
-
|
|
944
|
+
PUBLIC_ROLE_ARGV.merger.parse,
|
|
888
945
|
);
|
|
889
946
|
return {
|
|
890
947
|
exitCode: result.exitCode,
|
|
@@ -313,8 +313,9 @@ async function dispatchAdmittedCoder(input: {
|
|
|
313
313
|
PI_CODING_AGENT_DIR: env.agentDir,
|
|
314
314
|
AK_ROLE_RUN_DIR: admitted.runDirectory,
|
|
315
315
|
};
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
const correlationId = admitted.correlationId ?? env.correlationId;
|
|
317
|
+
if (correlationId !== undefined && correlationId.trim() !== "") {
|
|
318
|
+
childEnv.AK_CORRELATION_ID = correlationId;
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
let result: ExplicitInternalPiResult;
|
|
@@ -487,7 +488,10 @@ export async function runPublicCoder(
|
|
|
487
488
|
|
|
488
489
|
return await dispatchAdmittedCoder({
|
|
489
490
|
admitted,
|
|
490
|
-
env
|
|
491
|
+
env: {
|
|
492
|
+
...env,
|
|
493
|
+
...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
|
|
494
|
+
},
|
|
491
495
|
io,
|
|
492
496
|
extraArgs,
|
|
493
497
|
lease,
|
|
@@ -581,7 +585,10 @@ export async function runPublicCoderResume(
|
|
|
581
585
|
|
|
582
586
|
return await dispatchAdmittedCoder({
|
|
583
587
|
admitted,
|
|
584
|
-
env
|
|
588
|
+
env: {
|
|
589
|
+
...env,
|
|
590
|
+
...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
|
|
591
|
+
},
|
|
585
592
|
io,
|
|
586
593
|
extraArgs,
|
|
587
594
|
lease,
|