@akagilnc/pi-workflow-roles 0.1.1941 → 0.1.2014
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/public-cli/main.js +2470 -88
- package/dist/reviewer-construction.js +33 -2
- package/dist/reviewer-dispatch.js +142 -9
- package/dist/reviewer-execution-ledger.js +5 -1
- package/dist/reviewer-pinned-git.js +139 -10
- package/extensions/role-runtime.ts +2 -1
- package/package.json +1 -1
- package/src/atomic-write.ts +26 -0
- package/src/collector-github.ts +94 -0
- package/src/ledger-session-read.ts +260 -0
- package/src/package-contracts/reviewer-output.ts +2 -0
- package/src/public-cli/cli.ts +24 -0
- package/src/public-cli/invocation.ts +352 -1
- package/src/public-cli/main.ts +21 -2
- package/src/public-cli/registry.ts +18 -1
- package/src/public-cli/reviewer-run.ts +13 -0
- package/src/public-cli/settlement.ts +13 -0
- package/src/public-cli/taishi-run.ts +235 -0
- package/src/reviewer-construction.ts +81 -3
- package/src/reviewer-dispatch.ts +189 -9
- package/src/reviewer-execution-ledger.ts +5 -1
- package/src/reviewer-pinned-git.ts +154 -11
- package/src/reviewer-role.ts +8 -1
- package/src/reviewer-settlement.ts +4 -0
- package/src/role-runtime.ts +21 -1
- package/src/run-terminal-artifacts.ts +231 -0
- package/src/taishi-cohort.ts +232 -0
- package/src/taishi-entry.ts +429 -0
- package/src/taishi-index.ts +269 -0
- package/src/taishi-ledger.ts +466 -0
- package/src/taishi-median.ts +15 -0
- package/src/taishi-metric-families/acceptance-success-rework.ts +346 -0
- package/src/taishi-metric-families/b2-frame-buckets-actions.ts +274 -0
- package/src/taishi-metric-families/leg-wall-clock.ts +90 -0
- package/src/taishi-metric-families/round-timeline.ts +201 -0
- package/src/taishi-metric-families.ts +36 -0
- package/src/taishi-metric-family.ts +41 -0
- package/src/taishi-model-groups.ts +198 -0
- package/src/taishi-page.ts +320 -0
- package/src/ticket-trajectory.ts +9 -62
package/src/public-cli/main.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { dirname, join } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
|
|
4
5
|
import { ensureHostPiRuntimeResolvable } from "./host-pi-runtime.ts";
|
|
5
6
|
|
|
6
7
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve install package root from the public bin location.
|
|
11
|
+
*
|
|
12
|
+
* Shipped layout is `<packageRoot>/dist/public-cli/main.js` → two levels up when
|
|
13
|
+
* that ancestor owns package.json. A relocated single-file bundle (no package
|
|
14
|
+
* tree beside the bin) must NOT keep climbing: `join("/tmp/<bin>","..","..")`
|
|
15
|
+
* is `"/"` on Linux CI, and host-pi linking then does
|
|
16
|
+
* `mkdir('/node_modules/@earendil-works')` → EACCES. Fall back to the bin
|
|
17
|
+
* directory so links stay on the ESM ancestor walk and remain writable.
|
|
18
|
+
*/
|
|
19
|
+
function resolvePackageRoot(binDir: string): string {
|
|
20
|
+
const canonical = join(binDir, "..", "..");
|
|
21
|
+
if (existsSync(join(canonical, "package.json"))) {
|
|
22
|
+
return canonical;
|
|
23
|
+
}
|
|
24
|
+
return binDir;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const packageRoot = resolvePackageRoot(here);
|
|
9
28
|
|
|
10
29
|
// The host-provided runtime must be resolvable before the CLI module graph loads it.
|
|
11
30
|
ensureHostPiRuntimeResolvable(packageRoot);
|
|
@@ -97,6 +97,12 @@ export function publicStartupCandidates(
|
|
|
97
97
|
return STARTUP_CANDIDATES[seat];
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/** Deterministic public commands — discoverable, never LLM-configurable seats. */
|
|
101
|
+
export const PUBLIC_DETERMINISTIC_COMMANDS = ["taishi"] as const;
|
|
102
|
+
|
|
103
|
+
export type PublicDeterministicCommand =
|
|
104
|
+
(typeof PUBLIC_DETERMINISTIC_COMMANDS)[number];
|
|
105
|
+
|
|
100
106
|
export type HelpCapability =
|
|
101
107
|
| {
|
|
102
108
|
kind: "support";
|
|
@@ -107,11 +113,16 @@ export type HelpCapability =
|
|
|
107
113
|
name: PublicCallableRole;
|
|
108
114
|
phases: readonly (string | null)[];
|
|
109
115
|
defaultPhase: string | null;
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
kind: "deterministic";
|
|
119
|
+
name: PublicDeterministicCommand;
|
|
110
120
|
};
|
|
111
121
|
|
|
112
122
|
/**
|
|
113
123
|
* Typed help surface. Presentation formats these facts; tests must not assert
|
|
114
124
|
* exact help prose or layout (锚定宪法 / ADR 0016 / #105 AC).
|
|
125
|
+
* taishi is a deterministic analysis command on the public CLI — not an LLM seat.
|
|
115
126
|
*/
|
|
116
127
|
export function listHelpCapabilities(): readonly HelpCapability[] {
|
|
117
128
|
const support: HelpCapability[] = PUBLIC_CLI_SUPPORT_COMMANDS.map((name) => ({
|
|
@@ -133,7 +144,13 @@ export function listHelpCapabilities(): readonly HelpCapability[] {
|
|
|
133
144
|
defaultPhase,
|
|
134
145
|
};
|
|
135
146
|
});
|
|
136
|
-
|
|
147
|
+
const deterministic: HelpCapability[] = PUBLIC_DETERMINISTIC_COMMANDS.map(
|
|
148
|
+
(name) => ({
|
|
149
|
+
kind: "deterministic" as const,
|
|
150
|
+
name,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
return [...support, ...roles, ...deterministic];
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
export function isPublicCallableRole(value: string): value is PublicCallableRole {
|
|
@@ -100,6 +100,15 @@ function buildModelArgs(model: SeatModelConfig | undefined): string[] {
|
|
|
100
100
|
];
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/** Encode admitted ticketNumber as CLI argv for activation/resume (no defaults). */
|
|
104
|
+
function buildReviewerTicketNumberArgs(
|
|
105
|
+
ticketNumber: number | undefined,
|
|
106
|
+
): string[] {
|
|
107
|
+
return ticketNumber === undefined
|
|
108
|
+
? []
|
|
109
|
+
: ["--ak-review-ticket-number", String(ticketNumber)];
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
/**
|
|
104
113
|
* Build Internal activation extra-args for an admitted Reviewer run.
|
|
105
114
|
* Package code-review Skill is forced via --skill; ambient home skills stay off.
|
|
@@ -122,6 +131,7 @@ export function buildReviewerActivationExtraArgs(
|
|
|
122
131
|
admitted.authorityRefs.length === 0
|
|
123
132
|
? []
|
|
124
133
|
: ["--ak-review-authority-refs", JSON.stringify([...admitted.authorityRefs])];
|
|
134
|
+
const ticketNumberArgs = buildReviewerTicketNumberArgs(admitted.ticketNumber);
|
|
125
135
|
return [
|
|
126
136
|
"--no-skills",
|
|
127
137
|
"--skill",
|
|
@@ -139,6 +149,7 @@ export function buildReviewerActivationExtraArgs(
|
|
|
139
149
|
"--ak-review-base",
|
|
140
150
|
admitted.baseRevision,
|
|
141
151
|
...authorityRefArgs,
|
|
152
|
+
...ticketNumberArgs,
|
|
142
153
|
"--mode",
|
|
143
154
|
"json",
|
|
144
155
|
...buildModelArgs(options.model),
|
|
@@ -166,6 +177,7 @@ export function buildReviewerResumeActivationExtraArgs(
|
|
|
166
177
|
admitted.authorityRefs.length === 0
|
|
167
178
|
? []
|
|
168
179
|
: ["--ak-review-authority-refs", JSON.stringify([...admitted.authorityRefs])];
|
|
180
|
+
const ticketNumberArgs = buildReviewerTicketNumberArgs(admitted.ticketNumber);
|
|
169
181
|
return [
|
|
170
182
|
"--no-skills",
|
|
171
183
|
"--skill",
|
|
@@ -183,6 +195,7 @@ export function buildReviewerResumeActivationExtraArgs(
|
|
|
183
195
|
"--ak-review-base",
|
|
184
196
|
admitted.baseRevision,
|
|
185
197
|
...authorityRefArgs,
|
|
198
|
+
...ticketNumberArgs,
|
|
186
199
|
"--mode",
|
|
187
200
|
"json",
|
|
188
201
|
...buildModelArgs(options.model),
|
|
@@ -242,6 +242,15 @@ export function presentStructuralRejection(
|
|
|
242
242
|
io.stderr(formatCliDiagnostic(error.message));
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/** ControlledFailure face without admitted-run Terminal (stdout body + stderr line). */
|
|
246
|
+
export function presentControlledFailure(
|
|
247
|
+
failure: ControlledFailure,
|
|
248
|
+
io: { stdout: (text: string) => void; stderr: (text: string) => void },
|
|
249
|
+
): void {
|
|
250
|
+
io.stdout(`${JSON.stringify(failure, null, 2)}\n`);
|
|
251
|
+
io.stderr(formatFailureStderrDiagnostic(failure));
|
|
252
|
+
}
|
|
253
|
+
|
|
245
254
|
/** Session readiness after an admitted activation attempt. */
|
|
246
255
|
export type SessionReadiness =
|
|
247
256
|
| { readonly state: "missing" }
|
|
@@ -3176,6 +3185,10 @@ export async function publishReviewerArtifacts(
|
|
|
3176
3185
|
...(admitted.instructionEmpty
|
|
3177
3186
|
? {}
|
|
3178
3187
|
: { callerProvenance: admitted.instruction }),
|
|
3188
|
+
// Self-fetch Spec bytes + source annotation when primary path produced material (#343).
|
|
3189
|
+
...(options.reviewerReceipt?.specFetchedMaterial === undefined
|
|
3190
|
+
? {}
|
|
3191
|
+
: { specFetchedMaterial: options.reviewerReceipt.specFetchedMaterial }),
|
|
3179
3192
|
attachments: admitted.attachments.map((a) => ({
|
|
3180
3193
|
provenancePath: a.provenancePath,
|
|
3181
3194
|
frozenPath: a.frozenPath,
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public taishi adapter (#336/#337/#338): argv → typed query → runTaishi family.
|
|
3
|
+
* Deterministic analysis seat — no Pi runner, no admission lease.
|
|
4
|
+
* Reuses existing CLI failure envelope (CliUsageError + structural reject +
|
|
5
|
+
* ControlledFailure).
|
|
6
|
+
* Index read reuses readTaishiLibraryIndexPage / findTaishiLibraryIndexRow.
|
|
7
|
+
* #337 sweep: exactly one typed JSON attachment → TaishiSweepModeInput → #329 kernel.
|
|
8
|
+
* #338: three query faces; sync compute-if-missing; whole-compute failure →
|
|
9
|
+
* ControlledFailure terminal (code/projectRoot/issueNumber/real cause).
|
|
10
|
+
* "Unobtrusive" binds #337 merge auto-trigger only, not this user-initiated query.
|
|
11
|
+
*/
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import { isAbsolute, resolve } from "node:path";
|
|
14
|
+
import { Value } from "typebox/value";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
errnoCode,
|
|
18
|
+
physicalPathIdentity,
|
|
19
|
+
resolveActivationLedgerHome,
|
|
20
|
+
} from "../activation-ledger-topology.ts";
|
|
21
|
+
import { exactUtf8 } from "../exact-utf8.ts";
|
|
22
|
+
import {
|
|
23
|
+
findTaishiLibraryIndexRow,
|
|
24
|
+
readTaishiLibraryIndexPage,
|
|
25
|
+
} from "../taishi-index.ts";
|
|
26
|
+
import {
|
|
27
|
+
readOrComputeTaishiIssuePage,
|
|
28
|
+
runTaishi,
|
|
29
|
+
taishiSweepModeInputSchema,
|
|
30
|
+
TaishiIssueComputeError,
|
|
31
|
+
type TaishiIssueModeInput,
|
|
32
|
+
type TaishiSweepModeInput,
|
|
33
|
+
} from "../taishi-entry.ts";
|
|
34
|
+
import { CliUsageError } from "./cli-errors.ts";
|
|
35
|
+
import type { CliIo } from "./cli-io.ts";
|
|
36
|
+
import type {
|
|
37
|
+
ParseTaishiArgvResult,
|
|
38
|
+
ParseTaishiIssueArgv,
|
|
39
|
+
} from "./invocation.ts";
|
|
40
|
+
import { presentControlledFailure, presentStructuralRejection } from "./settlement.ts";
|
|
41
|
+
|
|
42
|
+
export type TaishiRunEnv = {
|
|
43
|
+
readonly home: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build the sole library issue-mode input from public argv faces.
|
|
48
|
+
* - ticket N → issueNumber = ticketNumber = N; projectRoot from index (or project-root fallback).
|
|
49
|
+
* - project-root P → direct mechanical key.
|
|
50
|
+
* - both + index hit → index projectRoot wins; when direct root differs, retain it as
|
|
51
|
+
* conflictingProjectRoot so runTaishi records the C4 dual-param conflict fact on the page.
|
|
52
|
+
* - both + index miss → project-root fallback.
|
|
53
|
+
* Bare both-missing is owned by parseTaishiArgv — no second reject here.
|
|
54
|
+
*/
|
|
55
|
+
export async function buildTaishiIssueModeInputFromPublicArgv(
|
|
56
|
+
parsed: ParseTaishiIssueArgv,
|
|
57
|
+
ledgerHome: string,
|
|
58
|
+
): Promise<TaishiIssueModeInput> {
|
|
59
|
+
const ticket = parsed.ticket;
|
|
60
|
+
const directRoot = parsed.projectRoot;
|
|
61
|
+
|
|
62
|
+
if (ticket === undefined) {
|
|
63
|
+
return {
|
|
64
|
+
mode: "issue",
|
|
65
|
+
projectRoot: directRoot!,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ticket N = issueNumber (no conversion); also the C4 typed ticket face.
|
|
70
|
+
const index = await readTaishiLibraryIndexPage(ledgerHome);
|
|
71
|
+
const row = findTaishiLibraryIndexRow(index, ticket);
|
|
72
|
+
|
|
73
|
+
let projectRoot: string;
|
|
74
|
+
if (row !== undefined) {
|
|
75
|
+
// Ticket-resolved index projectRoot wins over any concurrent --project-root.
|
|
76
|
+
projectRoot = row.projectRoot;
|
|
77
|
+
} else if (directRoot !== undefined) {
|
|
78
|
+
// Index miss with project-root fallback (ticket faces still set for C4).
|
|
79
|
+
projectRoot = directRoot;
|
|
80
|
+
} else {
|
|
81
|
+
throw new CliUsageError(
|
|
82
|
+
`taishi library index has no row for ticket ${ticket}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Dual-param conflict: index root won, but caller also supplied a distinct --project-root.
|
|
87
|
+
// Carry the losing root so the metrics page records the call-face conflict fact.
|
|
88
|
+
const dualParamConflict =
|
|
89
|
+
row !== undefined
|
|
90
|
+
&& directRoot !== undefined
|
|
91
|
+
&& physicalPathIdentity(directRoot) !== physicalPathIdentity(projectRoot);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
mode: "issue",
|
|
95
|
+
projectRoot,
|
|
96
|
+
ticketNumber: ticket,
|
|
97
|
+
issueNumber: ticket,
|
|
98
|
+
...(dualParamConflict ? { conflictingProjectRoot: directRoot } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Attachment JSON → library TaishiSweepModeInput via the sole schema (#337).
|
|
104
|
+
* No parallel hand shape; rejects missing/extra/wrong-type fields only.
|
|
105
|
+
*/
|
|
106
|
+
export function parseTaishiSweepModeInputFromJsonValue(
|
|
107
|
+
value: unknown,
|
|
108
|
+
): TaishiSweepModeInput {
|
|
109
|
+
if (!Value.Check(taishiSweepModeInputSchema, value)) {
|
|
110
|
+
throw new CliUsageError(
|
|
111
|
+
"taishi sweep attachment must match TaishiSweepModeInput",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Load sweep typed input from exactly one public CLI attachment path.
|
|
119
|
+
* Rejects: wrong cardinality, unreadable path, non-UTF-8, JSON fail, field contract.
|
|
120
|
+
* Zero ledger writes — read-only path resolve + parse.
|
|
121
|
+
*/
|
|
122
|
+
export async function buildTaishiSweepModeInputFromAttachmentPaths(
|
|
123
|
+
attachmentPaths: readonly string[],
|
|
124
|
+
): Promise<TaishiSweepModeInput> {
|
|
125
|
+
if (attachmentPaths.length !== 1) {
|
|
126
|
+
throw new CliUsageError(
|
|
127
|
+
"taishi sweep requires exactly one --attach typed JSON attachment",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sourcePath = attachmentPaths[0]!;
|
|
132
|
+
const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
|
|
133
|
+
|
|
134
|
+
let bytes: Buffer;
|
|
135
|
+
try {
|
|
136
|
+
bytes = await readFile(absolute);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new CliUsageError(
|
|
139
|
+
`taishi sweep attachment is not a readable regular file: ${sourcePath}`,
|
|
140
|
+
{ cause: error },
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let text: string;
|
|
145
|
+
try {
|
|
146
|
+
text = exactUtf8(bytes, "taishi sweep attachment");
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
149
|
+
throw new CliUsageError(detail, { cause: error });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let parsed: unknown;
|
|
153
|
+
try {
|
|
154
|
+
parsed = JSON.parse(text);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new CliUsageError(
|
|
157
|
+
"taishi sweep attachment is not valid JSON",
|
|
158
|
+
{ cause: error },
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return parseTaishiSweepModeInputFromJsonValue(parsed);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Public taishi run path — parse → resolve → query → typed receipt on stdout.
|
|
167
|
+
* Issue (#336/#338 compute-if-missing), sweep (#337), cohort/model-groups (#338).
|
|
168
|
+
*/
|
|
169
|
+
export async function runPublicTaishi(
|
|
170
|
+
argv: readonly string[],
|
|
171
|
+
_env: TaishiRunEnv,
|
|
172
|
+
io: CliIo,
|
|
173
|
+
parseTaishiArgv: (args: readonly string[]) => ParseTaishiArgvResult,
|
|
174
|
+
): Promise<{ exitCode: number }> {
|
|
175
|
+
try {
|
|
176
|
+
const parsed = parseTaishiArgv(argv);
|
|
177
|
+
// Machine home is package-owned (ADR 0048) — same primitive runTaishi uses.
|
|
178
|
+
const ledgerHome = resolveActivationLedgerHome();
|
|
179
|
+
|
|
180
|
+
if (parsed.query === "sweep") {
|
|
181
|
+
const input = await buildTaishiSweepModeInputFromAttachmentPaths(
|
|
182
|
+
parsed.attachmentPaths,
|
|
183
|
+
);
|
|
184
|
+
const result = await runTaishi(input);
|
|
185
|
+
io.stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
186
|
+
return { exitCode: 0 };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (parsed.query === "cohort") {
|
|
190
|
+
const result = await runTaishi({
|
|
191
|
+
mode: "cohort",
|
|
192
|
+
groups: parsed.groups,
|
|
193
|
+
});
|
|
194
|
+
io.stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
195
|
+
return { exitCode: 0 };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (parsed.query === "model-groups") {
|
|
199
|
+
const result = await runTaishi({
|
|
200
|
+
mode: "model-groups",
|
|
201
|
+
projectRoots: parsed.projectRoots,
|
|
202
|
+
});
|
|
203
|
+
io.stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
204
|
+
return { exitCode: 0 };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// issue query — compute-if-missing (#338); sole kernel on miss.
|
|
208
|
+
const input = await buildTaishiIssueModeInputFromPublicArgv(parsed, ledgerHome);
|
|
209
|
+
const result = await readOrComputeTaishiIssuePage(input);
|
|
210
|
+
io.stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
211
|
+
return { exitCode: 0 };
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof CliUsageError) {
|
|
214
|
+
presentStructuralRejection(error, io);
|
|
215
|
+
return { exitCode: 2 };
|
|
216
|
+
}
|
|
217
|
+
if (error instanceof TaishiIssueComputeError) {
|
|
218
|
+
// Existing ControlledFailure: details carry code/projectRoot/issueNumber;
|
|
219
|
+
// identity.code carries distinguishable real cause (errno). No parallel schema.
|
|
220
|
+
const code = errnoCode(error.cause);
|
|
221
|
+
presentControlledFailure({
|
|
222
|
+
cause: "output",
|
|
223
|
+
diagnostic: error.message,
|
|
224
|
+
...(code === undefined ? {} : { identity: { code } }),
|
|
225
|
+
details: {
|
|
226
|
+
code: error.code,
|
|
227
|
+
projectRoot: error.projectRoot,
|
|
228
|
+
...(error.issueNumber === undefined ? {} : { issueNumber: error.issueNumber }),
|
|
229
|
+
},
|
|
230
|
+
}, io);
|
|
231
|
+
return { exitCode: 1 };
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -101,12 +101,49 @@ export function reviewerAxisMethodAdapter(axis: ReviewerAxis): string {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export type ConstructedReviewerLeg = Readonly<{ axis: "standards" | "spec"; prompt: ReviewerPromptText }>;
|
|
104
|
+
|
|
105
|
+
/** Ticket-number provenance for Spec self-fetch (#343). High-priority source wins. */
|
|
106
|
+
export type ReviewerTicketNumberSource =
|
|
107
|
+
| "typed-ticket-number"
|
|
108
|
+
| "branch-token"
|
|
109
|
+
| "commit-message";
|
|
110
|
+
|
|
111
|
+
export type ReviewerTicketNumberCandidate = Readonly<{
|
|
112
|
+
source: ReviewerTicketNumberSource;
|
|
113
|
+
ticketNumber: number;
|
|
114
|
+
}>;
|
|
115
|
+
|
|
116
|
+
/** One docs/adr path referenced by the fetched issue body. */
|
|
117
|
+
export type ReviewerFetchedAdr =
|
|
118
|
+
| Readonly<{ path: string; status: "present"; body: string }>
|
|
119
|
+
| Readonly<{ path: string; status: "missing" }>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Actual Spec bytes pulled on the self-fetch primary path (fetch-then-store).
|
|
123
|
+
* Carried into Spec-child material and retained on the accepted dispatch for audit.
|
|
124
|
+
*/
|
|
125
|
+
export type ReviewerSpecFetchedMaterial = Readonly<{
|
|
126
|
+
issueRef: string;
|
|
127
|
+
owner: string;
|
|
128
|
+
repo: string;
|
|
129
|
+
ticketNumber: number;
|
|
130
|
+
adopted: ReviewerTicketNumberCandidate;
|
|
131
|
+
abandoned: readonly ReviewerTicketNumberCandidate[];
|
|
132
|
+
issueBody: string;
|
|
133
|
+
adrs: readonly ReviewerFetchedAdr[];
|
|
134
|
+
}>;
|
|
135
|
+
|
|
104
136
|
/**
|
|
105
137
|
* Unique discovery product for Skill step 2: durable refs Spec child can read, or confirmed missing.
|
|
138
|
+
* Optional `fetched` is present only when the self-fetch primary path produced issue bytes.
|
|
106
139
|
* Construction builds Standards/Spec solely from this product — no secondary launch decision.
|
|
107
140
|
*/
|
|
108
141
|
export type ReviewerSpecAuthorityDiscovery =
|
|
109
|
-
| Readonly<{
|
|
142
|
+
| Readonly<{
|
|
143
|
+
status: "available";
|
|
144
|
+
refs: readonly string[];
|
|
145
|
+
fetched?: ReviewerSpecFetchedMaterial;
|
|
146
|
+
}>
|
|
110
147
|
| Readonly<{ status: "missing" }>;
|
|
111
148
|
/** Spec-child cardinality decision recorded on the accepted dispatch. */
|
|
112
149
|
export type ReviewerSpecDisposition = "launched" | "skipped-missing";
|
|
@@ -123,6 +160,8 @@ export type ConstructedReviewerDispatch = Readonly<{
|
|
|
123
160
|
authorityRefs: readonly string[];
|
|
124
161
|
/** Honest Spec-child disposition: launched, or skipped after confirmed missing Spec. */
|
|
125
162
|
specDisposition: ReviewerSpecDisposition;
|
|
163
|
+
/** Self-fetch bytes + source annotation when primary path produced material. */
|
|
164
|
+
specFetchedMaterial?: ReviewerSpecFetchedMaterial;
|
|
126
165
|
legs: readonly ConstructedReviewerLeg[];
|
|
127
166
|
}>;
|
|
128
167
|
|
|
@@ -138,6 +177,35 @@ export function reviewerAuthorityRefsMaterial(authorityRefs: readonly string[]):
|
|
|
138
177
|
].join("\n");
|
|
139
178
|
}
|
|
140
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Spec-only material carrier for self-fetched issue bytes + source annotation (#343).
|
|
182
|
+
* Actual issue body and referenced ADR bytes are embedded for audit (fetch-then-store).
|
|
183
|
+
* Single JSON payload keeps external issue/ADR bytes inside structured field values so they
|
|
184
|
+
* cannot forge package framing markers on the same text layer (no plain-text section protocol).
|
|
185
|
+
*/
|
|
186
|
+
export function reviewerFetchedSpecMaterial(fetched: ReviewerSpecFetchedMaterial): string {
|
|
187
|
+
return [
|
|
188
|
+
"Authority-Fetched-Spec:",
|
|
189
|
+
JSON.stringify(
|
|
190
|
+
Object.freeze({
|
|
191
|
+
source: fetched.adopted.source,
|
|
192
|
+
ticketNumber: fetched.ticketNumber,
|
|
193
|
+
issueRef: fetched.issueRef,
|
|
194
|
+
abandoned: Object.freeze([...fetched.abandoned]),
|
|
195
|
+
issueBody: fetched.issueBody,
|
|
196
|
+
adrs: Object.freeze(
|
|
197
|
+
fetched.adrs.map((adr) =>
|
|
198
|
+
adr.status === "present"
|
|
199
|
+
? Object.freeze({ path: adr.path, status: adr.status, body: adr.body })
|
|
200
|
+
: Object.freeze({ path: adr.path, status: adr.status }),
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
}),
|
|
204
|
+
),
|
|
205
|
+
"These are self-fetched Spec grounding materials. Do not invent Spec prose from caller instruction.",
|
|
206
|
+
].join("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
141
209
|
/** Deterministic compiler: fixed target/range plus discovery product in, dispatch text out. */
|
|
142
210
|
export function constructReviewerDispatch(input: {
|
|
143
211
|
identity: string;
|
|
@@ -152,6 +220,10 @@ export function constructReviewerDispatch(input: {
|
|
|
152
220
|
const authorityRefs = Object.freeze(
|
|
153
221
|
input.specAuthority.status === "available" ? [...input.specAuthority.refs] : [],
|
|
154
222
|
);
|
|
223
|
+
const specFetchedMaterial =
|
|
224
|
+
input.specAuthority.status === "available" && input.specAuthority.fetched !== undefined
|
|
225
|
+
? input.specAuthority.fetched
|
|
226
|
+
: undefined;
|
|
155
227
|
const specDisposition: ReviewerSpecDisposition = launchSpec ? "launched" : "skipped-missing";
|
|
156
228
|
const common = [
|
|
157
229
|
`Target: ${input.range.target}`,
|
|
@@ -170,8 +242,13 @@ export function constructReviewerDispatch(input: {
|
|
|
170
242
|
const legs = axes.map((x) => {
|
|
171
243
|
const parts = [common, reviewerAxisMethodAdapter(x.axis)];
|
|
172
244
|
// Spec evidence-child only — never Standards or a parent replacement Spec leg.
|
|
173
|
-
if (x.axis === "spec"
|
|
174
|
-
|
|
245
|
+
if (x.axis === "spec") {
|
|
246
|
+
if (specFetchedMaterial !== undefined) {
|
|
247
|
+
parts.push(reviewerFetchedSpecMaterial(specFetchedMaterial));
|
|
248
|
+
}
|
|
249
|
+
if (authorityRefs.length > 0) {
|
|
250
|
+
parts.push(reviewerAuthorityRefsMaterial(authorityRefs));
|
|
251
|
+
}
|
|
175
252
|
}
|
|
176
253
|
return Object.freeze({
|
|
177
254
|
axis: x.axis,
|
|
@@ -189,6 +266,7 @@ export function constructReviewerDispatch(input: {
|
|
|
189
266
|
range: input.range,
|
|
190
267
|
authorityRefs,
|
|
191
268
|
specDisposition,
|
|
269
|
+
...(specFetchedMaterial === undefined ? {} : { specFetchedMaterial }),
|
|
192
270
|
legs: Object.freeze(legs),
|
|
193
271
|
});
|
|
194
272
|
}
|