@dzhechkov/harness-core 0.8.10 → 0.8.20

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 (251) hide show
  1. package/.dz-manifest.json +490 -170
  2. package/LICENSE +13 -0
  3. package/README.md +420 -6
  4. package/dist/agents-policy.d.ts +15 -1
  5. package/dist/agents-policy.d.ts.map +1 -1
  6. package/dist/agents-policy.js +27 -1
  7. package/dist/agents-policy.js.map +1 -1
  8. package/dist/amendment-trace.d.ts +72 -0
  9. package/dist/amendment-trace.d.ts.map +1 -1
  10. package/dist/amendment-trace.js +385 -17
  11. package/dist/amendment-trace.js.map +1 -1
  12. package/dist/backlog-public.d.ts +153 -0
  13. package/dist/backlog-public.d.ts.map +1 -0
  14. package/dist/backlog-public.js +415 -0
  15. package/dist/backlog-public.js.map +1 -0
  16. package/dist/backlog-transitions.d.ts +48 -0
  17. package/dist/backlog-transitions.d.ts.map +1 -0
  18. package/dist/backlog-transitions.js +64 -0
  19. package/dist/backlog-transitions.js.map +1 -0
  20. package/dist/backlog.d.ts.map +1 -1
  21. package/dist/backlog.js +13 -0
  22. package/dist/backlog.js.map +1 -1
  23. package/dist/claim-check.d.ts.map +1 -1
  24. package/dist/claim-check.js +24 -2
  25. package/dist/claim-check.js.map +1 -1
  26. package/dist/claude-hooks-assets.d.ts +93 -0
  27. package/dist/claude-hooks-assets.d.ts.map +1 -0
  28. package/dist/claude-hooks-assets.js +208 -0
  29. package/dist/claude-hooks-assets.js.map +1 -0
  30. package/dist/clean-room-smoke.d.ts +83 -0
  31. package/dist/clean-room-smoke.d.ts.map +1 -0
  32. package/dist/clean-room-smoke.js +138 -0
  33. package/dist/clean-room-smoke.js.map +1 -0
  34. package/dist/cmd-usage.d.ts.map +1 -1
  35. package/dist/cmd-usage.js +36 -6
  36. package/dist/cmd-usage.js.map +1 -1
  37. package/dist/codex-hooks-assets.d.ts +25 -7
  38. package/dist/codex-hooks-assets.d.ts.map +1 -1
  39. package/dist/codex-hooks-assets.js +138 -30
  40. package/dist/codex-hooks-assets.js.map +1 -1
  41. package/dist/codex-hooks.d.ts +21 -1
  42. package/dist/codex-hooks.d.ts.map +1 -1
  43. package/dist/codex-hooks.js +21 -1
  44. package/dist/codex-hooks.js.map +1 -1
  45. package/dist/codex-invoke.d.ts +73 -0
  46. package/dist/codex-invoke.d.ts.map +1 -0
  47. package/dist/codex-invoke.js +80 -0
  48. package/dist/codex-invoke.js.map +1 -0
  49. package/dist/course-staleness.d.ts +19 -0
  50. package/dist/course-staleness.d.ts.map +1 -0
  51. package/dist/course-staleness.js +95 -0
  52. package/dist/course-staleness.js.map +1 -0
  53. package/dist/destructive-guard-hook.d.ts +40 -0
  54. package/dist/destructive-guard-hook.d.ts.map +1 -0
  55. package/dist/destructive-guard-hook.js +109 -0
  56. package/dist/destructive-guard-hook.js.map +1 -0
  57. package/dist/destructive-guard.d.ts +27 -0
  58. package/dist/destructive-guard.d.ts.map +1 -0
  59. package/dist/destructive-guard.js +2808 -0
  60. package/dist/destructive-guard.js.map +1 -0
  61. package/dist/discrimination-gate.d.ts +88 -3
  62. package/dist/discrimination-gate.d.ts.map +1 -1
  63. package/dist/discrimination-gate.js +173 -16
  64. package/dist/discrimination-gate.js.map +1 -1
  65. package/dist/event-chain.d.ts +30 -0
  66. package/dist/event-chain.d.ts.map +1 -1
  67. package/dist/event-chain.js +24 -0
  68. package/dist/event-chain.js.map +1 -1
  69. package/dist/feature-adr-routing.d.ts +95 -1
  70. package/dist/feature-adr-routing.d.ts.map +1 -1
  71. package/dist/feature-adr-routing.js +193 -27
  72. package/dist/feature-adr-routing.js.map +1 -1
  73. package/dist/guard.d.ts +98 -0
  74. package/dist/guard.d.ts.map +1 -1
  75. package/dist/guard.js +308 -0
  76. package/dist/guard.js.map +1 -1
  77. package/dist/harness-core-location.d.ts +18 -0
  78. package/dist/harness-core-location.d.ts.map +1 -0
  79. package/dist/harness-core-location.js +42 -0
  80. package/dist/harness-core-location.js.map +1 -0
  81. package/dist/index.d.ts +32 -11
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +32 -7
  84. package/dist/index.js.map +1 -1
  85. package/dist/lead-shift-gate.d.ts +95 -0
  86. package/dist/lead-shift-gate.d.ts.map +1 -0
  87. package/dist/lead-shift-gate.js +100 -0
  88. package/dist/lead-shift-gate.js.map +1 -0
  89. package/dist/ledger-backfill.d.ts +11 -1
  90. package/dist/ledger-backfill.d.ts.map +1 -1
  91. package/dist/ledger-backfill.js +19 -0
  92. package/dist/ledger-backfill.js.map +1 -1
  93. package/dist/loop-blobs.generated.d.ts +1 -1
  94. package/dist/loop-blobs.generated.d.ts.map +1 -1
  95. package/dist/loop-blobs.generated.js +12 -3
  96. package/dist/loop-blobs.generated.js.map +1 -1
  97. package/dist/loop-lint.d.ts.map +1 -1
  98. package/dist/loop-lint.js +56 -7
  99. package/dist/loop-lint.js.map +1 -1
  100. package/dist/loop-plan-graph.d.ts +1 -3
  101. package/dist/loop-plan-graph.d.ts.map +1 -1
  102. package/dist/loop-plan-graph.js +70 -1
  103. package/dist/loop-plan-graph.js.map +1 -1
  104. package/dist/loop-trace.d.ts.map +1 -1
  105. package/dist/loop-trace.js +16 -2
  106. package/dist/loop-trace.js.map +1 -1
  107. package/dist/managed-hooks.d.ts +5 -6
  108. package/dist/managed-hooks.d.ts.map +1 -1
  109. package/dist/managed-hooks.js +2 -2
  110. package/dist/managed-hooks.js.map +1 -1
  111. package/dist/model-recommender.d.ts.map +1 -1
  112. package/dist/model-recommender.js +14 -3
  113. package/dist/model-recommender.js.map +1 -1
  114. package/dist/mutation-gate.d.ts +39 -36
  115. package/dist/mutation-gate.d.ts.map +1 -1
  116. package/dist/mutation-gate.js +111 -5
  117. package/dist/mutation-gate.js.map +1 -1
  118. package/dist/operations.d.ts.map +1 -1
  119. package/dist/operations.js +90 -5
  120. package/dist/operations.js.map +1 -1
  121. package/dist/patterns.d.ts +23 -0
  122. package/dist/patterns.d.ts.map +1 -1
  123. package/dist/patterns.js +10 -0
  124. package/dist/patterns.js.map +1 -1
  125. package/dist/plugin.d.ts.map +1 -1
  126. package/dist/plugin.js +27 -5
  127. package/dist/plugin.js.map +1 -1
  128. package/dist/publish.d.ts +11 -0
  129. package/dist/publish.d.ts.map +1 -1
  130. package/dist/publish.js +16 -2
  131. package/dist/publish.js.map +1 -1
  132. package/dist/recommend.d.ts +4 -5
  133. package/dist/recommend.d.ts.map +1 -1
  134. package/dist/recommend.js +110 -45
  135. package/dist/recommend.js.map +1 -1
  136. package/dist/registry.d.ts +32 -1
  137. package/dist/registry.d.ts.map +1 -1
  138. package/dist/registry.js +168 -11
  139. package/dist/registry.js.map +1 -1
  140. package/dist/run-records.d.ts +3 -0
  141. package/dist/run-records.d.ts.map +1 -1
  142. package/dist/run-records.js +18 -0
  143. package/dist/run-records.js.map +1 -1
  144. package/dist/score.d.ts +95 -0
  145. package/dist/score.d.ts.map +1 -1
  146. package/dist/score.js +289 -6
  147. package/dist/score.js.map +1 -1
  148. package/dist/session-retro.d.ts +121 -2
  149. package/dist/session-retro.d.ts.map +1 -1
  150. package/dist/session-retro.js +454 -11
  151. package/dist/session-retro.js.map +1 -1
  152. package/dist/setup.d.ts +28 -0
  153. package/dist/setup.d.ts.map +1 -1
  154. package/dist/setup.js +223 -6
  155. package/dist/setup.js.map +1 -1
  156. package/dist/sign.d.ts.map +1 -1
  157. package/dist/sign.js +18 -1
  158. package/dist/sign.js.map +1 -1
  159. package/dist/skill-selection.d.ts +72 -0
  160. package/dist/skill-selection.d.ts.map +1 -0
  161. package/dist/skill-selection.js +76 -0
  162. package/dist/skill-selection.js.map +1 -0
  163. package/dist/skills-verify.d.ts +34 -1
  164. package/dist/skills-verify.d.ts.map +1 -1
  165. package/dist/skills-verify.js +82 -0
  166. package/dist/skills-verify.js.map +1 -1
  167. package/dist/stage-line.d.ts +68 -0
  168. package/dist/stage-line.d.ts.map +1 -0
  169. package/dist/stage-line.js +129 -0
  170. package/dist/stage-line.js.map +1 -0
  171. package/dist/statusline.d.ts +99 -0
  172. package/dist/statusline.d.ts.map +1 -1
  173. package/dist/statusline.js +310 -44
  174. package/dist/statusline.js.map +1 -1
  175. package/dist/stem.d.ts +12 -0
  176. package/dist/stem.d.ts.map +1 -0
  177. package/dist/stem.js +89 -0
  178. package/dist/stem.js.map +1 -0
  179. package/dist/store-counts.d.ts +26 -0
  180. package/dist/store-counts.d.ts.map +1 -0
  181. package/dist/store-counts.js +125 -0
  182. package/dist/store-counts.js.map +1 -0
  183. package/dist/store-guard.d.ts +106 -0
  184. package/dist/store-guard.d.ts.map +1 -0
  185. package/dist/store-guard.js +294 -0
  186. package/dist/store-guard.js.map +1 -0
  187. package/dist/swarm-brief.d.ts +95 -0
  188. package/dist/swarm-brief.d.ts.map +1 -0
  189. package/dist/swarm-brief.js +660 -0
  190. package/dist/swarm-brief.js.map +1 -0
  191. package/dist/telemetry-vocabulary.d.ts +7 -0
  192. package/dist/telemetry-vocabulary.d.ts.map +1 -1
  193. package/dist/telemetry-vocabulary.js +29 -0
  194. package/dist/telemetry-vocabulary.js.map +1 -1
  195. package/dist/trace-bundle.d.ts +8 -0
  196. package/dist/trace-bundle.d.ts.map +1 -1
  197. package/dist/trace-bundle.js +11 -0
  198. package/dist/trace-bundle.js.map +1 -1
  199. package/package.json +10 -9
  200. package/sbom.json +1033 -233
  201. package/src/agents-policy.ts +46 -2
  202. package/src/amendment-trace.ts +441 -19
  203. package/src/backlog-public.ts +503 -0
  204. package/src/backlog-transitions.ts +77 -0
  205. package/src/backlog.ts +12 -0
  206. package/src/claim-check.ts +25 -2
  207. package/src/claude-hooks-assets.ts +227 -0
  208. package/src/clean-room-smoke.ts +195 -0
  209. package/src/cmd-usage.ts +29 -5
  210. package/src/codex-hooks-assets.ts +140 -30
  211. package/src/codex-hooks.ts +21 -1
  212. package/src/codex-invoke.ts +138 -0
  213. package/src/course-staleness.ts +125 -0
  214. package/src/destructive-guard-hook.ts +151 -0
  215. package/src/destructive-guard.ts +3027 -0
  216. package/src/discrimination-gate.ts +262 -19
  217. package/src/event-chain.ts +41 -0
  218. package/src/feature-adr-routing.ts +220 -22
  219. package/src/guard.ts +358 -0
  220. package/src/harness-core-location.ts +44 -0
  221. package/src/index.ts +121 -7
  222. package/src/lead-shift-gate.ts +145 -0
  223. package/src/ledger-backfill.ts +20 -1
  224. package/src/loop-blobs.generated.ts +12 -3
  225. package/src/loop-lint.ts +52 -7
  226. package/src/loop-plan-graph.ts +66 -1
  227. package/src/loop-trace.ts +13 -1
  228. package/src/managed-hooks.ts +5 -6
  229. package/src/model-recommender.ts +14 -3
  230. package/src/mutation-gate.ts +165 -5
  231. package/src/operations.ts +83 -5
  232. package/src/patterns.ts +33 -0
  233. package/src/plugin.ts +27 -5
  234. package/src/publish.ts +27 -2
  235. package/src/recommend.ts +116 -46
  236. package/src/registry.ts +147 -13
  237. package/src/run-records.ts +23 -0
  238. package/src/score.ts +377 -7
  239. package/src/session-retro.ts +466 -11
  240. package/src/setup.ts +246 -9
  241. package/src/sign.ts +18 -1
  242. package/src/skill-selection.ts +111 -0
  243. package/src/skills-verify.ts +99 -1
  244. package/src/stage-line.ts +151 -0
  245. package/src/statusline.ts +396 -47
  246. package/src/stem.ts +87 -0
  247. package/src/store-counts.ts +154 -0
  248. package/src/store-guard.ts +388 -0
  249. package/src/swarm-brief.ts +661 -0
  250. package/src/telemetry-vocabulary.ts +36 -0
  251. package/src/trace-bundle.ts +10 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The one supported way to invoke Codex from a Claude Code session.
3
+ *
4
+ * Three failure modes were measured on 2026-08-31, each violating knowledge we had already
5
+ * written down and could not enforce: the fire-and-forget wrapper returning a dispatch stub for a
6
+ * stage whose deliverable is its return value; a prompt passed as a shell ARGUMENT whose backticks
7
+ * the shell read as command substitution, leaving codex to read an empty stdin and hang for 26
8
+ * minutes; and an unscoped run spending its whole budget exploring the tree and returning no
9
+ * verdict at all.
10
+ *
11
+ * The cure is structural, not advisory: this module has no parameter that accepts a prompt STRING
12
+ * (so a shell can never mangle it), a timeout is always present, and the outcome set is CLOSED —
13
+ * either the model's text, or one of five named refusals. There is no third state, which is the
14
+ * property the tests pin.
15
+ */
16
+ /** Every way this can fail, enumerated. A refusal outside this set is a bug, not a new case. */
17
+ export type CodexRefusal = 'timeout' | 'no-output' | 'model-unavailable' | 'tool-error' | 'bad-usage';
18
+ export interface CodexOk {
19
+ readonly ok: true;
20
+ readonly text: string;
21
+ readonly model: string;
22
+ readonly elapsedMs: number;
23
+ }
24
+ export interface CodexRefused {
25
+ readonly ok: false;
26
+ readonly refusal: CodexRefusal;
27
+ /** Human-readable, names WHAT was observed — never a guess at the cause. */
28
+ readonly detail: string;
29
+ readonly model?: string;
30
+ readonly elapsedMs?: number;
31
+ }
32
+ export type CodexOutcome = CodexOk | CodexRefused;
33
+ export declare const CODEX_DEFAULT_TIMEOUT_MS = 600000;
34
+ export declare const CODEX_PROBE_TIMEOUT_MS = 90000;
35
+ /** Model ids this account has been seen to answer on. A name here is spellable, never available. */
36
+ export declare const CODEX_KNOWN_MODELS: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"];
37
+ export interface CodexRunInput {
38
+ /** Absolute path to a file holding the prompt. There is deliberately no string variant. */
39
+ readonly promptFile: string;
40
+ readonly model: string;
41
+ readonly timeoutMs?: number;
42
+ /**
43
+ * Files the model may read. REQUIRED for review-shaped work: an unscoped run was measured at
44
+ * 280s / exit 124 / 416KB of exploration and no verdict, while the same question scoped to two
45
+ * named files answered in 41s. Empty means "no scope declared" and is allowed only for a
46
+ * self-contained question that needs no repository access.
47
+ */
48
+ readonly scope?: readonly string[];
49
+ readonly effort?: 'low' | 'medium' | 'high' | 'xhigh';
50
+ }
51
+ /** What a runner must provide. Kept tiny so tests can substitute it without spawning anything. */
52
+ export interface CodexRunner {
53
+ (argv: readonly string[], timeoutMs: number): {
54
+ readonly status: number | null;
55
+ readonly stdout: string;
56
+ readonly stderr: string;
57
+ readonly timedOut: boolean;
58
+ };
59
+ }
60
+ /** Build the argv. Exported so a test can assert the prompt never rides on the command line. */
61
+ export declare function codexArgv(input: CodexRunInput): string[];
62
+ /** Turn a raw runner result into the closed outcome set. */
63
+ export declare function classifyCodexResult(raw: {
64
+ status: number | null;
65
+ stdout: string;
66
+ stderr: string;
67
+ timedOut: boolean;
68
+ }, model: string, elapsedMs: number): CodexOutcome;
69
+ /** Compose the prompt file's content: the task, plus the scope fence when one is declared. */
70
+ export declare function codexPromptBody(task: string, scope?: readonly string[]): string;
71
+ /** True when the outcome may be consumed as an answer. Exists so callers cannot forget the check. */
72
+ export declare function codexAnswered(outcome: CodexOutcome): outcome is CodexOk;
73
+ //# sourceMappingURL=codex-invoke.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-invoke.d.ts","sourceRoot":"","sources":["../src/codex-invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gGAAgG;AAChG,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,WAAW,GACX,mBAAmB,GACnB,YAAY,GACZ,WAAW,CAAC;AAEhB,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,YAAY,CAAC;AAElD,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAChD,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,oGAAoG;AACpG,eAAO,MAAM,kBAAkB,sEAAuE,CAAC;AAEvG,MAAM,WAAW,aAAa;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;CACvD;AAED,kGAAkG;AAClG,MAAM,WAAW,WAAW;IAC1B,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG;QAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,EAAE,CAIxD;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CACjC,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EACjF,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,YAAY,CAgCd;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAY/E;AAED,qGAAqG;AACrG,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,IAAI,OAAO,CAEvE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The one supported way to invoke Codex from a Claude Code session.
3
+ *
4
+ * Three failure modes were measured on 2026-08-31, each violating knowledge we had already
5
+ * written down and could not enforce: the fire-and-forget wrapper returning a dispatch stub for a
6
+ * stage whose deliverable is its return value; a prompt passed as a shell ARGUMENT whose backticks
7
+ * the shell read as command substitution, leaving codex to read an empty stdin and hang for 26
8
+ * minutes; and an unscoped run spending its whole budget exploring the tree and returning no
9
+ * verdict at all.
10
+ *
11
+ * The cure is structural, not advisory: this module has no parameter that accepts a prompt STRING
12
+ * (so a shell can never mangle it), a timeout is always present, and the outcome set is CLOSED —
13
+ * either the model's text, or one of five named refusals. There is no third state, which is the
14
+ * property the tests pin.
15
+ */
16
+ export const CODEX_DEFAULT_TIMEOUT_MS = 600_000;
17
+ export const CODEX_PROBE_TIMEOUT_MS = 90_000;
18
+ /** Model ids this account has been seen to answer on. A name here is spellable, never available. */
19
+ export const CODEX_KNOWN_MODELS = ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.5'];
20
+ /** Build the argv. Exported so a test can assert the prompt never rides on the command line. */
21
+ export function codexArgv(input) {
22
+ const argv = ['exec', '-m', input.model, '--skip-git-repo-check'];
23
+ if (input.effort !== undefined)
24
+ argv.push('-c', `model_reasoning_effort=${input.effort}`);
25
+ return argv;
26
+ }
27
+ /** Turn a raw runner result into the closed outcome set. */
28
+ export function classifyCodexResult(raw, model, elapsedMs) {
29
+ if (raw.timedOut) {
30
+ return {
31
+ ok: false,
32
+ refusal: 'timeout',
33
+ detail: `no answer within the deadline (${elapsedMs}ms); narrow the scope rather than raising the ceiling — an unscoped run spends the budget exploring`,
34
+ model,
35
+ elapsedMs,
36
+ };
37
+ }
38
+ if (raw.status !== 0) {
39
+ return {
40
+ ok: false,
41
+ refusal: 'tool-error',
42
+ detail: `codex exited ${raw.status === null ? 'by signal' : String(raw.status)}: ${raw.stderr.trim().slice(0, 300) || '(no stderr)'}`,
43
+ model,
44
+ elapsedMs,
45
+ };
46
+ }
47
+ const text = raw.stdout.trim();
48
+ if (text === '') {
49
+ // Silence is the failure this module exists to make impossible. A clean exit with nothing
50
+ // written is what a mangled prompt looks like from the outside, and it must never read as ok.
51
+ return {
52
+ ok: false,
53
+ refusal: 'no-output',
54
+ detail: 'codex exited 0 but wrote nothing — an empty answer is a refusal, never a clean result',
55
+ model,
56
+ elapsedMs,
57
+ };
58
+ }
59
+ return { ok: true, text, model, elapsedMs };
60
+ }
61
+ /** Compose the prompt file's content: the task, plus the scope fence when one is declared. */
62
+ export function codexPromptBody(task, scope) {
63
+ const trimmed = String(task ?? '').trim();
64
+ if (scope === undefined || scope.length === 0)
65
+ return trimmed;
66
+ const list = scope.map((p) => `- ${p}`).join('\n');
67
+ return [
68
+ trimmed,
69
+ '',
70
+ 'SCOPE — read ONLY these files and do not open others. This bound is what makes an answer',
71
+ 'possible at all: an unscoped run was measured spending its entire budget exploring the tree',
72
+ 'and returning no verdict.',
73
+ list,
74
+ ].join('\n');
75
+ }
76
+ /** True when the outcome may be consumed as an answer. Exists so callers cannot forget the check. */
77
+ export function codexAnswered(outcome) {
78
+ return outcome.ok === true;
79
+ }
80
+ //# sourceMappingURL=codex-invoke.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-invoke.js","sourceRoot":"","sources":["../src/codex-invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA4BH,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAC;AAChD,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C,oGAAoG;AACpG,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,SAAS,CAAU,CAAC;AA2BvG,gGAAgG;AAChG,MAAM,UAAU,SAAS,CAAC,KAAoB;IAC5C,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,0BAA0B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,mBAAmB,CACjC,GAAiF,EACjF,KAAa,EACb,SAAiB;IAEjB,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,kCAAkC,SAAS,qGAAqG;YACxJ,KAAK;YACL,SAAS;SACV,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,YAAY;YACrB,MAAM,EAAE,gBAAgB,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,aAAa,EAAE;YACrI,KAAK;YACL,SAAS;SACV,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,0FAA0F;QAC1F,8FAA8F;QAC9F,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,uFAAuF;YAC/F,KAAK;YACL,SAAS;SACV,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC9C,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAyB;IACrE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnD,OAAO;QACL,OAAO;QACP,EAAE;QACF,0FAA0F;QAC1F,6FAA6F;QAC7F,2BAA2B;QAC3B,IAAI;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,OAAO,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,19 @@
1
+ export type CourseStalenessState = 'S0_SHIPPED' | 'S3_TUTORIAL_STALE' | 'S4_PACKAGE_BEHIND' | 'E2_UNSTAMPED' | 'E3_INVALID_VERSION' | 'E4_PACKAGE_MISMATCH' | 'E5_REGISTRY_UNKNOWN';
2
+ export interface CourseStalenessInput {
3
+ readonly source: unknown;
4
+ readonly expectedPackage?: string | null;
5
+ readonly registryVersion?: string | null;
6
+ }
7
+ export interface CourseStalenessResult {
8
+ readonly state: CourseStalenessState;
9
+ readonly reason: string;
10
+ readonly courseVersion?: string;
11
+ readonly registryVersion?: string;
12
+ readonly package?: string;
13
+ }
14
+ /**
15
+ * Classify only the version evidence supplied by the caller. Obtaining a registry version is a
16
+ * caller responsibility; this function performs no filesystem, network, clock, or environment I/O.
17
+ */
18
+ export declare function classifyCourseStaleness(input: CourseStalenessInput): CourseStalenessResult;
19
+ //# sourceMappingURL=course-staleness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"course-staleness.d.ts","sourceRoot":"","sources":["../src/course-staleness.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAC5B,YAAY,GACZ,mBAAmB,GACnB,mBAAmB,GACnB,cAAc,GACd,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AASD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CAwF1F"}
@@ -0,0 +1,95 @@
1
+ import { compareVersions } from './publish.js';
2
+ const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
3
+ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
6
+ }
7
+ /**
8
+ * Classify only the version evidence supplied by the caller. Obtaining a registry version is a
9
+ * caller responsibility; this function performs no filesystem, network, clock, or environment I/O.
10
+ */
11
+ export function classifyCourseStaleness(input) {
12
+ const candidate = isRecord(input) ? input : undefined;
13
+ const source = candidate?.source;
14
+ // Presence and shape come first. Unknown values must never reach an equality comparison where
15
+ // two absences could be mistaken for parity.
16
+ if (!isRecord(source)
17
+ || typeof source.package !== 'string'
18
+ || source.package.trim() === ''
19
+ || typeof source.version !== 'string'
20
+ || source.version.trim() === '') {
21
+ return {
22
+ state: 'E2_UNSTAMPED',
23
+ reason: 'course.source.package and course.source.version must both be non-empty strings',
24
+ };
25
+ }
26
+ const packageName = source.package.trim();
27
+ const courseVersion = source.version.trim();
28
+ if (!SEMVER.test(courseVersion)) {
29
+ return {
30
+ state: 'E3_INVALID_VERSION',
31
+ reason: `course.source.version is not valid semver: ${courseVersion}`,
32
+ courseVersion,
33
+ package: packageName,
34
+ };
35
+ }
36
+ const expectedPackage = typeof candidate?.expectedPackage === 'string'
37
+ ? candidate.expectedPackage.trim()
38
+ : candidate?.expectedPackage;
39
+ if (!PACKAGE_NAME.test(packageName)
40
+ || (expectedPackage !== null && expectedPackage !== undefined
41
+ && (!PACKAGE_NAME.test(expectedPackage) || packageName !== expectedPackage))) {
42
+ return {
43
+ state: 'E4_PACKAGE_MISMATCH',
44
+ reason: expectedPackage
45
+ ? `course.source.package ${packageName} does not match expectedPackage ${expectedPackage}`
46
+ : `course.source.package is not a valid npm package name: ${packageName}`,
47
+ courseVersion,
48
+ package: packageName,
49
+ };
50
+ }
51
+ const registryVersion = candidate?.registryVersion;
52
+ if (registryVersion === null || registryVersion === undefined) {
53
+ return {
54
+ state: 'E5_REGISTRY_UNKNOWN',
55
+ reason: 'registryVersion is absent, so package parity is unknown',
56
+ courseVersion,
57
+ package: packageName,
58
+ };
59
+ }
60
+ if (typeof registryVersion !== 'string' || !SEMVER.test(registryVersion.trim())) {
61
+ return {
62
+ state: 'E3_INVALID_VERSION',
63
+ reason: `registryVersion is not valid semver: ${String(registryVersion)}`,
64
+ courseVersion,
65
+ package: packageName,
66
+ };
67
+ }
68
+ const normalizedRegistryVersion = registryVersion.trim();
69
+ const ordering = compareVersions(courseVersion, normalizedRegistryVersion);
70
+ const common = {
71
+ courseVersion,
72
+ registryVersion: normalizedRegistryVersion,
73
+ package: packageName,
74
+ };
75
+ if (ordering === 0) {
76
+ return {
77
+ state: 'S0_SHIPPED',
78
+ reason: 'course.source.version equals registryVersion',
79
+ ...common,
80
+ };
81
+ }
82
+ if (ordering < 0) {
83
+ return {
84
+ state: 'S3_TUTORIAL_STALE',
85
+ reason: 'course.source.version is below registryVersion',
86
+ ...common,
87
+ };
88
+ }
89
+ return {
90
+ state: 'S4_PACKAGE_BEHIND',
91
+ reason: 'course.source.version is above registryVersion',
92
+ ...common,
93
+ };
94
+ }
95
+ //# sourceMappingURL=course-staleness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"course-staleness.js","sourceRoot":"","sources":["../src/course-staleness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /** Prefix of a BLOCKING message. Must appear verbatim for a block to be attributed to this guard. */
2
+ export declare const DESTRUCTIVE_REFUSE_MARKER = "DZ-DESTRUCTIVE:";
3
+ /** Prefix of a non-blocking message: an `undecidable` verdict, or our own failure to read. */
4
+ export declare const DESTRUCTIVE_WARN_MARKER = "DZ-DESTRUCTIVE-WARN:";
5
+ /**
6
+ * Which host contract the payload follows. The two differ in ONE measured respect, so the
7
+ * difference is a parameter rather than a guess:
8
+ *
9
+ * - `claude` — Claude Code sends `tool_name` for every tool and the registered matcher is `^Bash$`.
10
+ * A payload whose `tool_name` is not a shell tool is skipped WITHOUT looking at any command field,
11
+ * which is what the host contract promises and what the test pins.
12
+ * - `codex` — AM-8 of `crossrt-2-codex-hooks`: key on the PRESENCE of `tool_input.command`, never on
13
+ * `tool_name` equality. Codex's shell tool has been observed under more than one name
14
+ * (`Bash|shell|local_shell` is the matcher the emitted registry entry already carries), so a
15
+ * tool_name test there would silently disarm the guard the day the runtime renames its tool.
16
+ */
17
+ export type DestructiveHookHost = 'claude' | 'codex';
18
+ export interface DestructiveHookDecision {
19
+ /** 2 = block the call (the host contract on BOTH runtimes). 0 = let it through. */
20
+ readonly exitCode: 0 | 2;
21
+ /** Exactly what the hook body writes to stderr. Empty string ⇒ write nothing at all. */
22
+ readonly stderr: string;
23
+ /**
24
+ * What happened, for tests and for the helper's own notes log:
25
+ * `refuse` / `allow` / `undecidable` are the classifier's three outcomes;
26
+ * `skipped` = not a shell command, the classifier was never consulted;
27
+ * `unreadable` = the payload or the classifier failed us, and we passed rather than guessed.
28
+ */
29
+ readonly outcome: 'refuse' | 'allow' | 'undecidable' | 'skipped' | 'unreadable';
30
+ /** The rule id from a refusal, so a note can record WHICH rule fired without the command line. */
31
+ readonly rule: string | null;
32
+ }
33
+ /**
34
+ * Turn one hook payload into an exit code and a piece of text. The whole policy, both hosts.
35
+ *
36
+ * @param payload the parsed stdin object, or `null`/anything else when it did not parse.
37
+ * @param host which host contract the payload follows — see {@link DestructiveHookHost}.
38
+ */
39
+ export declare function decideDestructiveHook(payload: unknown, host: DestructiveHookHost): DestructiveHookDecision;
40
+ //# sourceMappingURL=destructive-guard-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destructive-guard-hook.d.ts","sourceRoot":"","sources":["../src/destructive-guard-hook.ts"],"names":[],"mappings":"AAsBA,qGAAqG;AACrG,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB,yBAAyB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAKrD,MAAM,WAAW,uBAAuB;IACtC,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACzB,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;IAChF,kGAAkG;IAClG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAoDD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,uBAAuB,CAiC1G"}
@@ -0,0 +1,109 @@
1
+ // destructive-guard-hook — the CONSUMER half of the destructive-command guard
2
+ // (feature `destructive-command-guard`, ADR-001, plan task T8).
3
+ //
4
+ // WHY THIS FILE EXISTS AT ALL. The classifier (`destructive-guard.ts`) already returned a verdict,
5
+ // and nothing on either host read it: the export existed, the consumer did not, so
6
+ // `rm -rf .agentic-qe` still executed on both runtimes. A verdict nobody consumes is a document,
7
+ // not a guard. This module is that consumer — and it is ONE module rather than two hook bodies,
8
+ // because the two hosts must give the SAME answer in the SAME words. A refusal text duplicated in
9
+ // two shell-spawned strings drifts silently the first time either is edited.
10
+ //
11
+ // PURE BY CONTRACT, exactly like the classifier it wraps: a payload in, an exit code and a piece of
12
+ // text out. No filesystem access, no subprocess, no environment. The hook bodies that call it read
13
+ // stdin, hand the parsed object over, print what comes back and exit with the code — nothing else.
14
+ //
15
+ // FAIL-OPEN ON OUR OWN FAILURE. Every branch that is not a decided `refuse` exits 0. An unparseable
16
+ // payload, a classifier that throws, a tool that is not a shell — none of them is evidence of a
17
+ // violation, and a guard that blocks on its own confusion is a guard the user switches off. This is
18
+ // the same polarity the sibling guards ship with (`claim-check-hook.cjs`, `dz-codex-veto.cjs`).
19
+ import { classifyDestructive } from './destructive-guard.js';
20
+ /** Prefix of a BLOCKING message. Must appear verbatim for a block to be attributed to this guard. */
21
+ export const DESTRUCTIVE_REFUSE_MARKER = 'DZ-DESTRUCTIVE:';
22
+ /** Prefix of a non-blocking message: an `undecidable` verdict, or our own failure to read. */
23
+ export const DESTRUCTIVE_WARN_MARKER = 'DZ-DESTRUCTIVE-WARN:';
24
+ /** Tool names that ARE a shell on the Claude host. */
25
+ const SHELL_TOOL_NAMES = new Set(['Bash', 'bash', 'shell', 'local_shell']);
26
+ /**
27
+ * Render the limits block from THE VERDICT's own list — never from a copy kept here.
28
+ *
29
+ * ADR-001 obliges the guard to NAME what it does not decide, in its own text, on every verdict it
30
+ * speaks about: a guard silent about its limits reads as a guarantee that recursive deletion is
31
+ * covered, and it is not. Re-listing them in this file would be a second copy free to drift from
32
+ * the classifier's; `verdict.limits` is the single source.
33
+ */
34
+ function renderLimits(limits) {
35
+ const head = 'Чего этот страж НЕ решает (печатается при каждом вердикте, чтобы он не читался как гарантия):';
36
+ return [head, ...limits.map((l) => ` - ${l}`)].join('\n');
37
+ }
38
+ function renderRefusal(verdict) {
39
+ return [
40
+ `${DESTRUCTIVE_REFUSE_MARKER} ОТКАЗ — удаление по буквальному пути в защищаемое хранилище.`,
41
+ '',
42
+ `Путь: ${verdict.path ?? '(не назван)'}`,
43
+ `Правило: ${verdict.rule ?? '(не названо)'}`,
44
+ `Причина: ${verdict.reason}`,
45
+ '',
46
+ renderLimits(verdict.limits),
47
+ '',
48
+ 'Санкционированное удаление защищаемого хранилища выполняет владелец вручную, вне сеанса агента.',
49
+ '',
50
+ ].join('\n');
51
+ }
52
+ function renderUndecidable(verdict) {
53
+ return [
54
+ `${DESTRUCTIVE_WARN_MARKER} команду не удалось разобрать (${verdict.reason}) — ПРОПУЩЕНА без отказа: «проверить не удалось» никогда не выдаётся за «запрещено» (AC-10).`,
55
+ renderLimits(verdict.limits),
56
+ '',
57
+ ].join('\n');
58
+ }
59
+ function unreadable(message) {
60
+ return {
61
+ exitCode: 0,
62
+ // One line, and no limits block: there is no verdict here to quote limits FROM, and inventing
63
+ // one would be the same lie in the other direction.
64
+ stderr: `${DESTRUCTIVE_WARN_MARKER} ${message} — пропуск без отказа; отсутствие вердикта нарушением не является.\n`,
65
+ outcome: 'unreadable',
66
+ rule: null,
67
+ };
68
+ }
69
+ const SKIP = { exitCode: 0, stderr: '', outcome: 'skipped', rule: null };
70
+ const ALLOW = { exitCode: 0, stderr: '', outcome: 'allow', rule: null };
71
+ /**
72
+ * Turn one hook payload into an exit code and a piece of text. The whole policy, both hosts.
73
+ *
74
+ * @param payload the parsed stdin object, or `null`/anything else when it did not parse.
75
+ * @param host which host contract the payload follows — see {@link DestructiveHookHost}.
76
+ */
77
+ export function decideDestructiveHook(payload, host) {
78
+ if (payload === null || typeof payload !== 'object') {
79
+ return unreadable('полезная нагрузка хука не прочиталась');
80
+ }
81
+ const p = payload;
82
+ if (host === 'claude') {
83
+ // The Claude contract: decide from the tool NAME first, and on a non-shell tool return before
84
+ // any command field is even looked at.
85
+ const toolName = typeof p.tool_name === 'string' ? p.tool_name : '';
86
+ if (!SHELL_TOOL_NAMES.has(toolName))
87
+ return SKIP;
88
+ }
89
+ const input = p.tool_input;
90
+ const command = input !== null && typeof input === 'object' ? input.command : undefined;
91
+ if (typeof command !== 'string' || command === '')
92
+ return SKIP;
93
+ let verdict;
94
+ try {
95
+ verdict = classifyDestructive(command);
96
+ }
97
+ catch (err) {
98
+ // Our own failure. Never a refusal — see the FAIL-OPEN note at the top of the file.
99
+ return unreadable(`классификатор не смог вынести вердикт (${String(err?.message ?? err)})`);
100
+ }
101
+ if (verdict.outcome === 'refuse') {
102
+ return { exitCode: 2, stderr: renderRefusal(verdict), outcome: 'refuse', rule: verdict.rule };
103
+ }
104
+ if (verdict.outcome === 'undecidable') {
105
+ return { exitCode: 0, stderr: renderUndecidable(verdict), outcome: 'undecidable', rule: null };
106
+ }
107
+ return ALLOW;
108
+ }
109
+ //# sourceMappingURL=destructive-guard-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destructive-guard-hook.js","sourceRoot":"","sources":["../src/destructive-guard-hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /** A verdict on one shell command. `limits` is always populated — see LIMITS below. */
2
+ export interface DestructiveVerdict {
3
+ /** `refuse` = block it. `allow` = out of scope. `undecidable` = could not parse; consumer PASSES. */
4
+ outcome: 'refuse' | 'allow' | 'undecidable';
5
+ /** The literal path that triggered the refusal, as the SHELL would pass it to the program. */
6
+ path: string | null;
7
+ /** The id of the rule that fired — one of DESTRUCTIVE_RULES. */
8
+ rule: string | null;
9
+ /** One line naming what was decided and why. A refusal always names the path and the rule. */
10
+ reason: string;
11
+ /** What this guard does NOT decide. Printed on EVERY verdict, refusals included. */
12
+ limits: readonly string[];
13
+ }
14
+ /** The complete rule table. A refusal always carries one of these ids. */
15
+ export declare const DESTRUCTIVE_RULES: readonly {
16
+ id: string;
17
+ what: string;
18
+ }[];
19
+ /**
20
+ * Decide whether a shell command must be refused BEFORE it runs.
21
+ *
22
+ * Refuses only when it can name a concrete literal path and the rule that path broke. Everything
23
+ * else is allowed, and every verdict prints the guard's limits (LIMITS) so it is never read as a
24
+ * total guarantee.
25
+ */
26
+ export declare function classifyDestructive(command: string): DestructiveVerdict;
27
+ //# sourceMappingURL=destructive-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destructive-guard.d.ts","sourceRoot":"","sources":["../src/destructive-guard.ts"],"names":[],"mappings":"AAkBA,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,qGAAqG;IACrG,OAAO,EAAE,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAC;IAC5C,8FAA8F;IAC9F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,gEAAgE;IAChE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAiBD,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAInE,CAAC;AA4rFH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAoBvE"}