@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.
- package/adapters/codex/install-map.json +5 -0
- package/bin/superspec-hook.js +4 -0
- package/dist/src/apply_worker_chain_lifecycle.d.ts +19 -0
- package/dist/src/apply_worker_chain_lifecycle.js +278 -0
- package/dist/src/archive.js +2 -0
- package/dist/src/cli_args.d.ts +5 -0
- package/dist/src/cli_args.js +41 -0
- package/dist/src/core.js +36 -0
- package/dist/src/evidence.js +3 -0
- package/dist/src/gates.js +8 -258
- package/dist/src/hooks/adapter.d.ts +5 -0
- package/dist/src/hooks/adapter.js +311 -0
- package/dist/src/hooks/guard_api.d.ts +12 -0
- package/dist/src/hooks/guard_api.js +797 -0
- package/dist/src/hooks/health.d.ts +4 -0
- package/dist/src/hooks/health.js +98 -0
- package/dist/src/hooks/policy_event.d.ts +41 -0
- package/dist/src/hooks/policy_event.js +2624 -0
- package/dist/src/hooks/types.d.ts +72 -0
- package/dist/src/hooks/types.js +1 -0
- package/dist/src/hooks/validation.d.ts +3 -0
- package/dist/src/hooks/validation.js +70 -0
- package/dist/src/packet_measure.js +67 -102
- package/dist/src/packet_render.js +4 -85
- package/dist/superspec_hook.d.ts +4 -0
- package/dist/superspec_hook.js +35 -0
- package/package.json +7 -2
- package/schemas/hook-event.schema.json +27 -0
- package/templates/hooks/codex-hooks.json +61 -0
- package/templates/workflow/skills/superspec-apply/SKILL.md +7 -0
- package/templates/workflow/skills/superspec-archive/SKILL.md +8 -0
- package/templates/workflow/skills/superspec-explore/SKILL.md +7 -0
- package/templates/workflow/skills/superspec-propose/SKILL.md +7 -0
- 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,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,
|
|
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: "
|
|
11
|
+
description: "Package payload surface for discovery authoring plus proposal-entry critic review.",
|
|
11
12
|
files: [
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
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: "
|
|
20
|
+
description: "Package payload surface for proposal authoring and proposal critic review.",
|
|
20
21
|
files: [
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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: "
|
|
29
|
+
description: "Package payload surface for design authoring and the architect/critic/test-engineer review bundle.",
|
|
29
30
|
files: [
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
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: "
|
|
42
|
+
description: "Package payload surface for test-contract authoring and its review lanes.",
|
|
42
43
|
files: [
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
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: "
|
|
53
|
+
description: "Package payload surface for apply orchestration and bounded executor handoff.",
|
|
53
54
|
files: [
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
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: "
|
|
62
|
+
description: "Package payload surface for merged review + final verification on the allow path.",
|
|
62
63
|
files: [
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
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: "
|
|
77
|
+
description: "Package payload surface for archive handoff after review passes.",
|
|
77
78
|
files: [
|
|
78
|
-
"
|
|
79
|
+
"templates/workflow/skills/superspec-archive/SKILL.md",
|
|
79
80
|
],
|
|
80
81
|
},
|
|
81
82
|
{
|
|
82
83
|
name: "round2_reviewer_prompt",
|
|
83
|
-
description: "
|
|
84
|
+
description: "Package payload surface for a round>1 reviewer lane.",
|
|
84
85
|
files: [
|
|
85
|
-
"
|
|
86
|
-
"
|
|
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: "
|
|
92
|
+
description: "Package payload surface for a review round that routes back to apply via reopen_tasks.",
|
|
92
93
|
files: [
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
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: "
|
|
105
|
+
description: "Package payload surface for reopened apply work from revert through successor executor completion.",
|
|
105
106
|
files: [
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
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: "
|
|
114
|
+
description: "Package payload surface for apply-side user confirmation when task scope expands.",
|
|
114
115
|
files: [
|
|
115
|
-
"
|
|
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
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return
|
|
373
|
-
|
|
374
|
-
|
|
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
|
|
390
|
-
|
|
391
|
-
|
|
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,
|
|
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
|
|
712
|
-
|
|
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,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.
|
|
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
|
+
}
|