approval-md 0.1.0 → 0.2.0

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 (266) hide show
  1. package/README.md +584 -553
  2. package/SPEC.md +42 -13
  3. package/dist/src/adapters/agentmail.d.ts +426 -0
  4. package/dist/src/adapters/agentmail.js +2 -2
  5. package/dist/src/adapters/conformance.d.ts +149 -0
  6. package/dist/src/adapters/contract.d.ts +628 -0
  7. package/dist/src/adapters/contract.js +110 -16
  8. package/dist/src/adapters/contract.js.map +1 -1
  9. package/dist/src/adapters/email.d.ts +324 -0
  10. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  11. package/dist/src/adapters/public.d.ts +11 -0
  12. package/dist/src/adapters/public.js +11 -0
  13. package/dist/src/adapters/public.js.map +1 -0
  14. package/dist/src/adapters/registry.d.ts +59 -0
  15. package/dist/src/adapters/registry.js +2 -1
  16. package/dist/src/adapters/registry.js.map +1 -1
  17. package/dist/src/adapters/smtp.d.ts +213 -0
  18. package/dist/src/adapters/vault-provider.d.ts +114 -0
  19. package/dist/src/adapters/vault-provider.js +3 -3
  20. package/dist/src/adapters/zzz.d.ts +66 -0
  21. package/dist/src/adapters/zzz.js +299 -0
  22. package/dist/src/adapters/zzz.js.map +1 -0
  23. package/dist/src/channels/batch.d.ts +109 -0
  24. package/dist/src/channels/cli.d.ts +193 -0
  25. package/dist/src/channels/conformance.d.ts +92 -0
  26. package/dist/src/channels/contract.d.ts +623 -0
  27. package/dist/src/channels/payload-view.d.ts +35 -0
  28. package/dist/src/channels/render-queue.d.ts +149 -0
  29. package/dist/src/channels/tagging.d.ts +196 -0
  30. package/dist/src/channels/telegram.d.ts +1832 -0
  31. package/dist/src/channels/web.d.ts +341 -0
  32. package/dist/src/cli/adapter.d.ts +90 -0
  33. package/dist/src/cli/adapter.js +25 -15
  34. package/dist/src/cli/adapter.js.map +1 -1
  35. package/dist/src/cli/amend.d.ts +59 -0
  36. package/dist/src/cli/args.d.ts +43 -0
  37. package/dist/src/cli/attest.d.ts +41 -0
  38. package/dist/src/cli/audit-card.d.ts +62 -0
  39. package/dist/src/cli/audit.d.ts +59 -0
  40. package/dist/src/cli/channel-telegram.d.ts +806 -0
  41. package/dist/src/cli/channel-web.d.ts +131 -0
  42. package/dist/src/cli/channel.d.ts +71 -0
  43. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  44. package/dist/src/cli/codex.d.ts +2 -0
  45. package/dist/src/cli/codex.js +172 -0
  46. package/dist/src/cli/codex.js.map +1 -0
  47. package/dist/src/cli/coverage.d.ts +61 -0
  48. package/dist/src/cli/daemon.d.ts +120 -0
  49. package/dist/src/cli/doctor.d.ts +129 -0
  50. package/dist/src/cli/doctor.js +119 -5
  51. package/dist/src/cli/doctor.js.map +1 -1
  52. package/dist/src/cli/env.d.ts +65 -0
  53. package/dist/src/cli/execute.d.ts +202 -0
  54. package/dist/src/cli/exit-codes.d.ts +73 -0
  55. package/dist/src/cli/feedback.d.ts +60 -0
  56. package/dist/src/cli/gate-window.d.ts +40 -0
  57. package/dist/src/cli/gate.d.ts +68 -0
  58. package/dist/src/cli/git-scope.d.ts +190 -0
  59. package/dist/src/cli/gloss-attach.d.ts +85 -0
  60. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  61. package/dist/src/cli/gloss-codex.d.ts +24 -0
  62. package/dist/src/cli/gloss-options.d.ts +42 -0
  63. package/dist/src/cli/gloss.d.ts +265 -0
  64. package/dist/src/cli/help.d.ts +103 -0
  65. package/dist/src/cli/help.js +173 -51
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +78 -0
  68. package/dist/src/cli/hook-codex.js +167 -0
  69. package/dist/src/cli/hook-codex.js.map +1 -0
  70. package/dist/src/cli/hook.d.ts +331 -0
  71. package/dist/src/cli/hook.js +186 -80
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.d.ts +35 -0
  74. package/dist/src/cli/init.d.ts +84 -0
  75. package/dist/src/cli/init.js +2 -2
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.d.ts +23 -0
  78. package/dist/src/cli/journal.d.ts +41 -0
  79. package/dist/src/cli/log-advance.d.ts +287 -0
  80. package/dist/src/cli/log-advance.js +102 -11
  81. package/dist/src/cli/log-advance.js.map +1 -1
  82. package/dist/src/cli/log-anchor.d.ts +176 -0
  83. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  84. package/dist/src/cli/log-sync.d.ts +243 -0
  85. package/dist/src/cli/log-verbs.d.ts +16 -0
  86. package/dist/src/cli/log-verbs.js +7 -1
  87. package/dist/src/cli/log-verbs.js.map +1 -1
  88. package/dist/src/cli/long-help.d.ts +70 -0
  89. package/dist/src/cli/main.d.ts +77 -0
  90. package/dist/src/cli/main.js +155 -5
  91. package/dist/src/cli/main.js.map +1 -1
  92. package/dist/src/cli/mcp.d.ts +52 -0
  93. package/dist/src/cli/paths.d.ts +56 -0
  94. package/dist/src/cli/payload.d.ts +58 -0
  95. package/dist/src/cli/policy.d.ts +43 -0
  96. package/dist/src/cli/preflight.d.ts +363 -0
  97. package/dist/src/cli/preflight.js +294 -7
  98. package/dist/src/cli/preflight.js.map +1 -1
  99. package/dist/src/cli/progress.d.ts +78 -0
  100. package/dist/src/cli/prompt.d.ts +209 -0
  101. package/dist/src/cli/quickstart.d.ts +46 -0
  102. package/dist/src/cli/quickstart.js +297 -0
  103. package/dist/src/cli/quickstart.js.map +1 -0
  104. package/dist/src/cli/records.d.ts +34 -0
  105. package/dist/src/cli/render.d.ts +22 -0
  106. package/dist/src/cli/sandbox.d.ts +51 -0
  107. package/dist/src/cli/scaffold.d.ts +79 -0
  108. package/dist/src/cli/setup-adapter.d.ts +137 -0
  109. package/dist/src/cli/setup-adapter.js +38 -4
  110. package/dist/src/cli/setup-adapter.js.map +1 -1
  111. package/dist/src/cli/setup-channel.d.ts +117 -0
  112. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  113. package/dist/src/cli/setup-common.d.ts +275 -0
  114. package/dist/src/cli/setup-flow.d.ts +287 -0
  115. package/dist/src/cli/setup-service.d.ts +96 -0
  116. package/dist/src/cli/setup.d.ts +202 -0
  117. package/dist/src/cli/style.d.ts +320 -0
  118. package/dist/src/cli/token.d.ts +39 -0
  119. package/dist/src/cli/up.d.ts +155 -0
  120. package/dist/src/cli/up.js +4 -2
  121. package/dist/src/cli/up.js.map +1 -1
  122. package/dist/src/cli/usage.d.ts +37 -0
  123. package/dist/src/cli/values.d.ts +40 -0
  124. package/dist/src/cli/vault.d.ts +59 -0
  125. package/dist/src/cli/vault.js +2 -2
  126. package/dist/src/cli/vault.js.map +1 -1
  127. package/dist/src/cli/verb-registry.d.ts +76 -0
  128. package/dist/src/cli/verb-registry.js +176 -8
  129. package/dist/src/cli/verb-registry.js.map +1 -1
  130. package/dist/src/cli/wordmark.d.ts +31 -0
  131. package/dist/src/cli/wordmark.js +2 -2
  132. package/dist/src/codex/doctor.d.ts +13 -0
  133. package/dist/src/codex/doctor.js +41 -0
  134. package/dist/src/codex/doctor.js.map +1 -0
  135. package/dist/src/codex/manifest.d.ts +49 -0
  136. package/dist/src/codex/manifest.js +103 -0
  137. package/dist/src/codex/manifest.js.map +1 -0
  138. package/dist/src/codex/templates.d.ts +41 -0
  139. package/dist/src/codex/templates.js +319 -0
  140. package/dist/src/codex/templates.js.map +1 -0
  141. package/dist/src/codex/trust.d.ts +19 -0
  142. package/dist/src/codex/trust.js +183 -0
  143. package/dist/src/codex/trust.js.map +1 -0
  144. package/dist/src/codex/workspace-plan.d.ts +131 -0
  145. package/dist/src/codex/workspace-plan.js +561 -0
  146. package/dist/src/codex/workspace-plan.js.map +1 -0
  147. package/dist/src/core/actor.d.ts +2 -0
  148. package/dist/src/core/actor.js +5 -0
  149. package/dist/src/core/actor.js.map +1 -0
  150. package/dist/src/core/advance-cycle.d.ts +170 -0
  151. package/dist/src/core/agents-md.d.ts +276 -0
  152. package/dist/src/core/apply-patch.d.ts +49 -0
  153. package/dist/src/core/apply-patch.js +266 -0
  154. package/dist/src/core/apply-patch.js.map +1 -0
  155. package/dist/src/core/attest.d.ts +420 -0
  156. package/dist/src/core/attest.js +13 -1
  157. package/dist/src/core/attest.js.map +1 -1
  158. package/dist/src/core/audit.d.ts +492 -0
  159. package/dist/src/core/budgets.d.ts +238 -0
  160. package/dist/src/core/checkpoint.d.ts +500 -0
  161. package/dist/src/core/child-env.d.ts +88 -0
  162. package/dist/src/core/clock.d.ts +52 -0
  163. package/dist/src/core/command-class.d.ts +543 -0
  164. package/dist/src/core/command-class.js +43 -8
  165. package/dist/src/core/command-class.js.map +1 -1
  166. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  167. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  168. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  169. package/dist/src/core/coverage.d.ts +217 -0
  170. package/dist/src/core/credential-spec.d.ts +72 -0
  171. package/dist/src/core/dark-session.d.ts +331 -0
  172. package/dist/src/core/decision-refusal.d.ts +185 -0
  173. package/dist/src/core/env-file.d.ts +450 -0
  174. package/dist/src/core/execute.d.ts +858 -0
  175. package/dist/src/core/execute.js +44 -6
  176. package/dist/src/core/execute.js.map +1 -1
  177. package/dist/src/core/frontmatter.d.ts +78 -0
  178. package/dist/src/core/gate-window.d.ts +312 -0
  179. package/dist/src/core/gate.d.ts +1364 -0
  180. package/dist/src/core/gate.js +68 -13
  181. package/dist/src/core/gate.js.map +1 -1
  182. package/dist/src/core/git-run.d.ts +73 -0
  183. package/dist/src/core/harness-version.d.ts +157 -0
  184. package/dist/src/core/harness-version.js +2 -1
  185. package/dist/src/core/harness-version.js.map +1 -1
  186. package/dist/src/core/harness-wait.d.ts +55 -0
  187. package/dist/src/core/head-retry.d.ts +107 -0
  188. package/dist/src/core/instance.d.ts +253 -0
  189. package/dist/src/core/intake-limits.d.ts +247 -0
  190. package/dist/src/core/jcs.d.ts +52 -0
  191. package/dist/src/core/journal.d.ts +144 -0
  192. package/dist/src/core/live-draw.d.ts +436 -0
  193. package/dist/src/core/log-reconcile.d.ts +89 -0
  194. package/dist/src/core/log-subscribe.d.ts +36 -0
  195. package/dist/src/core/log-subscribe.js +162 -0
  196. package/dist/src/core/log-subscribe.js.map +1 -0
  197. package/dist/src/core/log.d.ts +278 -0
  198. package/dist/src/core/loop.d.ts +274 -0
  199. package/dist/src/core/loop.js +11 -0
  200. package/dist/src/core/loop.js.map +1 -1
  201. package/dist/src/core/md-fence.d.ts +41 -0
  202. package/dist/src/core/money.d.ts +147 -0
  203. package/dist/src/core/payload-census.d.ts +74 -0
  204. package/dist/src/core/payload-store.d.ts +175 -0
  205. package/dist/src/core/payload.d.ts +71 -0
  206. package/dist/src/core/policy-diff.d.ts +292 -0
  207. package/dist/src/core/policy-diff.js +27 -4
  208. package/dist/src/core/policy-diff.js.map +1 -1
  209. package/dist/src/core/policy-expectations.d.ts +199 -0
  210. package/dist/src/core/policy-explain.d.ts +150 -0
  211. package/dist/src/core/policy-explain.js +31 -3
  212. package/dist/src/core/policy-explain.js.map +1 -1
  213. package/dist/src/core/policy-load.d.ts +527 -0
  214. package/dist/src/core/policy-load.js +15 -3
  215. package/dist/src/core/policy-load.js.map +1 -1
  216. package/dist/src/core/policy-match.d.ts +281 -0
  217. package/dist/src/core/policy-match.js +20 -9
  218. package/dist/src/core/policy-match.js.map +1 -1
  219. package/dist/src/core/policy-proposal.d.ts +265 -0
  220. package/dist/src/core/prompt-layout.d.ts +221 -0
  221. package/dist/src/core/protected-path-guard.d.ts +453 -0
  222. package/dist/src/core/protected-path-guard.js +514 -35
  223. package/dist/src/core/protected-path-guard.js.map +1 -1
  224. package/dist/src/core/registration.d.ts +25 -0
  225. package/dist/src/core/reindex.d.ts +99 -0
  226. package/dist/src/core/sampler.d.ts +313 -0
  227. package/dist/src/core/sandbox.d.ts +290 -0
  228. package/dist/src/core/seal.d.ts +165 -0
  229. package/dist/src/core/state.d.ts +505 -0
  230. package/dist/src/core/task-file.d.ts +185 -0
  231. package/dist/src/core/telegram-config.d.ts +93 -0
  232. package/dist/src/core/token.d.ts +409 -0
  233. package/dist/src/core/token.js +21 -38
  234. package/dist/src/core/token.js.map +1 -1
  235. package/dist/src/core/validate.d.ts +138 -0
  236. package/dist/src/core/values.d.ts +137 -0
  237. package/dist/src/core/vault.d.ts +291 -0
  238. package/dist/src/core/verified-snapshot.d.ts +204 -0
  239. package/dist/src/core/verify.d.ts +336 -0
  240. package/dist/src/core/version.d.ts +8 -0
  241. package/dist/src/core/wysiwys.d.ts +370 -0
  242. package/dist/src/daemon/advance-child.d.ts +39 -0
  243. package/dist/src/daemon/advance.d.ts +466 -0
  244. package/dist/src/daemon/audit.d.ts +87 -0
  245. package/dist/src/daemon/daemon.d.ts +1180 -0
  246. package/dist/src/daemon/dark-session.d.ts +64 -0
  247. package/dist/src/daemon/draw-child.d.ts +36 -0
  248. package/dist/src/daemon/draw.d.ts +154 -0
  249. package/dist/src/daemon/git-evidence.d.ts +173 -0
  250. package/dist/src/daemon/git-evidence.js +1 -1
  251. package/dist/src/daemon/projection.d.ts +180 -0
  252. package/dist/src/daemon/prune.d.ts +207 -0
  253. package/dist/src/mcp/http.d.ts +113 -0
  254. package/dist/src/mcp/server.d.ts +265 -0
  255. package/dist/src/mcp/server.js +9 -1
  256. package/dist/src/mcp/server.js.map +1 -1
  257. package/docs/adapter-api.md +106 -0
  258. package/docs/cli-reference.md +389 -36
  259. package/docs/codex-enforced-session.md +30 -0
  260. package/package.json +12 -2
  261. package/schema/codex-instance.schema.json +82 -0
  262. package/schema/event.schema.json +2 -1
  263. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  264. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  265. package/schema/policy.schema.json +21 -1
  266. package/templates/codex/README.md +9 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * `approval daemon run` — the foreground daemon verb (SPEC.md §10.2, APRV-39).
3
+ *
4
+ * As everywhere else in this CLI, **no logic lives here**. The loop, the drift
5
+ * comparison, the TTL sweep, and the queue regeneration are `daemon/daemon.ts`
6
+ * and `daemon/projection.ts`; every append they make goes through the same
7
+ * `core/gate.ts` and `core/log.ts` paths every other verb uses. This file
8
+ * resolves paths, validates two durations, renders the daemon's event stream as
9
+ * text or JSON, installs the signal handlers, and chooses an exit code.
10
+ *
11
+ * ## Foreground, deliberately
12
+ *
13
+ * `approval daemon run` runs in the foreground and stops on SIGINT/SIGTERM. It
14
+ * does not fork, write a pidfile, or manage its own lifecycle: in v0.1
15
+ * backgrounding is the operator's business, and `systemd`, `launchd`, `tmux`,
16
+ * and `&` all already do it better than a bespoke daemonizer would. That is
17
+ * stated in `--help` because an operator has to know it before they type it, and
18
+ * it is exactly the stance `approval channel telegram listen` takes for the same
19
+ * reason.
20
+ *
21
+ * ## What the exit code means
22
+ *
23
+ * A clean stop is 0 — a signal is how this verb is *supposed* to end. The three
24
+ * non-zero endings are the log's, mapped onto the frozen table exactly as every
25
+ * other verb maps them: 4 when the log cannot be read, 3 when its tail is torn,
26
+ * 1 when the chain does not verify. The daemon stops rather than degrades on all
27
+ * three, because nothing may be appended onto a log that does not verify and a
28
+ * projection of one would be a screenshot of something nobody should read.
29
+ */
30
+ import { type DaemonEvent, type DaemonOutcome } from "../daemon/daemon.js";
31
+ import { type AdvanceCadence } from "../daemon/advance.js";
32
+ import { type GitEvidenceEvent } from "../daemon/git-evidence.js";
33
+ import { type LoadPolicyOptions } from "../core/policy-load.js";
34
+ import type { Streams } from "./main.js";
35
+ /**
36
+ * A duration flag, in the policy's own `<n><unit>` vocabulary (SPEC.md §5.2).
37
+ *
38
+ * Exported for `approval up` (APRV-110), which accepts the same three durations
39
+ * and must refuse a typo in exactly the same words.
40
+ */
41
+ export declare function durationFlag(flags: Record<string, string | boolean>, name: string, fallback: number): {
42
+ ok: true;
43
+ ms: number;
44
+ } | {
45
+ ok: false;
46
+ message: string;
47
+ };
48
+ /**
49
+ * The cadence-advance flags, as one cadence or none (APRV-204).
50
+ *
51
+ * Exported for `approval up`, which accepts every `daemon run` flag and must
52
+ * refuse a typo in exactly the same words — the reason {@link durationFlag} is
53
+ * exported, and the reason this parsing is not written twice.
54
+ *
55
+ * Every duration and count is judged HERE, before the first tick: a daemon that
56
+ * accepted `--advance-after twenty` and quietly advanced on the default would be
57
+ * lying about its own configuration for as long as it ran.
58
+ */
59
+ export declare function advanceFlags(flags: Record<string, string | boolean>): {
60
+ ok: true;
61
+ cadence: AdvanceCadence | null;
62
+ } | {
63
+ ok: false;
64
+ message: string;
65
+ };
66
+ /**
67
+ * The prefix-proof flags, resolved against the policy (APRV-217).
68
+ *
69
+ * Precedence is the one every override in this CLI uses: the flag wins for this
70
+ * run, the policy governs when no flag was typed, and an unloadable policy
71
+ * yields the default — which here is `full`, the strictest and most expensive
72
+ * proof, so a policy nobody could read cannot buy a cheaper one.
73
+ *
74
+ * Exported for `approval up`, which accepts every `daemon run` flag and must
75
+ * refuse a typo in exactly the same words — the reason {@link durationFlag} and
76
+ * {@link advanceFlags} are exported.
77
+ */
78
+ export declare function readProofFlags(flags: Record<string, string | boolean>, policy: LoadPolicyOptions): {
79
+ ok: true;
80
+ readProof: {
81
+ mode: "full" | "incremental";
82
+ everyReads: number;
83
+ afterMs: number;
84
+ };
85
+ } | {
86
+ ok: false;
87
+ message: string;
88
+ };
89
+ /**
90
+ * One daemon event as a human sentence.
91
+ *
92
+ * Warnings go to stderr and everything else to stdout, so `approval daemon run >
93
+ * daemon.log` keeps the narrative and leaves the complaints on the terminal.
94
+ */
95
+ export declare function describeDaemonEvent(event: DaemonEvent): {
96
+ text: string;
97
+ stderr: boolean;
98
+ };
99
+ /**
100
+ * One git-evidence line as a human sentence (APRV-42).
101
+ *
102
+ * Its own function rather than a branch of {@link describe}, because the
103
+ * hardening layer has its own frozen event shape and `daemon/daemon.ts`'s union
104
+ * is untouched by the opt-in. Failures go to stderr with everything else that
105
+ * complains.
106
+ */
107
+ export declare function describeGitEvidence(event: GitEvidenceEvent): {
108
+ text: string;
109
+ stderr: boolean;
110
+ };
111
+ /** The outcome → frozen exit code mapping, drawn where every other verb draws it. */
112
+ export declare function exitForDaemonOutcome(outcome: DaemonOutcome): number;
113
+ /**
114
+ * `approval daemon run`. Returns a promise, so `main` treats `daemon` the way it
115
+ * treats `channel`: the CLI is synchronous by contract, and the two long-lived
116
+ * verbs report their eventual code through `process.exitCode`.
117
+ */
118
+ export declare function commandDaemonRun(argv: string[], streams: Streams, cwd: string): number | Promise<number>;
119
+ /** `approval daemon <subcommand>` — `run`, and nothing else at v0.1. */
120
+ export declare function commandDaemon(argv: string[], streams: Streams, cwd: string): number | Promise<number>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `approval doctor` — environment sanity in one verb (APRV-31).
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * During the live policy-amendment ceremony the operator lost time twice to
7
+ * questions this command answers in a second. First they drove a **stale
8
+ * checkout**: `dist/` was older than the source tree, so verbs that existed in
9
+ * `src/` were simply absent from the built CLI and every invocation looked like
10
+ * a version confusion rather than a missing `npm run build`. Then they reached
11
+ * for what turned out to be an **unbuilt placeholder binary** — a `cli.js`
12
+ * loader with no `dist/` behind it, which fails with one line about a missing
13
+ * file and says nothing about which of the several checkouts on the machine is
14
+ * the real one. Only on the third try did they find the working install.
15
+ *
16
+ * Neither failure was a bug in the runtime. Both were facts about the
17
+ * environment that nothing was in a position to state out loud. `doctor` is
18
+ * that statement: eleven checks, in the order in which their failures cascade,
19
+ * each with a concrete repair.
20
+ *
21
+ * ## Every fix begins with a command (APRV-75)
22
+ *
23
+ * A `fix` string starts with something the operator can paste, and the prose
24
+ * comes after it. The reason is the reading order of a failed run: an operator
25
+ * scanning a wall of `fix:` lines is looking for the next thing to type, and a
26
+ * line that opens with "check that…" makes them read a sentence to discover
27
+ * there is nothing to type at all. {@link FIX_COMMAND_PREFIXES} is the pinned
28
+ * allowlist, and `tests/cli-doctor.test.ts` drives every failing verdict this
29
+ * command can produce and asserts the shape (never the wording).
30
+ *
31
+ * ## What it will not do
32
+ *
33
+ * **It appends nothing.** Not an event, not a marker, not a "doctor ran"
34
+ * breadcrumb. An operator reaching for a diagnostic while the log is in a state
35
+ * they do not understand must not have that state changed by looking at it; the
36
+ * test suite byte-compares the log across a run.
37
+ *
38
+ * **It sends no message.** The Telegram check calls `getMe` and only `getMe` —
39
+ * a pure identity read. It never calls `sendMessage` (a diagnostic that pings a
40
+ * human's phone is a diagnostic nobody runs twice) and it never calls
41
+ * `getUpdates`, because a running `channel telegram listen` owns that offset
42
+ * and a stray poll would consume an update the listener would then never see.
43
+ *
44
+ * **It repairs nothing.** Every failure yields a `fix` string the human runs
45
+ * themselves. A doctor that rebuilt, re-attested, or truncated on its own would
46
+ * be making exactly the decisions this project exists to keep human.
47
+ *
48
+ * ## Exit codes
49
+ *
50
+ * 0 when every check passed or skipped, 1 when any failed. {@link EXIT_IO} is
51
+ * reserved for doctor's own inability to look — the installation root cannot be
52
+ * stat'd for a reason other than "not there". An unreadable *log* or an
53
+ * unreadable *policy* is not that: those are environment facts, which is
54
+ * precisely what this command reports, so they are check failures (exit 1).
55
+ */
56
+ import { type HarnessKind } from "../core/harness-version.js";
57
+ import type { Streams } from "./main.js";
58
+ import { type Style } from "./style.js";
59
+ /** One check's verdict. `fix` is present only when there is something to do. */
60
+ export interface DoctorCheck {
61
+ check: string;
62
+ status: "pass" | "fail" | "skip";
63
+ detail: string;
64
+ fix?: string;
65
+ }
66
+ /**
67
+ * Every `fix` string begins with one of these, and the prose follows (APRV-75).
68
+ *
69
+ * A closed, small list rather than "looks like a command": the point is that a
70
+ * reader can paste the head of the line, and a `fix` that opened with a verb
71
+ * nobody has installed would be no better than a sentence. `approval ` is by far
72
+ * the commonest — most repairs in this runtime are another verb of this CLI —
73
+ * and the shell builtins here are the ones an actual repair needs: a variable to
74
+ * export, a mode to set, an ignore line to append, a directory to move aside.
75
+ *
76
+ * Note what is NOT here: no `rm`, no `sudo`, no `git commit`. Doctor repairs
77
+ * nothing, and a fix line that told an operator to delete or to commit would be
78
+ * making the decision this project exists to keep human.
79
+ */
80
+ export declare const FIX_COMMAND_PREFIXES: readonly string[];
81
+ /**
82
+ * Report only project configuration visible on disk.
83
+ *
84
+ * Codex owns hook trust and runtime loading. Neither is inferable from a file,
85
+ * and no historical log record proves what the current desktop session loaded.
86
+ */
87
+ export declare function checkCodexHookWiring(dir: string): DoctorCheck;
88
+ /**
89
+ * Every harness this checkout registers an `approval hook` command for.
90
+ *
91
+ * Shape-agnostic on purpose: `.claude/settings.json` nests the command under
92
+ * `hooks.PreToolUse[].hooks[].command` and `.cursor/hooks.json` under
93
+ * `hooks.preToolUse[].command`, and a third harness would nest it somewhere
94
+ * else again. What all of them have in common is a STRING somewhere in the
95
+ * document that invokes this CLI, so the document is parsed as JSON (a file
96
+ * that is not JSON registers nothing this can read) and its string leaves are
97
+ * searched. The row this feeds can only SKIP when the answer is empty, so a
98
+ * miss costs a skip and never a false red.
99
+ */
100
+ export declare function registeredHarnesses(dir: string): HarnessKind[];
101
+ /**
102
+ * The human report: one aligned row per check, fixes indented under their row.
103
+ *
104
+ * The line contract is load-bearing and older than the table (APRV-91 #9): a
105
+ * check occupies exactly one line, and a `fix` exactly one indented line under
106
+ * it, so an operator scanning a failed run counts rows rather than paragraphs.
107
+ * What the table changed is alignment and colour, never that arithmetic.
108
+ *
109
+ * A detail is abbreviated only when a TERMINAL WIDTH IS KNOWN and the row would
110
+ * not fit it, and `--verbose` (APRV-102) turns even that off. The brief asked
111
+ * for truncation outright; this is the narrowed version of it, for two reasons.
112
+ * A pipe has no width, so piped output — which every other suite pins, and
113
+ * which is what a bug report contains — is never abbreviated at all. And a
114
+ * `fix:` line is never touched on any path: repair instructions cut off
115
+ * mid-command are worse than a wide line, which is what the truncation was
116
+ * supposed to prevent.
117
+ */
118
+ export declare function renderDoctorHuman(checks: readonly DoctorCheck[], st?: Style, options?: {
119
+ verbose?: boolean;
120
+ width?: number | null;
121
+ }): string;
122
+ /**
123
+ * `approval doctor …` — run every check in order and report.
124
+ *
125
+ * Returns a number for the paths that are decided before any I/O (help, usage),
126
+ * and a promise otherwise, because two checks are asynchronous. `main`
127
+ * dispatches both shapes, as it already does for `channel`.
128
+ */
129
+ export declare function commandDoctor(argv: string[], streams: Streams, cwd: string): number | Promise<number>;
@@ -55,7 +55,7 @@
55
55
  */
56
56
  import { createServer } from "node:net";
57
57
  import { closeSync, existsSync, openSync, readFileSync, readdirSync, statSync, unlinkSync, } from "node:fs";
58
- import { dirname, isAbsolute, join, resolve as resolvePathSegments } from "node:path";
58
+ import { basename, dirname, isAbsolute, join, resolve as resolvePathSegments } from "node:path";
59
59
  import { WEB_DEFAULT_PORT } from "../channels/web.js";
60
60
  import { TELEGRAM_DEFAULT_API_BASE, telegramChatEnvFor, telegramTokenEnvFor, } from "../channels/telegram.js";
61
61
  import { HUMAN_ACTOR_ENV, checkAttestation, findOrganAttestation, latestOrganAttestation, policyBytesHash, resolveHumanActor, } from "../core/attest.js";
@@ -1947,15 +1947,123 @@ function checkHarnessWiring(dir) {
1947
1947
  detail: `WIRED on disk: ${path} registers \`approval hook\` for PreToolUse over ${GATED_TOOLS.join(", ")}. This is the file being present, NOT proof this session loaded it — the APRV-151 bypasses happened in worktrees carrying exactly this entry. The check that does not trust session wiring is the CI-side grant cross-check over the committed log, which asks whether the CHANGE was granted rather than whether the path ever was (APRV-202).`,
1948
1948
  };
1949
1949
  }
1950
+ /** The project-local Codex hook files doctor can observe without asking Codex. */
1951
+ const CODEX_HOOKS = join(".codex", "hooks.json");
1952
+ const CODEX_CONFIG = join(".codex", "config.toml");
1953
+ const CODEX_MATCHER = "Bash|apply_patch";
1954
+ const CODEX_HOOK_TIMEOUT_SECONDS = 600;
1955
+ function isDirectCodexHookCommand(command) {
1956
+ const match = command.match(/^(?:"([^"$`\\\r\n;&|<>]+)"|'([^'\\\r\n;&|<>]+)'|([^\s"'$`\\\r\n;&|<>]+)) hook codex --dir (?:"([^"$`\\\r\n;&|<>]+)"|'([^'\\\r\n;&|<>]+)'|([^\s"'$`\\\r\n;&|<>]+)) --as agent:codex --timeout 9m$/u);
1957
+ if (match === null)
1958
+ return false;
1959
+ const executable = match[1] ?? match[2] ?? match[3] ?? "";
1960
+ const primaryDir = match[4] ?? match[5] ?? match[6] ?? "";
1961
+ return isAbsolute(executable) && basename(executable) === "approval" && isAbsolute(primaryDir);
1962
+ }
1963
+ function codexEventConfigured(hooks, event) {
1964
+ if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks))
1965
+ return false;
1966
+ const groups = hooks[event];
1967
+ if (!Array.isArray(groups))
1968
+ return false;
1969
+ return groups.some((group) => {
1970
+ if (typeof group !== "object" || group === null || Array.isArray(group))
1971
+ return false;
1972
+ const fields = group;
1973
+ if (fields["matcher"] !== CODEX_MATCHER || !Array.isArray(fields["hooks"]))
1974
+ return false;
1975
+ return fields["hooks"].some((handler) => {
1976
+ if (typeof handler !== "object" || handler === null || Array.isArray(handler))
1977
+ return false;
1978
+ const entry = handler;
1979
+ return (entry["type"] === "command" &&
1980
+ typeof entry["command"] === "string" &&
1981
+ isDirectCodexHookCommand(entry["command"]) &&
1982
+ entry["timeout"] === CODEX_HOOK_TIMEOUT_SECONDS &&
1983
+ (entry["async"] === undefined || entry["async"] === false));
1984
+ });
1985
+ });
1986
+ }
1987
+ /**
1988
+ * Report only project configuration visible on disk.
1989
+ *
1990
+ * Codex owns hook trust and runtime loading. Neither is inferable from a file,
1991
+ * and no historical log record proves what the current desktop session loaded.
1992
+ */
1993
+ export function checkCodexHookWiring(dir) {
1994
+ const check = "codex-hook-wiring";
1995
+ const root = repoRoot(dir);
1996
+ const where = root ?? dir;
1997
+ const hooksPath = join(where, CODEX_HOOKS);
1998
+ const configPath = join(where, CODEX_CONFIG);
1999
+ const hasHooks = existsSync(hooksPath);
2000
+ const hasConfig = existsSync(configPath);
2001
+ if (!hasHooks && !hasConfig) {
2002
+ return {
2003
+ check,
2004
+ status: "skip",
2005
+ detail: `NOT CONFIGURED on disk: ${where} carries neither ${CODEX_HOOKS} nor ${CODEX_CONFIG}. Codex hook trust and observed execution are separate and remain unknown.`,
2006
+ };
2007
+ }
2008
+ if (!hasHooks) {
2009
+ return {
2010
+ check,
2011
+ status: "skip",
2012
+ detail: `${configPath} exists. Doctor does not interpret inline TOML hook tables, so Codex hook configuration, trust and observed execution are undetermined.`,
2013
+ fix: "approval hook codex --help — compare the documented PreToolUse and PostToolUse entries with .codex/config.toml",
2014
+ };
2015
+ }
2016
+ let parsed;
2017
+ try {
2018
+ parsed = JSON.parse(readFileSync(hooksPath, "utf8"));
2019
+ }
2020
+ catch (cause) {
2021
+ return {
2022
+ check,
2023
+ status: "fail",
2024
+ detail: `${hooksPath} cannot be read as JSON (${oneLine(detailOf(cause))}); configured wiring cannot be established, and trust or execution cannot be inferred.`,
2025
+ fix: "approval hook codex --help — compare and repair the project-local hook JSON after human review",
2026
+ };
2027
+ }
2028
+ const hooks = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
2029
+ ? parsed["hooks"]
2030
+ : null;
2031
+ const pre = codexEventConfigured(hooks, "PreToolUse");
2032
+ const post = codexEventConfigured(hooks, "PostToolUse");
2033
+ if (!pre || !post) {
2034
+ const missing = [!pre ? "PreToolUse" : null, !post ? "PostToolUse" : null]
2035
+ .filter((value) => value !== null)
2036
+ .join(" and ");
2037
+ return {
2038
+ check,
2039
+ status: "skip",
2040
+ detail: `${hooksPath} is present but does not match the expected APRV-313 profile for ${missing}: synchronous command hooks with matcher ${JSON.stringify(CODEX_MATCHER)}, command \`approval hook codex\`, and timeout ${String(CODEX_HOOK_TIMEOUT_SECONDS)} seconds. Other Codex hook configurations may be valid; this integration's coverage is undetermined. File presence proves neither trust nor observed execution.`,
2041
+ fix: "approval hook codex --help — install the documented pair only after human review",
2042
+ };
2043
+ }
2044
+ if (hasConfig) {
2045
+ return {
2046
+ check,
2047
+ status: "skip",
2048
+ detail: `CONFIGURED in ${hooksPath}: the required PreToolUse and PostToolUse entries are present. ${configPath} also exists and Codex merges hook sources; doctor does not interpret its TOML tables, so the effective configuration is not fully established. Trust and observed execution remain unknown.`,
2049
+ fix: "approval hook codex --help — compare .codex/config.toml with the reviewed hooks.json and keep one representation per layer",
2050
+ };
2051
+ }
2052
+ return {
2053
+ check,
2054
+ status: "pass",
2055
+ detail: `CONFIGURED on disk: ${hooksPath} carries synchronous PreToolUse and PostToolUse \`approval hook codex\` entries for ${CODEX_MATCHER}, each with a ${String(CODEX_HOOK_TIMEOUT_SECONDS)} second outer timeout. This does not establish Codex trust, loading, or observed execution; inspect and trust the exact hook with \`/hooks\`, then run the bounded smoke test.`,
2056
+ };
2057
+ }
1950
2058
  // ---------------------------------------------------------------------------
1951
2059
  // harness version provenance (APRV-227)
1952
2060
  // ---------------------------------------------------------------------------
1953
2061
  /** The Cursor counterpart of {@link CLAUDE_SETTINGS}. */
1954
2062
  const CURSOR_HOOKS = join(".cursor", "hooks.json");
1955
2063
  /** Where a harness hook registration can be written, one file per harness. */
1956
- const HARNESS_SETTINGS = [CLAUDE_SETTINGS, CURSOR_HOOKS];
2064
+ const HARNESS_SETTINGS = [CLAUDE_SETTINGS, CURSOR_HOOKS, CODEX_HOOKS];
1957
2065
  /** `approval hook <kind>` inside a command string, whichever file shape holds it. */
1958
- const HOOK_COMMAND = /\bapproval\s+hook\s+(claude-code|cursor)\b/u;
2066
+ const HOOK_COMMAND = /\bapproval["']?\s+hook\s+(claude-code|cursor|codex)\b/u;
1959
2067
  /**
1960
2068
  * Every harness this checkout registers an `approval hook` command for.
1961
2069
  *
@@ -1968,7 +2076,7 @@ const HOOK_COMMAND = /\bapproval\s+hook\s+(claude-code|cursor)\b/u;
1968
2076
  * searched. The row this feeds can only SKIP when the answer is empty, so a
1969
2077
  * miss costs a skip and never a false red.
1970
2078
  */
1971
- function registeredHarnesses(dir) {
2079
+ export function registeredHarnesses(dir) {
1972
2080
  const found = new Set();
1973
2081
  for (const relative of HARNESS_SETTINGS) {
1974
2082
  const path = join(dir, relative);
@@ -1986,8 +2094,11 @@ function registeredHarnesses(dir) {
1986
2094
  const node = stack.pop();
1987
2095
  if (typeof node === "string") {
1988
2096
  const match = HOOK_COMMAND.exec(node);
1989
- if (match !== null && isHarnessKind(match[1]))
2097
+ if (match !== null &&
2098
+ isHarnessKind(match[1]) &&
2099
+ (match[1] !== "codex" || isDirectCodexHookCommand(node))) {
1990
2100
  found.add(match[1]);
2101
+ }
1991
2102
  continue;
1992
2103
  }
1993
2104
  if (Array.isArray(node)) {
@@ -2631,6 +2742,9 @@ export function commandDoctor(argv, streams, cwd) {
2631
2742
  // `.approval/` is a directory people `git add` from, and the key store had
2632
2743
  // nothing telling them it must not come along.
2633
2744
  checkSealedKeys(logPath, dir),
2745
+ // APRV-313: appended, nineteenth time, same reason. Configuration on
2746
+ // disk is distinct from Codex trust, loading and observed execution.
2747
+ checkCodexHookWiring(dir),
2634
2748
  ];
2635
2749
  const ok = checks.every((entry) => entry.status !== "fail");
2636
2750
  if (json)