@peterxiaoyang/superspec 0.1.4 → 0.1.5

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 (31) hide show
  1. package/adapters/codex/agents/architect.toml +4 -148
  2. package/adapters/codex/agents/code-reviewer.toml +4 -166
  3. package/adapters/codex/agents/critic.toml +5 -106
  4. package/adapters/codex/agents/test-engineer.toml +4 -154
  5. package/adapters/codex/agents/verifier.toml +4 -110
  6. package/dist/src/cli.js +13 -0
  7. package/dist/src/cli_args.d.ts +5 -1
  8. package/dist/src/cli_args.js +121 -12
  9. package/dist/src/gates.d.ts +1 -1
  10. package/dist/src/gates.js +3 -19
  11. package/dist/src/i18n.js +4 -3
  12. package/dist/src/packet_measure.d.ts +43 -0
  13. package/dist/src/packet_measure.js +395 -0
  14. package/dist/src/packet_render.d.ts +4 -0
  15. package/dist/src/packet_render.js +652 -0
  16. package/dist/src/packet_schema.d.ts +55 -0
  17. package/dist/src/packet_schema.js +1 -0
  18. package/dist/src/project_init.js +7 -49
  19. package/dist/src/util.d.ts +10 -2
  20. package/dist/src/util.js +24 -6
  21. package/package.json +2 -2
  22. package/templates/workflow/prompts/architect.md +16 -109
  23. package/templates/workflow/prompts/code-reviewer.md +17 -137
  24. package/templates/workflow/prompts/critic.md +18 -75
  25. package/templates/workflow/prompts/test-engineer.md +16 -126
  26. package/templates/workflow/prompts/verifier.md +17 -80
  27. package/templates/workflow/skills/superspec-apply/SKILL.md +64 -78
  28. package/templates/workflow/skills/superspec-archive/SKILL.md +41 -37
  29. package/templates/workflow/skills/superspec-explore/SKILL.md +63 -77
  30. package/templates/workflow/skills/superspec-propose/SKILL.md +64 -85
  31. package/templates/workflow/skills/superspec-review/SKILL.md +76 -233
@@ -0,0 +1,652 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { load_config, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
4
+ import {} from "./cli_args.js";
5
+ import { CLAIM_ADJUDICATION_DECISIONS, FINDING_ADJUDICATION_DECISIONS, GuardError, MAIN_ADJUDICATION_REQUIRED_FIELDS, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, REVIEW_EVIDENCE_REQUIRED_FIELDS, ROLE_EVIDENCE_FIELDS, SOURCE_GUIDANCE_REQUIRED_FIELDS, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, isObject, renderList, runtime, } from "./util.js";
6
+ import { artifact_status_map, gate_route_phase, get_change_root, get_repo_root, normalize_gate, openspec_status, openspec_status_shape_reasons, } from "./openspec.js";
7
+ import { build_finding_ledger, enumerate_review_targets, render_finding_ledger, REVIEW_TARGETS_BY_GATE, review_round_number, } from "./disclosure.js";
8
+ import { evidence_schema_guard, check_apply_ready, check_archive_ready, check_review_complete, check_superspec_gate, check_task_complete, check_task_edit, check_task_reopen, } from "./gates.js";
9
+ import { final_verification_evidences, index_evidence, live_pass, live_user_confirmations } from "./evidence.js";
10
+ import { file_blob_sha, dirty_worktree_paths } from "./git.js";
11
+ import { preset_upgrade_reasons, preset_upgrade_required_from_context } from "./archive.js";
12
+ import { state_corrupt_reasons, state_stale_reasons } from "./state.js";
13
+ function load_packet_context(change) {
14
+ if (typeof runtime.load_context === "function") {
15
+ const [status, repoRoot, changeRoot, evidences] = runtime.load_context(change);
16
+ return { change, status, repoRoot, changeRoot, evidences };
17
+ }
18
+ const status = openspec_status(change);
19
+ return {
20
+ change,
21
+ repoRoot: get_repo_root(status),
22
+ changeRoot: get_change_root(status),
23
+ evidences: index_evidence(get_change_root(status)),
24
+ status,
25
+ };
26
+ }
27
+ function change_pinned_ref(changeRoot, relPath) {
28
+ const absPath = join(changeRoot, relPath);
29
+ if (!existsSync(absPath) || !statSync(absPath).isFile())
30
+ return null;
31
+ return { root: "change", path: relPath, blob_sha: file_blob_sha(absPath) };
32
+ }
33
+ function repo_pinned_ref(repoRoot, relPath) {
34
+ const absPath = join(repoRoot, relPath);
35
+ if (!existsSync(absPath) || !statSync(absPath).isFile())
36
+ return null;
37
+ return { root: "repo", path: relPath, blob_sha: file_blob_sha(absPath) };
38
+ }
39
+ function collect_change_refs(changeRoot, relPaths) {
40
+ const refs = [];
41
+ for (const relPath of relPaths) {
42
+ const ref = change_pinned_ref(changeRoot, relPath);
43
+ if (ref)
44
+ refs.push(ref);
45
+ }
46
+ return refs;
47
+ }
48
+ function collect_repo_refs(repoRoot, relPaths) {
49
+ const refs = [];
50
+ for (const relPath of relPaths) {
51
+ const ref = repo_pinned_ref(repoRoot, relPath);
52
+ if (ref)
53
+ refs.push(ref);
54
+ }
55
+ return refs;
56
+ }
57
+ function unique_pinned_refs(refs) {
58
+ const seen = new Set();
59
+ const out = [];
60
+ for (const ref of refs) {
61
+ const key = `${ref.root}\u0000${ref.path}\u0000${ref.blob_sha}`;
62
+ if (seen.has(key))
63
+ continue;
64
+ seen.add(key);
65
+ out.push(ref);
66
+ }
67
+ return out;
68
+ }
69
+ function unique_strings(items) {
70
+ return [...new Set(items.filter(Boolean))];
71
+ }
72
+ function evidence_pinned_ref(root, item) {
73
+ if (!isObject(item))
74
+ return null;
75
+ if (typeof item.path !== "string" || !item.path)
76
+ return null;
77
+ if (typeof item.blob_sha !== "string" || !item.blob_sha)
78
+ return null;
79
+ return { root, path: item.path, blob_sha: item.blob_sha };
80
+ }
81
+ function dirty_repo_refs(ctx, reason) {
82
+ try {
83
+ const dirtyPaths = typeof runtime.dirty_worktree_paths === "function"
84
+ ? runtime.dirty_worktree_paths(ctx.repoRoot)
85
+ : dirty_worktree_paths(ctx.repoRoot);
86
+ return collect_repo_refs(ctx.repoRoot, dirtyPaths);
87
+ }
88
+ catch (err) {
89
+ throw new GuardError(`${reason}: ${err.message}`);
90
+ }
91
+ }
92
+ function packet_data_problems(ctx) {
93
+ const { change, status, repoRoot, changeRoot, evidences } = ctx;
94
+ const [config, configProblems] = load_config(repoRoot, changeRoot);
95
+ const shapeProblems = openspec_status_shape_reasons(status);
96
+ const evidenceProblems = evidence_schema_guard(change, changeRoot, repoRoot, evidences);
97
+ const changedPaths = String(config.preset ?? "full") !== "full"
98
+ ? (typeof runtime.dirty_worktree_paths === "function" ? runtime.dirty_worktree_paths(repoRoot) : dirty_worktree_paths(repoRoot))
99
+ : [];
100
+ const presetRequired = preset_upgrade_required_from_context(config, changedPaths);
101
+ const presetHumanConfirmed = live_user_confirmations(evidences, "preset_upgrade").length > 0;
102
+ const presetProblems = preset_upgrade_reasons(config, changedPaths, presetHumanConfirmed);
103
+ return [
104
+ ...shapeProblems,
105
+ ...configProblems,
106
+ ...evidenceProblems,
107
+ ...state_corrupt_reasons(changeRoot),
108
+ ...state_stale_reasons(changeRoot, status),
109
+ ...(presetRequired ? presetProblems : presetProblems),
110
+ ];
111
+ }
112
+ function packet_contract_problems(ctx) {
113
+ const { change, status, repoRoot, changeRoot, evidences } = ctx;
114
+ const [, configProblems] = load_config(repoRoot, changeRoot);
115
+ const shapeProblems = openspec_status_shape_reasons(status);
116
+ const evidenceProblems = evidence_schema_guard(change, changeRoot, repoRoot, evidences);
117
+ return [...shapeProblems, ...configProblems, ...evidenceProblems];
118
+ }
119
+ function assert_packet_context_clean(ctx) {
120
+ const problems = packet_contract_problems(ctx);
121
+ if (problems.length === 0)
122
+ return;
123
+ throw new GuardError(problems[0].message);
124
+ }
125
+ function apply_data_problems(change, decision, dataProblems) {
126
+ if (dataProblems.length === 0)
127
+ return decision;
128
+ if (decision.allowed) {
129
+ return block(change, String(decision.gate ?? "guard_error"), dataProblems, {
130
+ task_id: decision.task_id,
131
+ openspec_summary: decision.openspec_status_summary,
132
+ });
133
+ }
134
+ return {
135
+ ...decision,
136
+ block_reasons: [...(Array.isArray(decision.block_reasons) ? decision.block_reasons : []), ...dataProblems],
137
+ };
138
+ }
139
+ function evaluate_workflow_decision(ctx, gateRaw, taskId) {
140
+ const gate = normalize_gate(gateRaw);
141
+ let decision;
142
+ if (gate === "apply_ready") {
143
+ decision = check_apply_ready(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences);
144
+ }
145
+ else if (gate === "task_edit") {
146
+ decision = check_task_edit(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, taskId ?? "");
147
+ }
148
+ else if (gate === "task_complete") {
149
+ decision = check_task_complete(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, taskId ?? "");
150
+ }
151
+ else if (gate === "task_reopen") {
152
+ decision = check_task_reopen(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, taskId ?? "");
153
+ }
154
+ else if (gate === "review_complete") {
155
+ decision = check_review_complete(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences);
156
+ }
157
+ else if (gate === "archive_ready") {
158
+ decision = check_archive_ready(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences);
159
+ }
160
+ else {
161
+ decision = check_superspec_gate(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, gate);
162
+ }
163
+ return apply_data_problems(ctx.change, decision, packet_data_problems(ctx));
164
+ }
165
+ function gate_recheck_command(change, gate, taskId, diagnostic = false) {
166
+ const format = diagnostic ? "json" : "agent";
167
+ if (gate === "apply_ready")
168
+ return `superspec guard check-apply-ready --change "${change}" --format ${format}`;
169
+ if (gate === "review_complete")
170
+ return `superspec guard check-review-complete --change "${change}" --format ${format}`;
171
+ if (gate === "archive_ready")
172
+ return `superspec guard check-archive-ready --change "${change}" --format ${format}`;
173
+ if (gate === "task_edit")
174
+ return `superspec guard check-task-edit --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
175
+ if (gate === "task_complete")
176
+ return `superspec guard check-task-complete --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
177
+ if (gate === "task_reopen")
178
+ return `superspec guard check-task-reopen --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
179
+ return `superspec guard check-enter --change "${change}" --gate "${gate}" --format ${format}`;
180
+ }
181
+ function default_allowed_next_action(gate) {
182
+ if (gate === "review_complete")
183
+ return "current review gate already passes; continue with archive-ready work.";
184
+ if (gate === "archive_ready")
185
+ return "current archive gate already passes; continue with archive handoff.";
186
+ if (gate === "apply_ready")
187
+ return "current apply gate already passes; continue with OpenSpec apply instructions and task execution.";
188
+ if (gate === "task_edit")
189
+ return "current task is clear to enter implementation edits.";
190
+ if (gate === "task_complete")
191
+ return "current task has enough completion proof to be checked off.";
192
+ if (gate === "task_reopen")
193
+ return "current task is authorized for a guarded reopen.";
194
+ return "current gate already passes; continue with the next workflow step.";
195
+ }
196
+ function openspec_cli_surfaces_for_gate(change, gate) {
197
+ if (gate === "explore_complete") {
198
+ return [
199
+ "openspec list --json",
200
+ `openspec status --change "${change}" --json`,
201
+ ];
202
+ }
203
+ if (gate === "apply_ready" || gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") {
204
+ return [`openspec instructions apply --change "${change}" --json`];
205
+ }
206
+ if (gate === "proposal_reviewed" || gate === "design_complete" || gate === "invariants_reviewed" || gate === "test_contract_drafted" || gate === "tasks_complete") {
207
+ return [
208
+ `openspec status --change "${change}" --json`,
209
+ `openspec instructions <artifact-id> --change "${change}" --json`,
210
+ ];
211
+ }
212
+ if (gate === "review_complete")
213
+ return [`openspec validate "${change}"`];
214
+ if (gate === "archive_ready") {
215
+ return [
216
+ `openspec validate "${change}"`,
217
+ `openspec archive -y "${change}"`,
218
+ ];
219
+ }
220
+ return [];
221
+ }
222
+ function workflow_gate_refs(ctx, gate) {
223
+ const reviewTargets = enumerate_review_targets(gate, ctx.changeRoot);
224
+ if (reviewTargets) {
225
+ return unique_pinned_refs([...reviewTargets.keys()].map((path) => change_pinned_ref(ctx.changeRoot, path)).filter(Boolean));
226
+ }
227
+ if (gate === "apply_ready" || gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") {
228
+ return unique_pinned_refs(collect_change_refs(ctx.changeRoot, [
229
+ "tasks.md",
230
+ "design.md",
231
+ ".superspec/artifacts/business-invariants.md",
232
+ ".superspec/artifacts/test-contract.md",
233
+ ]));
234
+ }
235
+ if (gate === "review_complete" || gate === "archive_ready") {
236
+ const changeRefs = collect_change_refs(ctx.changeRoot, [
237
+ "tasks.md",
238
+ "design.md",
239
+ ".superspec/artifacts/business-invariants.md",
240
+ ".superspec/artifacts/test-contract.md",
241
+ ]);
242
+ const repoRefs = dirty_repo_refs(ctx, `${gate}: failed to inspect dirty worktree`);
243
+ return unique_pinned_refs([...changeRefs, ...repoRefs]);
244
+ }
245
+ if (gate === "explore_complete")
246
+ return collect_change_refs(ctx.changeRoot, [".superspec/artifacts/discovery.md"]);
247
+ return [];
248
+ }
249
+ function evidence_ref_selector(changeRoot, ev) {
250
+ if (typeof ev._path !== "string" || !ev._path)
251
+ return null;
252
+ return change_pinned_ref(changeRoot, ev._path);
253
+ }
254
+ function decision_selectors_for_gate(ctx, gate) {
255
+ const selectors = [];
256
+ for (const ev of ctx.evidences) {
257
+ if (!isObject(ev) || ev._invalid)
258
+ continue;
259
+ if (normalize_gate(String(ev.gate ?? "")) !== gate)
260
+ continue;
261
+ const evidenceRef = evidence_ref_selector(ctx.changeRoot, ev);
262
+ if (!evidenceRef)
263
+ continue;
264
+ if (typeof ev.decision_scope_key === "string" && ev.decision_scope_key) {
265
+ selectors.push({
266
+ evidence_id: String(ev.evidence_id ?? ""),
267
+ decision_scope_key: ev.decision_scope_key,
268
+ evidence_ref: evidenceRef,
269
+ });
270
+ }
271
+ for (const item of Array.isArray(ev.finding_dispositions) ? ev.finding_dispositions : []) {
272
+ if (!isObject(item))
273
+ continue;
274
+ if (typeof item.decision_scope_key !== "string" || !item.decision_scope_key)
275
+ continue;
276
+ selectors.push({
277
+ evidence_id: String(ev.evidence_id ?? ""),
278
+ decision_scope_key: item.decision_scope_key,
279
+ evidence_ref: evidenceRef,
280
+ });
281
+ }
282
+ }
283
+ return selectors;
284
+ }
285
+ function finding_selectors_for_gate(ctx, gate, round) {
286
+ const selectors = [];
287
+ for (const ev of ctx.evidences) {
288
+ if (!isObject(ev) || ev._invalid)
289
+ continue;
290
+ if (normalize_gate(String(ev.gate ?? "")) !== gate)
291
+ continue;
292
+ if (!Array.isArray(ev.findings))
293
+ continue;
294
+ const evidenceRef = evidence_ref_selector(ctx.changeRoot, ev);
295
+ if (!evidenceRef)
296
+ continue;
297
+ if (round !== undefined) {
298
+ const roundNumber = review_round_number(gate, String(ev.review_round_id ?? ""));
299
+ if (roundNumber !== round)
300
+ continue;
301
+ }
302
+ for (const item of ev.findings) {
303
+ if (!isObject(item) || typeof item.finding_uid !== "string" || !item.finding_uid)
304
+ continue;
305
+ selectors.push({
306
+ evidence_id: String(ev.evidence_id ?? ""),
307
+ finding_uid: item.finding_uid,
308
+ evidence_ref: evidenceRef,
309
+ });
310
+ }
311
+ }
312
+ return selectors;
313
+ }
314
+ function review_complete_finding_selectors(ctx) {
315
+ const selectors = [];
316
+ for (const ev of live_pass(ctx.evidences, { gate: "review_complete", kind: "source_guidance" })) {
317
+ const evidenceRef = evidence_ref_selector(ctx.changeRoot, ev);
318
+ if (!evidenceRef)
319
+ continue;
320
+ for (const item of Array.isArray(ev.blocking_findings) ? ev.blocking_findings : []) {
321
+ if (!isObject(item))
322
+ continue;
323
+ const findingUid = typeof item.finding_uid === "string" && item.finding_uid
324
+ ? item.finding_uid
325
+ : (typeof item.finding_id === "string" && item.finding_id
326
+ ? `review_complete:${String(ev.evidence_id ?? "")}:${item.finding_id}`
327
+ : "");
328
+ if (!findingUid)
329
+ continue;
330
+ selectors.push({
331
+ evidence_id: String(ev.evidence_id ?? ""),
332
+ finding_uid: findingUid,
333
+ evidence_ref: evidenceRef,
334
+ });
335
+ }
336
+ }
337
+ return selectors;
338
+ }
339
+ const REVIEW_PACKET_ROLES_BY_GATE = {
340
+ explore_complete: ["critic"],
341
+ proposal_reviewed: ["critic"],
342
+ design_complete: ["architect", "critic", "test-engineer"],
343
+ invariants_reviewed: ["critic", "test-engineer"],
344
+ test_contract_drafted: ["critic", "test-engineer"],
345
+ tasks_complete: ["critic"],
346
+ review_complete: ["code-reviewer", "architect", "critic", "verifier"],
347
+ };
348
+ function validate_review_packet_gate_and_role(gate, role) {
349
+ if (gate !== "review_complete" && !(gate in REVIEW_TARGETS_BY_GATE)) {
350
+ throw new GuardError(`review-packet only supports disclosure gates and review_complete, got ${gate}`);
351
+ }
352
+ if (role === "main-thread")
353
+ return;
354
+ const allowedRoles = REVIEW_PACKET_ROLES_BY_GATE[gate] ?? [];
355
+ if (!allowedRoles.includes(role)) {
356
+ throw new GuardError(`review-packet role ${role} is not supported for gate ${gate}; expected one of ${renderList([...allowedRoles, "main-thread"])}`);
357
+ }
358
+ }
359
+ function workflow_packet(ctx, gateRaw, taskId) {
360
+ const gate = normalize_gate(gateRaw);
361
+ const decision = evaluate_workflow_decision(ctx, gate, taskId);
362
+ const reasonCodes = unique_strings((Array.isArray(decision.block_reasons) ? decision.block_reasons : []).map((item) => item.code));
363
+ const topBlockers = reasonCodes.slice(0, 5);
364
+ const nextActions = Array.isArray(decision.next_allowed_actions) ? decision.next_allowed_actions.filter((item) => typeof item === "string" && item.length > 0) : [];
365
+ const packet = {
366
+ stage: gate_route_phase(gate),
367
+ current_gate: gate,
368
+ status: decision.allowed ? "allowed" : "blocked",
369
+ next_action: nextActions[0] ?? default_allowed_next_action(gate),
370
+ next_command: gate_recheck_command(ctx.change, gate, taskId, false),
371
+ diagnostic_command: gate_recheck_command(ctx.change, gate, taskId, true),
372
+ must_read_refs: workflow_gate_refs(ctx, gate),
373
+ };
374
+ const cliSurfaces = openspec_cli_surfaces_for_gate(ctx.change, gate);
375
+ if (cliSurfaces.length > 0)
376
+ packet.openspec_cli_surfaces = cliSurfaces;
377
+ if (taskId)
378
+ packet.task_id = taskId;
379
+ if (!decision.allowed) {
380
+ packet.top_blockers = topBlockers;
381
+ packet.blocker_count = reasonCodes.length;
382
+ packet.has_more_blockers = reasonCodes.length > topBlockers.length;
383
+ }
384
+ const findings = finding_selectors_for_gate(ctx, gate);
385
+ if (findings.length > 0)
386
+ packet.must_read_verbatim_findings = findings;
387
+ const decisions = decision_selectors_for_gate(ctx, gate);
388
+ if (decisions.length > 0)
389
+ packet.must_read_verbatim_decisions = decisions;
390
+ return packet;
391
+ }
392
+ function live_output_refs(changeRoot, evidences) {
393
+ const refs = [];
394
+ for (const ev of evidences) {
395
+ if (!isObject(ev))
396
+ continue;
397
+ if (typeof ev.output_ref !== "string" || !ev.output_ref)
398
+ continue;
399
+ const ref = change_pinned_ref(changeRoot, ev.output_ref);
400
+ if (ref)
401
+ refs.push(ref);
402
+ }
403
+ return unique_pinned_refs(refs);
404
+ }
405
+ function disclosure_round_reviews(ctx, gate, round) {
406
+ return ctx.evidences.filter((ev) => isObject(ev)
407
+ && !ev._invalid
408
+ && normalize_gate(String(ev.gate ?? "")) === gate
409
+ && Boolean(ev.agent_role)
410
+ && review_round_number(gate, String(ev.review_round_id ?? "")) === round);
411
+ }
412
+ function review_complete_role_target_refs(ctx) {
413
+ const repoRefs = dirty_repo_refs(ctx, "review_complete: failed to inspect dirty worktree");
414
+ return unique_pinned_refs([
415
+ ...repoRefs,
416
+ ...collect_change_refs(ctx.changeRoot, [
417
+ "tasks.md",
418
+ "design.md",
419
+ ".superspec/artifacts/business-invariants.md",
420
+ ".superspec/artifacts/test-contract.md",
421
+ ]),
422
+ ]);
423
+ }
424
+ function verification_output_fields() {
425
+ return [...ROLE_EVIDENCE_FIELDS, ...VERIFY_EVIDENCE_REQUIRED_FIELDS, "scope_drift"];
426
+ }
427
+ function review_output_fields() {
428
+ return [...ROLE_EVIDENCE_FIELDS, "review_round_id", "findings", "acknowledged_accepted_deviation_uids"];
429
+ }
430
+ function source_guidance_output_fields() {
431
+ return [
432
+ ...ROLE_EVIDENCE_FIELDS,
433
+ ...SOURCE_GUIDANCE_REQUIRED_FIELDS,
434
+ ...REVIEW_EVIDENCE_REQUIRED_FIELDS,
435
+ "blocking_findings",
436
+ "non_blocking_findings",
437
+ "finding_dispositions",
438
+ "rollback_targets",
439
+ ];
440
+ }
441
+ function main_review_digest_fields() {
442
+ return [
443
+ "review_round_id",
444
+ "target_refs",
445
+ "source_review_evidence_refs",
446
+ "previous_digest_refs",
447
+ "finding_dispositions",
448
+ ];
449
+ }
450
+ function main_adjudication_fields() {
451
+ return [
452
+ ...MAIN_ADJUDICATION_REQUIRED_FIELDS,
453
+ "review_decision",
454
+ "request_changes_route",
455
+ "blocking_source_evidence_refs",
456
+ "reopen_task_ids",
457
+ `review_decision values: ${renderList([...MAIN_ADJUDICATION_DECISIONS])}`,
458
+ `request_changes_route values: ${renderList([...REQUEST_CHANGES_ROUTES])}`,
459
+ `claim_adjudications decision values: ${renderList([...CLAIM_ADJUDICATION_DECISIONS])}`,
460
+ `finding_adjudications decision values: ${renderList([...FINDING_ADJUDICATION_DECISIONS])}`,
461
+ ];
462
+ }
463
+ function review_packet(ctx, gateRaw, role, round, requestedKind) {
464
+ const gate = normalize_gate(gateRaw);
465
+ const consumer = role === "main-thread" ? "main-thread" : "role";
466
+ validate_review_packet_gate_and_role(gate, role);
467
+ let targetRefs = [];
468
+ let sourceRefs = [];
469
+ let requiredLoadRefs = [];
470
+ let requiredClaimIds = [];
471
+ let requiredOutputKind = "review";
472
+ let outputContractFields = review_output_fields();
473
+ let stopConditions = [];
474
+ if (gate === "review_complete") {
475
+ targetRefs = review_complete_role_target_refs(ctx);
476
+ if (consumer === "main-thread") {
477
+ if (requestedKind !== undefined)
478
+ throw new GuardError("review-packet --kind is only supported for review_complete role lanes");
479
+ const sourceGuidance = live_pass(ctx.evidences, { gate: "review_complete", kind: "source_guidance" });
480
+ const verification = final_verification_evidences(ctx.evidences);
481
+ sourceRefs = unique_pinned_refs([
482
+ ...live_output_refs(ctx.changeRoot, sourceGuidance),
483
+ ...live_output_refs(ctx.changeRoot, verification),
484
+ ]);
485
+ requiredLoadRefs = unique_pinned_refs(sourceGuidance.flatMap((ev) => Array.isArray(ev.required_load_refs)
486
+ ? ev.required_load_refs.map((item) => evidence_pinned_ref("repo", item)).filter(Boolean)
487
+ : []));
488
+ requiredClaimIds = unique_strings(sourceGuidance.flatMap((ev) => Array.isArray(ev.required_claim_ids) ? ev.required_claim_ids.map(String) : []));
489
+ requiredOutputKind = "main_adjudication";
490
+ outputContractFields = main_adjudication_fields();
491
+ stopConditions = [
492
+ "Stop if any required_load_refs item has not been actually read by the main thread.",
493
+ "Stop if any required_claim_ids entry is left unadjudicated.",
494
+ "Stop if any blocking finding remains needs_fix.",
495
+ "Stop if request_changes_route='change_update'; hand off back to propose instead of forcing review_complete.",
496
+ ];
497
+ }
498
+ else if (role === "verifier" || (role === "critic" && requestedKind === "verification_review")) {
499
+ if (role === "verifier" && requestedKind !== undefined && requestedKind !== "verification_review") {
500
+ throw new GuardError("review-packet role verifier only supports --kind verification_review");
501
+ }
502
+ sourceRefs = targetRefs;
503
+ requiredOutputKind = "verification_review";
504
+ outputContractFields = verification_output_fields();
505
+ stopConditions = [
506
+ "Stop after writing verification_review only; do not write main_adjudication.",
507
+ "Stop if validation or evidence gaps remain unresolved.",
508
+ ];
509
+ }
510
+ else {
511
+ if (requestedKind !== undefined && requestedKind !== "source_guidance") {
512
+ throw new GuardError(`review-packet role ${role} cannot write ${requestedKind} for gate ${gate}`);
513
+ }
514
+ sourceRefs = targetRefs;
515
+ requiredOutputKind = "source_guidance";
516
+ outputContractFields = source_guidance_output_fields();
517
+ stopConditions = [
518
+ "Stop after writing source_guidance only; do not write main_adjudication.",
519
+ "Stop if reviewed_files does not cover the implementation diff.",
520
+ "Stop if rollback_targets are missing.",
521
+ ];
522
+ }
523
+ }
524
+ else if (consumer === "main-thread") {
525
+ if (requestedKind !== undefined)
526
+ throw new GuardError("review-packet --kind is only supported for review_complete role lanes");
527
+ const roundReviews = disclosure_round_reviews(ctx, gate, round);
528
+ const targetSet = enumerate_review_targets(gate, ctx.changeRoot);
529
+ targetRefs = targetSet ? unique_pinned_refs([...targetSet.keys()].map((path) => change_pinned_ref(ctx.changeRoot, path)).filter(Boolean)) : [];
530
+ sourceRefs = live_output_refs(ctx.changeRoot, roundReviews);
531
+ requiredLoadRefs = sourceRefs;
532
+ requiredOutputKind = "main_review_digest";
533
+ outputContractFields = main_review_digest_fields();
534
+ stopConditions = [
535
+ "Stop if any finding from the round lacks a disposition in the digest.",
536
+ "Stop if any material finding lacks a user decision, standing authorization, or baseline decision anchor.",
537
+ "Stop if a round>1 digest is missing the deterministic ledger block.",
538
+ ];
539
+ }
540
+ else {
541
+ if (requestedKind !== undefined)
542
+ throw new GuardError("review-packet --kind is only supported for review_complete role lanes");
543
+ const targetSet = enumerate_review_targets(gate, ctx.changeRoot);
544
+ targetRefs = targetSet ? unique_pinned_refs([...targetSet.keys()].map((path) => change_pinned_ref(ctx.changeRoot, path)).filter(Boolean)) : [];
545
+ sourceRefs = targetRefs;
546
+ requiredOutputKind = "review";
547
+ outputContractFields = review_output_fields();
548
+ stopConditions = [
549
+ "Stop after writing role review evidence only; do not write main_review_digest or main_adjudication.",
550
+ "Stop if target_refs do not pin the current gate target set.",
551
+ ];
552
+ }
553
+ const packet = {
554
+ consumer,
555
+ gate,
556
+ role,
557
+ round,
558
+ target_refs: targetRefs,
559
+ source_refs: sourceRefs,
560
+ required_output_kind: requiredOutputKind,
561
+ output_contract_fields: outputContractFields,
562
+ required_review_scope: targetRefs.map((item) => item.path),
563
+ stop_conditions: stopConditions,
564
+ };
565
+ if (requiredLoadRefs.length > 0)
566
+ packet.required_load_refs = requiredLoadRefs;
567
+ if (requiredClaimIds.length > 0)
568
+ packet.required_claim_ids = requiredClaimIds;
569
+ const findingSelectors = gate === "review_complete"
570
+ ? (consumer === "main-thread" ? review_complete_finding_selectors(ctx) : [])
571
+ : finding_selectors_for_gate(ctx, gate, round);
572
+ if (findingSelectors.length > 0)
573
+ packet.must_read_verbatim_findings = findingSelectors;
574
+ const decisionSelectors = decision_selectors_for_gate(ctx, gate);
575
+ if (decisionSelectors.length > 0)
576
+ packet.must_read_verbatim_decisions = decisionSelectors;
577
+ return packet;
578
+ }
579
+ function render_ref(ref) {
580
+ return `- ${ref.root}:${ref.path} @ ${ref.blob_sha}`;
581
+ }
582
+ function render_review_prompt(ctx, packet) {
583
+ const lines = [
584
+ `# SuperSpec Review Packet`,
585
+ "",
586
+ `consumer: ${packet.consumer}`,
587
+ `gate: ${packet.gate}`,
588
+ `role: ${packet.role}`,
589
+ `round: ${packet.round}`,
590
+ "",
591
+ `Target refs:`,
592
+ ...(packet.target_refs.length > 0 ? packet.target_refs.map(render_ref) : ["- none"]),
593
+ "",
594
+ `Source refs:`,
595
+ ...(packet.source_refs.length > 0 ? packet.source_refs.map(render_ref) : ["- none"]),
596
+ ];
597
+ if (packet.required_load_refs && packet.required_load_refs.length > 0) {
598
+ lines.push("", "Required load refs:", ...packet.required_load_refs.map(render_ref));
599
+ }
600
+ if (packet.required_claim_ids && packet.required_claim_ids.length > 0) {
601
+ lines.push("", "Required claim ids:", ...packet.required_claim_ids.map((item) => `- ${item}`));
602
+ }
603
+ if (packet.must_read_verbatim_findings && packet.must_read_verbatim_findings.length > 0) {
604
+ lines.push("", "Must read verbatim findings:", ...packet.must_read_verbatim_findings.map((item) => `- ${item.evidence_id} :: ${item.finding_uid} @ ${item.evidence_ref.path}`));
605
+ }
606
+ if (packet.must_read_verbatim_decisions && packet.must_read_verbatim_decisions.length > 0) {
607
+ lines.push("", "Must read decision bindings:", ...packet.must_read_verbatim_decisions.map((item) => `- ${item.evidence_id} :: ${item.decision_scope_key} @ ${item.evidence_ref.path}`));
608
+ }
609
+ lines.push("", `Required output kind: ${packet.required_output_kind}`, "Output contract fields:", ...packet.output_contract_fields.map((item) => `- ${item}`));
610
+ if (packet.required_review_scope && packet.required_review_scope.length > 0) {
611
+ lines.push("", "Required review scope:", ...packet.required_review_scope.map((item) => `- ${item}`));
612
+ }
613
+ lines.push("", "Stop conditions:", ...packet.stop_conditions.map((item) => `- ${item}`));
614
+ if (packet.round > 1 && packet.gate in REVIEW_TARGETS_BY_GATE) {
615
+ lines.push("", render_finding_ledger(packet.gate, build_finding_ledger(packet.gate, ctx.evidences, packet.round)));
616
+ }
617
+ return `${lines.join("\n")}\n`;
618
+ }
619
+ function render_ledger(ctx, gateRaw, round) {
620
+ const gate = normalize_gate(gateRaw);
621
+ const entries = build_finding_ledger(gate, ctx.evidences, round ?? Number.POSITIVE_INFINITY);
622
+ return `${render_finding_ledger(gate, entries)}\n`;
623
+ }
624
+ export function dispatch_packet(args) {
625
+ const ctx = load_packet_context(args.change);
626
+ if (args.command === "workflow-packet") {
627
+ const gate = normalize_gate(args.gate ?? "");
628
+ if ((gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") && !args.task_id) {
629
+ throw new GuardError(`workflow-packet requires --task-id for gate ${gate}`);
630
+ }
631
+ return { output_format: "agent", payload: workflow_packet(ctx, gate, args.task_id) };
632
+ }
633
+ if (args.command === "review-packet") {
634
+ assert_packet_context_clean(ctx);
635
+ const round = args.round ?? 0;
636
+ if (round < 1)
637
+ throw new GuardError("review-packet requires --round >= 1");
638
+ const packet = review_packet(ctx, args.gate ?? "", args.role ?? "", round, args.evidence_kind);
639
+ if (args.packet_format === "prompt") {
640
+ return { output_format: "prompt", payload: render_review_prompt(ctx, packet) };
641
+ }
642
+ return { output_format: "agent", payload: packet };
643
+ }
644
+ if (args.command === "ledger-render") {
645
+ assert_packet_context_clean(ctx);
646
+ return { output_format: "prompt", payload: render_ledger(ctx, args.gate ?? "", args.round) };
647
+ }
648
+ throw new GuardError(`unknown packet command: ${args.command}`);
649
+ }
650
+ export function is_packet_command(command) {
651
+ return command === "workflow-packet" || command === "review-packet" || command === "ledger-render";
652
+ }