@akagilnc/pi-workflow-roles 0.1.1918 → 0.1.2004

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.
Files changed (42) hide show
  1. package/dist/evidence-child-executor.js +12 -6
  2. package/dist/package-contracts/reviewer-output.js +16 -0
  3. package/dist/public-cli/main.js +2520 -72
  4. package/dist/reviewer-construction.js +46 -6
  5. package/dist/reviewer-dispatch.js +64 -1
  6. package/dist/reviewer-execution-ledger.js +2 -1
  7. package/dist/reviewer-pinned-git.js +33 -0
  8. package/package.json +1 -1
  9. package/src/atomic-write.ts +26 -0
  10. package/src/evidence-child-executor.ts +13 -6
  11. package/src/ledger-session-read.ts +260 -0
  12. package/src/package-contracts/reviewer-output.ts +19 -0
  13. package/src/public-cli/cli.ts +24 -0
  14. package/src/public-cli/invocation.ts +398 -2
  15. package/src/public-cli/main.ts +21 -2
  16. package/src/public-cli/registry.ts +18 -1
  17. package/src/public-cli/reviewer-run.ts +12 -0
  18. package/src/public-cli/run-lifecycle.ts +22 -1
  19. package/src/public-cli/settlement.ts +17 -0
  20. package/src/public-cli/taishi-run.ts +235 -0
  21. package/src/reviewer-construction.ts +64 -7
  22. package/src/reviewer-dispatch.ts +81 -2
  23. package/src/reviewer-execution-ledger.ts +2 -1
  24. package/src/reviewer-pinned-git.ts +43 -0
  25. package/src/reviewer-role.ts +143 -132
  26. package/src/reviewer-settlement.ts +4 -0
  27. package/src/role-runtime.ts +186 -3
  28. package/src/run-terminal-artifacts.ts +231 -0
  29. package/src/taishi-cohort.ts +232 -0
  30. package/src/taishi-entry.ts +429 -0
  31. package/src/taishi-index.ts +269 -0
  32. package/src/taishi-ledger.ts +466 -0
  33. package/src/taishi-median.ts +15 -0
  34. package/src/taishi-metric-families/acceptance-success-rework.ts +346 -0
  35. package/src/taishi-metric-families/b2-frame-buckets-actions.ts +274 -0
  36. package/src/taishi-metric-families/leg-wall-clock.ts +90 -0
  37. package/src/taishi-metric-families/round-timeline.ts +201 -0
  38. package/src/taishi-metric-families.ts +36 -0
  39. package/src/taishi-metric-family.ts +41 -0
  40. package/src/taishi-model-groups.ts +198 -0
  41. package/src/taishi-page.ts +320 -0
  42. package/src/ticket-trajectory.ts +9 -62
@@ -3,10 +3,12 @@ import { Type } from "typebox";
3
3
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
4
4
 
5
5
  import type { AnyCanonicalSkillBinding, CanonicalSkillBinding } from "./canonical-skill-binding.ts";
6
+ export type { CanonicalSkillBinding };
6
7
  import { disposeComplianceDecision } from "./audit-escalation.ts";
7
8
  import type { ComplianceDecision } from "./compliance-transport.ts";
8
9
  import { appendActiveSessionCustomEntry } from "./compliance-transport.ts";
9
10
  import { REVIEWER_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
11
+ import { type ReviewerSpecDisposition } from "./reviewer-construction.ts";
10
12
  import { createReviewerDispatcher, type AcceptedReviewerDispatch, type AcceptedReviewerExecution, type ReviewerPinnedGitReader } from "./reviewer-dispatch.ts";
11
13
  import { ReviewerDispatchExecutionError, type ReviewerDispatchRunResult } from "./reviewer-agent.ts";
12
14
  import { createReviewerExecutionLedger, projectAcceptedDispatch, projectReviewerDispatchOutcome, type ReviewerExecutionRecord } from "./reviewer-execution-ledger.ts";
@@ -17,6 +19,13 @@ export { REVIEWER_OUTPUT_TOOL_NAME };
17
19
  export type { ReviewerIntent };
18
20
  export const AGENT_TOOL_NAME = "Agent";
19
21
 
22
+ /** Frozen admitted inputs the behavior layer may consume — no flag surface. */
23
+ export type ReviewerAdmittedInputs = Readonly<{
24
+ baseRevision: string;
25
+ reviewScopeKeys?: readonly string[];
26
+ authorityRefs?: readonly string[];
27
+ }>;
28
+
20
29
  const reviewerOutputVariants = Type.Union([
21
30
  Type.Object({ status: Type.Literal("completed", { description: "Reviewer dispatch completed." }) }, { additionalProperties: false }),
22
31
  Type.Object({ status: Type.Literal("refused", { description: "Reviewer dispatch was lawfully refused." }), diagnostic: Type.String({ minLength: 1, description: "Diagnostic explaining the refusal." }) }, { additionalProperties: false }),
@@ -42,157 +51,159 @@ function requireSoleReviewerOutputCall(id: string, ctx: ExtensionContext): void
42
51
  export type ReviewerActivation = Readonly<{
43
52
  dispatcher: ReturnType<typeof createReviewerDispatcher>;
44
53
  fixedBaseRevision: string;
54
+ soul: string;
55
+ /** Frozen code-review binding — envelope owns expansion capture against this data. */
56
+ skillBinding: CanonicalSkillBinding<"code-review">;
57
+ /** Honest Spec disposition after accepted dispatch, for envelope parent prompt assembly. */
58
+ getSpecDisposition(): ReviewerSpecDisposition | undefined;
45
59
  }>;
46
60
 
47
- export function createReviewerRoleRuntime(pi: ExtensionAPI, dependencies: ReviewerRoleDependencies, hostActions: ReviewerRoleHostActions): { activate(ctx?: ExtensionContext): Promise<ReviewerActivation> } {
61
+ /**
62
+ * Reviewer behavior runtime: label, soul, evidence tools, decision tool, projection.
63
+ * No flag registration/decoding and no agent_start prompt lifecycle (ADR 0018 / envelope).
64
+ */
65
+ export function createReviewerRoleRuntime(
66
+ pi: ExtensionAPI,
67
+ dependencies: ReviewerRoleDependencies,
68
+ hostActions: ReviewerRoleHostActions,
69
+ ): {
70
+ activate(ctx: ExtensionContext | undefined, admitted: ReviewerAdmittedInputs): Promise<ReviewerActivation>;
71
+ } {
48
72
  let soul: string | undefined;
49
73
  let binding: CanonicalSkillBinding<"code-review"> | undefined;
50
74
  let reader: ReviewerPinnedGitReader | undefined;
51
75
  let dispatcher: ReturnType<typeof createReviewerDispatcher> | undefined;
52
- let originalRequest: string | undefined;
53
- let expansionCaptured = false;
54
76
  let registered = false;
55
- let reviewScopeKeys: readonly string[] | undefined;
56
77
  let fixedBaseRevision: string | undefined;
78
+ let acceptedDispatch: AcceptedReviewerDispatch | undefined;
57
79
  const ledger = createReviewerExecutionLedger();
58
- pi.registerFlag("ak-review-base", { description: "Fixed base revision for the pinned review target", type: "string" });
59
- pi.registerFlag("ak-review-scope-keys", { description: "Optional comma-separated exact class keys limiting Reviewer scope", type: "string" });
60
80
 
61
- return { async activate(ctx) {
62
- soul = (await dependencies.loadSoul()).trim();
63
- if (!soul) throw new Error("Reviewer soul is empty");
64
- const rawScopeKeys = pi.getFlag("ak-review-scope-keys");
65
- reviewScopeKeys = undefined;
66
- if (rawScopeKeys !== undefined) {
67
- if (typeof rawScopeKeys !== "string" || rawScopeKeys.length === 0) {
68
- throw new Error("Reviewer scope keys must be a nonempty comma-separated string");
69
- }
70
- const parsed = rawScopeKeys.split(",");
71
- if (parsed.some((key) => key.trim().length === 0) || new Set(parsed).size !== parsed.length) {
72
- throw new Error("Reviewer scope keys contain a blank or exact duplicate key");
73
- }
74
- reviewScopeKeys = parsed;
75
- }
76
- const baseRevision = pi.getFlag("ak-review-base");
77
- if (typeof baseRevision !== "string" || !baseRevision.trim()) throw new Error("Reviewer role requires --ak-review-base");
78
- fixedBaseRevision = baseRevision;
79
- const loaded = await dependencies.loadCanonicalSkillBinding("code-review");
80
- if (loaded.name !== "code-review") throw new Error("Canonical Skill binding loader returned tdd for code-review");
81
- binding = loaded;
82
- reader = await dependencies.createPinnedGitReader();
81
+ return {
82
+ async activate(_ctx, admitted) {
83
+ soul = (await dependencies.loadSoul()).trim();
84
+ if (!soul) throw new Error("Reviewer soul is empty");
85
+ // Behavior layer receives frozen admitted inputs only — no pi.getFlag.
86
+ fixedBaseRevision = admitted.baseRevision;
87
+ const reviewScopeKeys = admitted.reviewScopeKeys;
88
+ const authorityRefs = admitted.authorityRefs;
89
+ const loaded = await dependencies.loadCanonicalSkillBinding("code-review");
90
+ if (loaded.name !== "code-review") throw new Error("Canonical Skill binding loader returned tdd for code-review");
91
+ binding = loaded;
92
+ reader = await dependencies.createPinnedGitReader();
83
93
 
84
- let acceptedDispatch: AcceptedReviewerDispatch | undefined;
85
- const executeAndProjectDispatch = async (execution: AcceptedReviewerExecution, invocation: unknown): Promise<ReviewerDispatchRunResult> => {
86
- const dispatch = acceptedDispatch;
87
- if (dispatch === undefined || dispatch.identity !== execution.identity) throw new Error("Reviewer execution lacks accepted construction evidence");
88
- const { context, signal } = invocation as { context: ExtensionContext; signal?: AbortSignal };
89
- ledger.append({ source: "reviewer-agent", type: "dispatch-started", dispatchIdentity: execution.identity, cardinality: execution.legs.length as 1 | 2 });
90
- try {
91
- const result = await dependencies.runDispatch(execution, { context, ...(signal === undefined ? {} : { signal }) });
92
- projectReviewerDispatchOutcome(ledger, dispatch, result);
93
- return result;
94
- } catch (error) {
95
- if (error instanceof ReviewerDispatchExecutionError) {
96
- try { projectReviewerDispatchOutcome(ledger, dispatch, error.outcome); }
97
- catch (mismatch) { throw ledger.recordInfrastructureFailure(mismatch); }
98
- throw error;
99
- }
100
- throw ledger.recordInfrastructureFailure(error);
101
- }
102
- };
103
- dispatcher = createReviewerDispatcher({
104
- canonicalSkill: binding.snapshot.raw,
105
- reader,
106
- ...(reviewScopeKeys === undefined ? {} : { reviewScopeKeys }),
107
- decisionEvidence(decision) {
94
+ acceptedDispatch = undefined;
95
+ const executeAndProjectDispatch = async (execution: AcceptedReviewerExecution, invocation: unknown): Promise<ReviewerDispatchRunResult> => {
96
+ const dispatch = acceptedDispatch;
97
+ if (dispatch === undefined || dispatch.identity !== execution.identity) throw new Error("Reviewer execution lacks accepted construction evidence");
98
+ const { context, signal } = invocation as { context: ExtensionContext; signal?: AbortSignal };
99
+ ledger.append({ source: "reviewer-agent", type: "dispatch-started", dispatchIdentity: execution.identity, cardinality: execution.legs.length as 1 | 2 });
108
100
  try {
109
- if (decision.disposition === "accepted") {
110
- ledger.append(projectAcceptedDispatch(decision.dispatch));
111
- acceptedDispatch = decision.dispatch;
112
- } else ledger.append({ source: "reviewer-dispatch", type: "rejected", identity: decision.identity, violations: decision.violations, started: false });
101
+ const result = await dependencies.runDispatch(execution, { context, ...(signal === undefined ? {} : { signal }) });
102
+ projectReviewerDispatchOutcome(ledger, dispatch, result);
103
+ return result;
113
104
  } catch (error) {
105
+ if (error instanceof ReviewerDispatchExecutionError) {
106
+ try { projectReviewerDispatchOutcome(ledger, dispatch, error.outcome); }
107
+ catch (mismatch) { throw ledger.recordInfrastructureFailure(mismatch); }
108
+ throw error;
109
+ }
114
110
  throw ledger.recordInfrastructureFailure(error);
115
111
  }
116
- },
117
- run: executeAndProjectDispatch,
118
- });
119
-
120
- if (!registered) {
121
- registered = true;
122
- pi.registerTool({ name: REVIEWER_OUTPUT_TOOL_NAME, label: "Reviewer Output", description: "Submit the thin Reviewer receipt after semantic compliance audit.", promptSnippet: "Submit the final Reviewer receipt", promptGuidelines: [`Use ${REVIEWER_OUTPUT_TOOL_NAME} as the sole final action.`,
123
- "This runtime executes the Standards and Spec review legs for you as package-managed evidence-child sessions — that IS this runtime's implementation of the review Skill's parallel sub-agents. Do not refuse because no Agent tool appears in your tool list, and do not substitute your own sub-processes; work with the legs the runtime provides. The same rule applies to corrections and redos after an auditor bounce-back: complete them in this session with your own tools. Evidence-leg model and thinking tier follow the seat's active order; the reviewer seat does not choose them."], parameters: reviewerOutputSchema,
124
- async execute(id, parameters, signal, _update, toolCtx): Promise<AgentToolResult<unknown>> {
125
- if (!soul || !binding) throw new Error("Reviewer inputs were not loaded");
126
- requireSoleReviewerOutputCall(id, toolCtx);
127
- const output = validateReviewerIntent(parameters);
128
- if (output.status === "completed" && !expansionCaptured) throw new Error("Reviewer completed requires canonical Skill expansion capture");
129
- let record: ReviewerExecutionRecord;
130
- try { record = ledger.recordForAudit(output.status); } catch (error) { if ((error as any)?.fatalReviewerInfrastructure) hostActions.failInfrastructure(error, toolCtx, id); throw error; }
131
- const candidate = assembleRuntimeReviewerReceipt({
132
- intent: output,
133
- record,
134
- canonicalSkillText: binding.snapshot.raw,
135
- });
136
- // First-record-then-audit: candidate lands on the parent session books
137
- // before the auditor is spawned (zero hand-delivery).
112
+ };
113
+ dispatcher = createReviewerDispatcher({
114
+ canonicalSkill: binding.snapshot.raw,
115
+ reader,
116
+ ...(reviewScopeKeys === undefined ? {} : { reviewScopeKeys }),
117
+ ...(authorityRefs === undefined ? {} : { authorityRefs }),
118
+ decisionEvidence(decision) {
138
119
  try {
139
- appendActiveSessionCustomEntry(
140
- toolCtx,
141
- REVIEWER_CANDIDATE_ENTRY_TYPE,
142
- { version: 1, candidate },
143
- {
144
- unavailable: "reviewer candidate retention is unavailable",
145
- failed: "reviewer candidate retention failed",
146
- },
147
- );
120
+ if (decision.disposition === "accepted") {
121
+ ledger.append(projectAcceptedDispatch(decision.dispatch));
122
+ acceptedDispatch = decision.dispatch;
123
+ } else ledger.append({ source: "reviewer-dispatch", type: "rejected", identity: decision.identity, violations: decision.violations, started: false });
148
124
  } catch (error) {
149
- hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id);
125
+ throw ledger.recordInfrastructureFailure(error);
150
126
  }
151
- let audit: ComplianceDecision;
152
- try {
153
- audit = await dependencies.auditCompliance({
154
- context: toolCtx,
155
- ...(signal === undefined ? {} : { signal }),
127
+ },
128
+ run: executeAndProjectDispatch,
129
+ });
130
+
131
+ if (!registered) {
132
+ registered = true;
133
+ pi.registerTool({ name: REVIEWER_OUTPUT_TOOL_NAME, label: "Reviewer Output", description: "Submit the thin Reviewer receipt after semantic compliance audit.", promptSnippet: "Submit the final Reviewer receipt", promptGuidelines: [`Use ${REVIEWER_OUTPUT_TOOL_NAME} as the sole final action.`,
134
+ "This runtime executes the Standards and Spec review legs for you as package-managed evidence-child sessions — that IS this runtime's implementation of the review Skill's parallel sub-agents. Do not refuse because no Agent tool appears in your tool list, and do not substitute your own sub-processes; work with the legs the runtime provides. The same rule applies to corrections and redos after an auditor bounce-back: complete them in this session with your own tools. Evidence-leg model and thinking tier follow the seat's active order; the reviewer seat does not choose them."], parameters: reviewerOutputSchema,
135
+ async execute(id, parameters, signal, _update, toolCtx): Promise<AgentToolResult<unknown>> {
136
+ if (!soul || !binding) throw new Error("Reviewer inputs were not loaded");
137
+ requireSoleReviewerOutputCall(id, toolCtx);
138
+ const output = validateReviewerIntent(parameters);
139
+ let record: ReviewerExecutionRecord;
140
+ try { record = ledger.recordForAudit(output.status); } catch (error) { if ((error as any)?.fatalReviewerInfrastructure) hostActions.failInfrastructure(error, toolCtx, id); throw error; }
141
+ const candidate = assembleRuntimeReviewerReceipt({
142
+ intent: output,
143
+ record,
144
+ canonicalSkillText: binding.snapshot.raw,
156
145
  });
157
- }
158
- catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
159
- return disposeComplianceDecision<AgentToolResult<unknown>>(
160
- audit,
161
- {
162
- pass: async (usage) => {
163
- try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
164
- return { content: [{ type: "text" as const, text: "Reviewer report accepted" }], details: candidate, terminate: true as const, ...(usage === undefined ? {} : { usage }) };
165
- },
166
- noReceipt: async (auditNoReceipt, usageProjection) => {
167
- try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
168
- return { content: [{ type: "text" as const, text: "Reviewer report accepted; compliance audit produced no receipt" }], details: { ...candidate, auditNoReceipt }, terminate: true as const, ...usageProjection };
169
- },
170
- revise: (violations) => {
171
- throw new AggregateError([], `Reviewer receipt rejected:\n${violations.join("\n")}`, { cause: Object.freeze([...violations]) });
172
- },
173
- escalate: async (result) => {
174
- try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
175
- return result;
146
+ // First-record-then-audit: candidate lands on the parent session books
147
+ // before the auditor is spawned (zero hand-delivery).
148
+ try {
149
+ appendActiveSessionCustomEntry(
150
+ toolCtx,
151
+ REVIEWER_CANDIDATE_ENTRY_TYPE,
152
+ { version: 1, candidate },
153
+ {
154
+ unavailable: "reviewer candidate retention is unavailable",
155
+ failed: "reviewer candidate retention failed",
156
+ },
157
+ );
158
+ } catch (error) {
159
+ hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id);
160
+ }
161
+ let audit: ComplianceDecision;
162
+ try {
163
+ audit = await dependencies.auditCompliance({
164
+ context: toolCtx,
165
+ ...(signal === undefined ? {} : { signal }),
166
+ });
167
+ }
168
+ catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
169
+ return disposeComplianceDecision<AgentToolResult<unknown>>(
170
+ audit,
171
+ {
172
+ pass: async (usage) => {
173
+ try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
174
+ return { content: [{ type: "text" as const, text: "Reviewer report accepted" }], details: candidate, terminate: true as const, ...(usage === undefined ? {} : { usage }) };
175
+ },
176
+ noReceipt: async (auditNoReceipt, usageProjection) => {
177
+ try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
178
+ return { content: [{ type: "text" as const, text: "Reviewer report accepted; compliance audit produced no receipt" }], details: { ...candidate, auditNoReceipt }, terminate: true as const, ...usageProjection };
179
+ },
180
+ revise: (violations) => {
181
+ throw new AggregateError([], `Reviewer receipt rejected:\n${violations.join("\n")}`, { cause: Object.freeze([...violations]) });
182
+ },
183
+ escalate: async (result) => {
184
+ try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
185
+ return result;
186
+ },
187
+ auditIncomplete: (result) => result,
176
188
  },
177
- auditIncomplete: (result) => result,
178
- },
179
- candidate,
180
- );
181
- } });
182
- pi.on("input", (event) => { if (originalRequest !== undefined) return { action: "continue" as const }; originalRequest = event.text; return { action: "transform" as const, text: binding!.invocation(event.text), ...(event.images === undefined ? {} : { images: event.images }) }; });
183
- pi.on("before_agent_start", async (event, toolCtx) => {
184
- if (!expansionCaptured) {
185
- if (originalRequest === undefined || binding!.captureExpansion(event.prompt, originalRequest) === undefined) {
186
- const error = ledger.recordInfrastructureFailure(new Error("Canonical code-review Skill expansion did not match the captured request"));
187
- hostActions.failInfrastructure(error, toolCtx);
188
- }
189
- expansionCaptured = true;
190
- }
191
- // Soul only — caller instruction is never injected as semantic control.
192
- return { systemPrompt: `${event.systemPrompt}\n\n<reviewer_soul>\n${soul}\n</reviewer_soul>` };
189
+ candidate,
190
+ );
191
+ } });
192
+ // Skill invocation transform + agent_start expansion/prompt lifecycle: shared envelope (ADR 0018).
193
+ pi.on("session_shutdown", async () => { try { await dependencies.shutdownAgent?.(); } catch (error) { throw ledger.recordInfrastructureFailure(error); } });
194
+ }
195
+ const activatedSoul = soul;
196
+ const activatedBase = fixedBaseRevision;
197
+ const activatedBinding = binding;
198
+ return Object.freeze({
199
+ dispatcher,
200
+ fixedBaseRevision: activatedBase,
201
+ soul: activatedSoul,
202
+ skillBinding: activatedBinding,
203
+ getSpecDisposition() {
204
+ return acceptedDispatch?.specDisposition;
205
+ },
193
206
  });
194
- pi.on("session_shutdown", async () => { try { await dependencies.shutdownAgent?.(); } catch (error) { throw ledger.recordInfrastructureFailure(error); } });
195
- }
196
- return Object.freeze({ dispatcher, fixedBaseRevision });
197
- } };
207
+ },
208
+ };
198
209
  }
@@ -52,6 +52,10 @@ export function assembleRuntimeReviewerReceipt(input: {
52
52
  identity: accepted.identity,
53
53
  legs: accepted.legs.map(({ axis, prompt }) => ({ axis, prompt: receiptPrompt(prompt) })),
54
54
  },
55
+ // Honest Spec skipped/missing notation when independent discovery confirmed absence.
56
+ ...(accepted.specDisposition === undefined
57
+ ? {}
58
+ : { specDisposition: accepted.specDisposition }),
55
59
  }),
56
60
  reports,
57
61
  outcomes,
@@ -59,9 +59,125 @@ import {
59
59
  import {
60
60
  createReviewerRoleRuntime,
61
61
  type ReviewerActivation,
62
+ type ReviewerAdmittedInputs,
62
63
  } from "./reviewer-role.ts";
63
64
  import type { AcceptedReviewerExecution, ReviewerPinnedGitReader } from "./reviewer-dispatch.ts";
64
65
  import type { ReviewerDispatchRunResult } from "./reviewer-agent.ts";
66
+ import {
67
+ REVIEWER_VERIFICATION_BOUNDARY,
68
+ type ReviewerSpecDisposition,
69
+ } from "./reviewer-construction.ts";
70
+
71
+ /**
72
+ * Private transport flag names/definitions for Reviewer admitted inputs.
73
+ * Shared activation envelope owns registration and decoding (ADR 0018).
74
+ */
75
+ const REVIEWER_TRANSPORT_FLAGS = Object.freeze([
76
+ Object.freeze({
77
+ name: "ak-review-base",
78
+ definition: Object.freeze({
79
+ description: "Fixed base revision for the pinned review target",
80
+ type: "string" as const,
81
+ }),
82
+ }),
83
+ Object.freeze({
84
+ name: "ak-review-scope-keys",
85
+ definition: Object.freeze({
86
+ description: "Optional comma-separated exact class keys limiting Reviewer scope",
87
+ type: "string" as const,
88
+ }),
89
+ }),
90
+ Object.freeze({
91
+ name: "ak-review-authority-refs",
92
+ definition: Object.freeze({
93
+ description: "JSON array of durable authority references for Spec evidence-child material only",
94
+ type: "string" as const,
95
+ }),
96
+ }),
97
+ ] as const);
98
+
99
+ /**
100
+ * Decode private transport flags into frozen admitted inputs.
101
+ * Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
102
+ */
103
+ function decodeReviewerAdmittedInputs(getFlag: (name: string) => unknown): ReviewerAdmittedInputs {
104
+ let reviewScopeKeys: readonly string[] | undefined;
105
+ const rawScopeKeys = getFlag("ak-review-scope-keys");
106
+ if (rawScopeKeys !== undefined) {
107
+ if (typeof rawScopeKeys !== "string" || rawScopeKeys.length === 0) {
108
+ throw new Error("Reviewer scope keys must be a nonempty comma-separated string");
109
+ }
110
+ const parsed = rawScopeKeys.split(",");
111
+ if (parsed.some((key) => key.trim().length === 0) || new Set(parsed).size !== parsed.length) {
112
+ throw new Error("Reviewer scope keys contain a blank or exact duplicate key");
113
+ }
114
+ reviewScopeKeys = Object.freeze(parsed);
115
+ }
116
+
117
+ let authorityRefs: readonly string[] | undefined;
118
+ const rawAuthorityRefs = getFlag("ak-review-authority-refs");
119
+ if (rawAuthorityRefs !== undefined) {
120
+ if (typeof rawAuthorityRefs !== "string") {
121
+ throw new Error("Reviewer authority refs transport error: flag value must be a string");
122
+ }
123
+ let parsed: unknown;
124
+ try {
125
+ parsed = JSON.parse(rawAuthorityRefs);
126
+ } catch (error) {
127
+ throw new Error(
128
+ `Reviewer authority refs transport error: JSON decode failed: ${error instanceof Error ? error.message : String(error)}`,
129
+ );
130
+ }
131
+ if (!Array.isArray(parsed) || parsed.some((ref) => typeof ref !== "string")) {
132
+ throw new Error("Reviewer authority refs transport error: expected a JSON array of strings");
133
+ }
134
+ authorityRefs = Object.freeze(parsed as string[]);
135
+ }
136
+
137
+ const baseRevision = getFlag("ak-review-base");
138
+ if (typeof baseRevision !== "string" || !baseRevision.trim()) {
139
+ throw new Error("Reviewer role requires --ak-review-base");
140
+ }
141
+ return Object.freeze({
142
+ baseRevision,
143
+ ...(reviewScopeKeys === undefined ? {} : { reviewScopeKeys }),
144
+ ...(authorityRefs === undefined ? {} : { authorityRefs }),
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Parent system-prompt assembly for the shared activation envelope.
150
+ * References REVIEWER_VERIFICATION_BOUNDARY as the single text true source (no copy).
151
+ */
152
+ function assembleReviewerParentSystemPrompt(input: {
153
+ baseSystemPrompt: string;
154
+ soul: string;
155
+ specDisposition?: ReviewerSpecDisposition;
156
+ }): string {
157
+ const specDispositionNote =
158
+ input.specDisposition === "skipped-missing"
159
+ ? [
160
+ "",
161
+ "<reviewer_spec_disposition>",
162
+ "Spec-Disposition: skipped-missing",
163
+ "Independent discovery confirmed authoritative Spec is absent.",
164
+ "No Spec evidence-child was launched. Note Spec skipped/missing honestly in the final report; do not invent requirements.",
165
+ "</reviewer_spec_disposition>",
166
+ ]
167
+ : [];
168
+ return [
169
+ input.baseSystemPrompt,
170
+ "",
171
+ "<reviewer_soul>",
172
+ input.soul,
173
+ "</reviewer_soul>",
174
+ "",
175
+ "<reviewer_verification_boundary>",
176
+ REVIEWER_VERIFICATION_BOUNDARY,
177
+ "</reviewer_verification_boundary>",
178
+ ...specDispositionNote,
179
+ ].join("\n");
180
+ }
65
181
  import {
66
182
  CODER_OUTPUT_TOOL_NAME,
67
183
  createCoderRoleRuntime,
@@ -214,7 +330,16 @@ type ActivationRuntime = {
214
330
  judge: { activate(): Promise<void> };
215
331
  fixer: WorkerArmable;
216
332
  coder: WorkerArmable;
217
- reviewer: { activate(context: ExtensionContext): Promise<ReviewerActivation> };
333
+ reviewer: {
334
+ activate(
335
+ context: ExtensionContext,
336
+ admitted: ReviewerAdmittedInputs,
337
+ ): Promise<ReviewerActivation>;
338
+ };
339
+ /** Envelope decodes Reviewer transport flags inside the activation stage. */
340
+ decodeReviewerAdmitted(): ReviewerAdmittedInputs;
341
+ /** Envelope stores live parent activation for agent_start prompt assembly. */
342
+ bindReviewerParent(activation: ReviewerActivation): void;
218
343
  collector: { activate(context: ExtensionContext, event: { reason: string }): Promise<void> };
219
344
  doctor: { activate(): Promise<void> };
220
345
  merger(): Promise<void>;
@@ -226,7 +351,10 @@ function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id:
226
351
  case "fixer": return { id: "load-and-install", run: async () => runtime.fixer.activate() };
227
352
  case "coder": return { id: "load-and-install", run: async () => runtime.coder.activate(runtime.context) };
228
353
  case "reviewer": return { id: "load-install-and-dispatch", run: async () => {
229
- const activation = await runtime.reviewer.activate(runtime.context);
354
+ // Envelope owns flag decode inside the activation stage (trace + fail-closed path).
355
+ const admitted = runtime.decodeReviewerAdmitted();
356
+ const activation = await runtime.reviewer.activate(runtime.context, admitted);
357
+ runtime.bindReviewerParent(activation);
230
358
  const result = await activation.dispatcher.dispatch(activation.fixedBaseRevision, { context: runtime.context });
231
359
  if (result.status !== "accepted") {
232
360
  const rejection = new ExplicitInternalActivationError(
@@ -419,9 +547,18 @@ export function createRoleRuntimeExtension(
419
547
  // #102: one shared package-owned tool registration surface for all role runtimes.
420
548
  installPackageOwnedToolRegistration(pi);
421
549
  pi.registerFlag(ROLE_FLAG.name, ROLE_FLAG.definition);
550
+ // Reviewer transport flags: shared envelope owns registration (ADR 0018).
551
+ for (const flag of REVIEWER_TRANSPORT_FLAGS) {
552
+ pi.registerFlag(flag.name, flag.definition);
553
+ }
422
554
 
423
555
  let admitted = false;
424
556
  let selectedRole: string | undefined;
557
+ /** Live Reviewer parent activation for envelope agent_start prompt assembly. */
558
+ let activeReviewerParent: ReviewerActivation | undefined;
559
+ /** Envelope-owned Reviewer Skill expansion state (ADR 0018 — not a role-module facade). */
560
+ let reviewerOriginalRequest: string | undefined;
561
+ let reviewerExpansionCaptured = false;
425
562
  let navigatorAttendance: NavigatorAttendanceDependency | undefined;
426
563
  let pendingNavigatorPresentation: { event: import("./navigator-attendance.ts").NavigatorEvent; report: import("./navigator-attendance.ts").NavigatorReport } | undefined;
427
564
  let pendingNavigatorSettlement: Promise<void> | undefined;
@@ -431,9 +568,23 @@ export function createRoleRuntimeExtension(
431
568
  // terminating-tool rejections and mechanical delivery requests share two turns.
432
569
  let receiptDelivery = createReceiptDeliveryPolicy();
433
570
  let noReceiptRecorded = false;
434
- pi.on("input", () => {
571
+ pi.on("input", (event) => {
435
572
  const role = pi.getFlag(ROLE_FLAG.name);
436
573
  if (role !== undefined && !admitted) return { action: "handled" as const };
574
+ // Envelope exclusively owns Reviewer Skill invocation transform + original-request capture.
575
+ if (
576
+ role === "reviewer"
577
+ && admitted
578
+ && activeReviewerParent !== undefined
579
+ && reviewerOriginalRequest === undefined
580
+ ) {
581
+ reviewerOriginalRequest = event.text;
582
+ return {
583
+ action: "transform" as const,
584
+ text: activeReviewerParent.skillBinding.invocation(event.text),
585
+ ...(event.images === undefined ? {} : { images: event.images }),
586
+ };
587
+ }
437
588
  return { action: "continue" as const };
438
589
  });
439
590
  pi.on("before_agent_start", (event, ctx) => {
@@ -469,6 +620,29 @@ export function createRoleRuntimeExtension(
469
620
  }
470
621
  }
471
622
  navigatorAttendance?.prepare();
623
+ // Envelope-owned Reviewer expansion capture + parent prompt assembly (no role-module callback).
624
+ if (role === "reviewer" && activeReviewerParent !== undefined) {
625
+ if (!reviewerExpansionCaptured) {
626
+ if (
627
+ reviewerOriginalRequest === undefined
628
+ || activeReviewerParent.skillBinding.captureExpansion(event.prompt, reviewerOriginalRequest) === undefined
629
+ ) {
630
+ failInfrastructure(
631
+ new Error("Canonical code-review Skill expansion did not match the captured request"),
632
+ ctx,
633
+ );
634
+ }
635
+ reviewerExpansionCaptured = true;
636
+ }
637
+ const specDisposition = activeReviewerParent.getSpecDisposition();
638
+ return {
639
+ systemPrompt: assembleReviewerParentSystemPrompt({
640
+ baseSystemPrompt: event.systemPrompt,
641
+ soul: activeReviewerParent.soul,
642
+ ...(specDisposition === undefined ? {} : { specDisposition }),
643
+ }),
644
+ };
645
+ }
472
646
  });
473
647
  pi.on("tool_result", async (event) => {
474
648
  const role = selectedRole;
@@ -839,6 +1013,9 @@ export function createRoleRuntimeExtension(
839
1013
  pi.on("session_start", async (event, ctx) => {
840
1014
  admitted = false;
841
1015
  selectedRole = undefined;
1016
+ activeReviewerParent = undefined;
1017
+ reviewerOriginalRequest = undefined;
1018
+ reviewerExpansionCaptured = false;
842
1019
  receiptDelivery = createReceiptDeliveryPolicy();
843
1020
  noReceiptRecorded = false;
844
1021
  observationFace.reset();
@@ -862,6 +1039,12 @@ export function createRoleRuntimeExtension(
862
1039
  fixer,
863
1040
  coder,
864
1041
  reviewer,
1042
+ decodeReviewerAdmitted() {
1043
+ return decodeReviewerAdmittedInputs((name) => pi.getFlag(name));
1044
+ },
1045
+ bindReviewerParent(activation) {
1046
+ activeReviewerParent = activation;
1047
+ },
865
1048
  collector,
866
1049
  doctor,
867
1050
  merger: async () => {