sneakoscope 0.9.11 → 0.9.13
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/README.md +28 -2
- package/crates/sks-core/Cargo.lock +7 -0
- package/crates/sks-core/Cargo.toml +10 -0
- package/crates/sks-core/src/main.rs +202 -0
- package/package.json +15 -3
- package/src/cli/args.mjs +49 -0
- package/src/cli/command-registry.mjs +128 -0
- package/src/cli/feature-commands.mjs +112 -6
- package/src/cli/install-helpers.mjs +14 -7
- package/src/cli/legacy-main.mjs +4147 -0
- package/src/cli/main.mjs +7 -4138
- package/src/cli/output.mjs +9 -0
- package/src/cli/router.mjs +30 -0
- package/src/commands/all-features.mjs +6 -0
- package/src/commands/codex-app.mjs +30 -0
- package/src/commands/codex-lb.mjs +47 -0
- package/src/commands/db.mjs +6 -0
- package/src/commands/doctor.mjs +46 -0
- package/src/commands/features.mjs +6 -0
- package/src/commands/help.mjs +77 -0
- package/src/commands/hooks.mjs +6 -0
- package/src/commands/perf.mjs +91 -0
- package/src/commands/proof.mjs +103 -0
- package/src/commands/root.mjs +24 -0
- package/src/commands/version.mjs +5 -0
- package/src/commands/wiki.mjs +95 -0
- package/src/core/codex-lb-circuit.mjs +130 -0
- package/src/core/db-safety.mjs +18 -3
- package/src/core/feature-fixtures.mjs +103 -0
- package/src/core/feature-registry.mjs +117 -11
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +17 -6
- package/src/core/language-preference.mjs +106 -0
- package/src/core/pipeline.mjs +24 -0
- package/src/core/proof/claim-ledger.mjs +9 -0
- package/src/core/proof/command-ledger.mjs +17 -0
- package/src/core/proof/evidence-collector.mjs +33 -0
- package/src/core/proof/file-change-ledger.mjs +6 -0
- package/src/core/proof/proof-reader.mjs +30 -0
- package/src/core/proof/proof-redaction.test-helper.mjs +9 -0
- package/src/core/proof/proof-schema.mjs +42 -0
- package/src/core/proof/proof-writer.mjs +81 -0
- package/src/core/proof/route-adapter.mjs +74 -0
- package/src/core/proof/route-proof-gate.mjs +33 -0
- package/src/core/proof/route-proof-policy.mjs +96 -0
- package/src/core/proof/selftest-proof-fixtures.mjs +54 -0
- package/src/core/proof/validation.mjs +20 -0
- package/src/core/routes.mjs +4 -3
- package/src/core/rust-accelerator.mjs +55 -3
- package/src/core/secret-redaction.mjs +69 -0
- package/src/core/version-manager.mjs +11 -7
- package/src/core/version.mjs +1 -0
- package/src/core/wiki-image/bbox.mjs +10 -0
- package/src/core/wiki-image/callout-parser.mjs +16 -0
- package/src/core/wiki-image/computer-use-ledger.mjs +38 -0
- package/src/core/wiki-image/image-hash.mjs +42 -0
- package/src/core/wiki-image/image-relation.mjs +2 -0
- package/src/core/wiki-image/image-voxel-ledger.mjs +147 -0
- package/src/core/wiki-image/image-voxel-schema.mjs +16 -0
- package/src/core/wiki-image/proof-linker.mjs +12 -0
- package/src/core/wiki-image/validation.mjs +53 -0
- package/src/core/wiki-image/visual-anchor.mjs +42 -0
package/README.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# Sneakoscope Codex
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fast proof-first Codex trust layer with image-based Voxel TriWiki.
|
|
4
|
+
|
|
5
|
+
Sneakoscope Codex (`sks`) is a Codex CLI/App harness for repeatable workflows. It adds terminal commands, Codex App `$` commands, tmux workspaces, Team/QA/Research routes, pipeline plans, Computer Use, imagegen UI/UX review, Goal, Context7, DB safety, Voxel TriWiki, design-system routing, skill dreaming, completion proof, and Honest Mode.
|
|
6
|
+
|
|
7
|
+
## 60-second start
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i -g sneakoscope
|
|
11
|
+
sks root
|
|
12
|
+
sks doctor
|
|
13
|
+
sks codex-app check
|
|
14
|
+
sks selftest --mock
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Three core promises
|
|
18
|
+
|
|
19
|
+
1. Image-based Voxel TriWiki memory
|
|
20
|
+
2. Codex App / codex-lb operational readiness
|
|
21
|
+
3. Completion proof for every serious route
|
|
4
22
|
|
|
5
23
|
## Quick Start
|
|
6
24
|
|
|
@@ -12,6 +30,14 @@ sks root
|
|
|
12
30
|
sks
|
|
13
31
|
```
|
|
14
32
|
|
|
33
|
+
`0.9.13` connects serious routes to Completion Proof, visual routes to image voxel anchors, hook replay to shared runtime policy, codex-lb launch failures to circuit health, and Rust accelerator commands to JS fallback parity. Rust accelerator source is included in the npm package; until prebuilt binaries ship, SKS uses JS fallbacks unless `SKS_RS_BIN` or a source-checkout `sks-rs` binary is available.
|
|
34
|
+
|
|
35
|
+
Learn more:
|
|
36
|
+
- Completion Proof: [docs/completion-proof.md](docs/completion-proof.md)
|
|
37
|
+
- Image Voxel TriWiki: [docs/image-voxel-ledger.md](docs/image-voxel-ledger.md)
|
|
38
|
+
- Codex App Hooks/PAT: [docs/hooks-pat.md](docs/hooks-pat.md)
|
|
39
|
+
- codex-lb: [docs/codex-lb.md](docs/codex-lb.md)
|
|
40
|
+
|
|
15
41
|
`npm i -g sneakoscope` automatically refreshes the `sks` command shim, global Codex App `$` skills, and SKS bootstrap surface. When the install is run from a project, postinstall bootstraps that project. When it is run outside a repo/project marker, postinstall bootstraps the per-user global runtime root instead of writing `.sneakoscope` into a random current directory. `sks root` tells you which root SKS will use.
|
|
16
42
|
|
|
17
43
|
If you only want a one-shot run without keeping `sks` installed globally:
|
|
@@ -187,7 +213,7 @@ sks codex-lb repair
|
|
|
187
213
|
sks
|
|
188
214
|
```
|
|
189
215
|
|
|
190
|
-
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, writes the upstream codex-lb Codex CLI / IDE Extension provider block into `~/.codex/config.toml` for Codex App routing, loads the provider env key for tmux launches, and syncs the macOS user launch environment so the Codex App can see `CODEX_LB_API_KEY` after restart. If the provider block disappears but the stored env file is still recoverable, bare `sks`, npm postinstall upgrades, `sks doctor --fix`, and `sks codex-lb repair` restore it with `env_key = "CODEX_LB_API_KEY"`, `supports_websockets = true`, and `requires_openai_auth = true` as documented by codex-lb. If an older SKS release left the codex-lb dashboard key only in the shared Codex `auth.json` login cache, SKS migrates that key back into `~/.codex/sks-codex-lb.env` when a codex-lb provider or env base URL is already recoverable. It does not rewrite the shared Codex `auth.json` login cache by default; set `SKS_CODEX_LB_SYNC_CODEX_LOGIN=1` only if you intentionally want the old API-key login-cache behavior. When codex-lb is active, SKS opens a fresh `sks-codex-lb-*` tmux session and sweeps older detached codex-lb sessions for the same repo before launch so stale Responses API chains are not reused. Configured launch paths
|
|
216
|
+
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, writes the upstream codex-lb Codex CLI / IDE Extension provider block into `~/.codex/config.toml` for Codex App routing, loads the provider env key for tmux launches, and syncs the macOS user launch environment so the Codex App can see `CODEX_LB_API_KEY` after restart. If the provider block disappears but the stored env file is still recoverable, bare `sks`, npm postinstall upgrades, `sks doctor --fix`, and `sks codex-lb repair` restore it with `env_key = "CODEX_LB_API_KEY"`, `supports_websockets = true`, and `requires_openai_auth = true` as documented by codex-lb. If an older SKS release left the codex-lb dashboard key only in the shared Codex `auth.json` login cache, SKS migrates that key back into `~/.codex/sks-codex-lb.env` when a codex-lb provider or env base URL is already recoverable. It does not rewrite the shared Codex `auth.json` login cache by default; set `SKS_CODEX_LB_SYNC_CODEX_LOGIN=1` only if you intentionally want the old API-key login-cache behavior. When codex-lb is active, SKS opens a fresh `sks-codex-lb-*` tmux session and sweeps older detached codex-lb sessions for the same repo before launch so stale Responses API chains are not reused. Configured launch paths run a response-chain health check. `previous_response_not_found` is treated as a stateless-LB warning and keeps codex-lb active. Hard failures are surfaced to the user; SKS only bypasses codex-lb when the user chooses OAuth fallback or `SKS_CODEX_LB_AUTOBYPASS=1` is set.
|
|
191
217
|
|
|
192
218
|
If codex-lb provider auth drifts after launch/reinstall, run `sks doctor --fix` or `sks codex-lb repair`; to replace it, run `sks codex-lb reconfigure --host <domain> --api-key <key>`.
|
|
193
219
|
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
use std::fs::File;
|
|
2
|
+
use std::io::{self, Read, Seek, SeekFrom};
|
|
3
|
+
|
|
4
|
+
fn main() {
|
|
5
|
+
let mut args = std::env::args().skip(1);
|
|
6
|
+
match args.next().as_deref() {
|
|
7
|
+
Some("--version") => println!("sks-rs 0.9.13"),
|
|
8
|
+
Some("compact-info") => {
|
|
9
|
+
let mut input = String::new();
|
|
10
|
+
let _ = io::stdin().read_to_string(&mut input);
|
|
11
|
+
println!("{{\"ok\":true,\"engine\":\"rust\",\"input_bytes\":{}}}", input.as_bytes().len());
|
|
12
|
+
}
|
|
13
|
+
Some("jsonl-tail") => {
|
|
14
|
+
let path = args.next().unwrap_or_default();
|
|
15
|
+
let mut bytes: u64 = 262144;
|
|
16
|
+
while let Some(arg) = args.next() {
|
|
17
|
+
if arg == "--bytes" {
|
|
18
|
+
if let Some(raw) = args.next() {
|
|
19
|
+
bytes = raw.parse().unwrap_or(bytes);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
match tail_file(&path, bytes) {
|
|
24
|
+
Ok(text) => print!("{}", text),
|
|
25
|
+
Err(err) => {
|
|
26
|
+
eprintln!("{}", err);
|
|
27
|
+
std::process::exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
Some("image-hash") => {
|
|
32
|
+
let path = args.next().unwrap_or_default();
|
|
33
|
+
match image_hash(&path) {
|
|
34
|
+
Ok((sha, bytes)) => println!("{{\"ok\":true,\"engine\":\"rust\",\"path\":\"{}\",\"sha256\":\"{}\",\"bytes\":{}}}", json_escape(&path), sha, bytes),
|
|
35
|
+
Err(err) => {
|
|
36
|
+
println!("{{\"ok\":false,\"engine\":\"rust\",\"path\":\"{}\",\"error\":\"{}\"}}", json_escape(&path), json_escape(&err.to_string()));
|
|
37
|
+
std::process::exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
Some("voxel-validate") => {
|
|
42
|
+
let path = args.next().unwrap_or_default();
|
|
43
|
+
match std::fs::read_to_string(&path) {
|
|
44
|
+
Ok(text) => {
|
|
45
|
+
let report = voxel_validate(&text);
|
|
46
|
+
println!("{}", report);
|
|
47
|
+
if report.contains("\"ok\":false") { std::process::exit(1); }
|
|
48
|
+
}
|
|
49
|
+
Err(err) => {
|
|
50
|
+
println!("{{\"ok\":false,\"engine\":\"rust\",\"issues\":[\"read_error\"],\"error\":\"{}\"}}", json_escape(&err.to_string()));
|
|
51
|
+
std::process::exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
Some("secret-scan") => {
|
|
56
|
+
let path = args.next().unwrap_or_default();
|
|
57
|
+
match std::fs::read_to_string(&path) {
|
|
58
|
+
Ok(text) => {
|
|
59
|
+
let found = ["CODEX_ACCESS_TOKEN", "OPENAI_API_KEY", "CODEX_LB_API_KEY", "sk-proj-", "sk-clb-", "github_pat_"]
|
|
60
|
+
.iter()
|
|
61
|
+
.any(|needle| text.contains(needle));
|
|
62
|
+
println!("{{\"ok\":{},\"engine\":\"rust\",\"findings\":{}}}", if found { "false" } else { "true" }, if found { 1 } else { 0 });
|
|
63
|
+
if found { std::process::exit(1); }
|
|
64
|
+
}
|
|
65
|
+
Err(err) => {
|
|
66
|
+
eprintln!("{}", err);
|
|
67
|
+
std::process::exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
_ => {
|
|
72
|
+
eprintln!("sks-rs optional accelerator. Commands: --version, compact-info, jsonl-tail, secret-scan, image-hash, voxel-validate");
|
|
73
|
+
std::process::exit(2);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn image_hash(path: &str) -> io::Result<(String, u64)> {
|
|
79
|
+
let mut file = File::open(path)?;
|
|
80
|
+
let mut data = Vec::new();
|
|
81
|
+
file.read_to_end(&mut data)?;
|
|
82
|
+
let bytes = data.len() as u64;
|
|
83
|
+
Ok((sha256_hex(&data), bytes))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn sha256_hex(data: &[u8]) -> String {
|
|
87
|
+
const K: [u32; 64] = [
|
|
88
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
89
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
90
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
91
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
92
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
93
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
94
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
95
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
96
|
+
];
|
|
97
|
+
let mut h: [u32; 8] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
|
98
|
+
let bit_len = (data.len() as u64) * 8;
|
|
99
|
+
let mut msg = data.to_vec();
|
|
100
|
+
msg.push(0x80);
|
|
101
|
+
while (msg.len() % 64) != 56 { msg.push(0); }
|
|
102
|
+
msg.extend_from_slice(&bit_len.to_be_bytes());
|
|
103
|
+
for chunk in msg.chunks(64) {
|
|
104
|
+
let mut w = [0u32; 64];
|
|
105
|
+
for i in 0..16 {
|
|
106
|
+
w[i] = u32::from_be_bytes([chunk[i * 4], chunk[i * 4 + 1], chunk[i * 4 + 2], chunk[i * 4 + 3]]);
|
|
107
|
+
}
|
|
108
|
+
for i in 16..64 {
|
|
109
|
+
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
|
|
110
|
+
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
|
|
111
|
+
w[i] = w[i - 16].wrapping_add(s0).wrapping_add(w[i - 7]).wrapping_add(s1);
|
|
112
|
+
}
|
|
113
|
+
let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) = (h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]);
|
|
114
|
+
for i in 0..64 {
|
|
115
|
+
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
|
|
116
|
+
let ch = (e & f) ^ ((!e) & g);
|
|
117
|
+
let temp1 = hh.wrapping_add(s1).wrapping_add(ch).wrapping_add(K[i]).wrapping_add(w[i]);
|
|
118
|
+
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
|
|
119
|
+
let maj = (a & b) ^ (a & c) ^ (b & c);
|
|
120
|
+
let temp2 = s0.wrapping_add(maj);
|
|
121
|
+
hh = g;
|
|
122
|
+
g = f;
|
|
123
|
+
f = e;
|
|
124
|
+
e = d.wrapping_add(temp1);
|
|
125
|
+
d = c;
|
|
126
|
+
c = b;
|
|
127
|
+
b = a;
|
|
128
|
+
a = temp1.wrapping_add(temp2);
|
|
129
|
+
}
|
|
130
|
+
h[0] = h[0].wrapping_add(a);
|
|
131
|
+
h[1] = h[1].wrapping_add(b);
|
|
132
|
+
h[2] = h[2].wrapping_add(c);
|
|
133
|
+
h[3] = h[3].wrapping_add(d);
|
|
134
|
+
h[4] = h[4].wrapping_add(e);
|
|
135
|
+
h[5] = h[5].wrapping_add(f);
|
|
136
|
+
h[6] = h[6].wrapping_add(g);
|
|
137
|
+
h[7] = h[7].wrapping_add(hh);
|
|
138
|
+
}
|
|
139
|
+
h.iter().map(|x| format!("{:08x}", x)).collect::<String>()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn voxel_validate(text: &str) -> String {
|
|
143
|
+
let mut issues: Vec<&str> = Vec::new();
|
|
144
|
+
if !text.contains("\"schema\"") || !text.contains("sks.image-voxel-ledger.v1") { issues.push("schema"); }
|
|
145
|
+
if !text.contains("\"images\"") { issues.push("missing_images"); }
|
|
146
|
+
if !text.contains("\"anchors\"") { issues.push("missing_anchors"); }
|
|
147
|
+
let image_count = count_object_entries(text, "images");
|
|
148
|
+
let anchor_count = count_object_entries(text, "anchors");
|
|
149
|
+
if image_count == 0 { issues.push("missing_images"); }
|
|
150
|
+
if anchor_count == 0 { issues.push("missing_anchors"); }
|
|
151
|
+
issues.sort();
|
|
152
|
+
issues.dedup();
|
|
153
|
+
let ok = issues.is_empty();
|
|
154
|
+
let issue_json = issues.iter().map(|x| format!("\"{}\"", json_escape(x))).collect::<Vec<_>>().join(",");
|
|
155
|
+
format!("{{\"ok\":{},\"engine\":\"rust\",\"schema\":\"sks.image-voxel-ledger.v1\",\"images\":{},\"anchors\":{},\"issues\":[{}]}}", if ok { "true" } else { "false" }, image_count, anchor_count, issue_json)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn count_object_entries(text: &str, key: &str) -> usize {
|
|
159
|
+
let marker = format!("\"{}\"", key);
|
|
160
|
+
let Some(start) = text.find(&marker) else { return 0; };
|
|
161
|
+
let Some(open) = text[start..].find('[').map(|i| start + i) else { return 0; };
|
|
162
|
+
let mut depth = 0i32;
|
|
163
|
+
let mut entries = 0usize;
|
|
164
|
+
let mut in_string = false;
|
|
165
|
+
let mut escape = false;
|
|
166
|
+
for ch in text[open..].chars() {
|
|
167
|
+
if escape {
|
|
168
|
+
escape = false;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if ch == '\\' && in_string {
|
|
172
|
+
escape = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if ch == '"' {
|
|
176
|
+
in_string = !in_string;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if in_string { continue; }
|
|
180
|
+
if ch == '[' { depth += 1; }
|
|
181
|
+
else if ch == ']' {
|
|
182
|
+
depth -= 1;
|
|
183
|
+
if depth == 0 { break; }
|
|
184
|
+
}
|
|
185
|
+
else if ch == '{' && depth == 1 { entries += 1; }
|
|
186
|
+
}
|
|
187
|
+
entries
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn json_escape(value: &str) -> String {
|
|
191
|
+
value.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n").replace('\r', "\\r")
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn tail_file(path: &str, bytes: u64) -> io::Result<String> {
|
|
195
|
+
let mut file = File::open(path)?;
|
|
196
|
+
let len = file.metadata()?.len();
|
|
197
|
+
let start = len.saturating_sub(bytes);
|
|
198
|
+
file.seek(SeekFrom::Start(start))?;
|
|
199
|
+
let mut out = String::new();
|
|
200
|
+
file.read_to_string(&mut out)?;
|
|
201
|
+
Ok(out)
|
|
202
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.9.
|
|
5
|
-
"description": "Sneakoscope Codex:
|
|
4
|
+
"version": "0.9.13",
|
|
5
|
+
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
8
8
|
"repository": {
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"bin",
|
|
25
25
|
"src",
|
|
26
|
+
"crates/sks-core/Cargo.lock",
|
|
27
|
+
"crates/sks-core/Cargo.toml",
|
|
28
|
+
"crates/sks-core/src",
|
|
26
29
|
"README.md",
|
|
27
30
|
"LICENSE"
|
|
28
31
|
],
|
|
@@ -36,11 +39,20 @@
|
|
|
36
39
|
"doctor": "node ./bin/sks.mjs doctor",
|
|
37
40
|
"packcheck": "find bin src scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check",
|
|
38
41
|
"changelog:check": "node ./scripts/changelog-check.mjs",
|
|
42
|
+
"cli-entrypoint:check": "node ./scripts/check-cli-entrypoint.mjs",
|
|
43
|
+
"legacy-budget:check": "node ./scripts/check-legacy-budget.mjs",
|
|
39
44
|
"sizecheck": "node ./scripts/sizecheck.mjs",
|
|
40
45
|
"registry:check": "node ./scripts/release-registry-check.mjs",
|
|
41
46
|
"feature:check": "node ./bin/sks.mjs features check --json",
|
|
42
47
|
"all-features:selftest": "node ./bin/sks.mjs all-features selftest --mock --json",
|
|
43
|
-
"
|
|
48
|
+
"all-features:execute-fixtures": "node ./bin/sks.mjs all-features selftest --mock --execute-fixtures --json",
|
|
49
|
+
"perf:cold-start": "node ./bin/sks.mjs perf cold-start --json",
|
|
50
|
+
"perf:gate": "node ./scripts/perf-gate.mjs",
|
|
51
|
+
"test": "node --test \"test/**/*.test.mjs\"",
|
|
52
|
+
"test:unit": "node --test \"test/unit/**/*.test.mjs\"",
|
|
53
|
+
"test:integration:mock": "node --test \"test/integration/**/*.test.mjs\"",
|
|
54
|
+
"coverage": "node --experimental-test-coverage --test \"test/**/*.test.mjs\"",
|
|
55
|
+
"release:check": "npm run repo-audit && npm run changelog:check && npm run cli-entrypoint:check && npm run legacy-budget:check && npm run packcheck && npm run feature:check && npm run all-features:selftest && npm run all-features:execute-fixtures && npm run selftest && npm run test:unit && npm run test:integration:mock && npm run perf:gate && npm run sizecheck && npm run registry:check",
|
|
44
56
|
"publish:dry": "npm run release:check && npm --cache /tmp/sks-npm-cache publish --dry-run --registry https://registry.npmjs.org/ --access public",
|
|
45
57
|
"publish:npm": "npm --cache /tmp/sks-npm-cache publish --registry https://registry.npmjs.org/ --access public",
|
|
46
58
|
"prepublishOnly": "npm run release:check && node ./scripts/release-registry-check.mjs --require-unpublished"
|
package/src/cli/args.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function flag(args = [], name) {
|
|
2
|
+
return args.includes(name);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function readOption(args = [], name, fallback = null) {
|
|
6
|
+
const i = args.indexOf(name);
|
|
7
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function positionalArgs(args = []) {
|
|
11
|
+
const out = [];
|
|
12
|
+
const valueFlags = new Set([
|
|
13
|
+
'--source',
|
|
14
|
+
'--format',
|
|
15
|
+
'--iterations',
|
|
16
|
+
'--out',
|
|
17
|
+
'--baseline',
|
|
18
|
+
'--candidate',
|
|
19
|
+
'--install-scope',
|
|
20
|
+
'--max-cycles',
|
|
21
|
+
'--cycle-timeout-minutes',
|
|
22
|
+
'--depth',
|
|
23
|
+
'--scope',
|
|
24
|
+
'--transport',
|
|
25
|
+
'--query',
|
|
26
|
+
'--topic',
|
|
27
|
+
'--tokens',
|
|
28
|
+
'--timeout-ms',
|
|
29
|
+
'--sql',
|
|
30
|
+
'--command',
|
|
31
|
+
'--project-ref',
|
|
32
|
+
'--agent',
|
|
33
|
+
'--phase',
|
|
34
|
+
'--message',
|
|
35
|
+
'--role',
|
|
36
|
+
'--max-anchors',
|
|
37
|
+
'--lines',
|
|
38
|
+
'--dir'
|
|
39
|
+
]);
|
|
40
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
41
|
+
const arg = String(args[i]);
|
|
42
|
+
if (valueFlags.has(arg)) {
|
|
43
|
+
i += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!arg.startsWith('--')) out.push(arg);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const legacy = () => import('./legacy-main.mjs');
|
|
2
|
+
|
|
3
|
+
export const COMMANDS = {
|
|
4
|
+
help: {
|
|
5
|
+
maturity: 'stable',
|
|
6
|
+
summary: 'Show SKS help',
|
|
7
|
+
lazy: () => import('../commands/help.mjs')
|
|
8
|
+
},
|
|
9
|
+
version: {
|
|
10
|
+
maturity: 'stable',
|
|
11
|
+
summary: 'Show SKS version',
|
|
12
|
+
lazy: () => import('../commands/version.mjs')
|
|
13
|
+
},
|
|
14
|
+
commands: {
|
|
15
|
+
maturity: 'stable',
|
|
16
|
+
summary: 'List SKS commands',
|
|
17
|
+
lazy: () => import('../commands/help.mjs')
|
|
18
|
+
},
|
|
19
|
+
root: {
|
|
20
|
+
maturity: 'stable',
|
|
21
|
+
summary: 'Show active SKS root',
|
|
22
|
+
lazy: () => import('../commands/root.mjs')
|
|
23
|
+
},
|
|
24
|
+
features: {
|
|
25
|
+
maturity: 'beta',
|
|
26
|
+
summary: 'Validate feature registry',
|
|
27
|
+
lazy: () => import('../commands/features.mjs')
|
|
28
|
+
},
|
|
29
|
+
'all-features': {
|
|
30
|
+
maturity: 'beta',
|
|
31
|
+
summary: 'Run all-features selftest',
|
|
32
|
+
lazy: () => import('../commands/all-features.mjs')
|
|
33
|
+
},
|
|
34
|
+
hooks: {
|
|
35
|
+
maturity: 'beta',
|
|
36
|
+
summary: 'Explain and inspect Codex hooks',
|
|
37
|
+
lazy: () => import('../commands/hooks.mjs')
|
|
38
|
+
},
|
|
39
|
+
proof: {
|
|
40
|
+
maturity: 'beta',
|
|
41
|
+
summary: 'Show and validate completion proof',
|
|
42
|
+
lazy: () => import('../commands/proof.mjs')
|
|
43
|
+
},
|
|
44
|
+
wiki: {
|
|
45
|
+
maturity: 'beta',
|
|
46
|
+
summary: 'Manage TriWiki and image voxel ledgers',
|
|
47
|
+
lazy: () => import('../commands/wiki.mjs')
|
|
48
|
+
},
|
|
49
|
+
perf: {
|
|
50
|
+
maturity: 'beta',
|
|
51
|
+
summary: 'Run performance checks',
|
|
52
|
+
lazy: () => import('../commands/perf.mjs')
|
|
53
|
+
},
|
|
54
|
+
'codex-lb': {
|
|
55
|
+
maturity: 'beta',
|
|
56
|
+
summary: 'Inspect codex-lb status and circuit health',
|
|
57
|
+
lazy: () => import('../commands/codex-lb.mjs')
|
|
58
|
+
},
|
|
59
|
+
auth: {
|
|
60
|
+
maturity: 'beta',
|
|
61
|
+
summary: 'Alias for codex-lb auth commands',
|
|
62
|
+
lazy: () => import('../commands/codex-lb.mjs')
|
|
63
|
+
},
|
|
64
|
+
postinstall: { maturity: 'stable', summary: 'Run postinstall bootstrap', lazy: legacy },
|
|
65
|
+
wizard: { maturity: 'stable', summary: 'Open setup wizard', lazy: legacy },
|
|
66
|
+
ui: { maturity: 'stable', summary: 'Open setup UI', lazy: legacy },
|
|
67
|
+
'update-check': { maturity: 'stable', summary: 'Check npm package freshness', lazy: legacy },
|
|
68
|
+
usage: { maturity: 'stable', summary: 'Show focused usage topic', lazy: legacy },
|
|
69
|
+
quickstart: { maturity: 'stable', summary: 'Show quickstart flow', lazy: legacy },
|
|
70
|
+
'codex-app': { maturity: 'beta', summary: 'Check Codex App readiness', lazy: () => import('../commands/codex-app.mjs') },
|
|
71
|
+
openclaw: { maturity: 'labs', summary: 'Create OpenClaw skill package', lazy: legacy },
|
|
72
|
+
bootstrap: { maturity: 'stable', summary: 'Initialize SKS project files', lazy: legacy },
|
|
73
|
+
deps: { maturity: 'stable', summary: 'Check or install local dependencies', lazy: legacy },
|
|
74
|
+
'qa-loop': { maturity: 'beta', summary: 'Run QA loop missions', lazy: legacy },
|
|
75
|
+
ppt: { maturity: 'labs', summary: 'Inspect/build PPT artifacts', lazy: legacy },
|
|
76
|
+
'image-ux-review': { maturity: 'labs', summary: 'Inspect image UX artifacts', lazy: legacy },
|
|
77
|
+
'ux-review': { maturity: 'labs', summary: 'Alias for image UX review', lazy: legacy },
|
|
78
|
+
'visual-review': { maturity: 'labs', summary: 'Alias for image UX review', lazy: legacy },
|
|
79
|
+
'ui-ux-review': { maturity: 'labs', summary: 'Alias for image UX review', lazy: legacy },
|
|
80
|
+
context7: { maturity: 'beta', summary: 'Context7 checks and docs', lazy: legacy },
|
|
81
|
+
recallpulse: { maturity: 'labs', summary: 'RecallPulse evidence route', lazy: legacy },
|
|
82
|
+
pipeline: { maturity: 'beta', summary: 'Inspect pipeline missions', lazy: legacy },
|
|
83
|
+
guard: { maturity: 'beta', summary: 'Check harness guard', lazy: legacy },
|
|
84
|
+
conflicts: { maturity: 'beta', summary: 'Check harness conflicts', lazy: legacy },
|
|
85
|
+
versioning: { maturity: 'stable', summary: 'Manage release version metadata', lazy: legacy },
|
|
86
|
+
reasoning: { maturity: 'labs', summary: 'Show reasoning route', lazy: legacy },
|
|
87
|
+
aliases: { maturity: 'stable', summary: 'Show command aliases', lazy: legacy },
|
|
88
|
+
setup: { maturity: 'stable', summary: 'Initialize SKS state', lazy: legacy },
|
|
89
|
+
'fix-path': { maturity: 'stable', summary: 'Repair hook command paths', lazy: legacy },
|
|
90
|
+
doctor: { maturity: 'stable', summary: 'Check and repair SKS install', lazy: () => import('../commands/doctor.mjs') },
|
|
91
|
+
init: { maturity: 'stable', summary: 'Initialize local control surface', lazy: legacy },
|
|
92
|
+
selftest: { maturity: 'stable', summary: 'Run local mock selftest', lazy: legacy },
|
|
93
|
+
goal: { maturity: 'beta', summary: 'Manage Goal bridge workflow', lazy: legacy },
|
|
94
|
+
research: { maturity: 'labs', summary: 'Run research missions', lazy: legacy },
|
|
95
|
+
hook: { maturity: 'beta', summary: 'Codex hook entrypoint', lazy: legacy },
|
|
96
|
+
profile: { maturity: 'labs', summary: 'Inspect/set profile', lazy: legacy },
|
|
97
|
+
hproof: { maturity: 'beta', summary: 'Evaluate H-Proof gate', lazy: legacy },
|
|
98
|
+
'validate-artifacts': { maturity: 'beta', summary: 'Validate mission artifacts', lazy: legacy },
|
|
99
|
+
'proof-field': { maturity: 'beta', summary: 'Scan proof field', lazy: legacy },
|
|
100
|
+
'skill-dream': { maturity: 'labs', summary: 'Track skill dream counters', lazy: legacy },
|
|
101
|
+
'code-structure': { maturity: 'labs', summary: 'Scan source structure', lazy: legacy },
|
|
102
|
+
memory: { maturity: 'labs', summary: 'Run retention checks', lazy: legacy },
|
|
103
|
+
gx: { maturity: 'labs', summary: 'Render/validate GX cartridges', lazy: legacy },
|
|
104
|
+
team: { maturity: 'beta', summary: 'Create and observe Team missions', lazy: legacy },
|
|
105
|
+
db: { maturity: 'beta', summary: 'Inspect DB safety policy', lazy: () => import('../commands/db.mjs') },
|
|
106
|
+
eval: { maturity: 'labs', summary: 'Run eval reports', lazy: legacy },
|
|
107
|
+
harness: { maturity: 'labs', summary: 'Run harness fixtures', lazy: legacy },
|
|
108
|
+
gc: { maturity: 'labs', summary: 'Compact/prune runtime state', lazy: legacy },
|
|
109
|
+
stats: { maturity: 'labs', summary: 'Show storage stats', lazy: legacy },
|
|
110
|
+
tmux: { maturity: 'beta', summary: 'Open/check SKS tmux UI', lazy: legacy },
|
|
111
|
+
'auto-review': { maturity: 'beta', summary: 'Manage auto-review profile', lazy: legacy },
|
|
112
|
+
autoreview: { maturity: 'beta', summary: 'Alias for auto-review', lazy: legacy },
|
|
113
|
+
'dollar-commands': { maturity: 'stable', summary: 'List Codex App dollar commands', lazy: legacy },
|
|
114
|
+
dollars: { maturity: 'stable', summary: 'Alias for dollar-commands', lazy: legacy },
|
|
115
|
+
'$': { maturity: 'stable', summary: 'Alias for dollar-commands', lazy: legacy },
|
|
116
|
+
dfix: { maturity: 'stable', summary: 'Explain DFix route', lazy: legacy }
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const COMMAND_ALIASES = {
|
|
120
|
+
'--help': 'help',
|
|
121
|
+
'-h': 'help',
|
|
122
|
+
'--version': 'version',
|
|
123
|
+
'-v': 'version'
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export function commandNames() {
|
|
127
|
+
return Object.keys(COMMANDS).sort();
|
|
128
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { exists, projectRoot, readJson, writeJsonAtomic } from '../core/fsx.mjs';
|
|
3
5
|
import { CODEX_ACCESS_TOKENS_DOCS_URL } from '../core/codex-app.mjs';
|
|
6
|
+
import { redactSecrets } from '../core/secret-redaction.mjs';
|
|
7
|
+
import { evaluateHookPayload } from '../core/hooks-runtime.mjs';
|
|
4
8
|
import { buildAllFeaturesSelftest, buildFeatureRegistry, validateFeatureRegistry, writeFeatureInventoryDocs } from '../core/feature-registry.mjs';
|
|
5
9
|
|
|
6
10
|
const flag = (args, name) => args.includes(name);
|
|
@@ -42,13 +46,13 @@ export async function featuresCommand(sub = 'list', args = []) {
|
|
|
42
46
|
export async function allFeaturesCommand(sub = 'selftest', args = []) {
|
|
43
47
|
const action = sub || 'selftest';
|
|
44
48
|
if (action !== 'selftest') {
|
|
45
|
-
console.error('Usage: sks all-features selftest --mock [--json]');
|
|
49
|
+
console.error('Usage: sks all-features selftest --mock [--execute-fixtures] [--json]');
|
|
46
50
|
process.exitCode = 1;
|
|
47
51
|
return;
|
|
48
52
|
}
|
|
49
53
|
const root = await projectRoot();
|
|
50
54
|
const registry = await buildFeatureRegistry({ root });
|
|
51
|
-
const result = buildAllFeaturesSelftest(registry);
|
|
55
|
+
const result = buildAllFeaturesSelftest(registry, { executeFixtures: flag(args, '--execute-fixtures'), root });
|
|
52
56
|
if (flag(args, '--json')) console.log(JSON.stringify(result, null, 2));
|
|
53
57
|
else {
|
|
54
58
|
console.log('SKS all-features selftest');
|
|
@@ -59,10 +63,33 @@ export async function allFeaturesCommand(sub = 'selftest', args = []) {
|
|
|
59
63
|
if (!result.ok) process.exitCode = 1;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
export function hooksCommand(sub = 'explain', args = []) {
|
|
66
|
+
export async function hooksCommand(sub = 'explain', args = []) {
|
|
63
67
|
const action = sub || 'explain';
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
const root = await projectRoot();
|
|
69
|
+
if (action === 'status') {
|
|
70
|
+
const report = await hooksStatusReport(root);
|
|
71
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(report, null, 2));
|
|
72
|
+
console.log(`Hooks: ${report.ok ? 'ok' : 'missing'}`);
|
|
73
|
+
for (const file of report.hooks_files) console.log(`- ${file.path}: ${file.exists ? 'present' : 'missing'}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (action === 'trust-report') {
|
|
77
|
+
const report = await hooksTrustReport(root);
|
|
78
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(report, null, 2));
|
|
79
|
+
console.log(`Hooks trust report: ${report.ok ? 'ok' : 'blocked'}`);
|
|
80
|
+
for (const event of report.events) console.log(`- ${event.event}: ${event.command}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (action === 'replay') {
|
|
84
|
+
const fixture = args.find((arg) => !String(arg).startsWith('--'));
|
|
85
|
+
const report = await hooksReplayReport(fixture);
|
|
86
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(report, null, 2));
|
|
87
|
+
console.log(`Hook replay: ${report.ok ? 'ok' : 'blocked'} ${report.event || 'unknown'}`);
|
|
88
|
+
if (report.decision) console.log(`Decision: ${report.decision}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (action !== 'explain') {
|
|
92
|
+
console.error('Usage: sks hooks explain|status|trust-report|replay <fixture.json> [--json]');
|
|
66
93
|
process.exitCode = 1;
|
|
67
94
|
return;
|
|
68
95
|
}
|
|
@@ -80,6 +107,85 @@ export function hooksCommand(sub = 'explain', args = []) {
|
|
|
80
107
|
for (const source of report.sources) console.log(`- ${source.title}: ${source.url}`);
|
|
81
108
|
}
|
|
82
109
|
|
|
110
|
+
async function hooksStatusReport(root) {
|
|
111
|
+
const files = [
|
|
112
|
+
path.join(os.homedir(), '.codex', 'hooks.json'),
|
|
113
|
+
path.join(root, '.codex', 'hooks.json')
|
|
114
|
+
];
|
|
115
|
+
const hooksFiles = [];
|
|
116
|
+
for (const file of files) {
|
|
117
|
+
hooksFiles.push({ path: file, exists: await exists(file) });
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
schema: 'sks.hooks-status.v1',
|
|
121
|
+
hooks_files: hooksFiles,
|
|
122
|
+
ok: hooksFiles.some((file) => file.exists)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function hooksTrustReport(root) {
|
|
127
|
+
const status = await hooksStatusReport(root);
|
|
128
|
+
return redactSecrets({
|
|
129
|
+
schema: 'sks.hooks-trust-report.v1',
|
|
130
|
+
hooks_files: status.hooks_files.map((file) => file.path),
|
|
131
|
+
events: [
|
|
132
|
+
{ event: 'PreToolUse', command: 'sks hook pre-tool', writes: ['.sneakoscope/bus/tool-events.jsonl'], network: false, secret_policy: 'redacted', risk: 'medium' },
|
|
133
|
+
{ event: 'PermissionRequest', command: 'sks hook permission-request', writes: ['.sneakoscope/state'], network: false, secret_policy: 'redacted', risk: 'medium' },
|
|
134
|
+
{ event: 'UserPromptSubmit', command: 'sks hook user-prompt-submit', writes: ['.sneakoscope/missions'], network: false, secret_policy: 'redacted', risk: 'medium' },
|
|
135
|
+
{ event: 'Stop', command: 'sks hook stop', writes: ['.sneakoscope/missions', '.sneakoscope/proof'], network: false, secret_policy: 'redacted', risk: 'high' }
|
|
136
|
+
],
|
|
137
|
+
ok: true,
|
|
138
|
+
warnings: status.ok ? [] : ['no hooks.json file found in project or user config']
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function hooksReplayReport(fixturePath) {
|
|
143
|
+
if (!fixturePath) return { schema: 'sks.hooks-replay.v1', ok: false, reason: 'fixture_required' };
|
|
144
|
+
const absolute = path.resolve(fixturePath);
|
|
145
|
+
const fixture = await readJson(absolute, {});
|
|
146
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-hooks-replay-'));
|
|
147
|
+
const payload = { ...(fixture.payload || fixture), cwd: tempRoot };
|
|
148
|
+
const state = fixture.state || {};
|
|
149
|
+
if (state.mission_id && fixture.proof) {
|
|
150
|
+
await writeJsonAtomic(path.join(tempRoot, '.sneakoscope', 'missions', state.mission_id, 'completion-proof.json'), fixture.proof);
|
|
151
|
+
}
|
|
152
|
+
const event = fixture.event || fixture.hook_event_name || fixture.name || 'unknown';
|
|
153
|
+
const hookName = normalizeReplayHookName(event);
|
|
154
|
+
const runtime = await evaluateHookPayload(hookName, payload, { root: tempRoot, state });
|
|
155
|
+
const decision = runtime.decision || runtime.permissionDecision || (runtime.continue === true ? 'continue' : 'continue');
|
|
156
|
+
const expected = await readExpectedReplay(absolute);
|
|
157
|
+
const comparable = { decision, reason: runtime.reason || 'fixture_safe' };
|
|
158
|
+
const matchesExpected = expected ? expected.decision === comparable.decision && (!expected.reason || expected.reason === comparable.reason) : null;
|
|
159
|
+
const ok = expected ? matchesExpected : decision !== 'block' && decision !== 'deny';
|
|
160
|
+
return redactSecrets({
|
|
161
|
+
schema: 'sks.hooks-replay.v1',
|
|
162
|
+
ok,
|
|
163
|
+
event,
|
|
164
|
+
hook: hookName,
|
|
165
|
+
command: payload.command || payload.tool_input?.command || payload.toolInput?.command || payload.input?.command || '',
|
|
166
|
+
decision,
|
|
167
|
+
reason: runtime.reason || 'fixture_safe',
|
|
168
|
+
matches_expected: matchesExpected,
|
|
169
|
+
secret_policy: 'redacted'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function normalizeReplayHookName(event = '') {
|
|
174
|
+
const normalized = String(event || '').replace(/[_\s]+/g, '-').toLowerCase();
|
|
175
|
+
if (normalized.includes('pretool') || normalized.includes('pre-tool')) return 'pre-tool';
|
|
176
|
+
if (normalized.includes('permission')) return 'permission-request';
|
|
177
|
+
if (normalized.includes('userprompt') || normalized.includes('user-prompt')) return 'user-prompt-submit';
|
|
178
|
+
if (normalized.includes('posttool') || normalized.includes('post-tool')) return 'post-tool';
|
|
179
|
+
if (normalized.includes('stop')) return 'stop';
|
|
180
|
+
return normalized || 'pre-tool';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function readExpectedReplay(fixturePath) {
|
|
184
|
+
const expectedPath = path.join(path.dirname(fixturePath), 'expected', `${path.basename(fixturePath, '.json')}.expected.json`);
|
|
185
|
+
if (!await exists(expectedPath)) return null;
|
|
186
|
+
return readJson(expectedPath, null);
|
|
187
|
+
}
|
|
188
|
+
|
|
83
189
|
export function hooksExplainReport() {
|
|
84
190
|
return {
|
|
85
191
|
schema: 'sks.hooks-explain.v1',
|