@peterxiaoyang/superspec 0.1.6 → 0.1.8

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 (34) hide show
  1. package/adapters/codex/install-map.json +5 -0
  2. package/bin/superspec-hook.js +4 -0
  3. package/dist/src/apply_worker_chain_lifecycle.d.ts +19 -0
  4. package/dist/src/apply_worker_chain_lifecycle.js +278 -0
  5. package/dist/src/archive.js +2 -0
  6. package/dist/src/cli_args.d.ts +5 -0
  7. package/dist/src/cli_args.js +41 -0
  8. package/dist/src/core.js +36 -0
  9. package/dist/src/evidence.js +3 -0
  10. package/dist/src/gates.js +8 -258
  11. package/dist/src/hooks/adapter.d.ts +5 -0
  12. package/dist/src/hooks/adapter.js +311 -0
  13. package/dist/src/hooks/guard_api.d.ts +12 -0
  14. package/dist/src/hooks/guard_api.js +797 -0
  15. package/dist/src/hooks/health.d.ts +4 -0
  16. package/dist/src/hooks/health.js +98 -0
  17. package/dist/src/hooks/policy_event.d.ts +41 -0
  18. package/dist/src/hooks/policy_event.js +2624 -0
  19. package/dist/src/hooks/types.d.ts +72 -0
  20. package/dist/src/hooks/types.js +1 -0
  21. package/dist/src/hooks/validation.d.ts +3 -0
  22. package/dist/src/hooks/validation.js +70 -0
  23. package/dist/src/packet_measure.js +67 -102
  24. package/dist/src/packet_render.js +4 -85
  25. package/dist/superspec_hook.d.ts +4 -0
  26. package/dist/superspec_hook.js +35 -0
  27. package/package.json +7 -2
  28. package/schemas/hook-event.schema.json +27 -0
  29. package/templates/hooks/codex-hooks.json +61 -0
  30. package/templates/workflow/skills/superspec-apply/SKILL.md +7 -0
  31. package/templates/workflow/skills/superspec-archive/SKILL.md +8 -0
  32. package/templates/workflow/skills/superspec-explore/SKILL.md +7 -0
  33. package/templates/workflow/skills/superspec-propose/SKILL.md +7 -0
  34. package/templates/workflow/skills/superspec-review/SKILL.md +7 -0
@@ -0,0 +1,72 @@
1
+ import type { JsonMap, Reason } from "../util.ts";
2
+ export declare const HOOK_ADAPTER_VERSION = "superspec-hook@2";
3
+ export type HookTrust = "trusted" | "audit-only" | "untrusted";
4
+ export type HookStrictProfile = "available" | "unavailable";
5
+ export type HookEvent = JsonMap & {
6
+ hook_event_name?: string;
7
+ session_id?: string;
8
+ turn_id?: string;
9
+ cwd?: string;
10
+ tool_name?: string;
11
+ tool_use_id?: string;
12
+ tool_input?: JsonMap;
13
+ tool_response?: JsonMap;
14
+ agent_id?: string;
15
+ agent_type?: string;
16
+ };
17
+ export type HookDecision = {
18
+ allowed: boolean;
19
+ decision: "allow" | "block" | "status";
20
+ change_id: string;
21
+ gate: string;
22
+ strict_profile: HookStrictProfile;
23
+ enforcement: "pass-through" | "guarded" | "deny" | "audit-only";
24
+ trust: HookTrust;
25
+ block_reasons: Reason[];
26
+ audit_only_reasons: Reason[];
27
+ target_paths?: string[];
28
+ tool_name?: string;
29
+ hook_event_name?: string;
30
+ hook_event_id?: string;
31
+ actions?: JsonMap[];
32
+ next_allowed_actions: string[];
33
+ trust_warnings: string[];
34
+ };
35
+ export type HookSessionRecord = {
36
+ schema_version: 2;
37
+ kind: "hook_active_session";
38
+ trust: HookTrust;
39
+ change_id: string;
40
+ repo_root: string;
41
+ session_id: string;
42
+ workflow: string;
43
+ started_at: string;
44
+ expires_at: string;
45
+ guard_version: string;
46
+ adapter_version: string;
47
+ hook_manifest_hash: string | null;
48
+ strict_profile: HookStrictProfile;
49
+ audit_only_reasons: Reason[];
50
+ };
51
+ export type HookRunlogRecord = {
52
+ schema_version: 2;
53
+ kind: "subagent_start" | "subagent_stop";
54
+ trust: HookTrust;
55
+ hook_event_id: string;
56
+ hook_event_name: string;
57
+ hook_provenance: JsonMap;
58
+ session_id: string;
59
+ turn_id?: string;
60
+ run_id: string;
61
+ agent_id: string;
62
+ agent_type: string;
63
+ cwd?: string;
64
+ prompt_hash?: string;
65
+ prompt_ref?: string;
66
+ output_ref?: string;
67
+ output_hash?: string;
68
+ status?: string;
69
+ error?: string;
70
+ started_at?: string;
71
+ stopped_at?: string;
72
+ };
@@ -0,0 +1 @@
1
+ export const HOOK_ADAPTER_VERSION = "superspec-hook@2";
@@ -0,0 +1,3 @@
1
+ import type { JsonMap, Reason } from "../util.ts";
2
+ export declare function strictRoleRunlogReasons(changeRoot: string, ev: JsonMap): Reason[];
3
+ export declare function strictRuntimeEvidenceReasons(ev: JsonMap): Reason[];
@@ -0,0 +1,70 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { isObject, reason, safe_within, sha256_file } from "../util.js";
4
+ import { superspec_dir } from "../paths.js";
5
+ function runlogPath(changeRoot) {
6
+ return join(superspec_dir(changeRoot), "subagent-runlog.jsonl");
7
+ }
8
+ export function strictRoleRunlogReasons(changeRoot, ev) {
9
+ if (ev.trust !== "runtime-verified" && ev.requires_hook_runlog !== true)
10
+ return [];
11
+ const problems = [
12
+ reason("hook_runlog_strict_profile_unavailable", `${ev._path}: strict hook runlog validation is unavailable until R-1 deny and hook provenance pass`),
13
+ ];
14
+ const target = runlogPath(changeRoot);
15
+ if (!existsSync(target) || !statSync(target).isFile()) {
16
+ problems.push(reason("hook_runlog_missing", `${ev._path}: strict role evidence requires SuperSpec hook subagent runlog`));
17
+ return problems;
18
+ }
19
+ const records = readFileSync(target, "utf8").split(/\r?\n/u).filter(Boolean).flatMap((line) => {
20
+ try {
21
+ const parsed = JSON.parse(line);
22
+ return isObject(parsed) ? [parsed] : [];
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ });
28
+ const agentId = String(ev.agent_id ?? "");
29
+ const role = String(ev.agent_role ?? "");
30
+ const starts = records.filter((item) => item.kind === "subagent_start" && item.agent_id === agentId && item.agent_type === role && item.trust === "trusted");
31
+ const stops = records.filter((item) => item.kind === "subagent_stop" && item.agent_id === agentId && item.agent_type === role && item.trust === "trusted");
32
+ if (starts.length === 0)
33
+ problems.push(reason("hook_runlog_start_missing", `${ev._path}: strict role evidence has no trusted matching SubagentStart`, [agentId]));
34
+ if (stops.length === 0)
35
+ problems.push(reason("hook_runlog_stop_missing", `${ev._path}: strict role evidence has no trusted matching SubagentStop`, [agentId]));
36
+ const outputRef = typeof ev.output_ref === "string" ? ev.output_ref : "";
37
+ const outputPath = outputRef ? safe_within(changeRoot, outputRef) : null;
38
+ const outputSha = outputPath && existsSync(outputPath) && statSync(outputPath).isFile() ? sha256_file(outputPath) : null;
39
+ if (outputSha && stops.length > 0 && !stops.some((item) => item.output_hash === outputSha || item.output_ref === outputRef)) {
40
+ problems.push(reason("hook_runlog_output_mismatch", `${ev._path}: strict role evidence output_ref does not match trusted SubagentStop output hash/ref`, [agentId]));
41
+ }
42
+ return problems;
43
+ }
44
+ export function strictRuntimeEvidenceReasons(ev) {
45
+ if (ev.trust !== "runtime-verified")
46
+ return [];
47
+ const problems = [
48
+ reason("runtime_evidence_strict_profile_unavailable", `${ev._path}: strict runtime evidence is unavailable until R-1 deny and hook provenance pass`),
49
+ ];
50
+ if (typeof ev.hook_event_id !== "string" || !ev.hook_event_id)
51
+ problems.push(reason("runtime_evidence_missing_hook_event", `${ev._path}: runtime-verified evidence requires hook_event_id`));
52
+ if (!isObject(ev.hook_provenance) || ev.hook_provenance.validation !== "trusted")
53
+ problems.push(reason("runtime_evidence_untrusted_provenance", `${ev._path}: runtime-verified evidence requires trusted hook_provenance`));
54
+ if (!isObject(ev.token_binding))
55
+ problems.push(reason("runtime_evidence_missing_token", `${ev._path}: runtime-verified evidence requires Guard token_binding`));
56
+ if (typeof ev.command_fingerprint !== "string" || !ev.command_fingerprint.startsWith("sha256:"))
57
+ problems.push(reason("runtime_evidence_missing_command_fingerprint", `${ev._path}: runtime-verified evidence requires command_fingerprint`));
58
+ if (!Array.isArray(ev.raw_log_pinned_refs) || ev.raw_log_pinned_refs.length === 0)
59
+ problems.push(reason("runtime_evidence_missing_raw_log_pin", `${ev._path}: runtime-verified evidence requires raw_log_pinned_refs`));
60
+ if (typeof ev.exit_code !== "number") {
61
+ problems.push(reason("runtime_evidence_missing_exit_code", `${ev._path}: runtime-verified evidence requires numeric exit_code`));
62
+ }
63
+ else if ((ev.kind === "final_test" || ev.semantic_status === "expected_success") && ev.exit_code !== 0) {
64
+ problems.push(reason("runtime_evidence_exit_code_mismatch", `${ev._path}: expected_success/final_test runtime evidence requires exit_code=0`));
65
+ }
66
+ else if (ev.semantic_status === "expected_failure" && ev.exit_code === 0) {
67
+ problems.push(reason("runtime_evidence_exit_code_mismatch", `${ev._path}: RED runtime evidence requires non-zero exit_code`));
68
+ }
69
+ return problems;
70
+ }
@@ -1,118 +1,119 @@
1
1
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
+ import { load_install_map } from "./install_engine.js";
4
5
  import { dispatch_packet } from "./packet_render.js";
5
- import { GuardError, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS, runtime, } from "./util.js";
6
+ import { GuardError, runtime, } from "./util.js";
6
7
  import { file_blob_sha } from "./git.js";
7
8
  const REPRESENTATIVE_SCENARIOS = [
8
9
  {
9
10
  name: "explore_complete",
10
- description: "Post-bridge runtime surface for discovery authoring plus proposal-entry critic review.",
11
+ description: "Package payload surface for discovery authoring plus proposal-entry critic review.",
11
12
  files: [
12
- ".codex/skills/superspec-explore/SKILL.md",
13
- ".codex/prompts/critic.md",
14
- ".codex/agents/critic.toml",
13
+ "templates/workflow/skills/superspec-explore/SKILL.md",
14
+ "templates/workflow/prompts/critic.md",
15
+ "adapters/codex/agents/critic.toml",
15
16
  ],
16
17
  },
17
18
  {
18
19
  name: "proposal_reviewed",
19
- description: "Post-bridge runtime surface for proposal authoring and proposal critic review.",
20
+ description: "Package payload surface for proposal authoring and proposal critic review.",
20
21
  files: [
21
- ".codex/skills/superspec-propose/SKILL.md",
22
- ".codex/prompts/critic.md",
23
- ".codex/agents/critic.toml",
22
+ "templates/workflow/skills/superspec-propose/SKILL.md",
23
+ "templates/workflow/prompts/critic.md",
24
+ "adapters/codex/agents/critic.toml",
24
25
  ],
25
26
  },
26
27
  {
27
28
  name: "design_complete",
28
- description: "Post-bridge runtime surface for design authoring and the architect/critic/test-engineer review bundle.",
29
+ description: "Package payload surface for design authoring and the architect/critic/test-engineer review bundle.",
29
30
  files: [
30
- ".codex/skills/superspec-propose/SKILL.md",
31
- ".codex/prompts/architect.md",
32
- ".codex/prompts/critic.md",
33
- ".codex/prompts/test-engineer.md",
34
- ".codex/agents/architect.toml",
35
- ".codex/agents/critic.toml",
36
- ".codex/agents/test-engineer.toml",
31
+ "templates/workflow/skills/superspec-propose/SKILL.md",
32
+ "templates/workflow/prompts/architect.md",
33
+ "templates/workflow/prompts/critic.md",
34
+ "templates/workflow/prompts/test-engineer.md",
35
+ "adapters/codex/agents/architect.toml",
36
+ "adapters/codex/agents/critic.toml",
37
+ "adapters/codex/agents/test-engineer.toml",
37
38
  ],
38
39
  },
39
40
  {
40
41
  name: "test_contract_drafted",
41
- description: "Post-bridge runtime surface for test-contract authoring and its review lanes.",
42
+ description: "Package payload surface for test-contract authoring and its review lanes.",
42
43
  files: [
43
- ".codex/skills/superspec-propose/SKILL.md",
44
- ".codex/prompts/critic.md",
45
- ".codex/prompts/test-engineer.md",
46
- ".codex/agents/critic.toml",
47
- ".codex/agents/test-engineer.toml",
44
+ "templates/workflow/skills/superspec-propose/SKILL.md",
45
+ "templates/workflow/prompts/critic.md",
46
+ "templates/workflow/prompts/test-engineer.md",
47
+ "adapters/codex/agents/critic.toml",
48
+ "adapters/codex/agents/test-engineer.toml",
48
49
  ],
49
50
  },
50
51
  {
51
52
  name: "apply_ready",
52
- description: "Post-bridge runtime surface for apply orchestration and bounded executor handoff.",
53
+ description: "Package payload surface for apply orchestration and bounded executor handoff.",
53
54
  files: [
54
- ".codex/skills/superspec-apply/SKILL.md",
55
- ".codex/prompts/executor.md",
56
- ".codex/agents/executor.toml",
55
+ "templates/workflow/skills/superspec-apply/SKILL.md",
56
+ "templates/workflow/prompts/executor.md",
57
+ "adapters/codex/agents/executor.toml",
57
58
  ],
58
59
  },
59
60
  {
60
61
  name: "review_complete_allow",
61
- description: "Post-bridge runtime surface for merged review + final verification on the allow path.",
62
+ description: "Package payload surface for merged review + final verification on the allow path.",
62
63
  files: [
63
- ".codex/skills/superspec-review/SKILL.md",
64
- ".codex/prompts/code-reviewer.md",
65
- ".codex/prompts/architect.md",
66
- ".codex/prompts/critic.md",
67
- ".codex/prompts/verifier.md",
68
- ".codex/agents/code-reviewer.toml",
69
- ".codex/agents/architect.toml",
70
- ".codex/agents/critic.toml",
71
- ".codex/agents/verifier.toml",
64
+ "templates/workflow/skills/superspec-review/SKILL.md",
65
+ "templates/workflow/prompts/code-reviewer.md",
66
+ "templates/workflow/prompts/architect.md",
67
+ "templates/workflow/prompts/critic.md",
68
+ "templates/workflow/prompts/verifier.md",
69
+ "adapters/codex/agents/code-reviewer.toml",
70
+ "adapters/codex/agents/architect.toml",
71
+ "adapters/codex/agents/critic.toml",
72
+ "adapters/codex/agents/verifier.toml",
72
73
  ],
73
74
  },
74
75
  {
75
76
  name: "archive_ready",
76
- description: "Post-bridge runtime surface for archive handoff after review passes.",
77
+ description: "Package payload surface for archive handoff after review passes.",
77
78
  files: [
78
- ".codex/skills/superspec-archive/SKILL.md",
79
+ "templates/workflow/skills/superspec-archive/SKILL.md",
79
80
  ],
80
81
  },
81
82
  {
82
83
  name: "round2_reviewer_prompt",
83
- description: "Post-bridge runtime surface for a round>1 reviewer lane.",
84
+ description: "Package payload surface for a round>1 reviewer lane.",
84
85
  files: [
85
- ".codex/prompts/critic.md",
86
- ".codex/agents/critic.toml",
86
+ "templates/workflow/prompts/critic.md",
87
+ "adapters/codex/agents/critic.toml",
87
88
  ],
88
89
  },
89
90
  {
90
91
  name: "request_changes_reopen_tasks",
91
- description: "Post-bridge runtime surface for a review round that routes back to apply via reopen_tasks.",
92
+ description: "Package payload surface for a review round that routes back to apply via reopen_tasks.",
92
93
  files: [
93
- ".codex/skills/superspec-review/SKILL.md",
94
- ".codex/prompts/code-reviewer.md",
95
- ".codex/prompts/architect.md",
96
- ".codex/prompts/critic.md",
97
- ".codex/agents/code-reviewer.toml",
98
- ".codex/agents/architect.toml",
99
- ".codex/agents/critic.toml",
94
+ "templates/workflow/skills/superspec-review/SKILL.md",
95
+ "templates/workflow/prompts/code-reviewer.md",
96
+ "templates/workflow/prompts/architect.md",
97
+ "templates/workflow/prompts/critic.md",
98
+ "adapters/codex/agents/code-reviewer.toml",
99
+ "adapters/codex/agents/architect.toml",
100
+ "adapters/codex/agents/critic.toml",
100
101
  ],
101
102
  },
102
103
  {
103
104
  name: "task_reopen_to_resolved",
104
- description: "Post-bridge runtime surface for reopened apply work from revert through successor executor completion.",
105
+ description: "Package payload surface for reopened apply work from revert through successor executor completion.",
105
106
  files: [
106
- ".codex/skills/superspec-apply/SKILL.md",
107
- ".codex/prompts/executor.md",
108
- ".codex/agents/executor.toml",
107
+ "templates/workflow/skills/superspec-apply/SKILL.md",
108
+ "templates/workflow/prompts/executor.md",
109
+ "adapters/codex/agents/executor.toml",
109
110
  ],
110
111
  },
111
112
  {
112
113
  name: "scope_expansion",
113
- description: "Post-bridge runtime surface for apply-side user confirmation when task scope expands.",
114
+ description: "Package payload surface for apply-side user confirmation when task scope expands.",
114
115
  files: [
115
- ".codex/skills/superspec-apply/SKILL.md",
116
+ "templates/workflow/skills/superspec-apply/SKILL.md",
116
117
  ],
117
118
  },
118
119
  ];
@@ -166,24 +167,6 @@ function ensureReadableTextFile(repoRoot, relPath) {
166
167
  if (existsSync(absPath) && statSync(absPath).isFile()) {
167
168
  return readFileSync(absPath, "utf8");
168
169
  }
169
- const skillMatch = /^\.codex\/skills\/([^/]+)\/SKILL\.md$/u.exec(relPath);
170
- if (skillMatch) {
171
- const fallback = join(repoRoot, "templates", "workflow", "skills", skillMatch[1], "SKILL.md");
172
- if (existsSync(fallback) && statSync(fallback).isFile())
173
- return readFileSync(fallback, "utf8");
174
- }
175
- const promptMatch = /^\.codex\/prompts\/([^/]+)\.md$/u.exec(relPath);
176
- if (promptMatch) {
177
- const fallback = join(repoRoot, "templates", "workflow", "prompts", `${promptMatch[1]}.md`);
178
- if (existsSync(fallback) && statSync(fallback).isFile())
179
- return readFileSync(fallback, "utf8");
180
- }
181
- const agentMatch = /^\.codex\/agents\/([^/]+)\.toml$/u.exec(relPath);
182
- if (agentMatch) {
183
- const fallback = join(repoRoot, "adapters", "codex", "agents", `${agentMatch[1]}.toml`);
184
- if (existsSync(fallback) && statSync(fallback).isFile())
185
- return readFileSync(fallback, "utf8");
186
- }
187
170
  throw new GuardError(`packet_measure_missing_file: ${relPath}`);
188
171
  }
189
172
  function dedupePaths(paths) {
@@ -365,39 +348,21 @@ function measureMaterializedSamples(specs) {
365
348
  ctx.cleanup();
366
349
  }
367
350
  }
368
- function templateWorkflowSkillPaths() {
369
- return REQUIRED_SUPERSPEC_WORKFLOW_SKILLS.map((name) => `templates/workflow/skills/${name}/SKILL.md`);
370
- }
371
- function templatePromptPaths() {
372
- return REQUIRED_SUPERSPEC_AGENT_ROLES.map((name) => `templates/workflow/prompts/${name}.md`);
373
- }
374
- function adapterAgentPaths() {
375
- return REQUIRED_SUPERSPEC_AGENT_ROLES.map((name) => `adapters/codex/agents/${name}.toml`);
376
- }
377
- function repoLocalSkillPaths(repoRoot) {
378
- return REQUIRED_SUPERSPEC_WORKFLOW_SKILLS
379
- .map((name) => join(".codex", "skills", name, "SKILL.md"))
380
- .filter((relPath) => existsSync(join(repoRoot, relPath)));
381
- }
382
- function runtimeBridgeSkillPaths() {
383
- return [];
351
+ function installPayloadSourcePaths(repoRoot) {
352
+ const { mappings, problems } = load_install_map(repoRoot);
353
+ if (problems.length > 0)
354
+ throw new GuardError(`packet_measure_install_map_invalid: ${problems.join("; ")}`);
355
+ return mappings
356
+ .filter((mapping) => mapping.kind === "skill" || mapping.kind === "prompt" || mapping.kind === "agent")
357
+ .map((mapping) => mapping.source);
384
358
  }
385
359
  export function representative_scenarios() {
386
360
  return REPRESENTATIVE_SCENARIOS;
387
361
  }
388
362
  export function measure_packet_surface_report(repoRoot) {
389
- const fixedSurfaceInstallUpperBound = measureSurface(repoRoot, [
390
- ...templateWorkflowSkillPaths(),
391
- ...templatePromptPaths(),
392
- ...adapterAgentPaths(),
393
- ...repoLocalSkillPaths(repoRoot),
394
- ]);
395
- const fixedSurfaceRuntimeRequiredSubset = measureSurface(repoRoot, [
396
- ...templateWorkflowSkillPaths(),
397
- ...templatePromptPaths(),
398
- ...adapterAgentPaths(),
399
- ...runtimeBridgeSkillPaths(),
400
- ]);
363
+ const packagePayloadSourcePaths = installPayloadSourcePaths(repoRoot);
364
+ const fixedSurfaceInstallUpperBound = measureSurface(repoRoot, packagePayloadSourcePaths);
365
+ const fixedSurfaceRuntimeRequiredSubset = measureSurface(repoRoot, packagePayloadSourcePaths);
401
366
  const scenarios = REPRESENTATIVE_SCENARIOS.map((scenario) => ({
402
367
  name: scenario.name,
403
368
  description: scenario.description,
@@ -5,13 +5,14 @@ import {} from "./cli_args.js";
5
5
  import { CLAIM_ADJUDICATION_DECISIONS, FINDING_ADJUDICATION_DECISIONS, fingerprint_obj, 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, deepEqual, isObject, reason, renderList, runtime, safe_within, sha256_text, } from "./util.js";
6
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
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, apply_worker_chain_lifecycle_reasons, } from "./gates.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
9
  import { final_verification_evidences, index_evidence, live_pass, live_user_confirmations } from "./evidence.js";
10
10
  import { file_blob_sha, dirty_worktree_paths } from "./git.js";
11
11
  import { preset_upgrade_reasons, preset_upgrade_required_from_context } from "./archive.js";
12
12
  import { state_corrupt_reasons, state_stale_reasons } from "./state.js";
13
13
  import { parse_tasks, resolve_test_contract_command, splitList, test_contract_invariant_refs_by_test } from "./tasks.js";
14
14
  import { APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS, APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS, APPLY_TEST_RUNNER_REPORT_REQUIRED_FIELDS, APPLY_VERIFIER_REPORT_REQUIRED_FIELDS, apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, apply_worker_protected_path_refs, compute_apply_worker_freshness, fingerprint_digest, fingerprint_matches, pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, pre_edit_evidence_ref_reasons, read_pinned_artifact_json, worker_input_ref_digest, worker_test_run_reasons, } from "./apply_worker_chain.js";
15
+ import { apply_worker_chain_lifecycle_state } from "./apply_worker_chain_lifecycle.js";
15
16
  function load_packet_context(change) {
16
17
  if (typeof runtime.load_context === "function") {
17
18
  const [status, repoRoot, changeRoot, evidences] = runtime.load_context(change);
@@ -688,91 +689,9 @@ function active_apply_worker_chain_id(ctx, taskId) {
688
689
  const active = active_apply_worker_chain(ctx, taskId).active;
689
690
  return active ? String(active.apply_worker_chain_id) : null;
690
691
  }
691
- function terminal_apply_worker_chain_valid(ctx, ev, taskId, chainId) {
692
- const state = String(ev.chain_state ?? "");
693
- if (state === "closed") {
694
- return shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.executor_report_ref, "executor_report_ref", { kind: "worker_report", role: "executor", taskId, chainId }).length === 0
695
- && shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.task_code_review_report_ref, "task_code_review_report_ref", { kind: "worker_report", role: "code-reviewer", taskId, chainId }).length === 0
696
- && shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.verifier_report_ref, "verifier_report_ref", { kind: "worker_report", role: "verifier", taskId, chainId }).length === 0;
697
- }
698
- if (state === "abandoned") {
699
- if ("restored_implementation_fingerprint" in ev && fingerprint_digest(ev.restored_implementation_fingerprint))
700
- return true;
701
- if ("serial_takeover_baseline_ref" in ev) {
702
- return shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.serial_takeover_baseline_ref, "serial_takeover_baseline_ref", { kind: "status_report", role: "verifier", taskId, chainId }).length === 0
703
- && Array.isArray(ev.successor_green_evidence_refs)
704
- && ev.successor_green_evidence_refs.length > 0
705
- && ev.successor_green_evidence_refs.every((item) => typeof item === "string" && item.length > 0);
706
- }
707
- }
708
- return false;
709
- }
710
692
  function apply_worker_chain_packet_state(ctx, taskId) {
711
- const chains = ctx.evidences
712
- .filter((ev) => isObject(ev) && !ev._invalid && ev.status === "pass" && ev.gate === "task_complete" && ev.kind === "apply_worker_chain" && ev.task_id === taskId)
713
- .filter((ev) => typeof ev.apply_worker_chain_id === "string" && ev.apply_worker_chain_id);
714
- const blockers = [];
715
- const activeByChain = new Map();
716
- const terminalsByChain = new Map();
717
- for (const ev of chains) {
718
- const chainId = String(ev.apply_worker_chain_id ?? "");
719
- if (String(ev.chain_state ?? "") === "active") {
720
- const bucket = activeByChain.get(chainId) ?? [];
721
- bucket.push(ev);
722
- activeByChain.set(chainId, bucket);
723
- }
724
- if (String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned") {
725
- const bucket = terminalsByChain.get(chainId) ?? [];
726
- bucket.push(ev);
727
- terminalsByChain.set(chainId, bucket);
728
- }
729
- }
730
- const validTerminalChainIds = new Set(chains
731
- .filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
732
- .filter((ev) => terminal_apply_worker_chain_valid(ctx, ev, taskId, String(ev.apply_worker_chain_id ?? "")))
733
- .map((ev) => String(ev.apply_worker_chain_id ?? ""))
734
- .filter(Boolean));
735
- const duplicateActives = [...activeByChain.entries()]
736
- .filter(([chainId]) => !validTerminalChainIds.has(chainId))
737
- .map(([, items]) => items)
738
- .filter((items) => items.length > 1)
739
- .flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
740
- .filter(Boolean)
741
- .sort();
742
- const activeCandidates = [...activeByChain.entries()]
743
- .filter(([chainId]) => !validTerminalChainIds.has(chainId))
744
- .map(([, items]) => items[0])
745
- .filter(Boolean);
746
- blockers.push(...apply_worker_chain_lifecycle_reasons(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId).filter((item) => (item.code === "apply_worker_chain_active_conflict"
747
- || item.code === "apply_worker_chain_terminal_conflict"
748
- || item.code === "apply_worker_chain_terminal_invalid"
749
- || item.code === "apply_worker_chain_missing_active")));
750
- const parallelActives = activeCandidates.length > 1
751
- ? activeCandidates.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")).filter(Boolean).sort()
752
- : [];
753
- const activeConflicts = [...new Set([...duplicateActives, ...parallelActives])].sort();
754
- if (activeConflicts.length > 0) {
755
- blockers.push(reason("apply_worker_chain_active_conflict", `task ${taskId} has conflicting active apply worker chain markers: ${renderList(activeConflicts)}`, activeConflicts));
756
- }
757
- const duplicateTerminals = [...terminalsByChain.values()]
758
- .filter((items) => items.length > 1)
759
- .flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
760
- .filter(Boolean)
761
- .sort();
762
- if (duplicateTerminals.length > 0) {
763
- blockers.push(reason("apply_worker_chain_terminal_conflict", `task ${taskId} has duplicate apply worker chain terminal markers: ${renderList(duplicateTerminals)}`, duplicateTerminals));
764
- }
765
- const invalidTerminals = chains
766
- .filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
767
- .filter((ev) => !terminal_apply_worker_chain_valid(ctx, ev, taskId, String(ev.apply_worker_chain_id ?? "")))
768
- .map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? ""))
769
- .filter(Boolean)
770
- .sort();
771
- if (invalidTerminals.length > 0) {
772
- blockers.push(reason("apply_worker_chain_terminal_invalid", `task ${taskId} has invalid apply worker chain terminal markers: ${renderList(invalidTerminals)}`, invalidTerminals));
773
- }
774
- const active = activeCandidates[0] ?? null;
775
- return { active: blockers.length === 0 ? active : null, blockers };
693
+ const state = apply_worker_chain_lifecycle_state(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId);
694
+ return { active: state.active, blockers: state.packetBlockers };
776
695
  }
777
696
  function active_apply_worker_chain(ctx, taskId) {
778
697
  return apply_worker_chain_packet_state(ctx, taskId);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import "./src/core.ts";
3
+ export * from "./src/hooks/adapter.ts";
4
+ export declare function main_hook(argv?: string[]): number;
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import "./src/core.js";
3
+ export * from "./src/hooks/adapter.js";
4
+ import { readFileSync, realpathSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { runHookAdapter } from "./src/hooks/adapter.js";
7
+ function realpathMaybe(filePath) {
8
+ try {
9
+ return realpathSync(filePath);
10
+ }
11
+ catch {
12
+ return resolve(filePath);
13
+ }
14
+ }
15
+ const currentFile = realpathMaybe(process.argv[1] ?? "");
16
+ export function main_hook(argv = process.argv.slice(2)) {
17
+ const stdin = readStdin();
18
+ const result = runHookAdapter(argv, stdin);
19
+ if (result.stdout)
20
+ process.stdout.write(result.stdout);
21
+ if (result.stderr)
22
+ process.stderr.write(result.stderr);
23
+ return result.code;
24
+ }
25
+ function readStdin() {
26
+ try {
27
+ return readFileSync(0, "utf8");
28
+ }
29
+ catch {
30
+ return "";
31
+ }
32
+ }
33
+ if (currentFile === realpathMaybe(new URL(import.meta.url).pathname)) {
34
+ process.exitCode = main_hook();
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "SuperSpec workflow package: guard runtime, generic workflow templates, and Codex adapter payload.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,7 @@
21
21
  "bin": {
22
22
  "superspec": "bin/superspec.js",
23
23
  "superspec-guard": "bin/superspec-guard.js",
24
+ "superspec-hook": "bin/superspec-hook.js",
24
25
  "superspec-init": "bin/superspec-init.js"
25
26
  },
26
27
  "exports": {
@@ -32,6 +33,10 @@
32
33
  "types": "./dist/superspec_guard.d.ts",
33
34
  "default": "./dist/superspec_guard.js"
34
35
  },
36
+ "./superspec_hook": {
37
+ "types": "./dist/superspec_hook.d.ts",
38
+ "default": "./dist/superspec_hook.js"
39
+ },
35
40
  "./superspec_init": {
36
41
  "types": "./dist/superspec_init.d.ts",
37
42
  "default": "./dist/superspec_init.js"
@@ -51,7 +56,7 @@
51
56
  "scripts": {
52
57
  "build": "node build.js",
53
58
  "typecheck": "tsc --noEmit",
54
- "test": "node --test tests/test_install_engine.test.ts tests/test_packet_measure.test.ts tests/test_real_openspec_smoke.test.ts tests/test_superspec_cli.test.ts tests/test_superspec_guard_output.test.ts tests/test_superspec_guard_core.test.ts tests/test_superspec_guard_review.test.ts tests/test_superspec_guard_request_reopen.test.ts tests/test_superspec_guard_archive.test.ts tests/test_superspec_guard_packet.test.ts tests/test_superspec_guard_disclosure.test.ts tests/test_superspec_skills.test.ts",
59
+ "test": "node --test tests/test_install_engine.test.ts tests/test_packet_measure.test.ts tests/test_real_openspec_smoke.test.ts tests/test_superspec_cli.test.ts tests/test_superspec_guard_output.test.ts tests/test_superspec_guard_core.test.ts tests/test_superspec_guard_review.test.ts tests/test_superspec_guard_request_reopen.test.ts tests/test_superspec_guard_archive.test.ts tests/test_superspec_guard_packet.test.ts tests/test_superspec_guard_disclosure.test.ts tests/test_superspec_hooks.test.ts tests/test_superspec_skills.test.ts",
55
60
  "prepack": "npm run build",
56
61
  "prepublishOnly": "npm run build",
57
62
  "pack:dry-run": "npm pack --dry-run"
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://superspec.local/schemas/hook-event.schema.json",
4
+ "title": "SuperSpec Codex Hook Event",
5
+ "type": "object",
6
+ "required": ["hook_event_name", "cwd"],
7
+ "properties": {
8
+ "session_id": { "type": "string" },
9
+ "turn_id": { "type": "string" },
10
+ "transcript_path": { "type": ["string", "null"] },
11
+ "cwd": { "type": "string" },
12
+ "hook_event_name": {
13
+ "type": "string",
14
+ "enum": ["PreToolUse", "PostToolUse", "SubagentStart", "SubagentStop"]
15
+ },
16
+ "permission_mode": { "type": "string" },
17
+ "tool_name": { "type": "string" },
18
+ "tool_use_id": { "type": "string" },
19
+ "tool_input": { "type": "object" },
20
+ "tool_response": { "type": ["object", "array", "string", "number", "boolean", "null"] },
21
+ "agent_id": { "type": "string" },
22
+ "agent_type": { "type": "string" },
23
+ "agent_transcript_path": { "type": ["string", "null"] },
24
+ "last_assistant_message": { "type": ["string", "null"] }
25
+ },
26
+ "additionalProperties": true
27
+ }