@lannguyensi/harness 0.44.0 → 0.46.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 (234) hide show
  1. package/CHANGELOG.md +1245 -0
  2. package/dist/cli/adopt/derive.d.ts +16 -7
  3. package/dist/cli/adopt/derive.js +19 -2
  4. package/dist/cli/adopt/derive.js.map +1 -1
  5. package/dist/cli/adopt/index.js +18 -3
  6. package/dist/cli/adopt/index.js.map +1 -1
  7. package/dist/cli/apply/apply.d.ts +1 -0
  8. package/dist/cli/apply/apply.js +62 -6
  9. package/dist/cli/apply/apply.js.map +1 -1
  10. package/dist/cli/apply/generate-codex-config.js +5 -6
  11. package/dist/cli/apply/generate-codex-config.js.map +1 -1
  12. package/dist/cli/apply/generate-opencode-config.d.ts +58 -0
  13. package/dist/cli/apply/generate-opencode-config.js +210 -0
  14. package/dist/cli/apply/generate-opencode-config.js.map +1 -0
  15. package/dist/cli/apply/generate-settings.d.ts +90 -32
  16. package/dist/cli/apply/generate-settings.js +133 -10
  17. package/dist/cli/apply/generate-settings.js.map +1 -1
  18. package/dist/cli/apply/index.d.ts +2 -1
  19. package/dist/cli/apply/index.js +2 -1
  20. package/dist/cli/apply/index.js.map +1 -1
  21. package/dist/cli/apply/next-steps.d.ts +20 -0
  22. package/dist/cli/apply/next-steps.js +60 -0
  23. package/dist/cli/apply/next-steps.js.map +1 -1
  24. package/dist/cli/approve/stdin-report.d.ts +15 -0
  25. package/dist/cli/approve/stdin-report.js +1 -1
  26. package/dist/cli/approve/stdin-report.js.map +1 -1
  27. package/dist/cli/approve/understanding.d.ts +75 -0
  28. package/dist/cli/approve/understanding.js +150 -2
  29. package/dist/cli/approve/understanding.js.map +1 -1
  30. package/dist/cli/audit.d.ts +1 -1
  31. package/dist/cli/audit.js +3 -2
  32. package/dist/cli/audit.js.map +1 -1
  33. package/dist/cli/doctor/claude-mcp.d.ts +19 -1
  34. package/dist/cli/doctor/claude-mcp.js +36 -10
  35. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  36. package/dist/cli/doctor/codex.d.ts +3 -1
  37. package/dist/cli/doctor/codex.js +6 -9
  38. package/dist/cli/doctor/codex.js.map +1 -1
  39. package/dist/cli/doctor/format.js +94 -8
  40. package/dist/cli/doctor/format.js.map +1 -1
  41. package/dist/cli/doctor/index.d.ts +23 -0
  42. package/dist/cli/doctor/index.js +106 -2
  43. package/dist/cli/doctor/index.js.map +1 -1
  44. package/dist/cli/doctor/opencode.d.ts +24 -0
  45. package/dist/cli/doctor/opencode.js +107 -0
  46. package/dist/cli/doctor/opencode.js.map +1 -0
  47. package/dist/cli/doctor/target-checks.d.ts +11 -0
  48. package/dist/cli/doctor/target-checks.js +25 -0
  49. package/dist/cli/doctor/target-checks.js.map +1 -0
  50. package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
  51. package/dist/cli/doctor/toolchain-parity.js +154 -0
  52. package/dist/cli/doctor/toolchain-parity.js.map +1 -0
  53. package/dist/cli/doctor/types.d.ts +85 -1
  54. package/dist/cli/doctor/types.js +1 -1
  55. package/dist/cli/doctor/types.js.map +1 -1
  56. package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
  57. package/dist/cli/doctor/understanding-mode-env.js +83 -0
  58. package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
  59. package/dist/cli/dry-run.js +32 -17
  60. package/dist/cli/dry-run.js.map +1 -1
  61. package/dist/cli/explain.d.ts +1 -1
  62. package/dist/cli/explain.js +1 -1
  63. package/dist/cli/explain.js.map +1 -1
  64. package/dist/cli/index.js +64 -5
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/cli/init/composer.js +36 -13
  67. package/dist/cli/init/composer.js.map +1 -1
  68. package/dist/cli/init/interactive.d.ts +7 -4
  69. package/dist/cli/init/interactive.js +79 -27
  70. package/dist/cli/init/interactive.js.map +1 -1
  71. package/dist/cli/init/profiles.d.ts +2 -2
  72. package/dist/cli/init/profiles.js +32 -1
  73. package/dist/cli/init/profiles.js.map +1 -1
  74. package/dist/cli/init/templates.d.ts +2 -1
  75. package/dist/cli/init/templates.js +130 -20
  76. package/dist/cli/init/templates.js.map +1 -1
  77. package/dist/cli/pack/approve-escape.js +69 -8
  78. package/dist/cli/pack/approve-escape.js.map +1 -1
  79. package/dist/cli/pack/hook-branch-protection.js +1 -1
  80. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  81. package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
  82. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  83. package/dist/cli/pack/hook-post-merge-gate.js +1 -1
  84. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
  85. package/dist/cli/pack/hook-pre-tool-use.js +91 -4
  86. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  87. package/dist/cli/pack/hook-solution-acceptance.js +19 -2
  88. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
  89. package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
  90. package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
  91. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  92. package/dist/cli/pause/index.js +1 -1
  93. package/dist/cli/pause/index.js.map +1 -1
  94. package/dist/cli/policy/intercept.d.ts +148 -0
  95. package/dist/cli/policy/intercept.js +379 -48
  96. package/dist/cli/policy/intercept.js.map +1 -1
  97. package/dist/cli/session-start/index.d.ts +12 -3
  98. package/dist/cli/session-start/index.js +67 -11
  99. package/dist/cli/session-start/index.js.map +1 -1
  100. package/dist/cli/session-start/stale-base-check.d.ts +71 -0
  101. package/dist/cli/session-start/stale-base-check.js +379 -0
  102. package/dist/cli/session-start/stale-base-check.js.map +1 -0
  103. package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
  104. package/dist/cli/session-start/toolchain-parity.js +244 -36
  105. package/dist/cli/session-start/toolchain-parity.js.map +1 -1
  106. package/dist/cli/smoke/assertions.d.ts +9 -2
  107. package/dist/cli/smoke/assertions.js +9 -2
  108. package/dist/cli/smoke/assertions.js.map +1 -1
  109. package/dist/cli/uninstall/index.js +18 -2
  110. package/dist/cli/uninstall/index.js.map +1 -1
  111. package/dist/cli/validate/checks.d.ts +3 -1
  112. package/dist/cli/validate/checks.js +298 -3
  113. package/dist/cli/validate/checks.js.map +1 -1
  114. package/dist/io/claude-mcp.d.ts +122 -1
  115. package/dist/io/claude-mcp.js +214 -12
  116. package/dist/io/claude-mcp.js.map +1 -1
  117. package/dist/io/duration.js.map +1 -0
  118. package/dist/io/expand-home.js.map +1 -0
  119. package/dist/io/extract.js.map +1 -0
  120. package/dist/io/harness-lock.js +1 -1
  121. package/dist/io/harness-lock.js.map +1 -1
  122. package/dist/{runtime → io}/ledger-record.d.ts +1 -1
  123. package/dist/io/ledger-record.js.map +1 -0
  124. package/dist/policies/index.d.ts +2 -2
  125. package/dist/policies/index.js +2 -2
  126. package/dist/policies/index.js.map +1 -1
  127. package/dist/policies/ledger-client.js +10 -6
  128. package/dist/policies/ledger-client.js.map +1 -1
  129. package/dist/policies/producers.js +1 -1
  130. package/dist/policies/producers.js.map +1 -1
  131. package/dist/policies/requires.js +2 -2
  132. package/dist/policies/requires.js.map +1 -1
  133. package/dist/policy-packs/builtin/branch-protection.js +28 -3
  134. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  135. package/dist/policy-packs/builtin/post-merge-gate.js +45 -9
  136. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
  137. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +139 -8
  138. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +246 -12
  139. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  140. package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
  141. package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
  142. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
  143. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
  144. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
  145. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
  146. package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
  147. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
  148. package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
  149. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
  150. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
  151. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
  152. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
  153. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
  154. package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
  155. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
  156. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
  157. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
  158. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
  159. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
  160. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
  161. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
  162. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
  163. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
  164. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
  165. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
  166. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
  167. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
  168. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
  169. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
  170. package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
  171. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  172. package/dist/policy-packs/registry.d.ts +11 -0
  173. package/dist/policy-packs/registry.js +1 -1
  174. package/dist/policy-packs/registry.js.map +1 -1
  175. package/dist/policy-packs/runtime.d.ts +1 -1
  176. package/dist/policy-packs/runtime.js +10 -1
  177. package/dist/policy-packs/runtime.js.map +1 -1
  178. package/dist/runtime/agent-facing.js +1 -1
  179. package/dist/runtime/agent-facing.js.map +1 -1
  180. package/dist/runtime/bash-match-facts.d.ts +9 -7
  181. package/dist/runtime/bash-match-facts.js +9 -7
  182. package/dist/runtime/bash-match-facts.js.map +1 -1
  183. package/dist/runtime/bash-match-registry.d.ts +75 -38
  184. package/dist/runtime/bash-match-registry.js +279 -232
  185. package/dist/runtime/bash-match-registry.js.map +1 -1
  186. package/dist/runtime/bash-prefix-parse.d.ts +7 -0
  187. package/dist/runtime/bash-prefix-parse.js +205 -17
  188. package/dist/runtime/bash-prefix-parse.js.map +1 -1
  189. package/dist/runtime/command-normalize.d.ts +112 -0
  190. package/dist/runtime/command-normalize.js +411 -38
  191. package/dist/runtime/command-normalize.js.map +1 -1
  192. package/dist/runtime/hermetic-spawn-guard.js +6 -0
  193. package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
  194. package/dist/runtime/index.d.ts +2 -2
  195. package/dist/runtime/index.js +2 -2
  196. package/dist/runtime/index.js.map +1 -1
  197. package/dist/runtime/intercept.d.ts +78 -3
  198. package/dist/runtime/intercept.js +151 -39
  199. package/dist/runtime/intercept.js.map +1 -1
  200. package/dist/runtime/ledger-add.js +1 -1
  201. package/dist/runtime/ledger-add.js.map +1 -1
  202. package/dist/runtime/read-only-bash.js +303 -24
  203. package/dist/runtime/read-only-bash.js.map +1 -1
  204. package/dist/schema/doctor.d.ts +25 -0
  205. package/dist/schema/doctor.js +23 -0
  206. package/dist/schema/doctor.js.map +1 -0
  207. package/dist/schema/extract.js +1 -1
  208. package/dist/schema/extract.js.map +1 -1
  209. package/dist/schema/index.d.ts +76 -0
  210. package/dist/schema/index.js +9 -0
  211. package/dist/schema/index.js.map +1 -1
  212. package/dist/schema/requires.js +2 -2
  213. package/dist/schema/requires.js.map +1 -1
  214. package/dist/schema/risk.d.ts +7 -0
  215. package/dist/schema/risk.js +23 -0
  216. package/dist/schema/risk.js.map +1 -1
  217. package/dist/schema/stale-base-check.d.ts +18 -0
  218. package/dist/schema/stale-base-check.js +37 -0
  219. package/dist/schema/stale-base-check.js.map +1 -0
  220. package/dist/schema/toolchain-parity.d.ts +3 -0
  221. package/dist/schema/toolchain-parity.js +13 -0
  222. package/dist/schema/toolchain-parity.js.map +1 -1
  223. package/package.json +3 -2
  224. package/dist/policies/duration.js.map +0 -1
  225. package/dist/policies/extract.js.map +0 -1
  226. package/dist/runtime/expand-home.js.map +0 -1
  227. package/dist/runtime/ledger-record.js.map +0 -1
  228. /package/dist/{policies → io}/duration.d.ts +0 -0
  229. /package/dist/{policies → io}/duration.js +0 -0
  230. /package/dist/{runtime → io}/expand-home.d.ts +0 -0
  231. /package/dist/{runtime → io}/expand-home.js +0 -0
  232. /package/dist/{policies → io}/extract.d.ts +0 -0
  233. /package/dist/{policies → io}/extract.js +0 -0
  234. /package/dist/{runtime → io}/ledger-record.js +0 -0
@@ -0,0 +1,309 @@
1
+ // Persisted JSON report handling (`.understanding-gate/reports/`), split
2
+ // out of the former monolithic understanding-before-execution-runtime.ts
3
+ // (structural concentration slice 2, agent-tasks 348a4d42). Pure move: see
4
+ // src/policy-packs/builtin/understanding-before-execution/index.ts for
5
+ // the re-exported public surface.
6
+ //
7
+ // `safeJsonParse` is exported (it was module-private in the monolith) so
8
+ // markers.ts can reuse it for marker-body parsing -- import/export
9
+ // mechanics only, no behavior change.
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { atomicWriteFile } from "../../../io/atomic-write.js";
13
+ const DEFAULT_REPORTS_DIRNAME = ".understanding-gate";
14
+ const REPORTS_SUBDIR = "reports";
15
+ /**
16
+ * Env var the persisted-report directory can be set from. Honored by
17
+ * harness (`defaultReportsDir` below + emitted by `harness apply` onto
18
+ * the pack-contributed hook commands) AND by `@lannguyensi/understanding-gate`
19
+ * (its `core/persistence.js:resolveReportDir` reads the same name), so
20
+ * the three actors that touch the directory — Stop hook (package),
21
+ * PreToolUse blocker (harness), `harness approve understanding` — can
22
+ * agree on the path regardless of each process's cwd.
23
+ */
24
+ export const REPORTS_DIR_ENV = "UNDERSTANDING_GATE_REPORT_DIR";
25
+ /**
26
+ * Resolve the persisted-report directory. Precedence:
27
+ * 1. `UNDERSTANDING_GATE_REPORT_DIR` (taken verbatim — apply emits an
28
+ * absolute path, operator-exported values are shell-expanded before
29
+ * we see them).
30
+ * 2. `<cwd>/.understanding-gate/reports` — backward-compat fallback.
31
+ * Callers that have a stable anchor (the manifest directory) pass
32
+ * it as `cwd` so the fallback agrees with whatever path apply
33
+ * baked into the hook commands.
34
+ */
35
+ export function defaultReportsDir(cwd = process.cwd()) {
36
+ const fromEnv = process.env[REPORTS_DIR_ENV];
37
+ if (typeof fromEnv === "string" && fromEnv.length > 0)
38
+ return fromEnv;
39
+ return path.join(cwd, DEFAULT_REPORTS_DIRNAME, REPORTS_SUBDIR);
40
+ }
41
+ /**
42
+ * Project root anchor for the reports directory: `<dir-of-manifest>/.understanding-gate/reports`.
43
+ * Used by `harness apply` to bake an absolute, manifest-anchored value into
44
+ * the pack-contributed hook commands' env, and by `harness approve` as the
45
+ * fallback when `UNDERSTANDING_GATE_REPORT_DIR` is unset.
46
+ */
47
+ export function reportsDirForManifest(manifestPath) {
48
+ return path.join(path.dirname(manifestPath), DEFAULT_REPORTS_DIRNAME, REPORTS_SUBDIR);
49
+ }
50
+ export function safeJsonParse(text) {
51
+ try {
52
+ return JSON.parse(text);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /**
59
+ * Parse the ISO prefix of a producer filename
60
+ * (`2026-05-24T06-16-39-409Z-<slug>-<hash>.json`) into epoch ms. The
61
+ * producer flattens `:` and `.` to `-` for filesystem safety; undo that
62
+ * before `Date.parse`. null when the name does not carry the prefix.
63
+ */
64
+ function parseFilenameIsoMs(name) {
65
+ const m = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/.exec(name);
66
+ if (!m)
67
+ return null;
68
+ const ms = Date.parse(`${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`);
69
+ return Number.isNaN(ms) ? null : ms;
70
+ }
71
+ function readPersistedReport(filePath, mtimeMs) {
72
+ let raw;
73
+ try {
74
+ raw = fs.readFileSync(filePath, "utf8");
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ const parsed = safeJsonParse(raw);
80
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
81
+ return null;
82
+ const obj = parsed;
83
+ const createdAt = typeof obj["createdAt"] === "string" ? obj["createdAt"] : null;
84
+ const createdAtJsonMs = createdAt !== null ? Date.parse(createdAt) : Number.NaN;
85
+ const createdAtMs = !Number.isNaN(createdAtJsonMs)
86
+ ? createdAtJsonMs
87
+ : (parseFilenameIsoMs(path.basename(filePath)) ?? mtimeMs);
88
+ return {
89
+ filePath,
90
+ sessionId: typeof obj["sessionId"] === "string" ? obj["sessionId"] : null,
91
+ approvalStatus: typeof obj["approvalStatus"] === "string" ? obj["approvalStatus"] : null,
92
+ approvedAt: typeof obj["approvedAt"] === "string" ? obj["approvedAt"] : null,
93
+ createdAt,
94
+ createdAtMs,
95
+ };
96
+ }
97
+ /**
98
+ * List persisted reports under `dir`, newest-first by creation time
99
+ * (JSON `createdAt`, falling back to the filename ISO prefix, falling
100
+ * back to mtime). Missing directory returns []. Any I/O error on a
101
+ * single file is silently skipped; the caller falls through to the
102
+ * ledger result.
103
+ *
104
+ * Creation time, NOT mtime, is the sort key: `harness approve
105
+ * understanding` rewrites the report it flips, which bumps mtime and
106
+ * made an old just-approved report sort as the freshest
107
+ * (harness-discovery C1). mtime survives only as the last-resort
108
+ * fallback for files that carry neither timestamp.
109
+ */
110
+ export function listPersistedReports(dir) {
111
+ let names;
112
+ try {
113
+ names = fs.readdirSync(dir);
114
+ }
115
+ catch {
116
+ return [];
117
+ }
118
+ const reports = [];
119
+ for (const name of names) {
120
+ if (!name.endsWith(".json"))
121
+ continue;
122
+ const full = path.join(dir, name);
123
+ let stat;
124
+ try {
125
+ stat = fs.statSync(full);
126
+ }
127
+ catch {
128
+ continue;
129
+ }
130
+ if (!stat.isFile())
131
+ continue;
132
+ const report = readPersistedReport(full, stat.mtimeMs);
133
+ if (!report)
134
+ continue;
135
+ reports.push(report);
136
+ }
137
+ reports.sort((a, b) => b.createdAtMs - a.createdAtMs);
138
+ return reports;
139
+ }
140
+ /**
141
+ * Maximum age a sessionId-null report may have for the tolerant
142
+ * fallback to adopt it on the `harness approve understanding` path.
143
+ * Sized for the real flow (Stop hook persists at turn end, operator
144
+ * approves within minutes) with slack for a slow read-through. Live
145
+ * repro that motivated it: a 17-day-old pending report got adopted,
146
+ * validated, and stamped for a fresh session because the producer had
147
+ * silently failed to persist the fresh report (harness-discovery C1,
148
+ * friction-log #67).
149
+ */
150
+ export const TOLERANT_FALLBACK_MAX_AGE_MS = 15 * 60_000;
151
+ /**
152
+ * Tolerance for a sessionId-less candidate whose `createdAt` lies in
153
+ * the FUTURE relative to the approve-time clock. A future creation
154
+ * time is suspect either way: a forged `createdAt` (the producer's
155
+ * Metadata block lets the agent author it) or serious clock skew.
156
+ * Beyond this skew the fallback rejects the candidate just like a
157
+ * stale one rather than trusting a timestamp that cannot be right.
158
+ */
159
+ export const TOLERANT_FALLBACK_FUTURE_SKEW_MS = 5 * 60_000;
160
+ /**
161
+ * Select the freshest report for a given session_id, or the freshest
162
+ * applicable report when the persisted file lacks a sessionId field
163
+ * (older package versions). `report: null` when nothing matches.
164
+ *
165
+ * The strict (sessionId-equals) match always wins. The tolerant
166
+ * fallback's appetite is controlled by `opts.tolerantFallback` and
167
+ * `opts.maxFallbackAgeMs` — see `FindReportOptions`. The selection
168
+ * result carries enough context (`fallbackAdopted`, `staleRejected`)
169
+ * for the caller to be loud about non-strict adoptions.
170
+ */
171
+ export function selectReportForSession(reports, sessionId, opts = {}) {
172
+ // Strict match first.
173
+ for (const r of reports) {
174
+ if (r.sessionId === sessionId) {
175
+ return { report: r, fallbackAdopted: false, staleRejected: [] };
176
+ }
177
+ }
178
+ // Tolerant fallback: a report without sessionId is treated as
179
+ // applicable to whichever session is asking. Only kicks in when no
180
+ // matching sessionId-tagged report exists — which includes the case
181
+ // where the producer Stop hook silently failed to persist the live
182
+ // session's report, so the candidates here may be entirely unrelated
183
+ // leftovers. `maxFallbackAgeMs` is the guard against adopting those.
184
+ const mode = opts.tolerantFallback ?? "any";
185
+ const nowMs = (opts.now ?? new Date()).getTime();
186
+ const staleRejected = [];
187
+ for (const r of reports) {
188
+ if (r.sessionId !== null)
189
+ continue;
190
+ if (mode === "uncompleted" &&
191
+ (r.approvalStatus === "approved" || r.approvalStatus === "expired")) {
192
+ // A completed-cycle report from another session/task; skipping it
193
+ // here is what stops `harness approve understanding` from binding
194
+ // the live session to a stale, unrelated report.
195
+ continue;
196
+ }
197
+ if (opts.maxFallbackAgeMs !== undefined) {
198
+ const ageMs = nowMs - r.createdAtMs;
199
+ if (ageMs > opts.maxFallbackAgeMs || ageMs < -TOLERANT_FALLBACK_FUTURE_SKEW_MS) {
200
+ staleRejected.push(r);
201
+ continue;
202
+ }
203
+ }
204
+ return { report: r, fallbackAdopted: true, staleRejected };
205
+ }
206
+ return { report: null, fallbackAdopted: false, staleRejected };
207
+ }
208
+ /**
209
+ * Back-compat wrapper around `selectReportForSession` for callers that
210
+ * only need the report (the gate read and expiry paths).
211
+ */
212
+ export function findLatestReportForSession(reports, sessionId, opts = {}) {
213
+ return selectReportForSession(reports, sessionId, opts).report;
214
+ }
215
+ /**
216
+ * Flip the latest matching persisted report's approvalStatus to
217
+ * `expired` so it no longer satisfies the gate's persisted-report
218
+ * fallback (harness/1ee26e77 follow-up: post-tool-use expiry was
219
+ * marker-only; the persisted report at .understanding-gate/reports/
220
+ * silently kept satisfying the gate even after task_finish deleted
221
+ * the marker).
222
+ *
223
+ * Atomic rewrite. Preserves the rest of the report body so the audit
224
+ * trail (the operator's actual Understanding text + previous approval
225
+ * timestamps) stays intact; only the status fields change.
226
+ *
227
+ * Returns `{ ok: true, filePath, previousStatus }` on success,
228
+ * `{ ok: false, reason }` when no matching report exists or rewrite
229
+ * failed. Non-throwing: caller (post-tool-use hook) uses this as a
230
+ * best-effort cleanup so a missing report dir or unrelated I/O issue
231
+ * does not escalate into a session-breaking hook failure.
232
+ */
233
+ export function expirePersistedReport(reportsDir, sessionId, now = new Date()) {
234
+ const reports = listPersistedReports(reportsDir);
235
+ if (reports.length === 0) {
236
+ return { ok: false, reason: `no reports under ${reportsDir}` };
237
+ }
238
+ const latest = findLatestReportForSession(reports, sessionId);
239
+ if (!latest) {
240
+ return {
241
+ ok: false,
242
+ reason: `no report matched session_id=${sessionId} (${reports.length} report(s) for other sessions)`,
243
+ };
244
+ }
245
+ if (latest.approvalStatus !== "approved") {
246
+ return {
247
+ ok: false,
248
+ reason: `latest report ${path.basename(latest.filePath)} already has approvalStatus=${latest.approvalStatus ?? "<missing>"}, nothing to expire`,
249
+ };
250
+ }
251
+ let raw;
252
+ try {
253
+ raw = fs.readFileSync(latest.filePath, "utf8");
254
+ }
255
+ catch (err) {
256
+ return { ok: false, reason: `failed to read ${latest.filePath}: ${err.message}` };
257
+ }
258
+ let parsed;
259
+ try {
260
+ parsed = JSON.parse(raw);
261
+ }
262
+ catch (err) {
263
+ return { ok: false, reason: `failed to parse ${latest.filePath}: ${err.message}` };
264
+ }
265
+ const previousStatus = typeof parsed["approvalStatus"] === "string" ? parsed["approvalStatus"] : null;
266
+ parsed["approvalStatus"] = "expired";
267
+ parsed["expiredAt"] = now.toISOString();
268
+ try {
269
+ atomicWriteFile(latest.filePath, `${JSON.stringify(parsed, null, 2)}\n`);
270
+ }
271
+ catch (err) {
272
+ return {
273
+ ok: false,
274
+ reason: `failed to rewrite ${latest.filePath}: ${err.message}`,
275
+ };
276
+ }
277
+ return { ok: true, filePath: latest.filePath, previousStatus };
278
+ }
279
+ export function checkPersistedReport(reportsDir, sessionId) {
280
+ const reports = listPersistedReports(reportsDir);
281
+ if (reports.length === 0) {
282
+ return {
283
+ approved: false,
284
+ detail: `no reports found at ${reportsDir}`,
285
+ report: null,
286
+ };
287
+ }
288
+ const latest = findLatestReportForSession(reports, sessionId);
289
+ if (!latest) {
290
+ return {
291
+ approved: false,
292
+ detail: `no report matched session_id=${sessionId} (${reports.length} report(s) for other sessions)`,
293
+ report: null,
294
+ };
295
+ }
296
+ if (latest.approvalStatus !== "approved") {
297
+ return {
298
+ approved: false,
299
+ detail: `latest report ${path.basename(latest.filePath)} has approvalStatus=${latest.approvalStatus ?? "<missing>"}`,
300
+ report: latest,
301
+ };
302
+ }
303
+ return {
304
+ approved: true,
305
+ detail: `approved via persisted report ${path.basename(latest.filePath)}${latest.approvedAt ? ` (approved at ${latest.approvedAt})` : ""}`,
306
+ report: latest,
307
+ };
308
+ }
309
+ //# sourceMappingURL=persisted-reports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persisted-reports.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/persisted-reports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { type ApprovalLifecycle } from "./lifecycle.js";
2
+ import { expirePersistedReport } from "./persisted-reports.js";
3
+ /**
4
+ * Tool names treated as "the Bash tool" for `expire_on_bash_match`
5
+ * command-regex matching. Claude Code has exactly one Bash tool name;
6
+ * a runtime with shell-tool aliases (e.g. Codex's `shell` /
7
+ * `exec_command` / `functions.exec_command`) passes its own set.
8
+ */
9
+ export declare const DEFAULT_BASH_TOOL_NAMES: ReadonlySet<string>;
10
+ /**
11
+ * Tool-name membership test against `expire_on_tool_match`. No GLOB
12
+ * wildcard expansion by design (agent-tasks/d8ee60ca): operators write
13
+ * the exact tool name they mean.
14
+ *
15
+ * It IS alias-aware, though: a tool name is compared against `patterns`
16
+ * after expanding it through `expandToolNameAliases` (shell-tool
17
+ * aliases `Bash`/`shell`/`exec_command`/`functions.exec_command`, and
18
+ * MCP tool-name variants — server hyphen/underscore swap, the
19
+ * `mcp__server__.tool` dotted form). This mirrors the normalization
20
+ * `harness policy intercept`'s `policyMatchesEvent` already applies to
21
+ * the incoming `event.tool_name` (`src/runtime/intercept.ts`, commit
22
+ * 9aacbcd "Fix Codex hook tool matching") — that fix exists precisely
23
+ * because Codex can emit an MCP tool name in one of these variant
24
+ * forms for the identical tool. Before this, a Codex session sending
25
+ * `mcp__agent-tasks__.task_finish` (dotted) or an underscore-server
26
+ * variant would silently never expire the marker: the Codex generator
27
+ * alias-expands the EMITTED TOML `matcher` (`expandCodexHookMatchPattern`
28
+ * in `generate-codex-config.ts`) so Codex's own dispatcher still
29
+ * invokes the hook command, but this function's comparison against the
30
+ * canonical `expire_on_tool_match` config list would reject the
31
+ * variant `tool_name` once inside the hook body — the boundary would
32
+ * silently never fire even though the hook ran (review finding on task
33
+ * a1348c89).
34
+ */
35
+ export declare function toolNameMatchesAny(toolName: string, patterns: readonly string[]): boolean;
36
+ /** First `expire_on_bash_match` regex the command satisfies, or
37
+ * undefined. Patterns are pre-compiled by `parseApprovalLifecycle`
38
+ * (invalid ones already dropped with a warning). Empty command
39
+ * short-circuits to undefined. */
40
+ export declare function bashCommandMatchesAny(command: string, patterns: readonly RegExp[]): RegExp | undefined;
41
+ /** Pull the Bash command out of a tool_input payload, `""` when absent
42
+ * or malformed. */
43
+ export declare function extractBashCommandFromToolInput(toolInput: unknown): string;
44
+ /** Pull `taskId` out of an MCP tool_input payload (`""` when absent /
45
+ * malformed). Task-boundary agent-tasks verbs carry this as a
46
+ * top-level string field. */
47
+ export declare function extractTaskIdFromToolInput(toolInput: unknown): string;
48
+ /** Pull the legacy v1 `tasks_transition` `status` field out of a
49
+ * tool_input payload (`""` when absent / malformed). Only `"done"`
50
+ * releases the work claim; the caller treats any other value as
51
+ * keep-claim. */
52
+ export declare function extractTasksTransitionStatusFromToolInput(toolInput: unknown): string;
53
+ export interface PostToolUseBoundaryMatch {
54
+ /** Final match decision: `toolNameMatched || bashRegex !== undefined`. */
55
+ matched: boolean;
56
+ /** Tool-name match, refined by the tasks_transition status filter. */
57
+ toolNameMatched: boolean;
58
+ /** Tool-name match BEFORE the tasks_transition status filter — lets
59
+ * the caller distinguish "not in the list" from "in the list, but
60
+ * status keeps the claim" for its diagnostic. */
61
+ rawToolNameMatched: boolean;
62
+ /** The `expire_on_bash_match` regex the command satisfied, if any. */
63
+ bashRegex: RegExp | undefined;
64
+ }
65
+ /**
66
+ * Decide whether `toolName` (+ its `tool_input`) crosses one of the
67
+ * configured `approval_lifecycle` boundaries. Pure — no filesystem
68
+ * access. Callers branch on `.matched` before touching marker/report
69
+ * state (see `applyPostToolUseExpiry`).
70
+ */
71
+ export declare function matchPostToolUseBoundary(toolName: string, toolInput: unknown, lifecycle: Pick<ApprovalLifecycle, "expireOnToolMatch" | "expireOnBashMatch">, bashToolNames?: ReadonlySet<string>): PostToolUseBoundaryMatch;
72
+ export interface ApplyPostToolUseExpiryResult {
73
+ /** Did the session marker exist before this call cleared it? */
74
+ wasMarkerPresent: boolean;
75
+ /** Was a task-scoped marker also cleared? Only possible when
76
+ * `toolNameMatched` (Bash regex boundaries never carry a taskId). */
77
+ taskMarkerCleared: boolean;
78
+ /** The task id whose marker was cleared, `""` when none. */
79
+ clearedTaskId: string;
80
+ /** Did the persisted-report expiry succeed? */
81
+ persistedReportExpired: boolean;
82
+ /** Full expiry result (success detail or skip reason) for diagnostics. */
83
+ reportExpiry: ReturnType<typeof expirePersistedReport>;
84
+ }
85
+ /**
86
+ * Side-effecting: clear the session marker, the task-scoped marker
87
+ * (when `toolNameMatched` and `tool_input.taskId` names an existing
88
+ * marker), and expire the persisted report. Call ONLY after
89
+ * `matchPostToolUseBoundary(...).matched` is true — this function does
90
+ * not re-check the match itself. Mirrors the Claude hook's original
91
+ * side effects exactly so both runtimes share one clearing
92
+ * implementation (task a1348c89).
93
+ */
94
+ export declare function applyPostToolUseExpiry(generatedDir: string, sessionId: string, toolInput: unknown, toolNameMatched: boolean, reportsDir: string, now?: Date): ApplyPostToolUseExpiryResult;
95
+ /**
96
+ * Compose the stderr diagnostic line for a matched-and-applied PostToolUse
97
+ * expiry. Shared by `harness pack hook post-tool-use` (Claude) and
98
+ * `harness pack hook codex-post-tool-use` (Codex) — before this extraction
99
+ * the two hooks each built this line by hand from the same four pieces
100
+ * (`matchSource`/`taskNote`/`reportNote`/the present-vs-absent branch),
101
+ * which `check:duplication` flagged as new clones the moment the Codex
102
+ * sibling landed (task a1348c89). `hookLabel` is the caller's own
103
+ * `harness pack hook ...` command string so the message still names the
104
+ * right verb.
105
+ */
106
+ export declare function describePostToolUseExpiry(hookLabel: string, sessionId: string, toolName: string, bashRegex: RegExp | undefined, expiry: ApplyPostToolUseExpiryResult): string;
@@ -0,0 +1,209 @@
1
+ // PostToolUse boundary matching + expiry (task a1348c89, Codex parity),
2
+ // split out of the former monolithic understanding-before-execution-runtime.ts
3
+ // (structural concentration slice 2, agent-tasks 348a4d42). Pure move: see
4
+ // src/policy-packs/builtin/understanding-before-execution/index.ts for
5
+ // the re-exported public surface.
6
+ import * as fs from "node:fs";
7
+ import { expandToolNameAliases } from "../../../runtime/tool-name-aliases.js";
8
+ import { approvalMarkerPathFor, clearApprovalMarker } from "./markers.js";
9
+ import { expirePersistedReport } from "./persisted-reports.js";
10
+ import { clearTaskApprovalMarker, taskApprovalMarkerPathFor } from "./task-markers.js";
11
+ // ---------------------------------------------------------------------
12
+ // PostToolUse boundary matching + expiry (task a1348c89, Codex parity).
13
+ // Shared between `harness pack hook post-tool-use` (Claude) and
14
+ // `harness pack hook codex-post-tool-use` (Codex): both clear the SAME
15
+ // marker/task-marker/persisted-report state on the SAME configured
16
+ // task-boundary tools. Before this extraction the matching logic lived
17
+ // only inline in the Claude hook file; a Codex sibling copying it by
18
+ // hand would have re-implemented (and eventually drifted from) it —
19
+ // the exact drift class task e7c2ec3c fixed on the PreToolUse side via
20
+ // `checkOperatorApprovalMarkers` above. One implementation, two thin
21
+ // runtime-specific callers.
22
+ // ---------------------------------------------------------------------
23
+ /**
24
+ * Tool names treated as "the Bash tool" for `expire_on_bash_match`
25
+ * command-regex matching. Claude Code has exactly one Bash tool name;
26
+ * a runtime with shell-tool aliases (e.g. Codex's `shell` /
27
+ * `exec_command` / `functions.exec_command`) passes its own set.
28
+ */
29
+ export const DEFAULT_BASH_TOOL_NAMES = new Set(["Bash"]);
30
+ /** Canonical agent-tasks v1 tool name whose `tool_input.status` gates
31
+ * marker expiry (see `matchPostToolUseBoundary` below). Not imported
32
+ * from `hook-track-active-claim.ts`'s own `TOOL_NAME_TASKS_TRANSITION`
33
+ * constant: `policy-packs/` may not import from `cli/` (layering rule,
34
+ * `.dependency-cruiser.cjs`). */
35
+ const TASKS_TRANSITION_TOOL_NAME = "mcp__agent-tasks__tasks_transition";
36
+ /**
37
+ * Tool-name membership test against `expire_on_tool_match`. No GLOB
38
+ * wildcard expansion by design (agent-tasks/d8ee60ca): operators write
39
+ * the exact tool name they mean.
40
+ *
41
+ * It IS alias-aware, though: a tool name is compared against `patterns`
42
+ * after expanding it through `expandToolNameAliases` (shell-tool
43
+ * aliases `Bash`/`shell`/`exec_command`/`functions.exec_command`, and
44
+ * MCP tool-name variants — server hyphen/underscore swap, the
45
+ * `mcp__server__.tool` dotted form). This mirrors the normalization
46
+ * `harness policy intercept`'s `policyMatchesEvent` already applies to
47
+ * the incoming `event.tool_name` (`src/runtime/intercept.ts`, commit
48
+ * 9aacbcd "Fix Codex hook tool matching") — that fix exists precisely
49
+ * because Codex can emit an MCP tool name in one of these variant
50
+ * forms for the identical tool. Before this, a Codex session sending
51
+ * `mcp__agent-tasks__.task_finish` (dotted) or an underscore-server
52
+ * variant would silently never expire the marker: the Codex generator
53
+ * alias-expands the EMITTED TOML `matcher` (`expandCodexHookMatchPattern`
54
+ * in `generate-codex-config.ts`) so Codex's own dispatcher still
55
+ * invokes the hook command, but this function's comparison against the
56
+ * canonical `expire_on_tool_match` config list would reject the
57
+ * variant `tool_name` once inside the hook body — the boundary would
58
+ * silently never fire even though the hook ran (review finding on task
59
+ * a1348c89).
60
+ */
61
+ export function toolNameMatchesAny(toolName, patterns) {
62
+ const patternSet = new Set(patterns);
63
+ return expandToolNameAliases(toolName).some((alias) => patternSet.has(alias));
64
+ }
65
+ /** First `expire_on_bash_match` regex the command satisfies, or
66
+ * undefined. Patterns are pre-compiled by `parseApprovalLifecycle`
67
+ * (invalid ones already dropped with a warning). Empty command
68
+ * short-circuits to undefined. */
69
+ export function bashCommandMatchesAny(command, patterns) {
70
+ if (command === "")
71
+ return undefined;
72
+ for (const re of patterns) {
73
+ if (re.test(command))
74
+ return re;
75
+ }
76
+ return undefined;
77
+ }
78
+ function toolInputRecord(toolInput) {
79
+ if (typeof toolInput !== "object" || toolInput === null || Array.isArray(toolInput)) {
80
+ return null;
81
+ }
82
+ return toolInput;
83
+ }
84
+ /** Pull the Bash command out of a tool_input payload, `""` when absent
85
+ * or malformed. */
86
+ export function extractBashCommandFromToolInput(toolInput) {
87
+ const command = toolInputRecord(toolInput)?.["command"];
88
+ return typeof command === "string" ? command : "";
89
+ }
90
+ /** Pull `taskId` out of an MCP tool_input payload (`""` when absent /
91
+ * malformed). Task-boundary agent-tasks verbs carry this as a
92
+ * top-level string field. */
93
+ export function extractTaskIdFromToolInput(toolInput) {
94
+ const tid = toolInputRecord(toolInput)?.["taskId"];
95
+ return typeof tid === "string" ? tid : "";
96
+ }
97
+ /** Pull the legacy v1 `tasks_transition` `status` field out of a
98
+ * tool_input payload (`""` when absent / malformed). Only `"done"`
99
+ * releases the work claim; the caller treats any other value as
100
+ * keep-claim. */
101
+ export function extractTasksTransitionStatusFromToolInput(toolInput) {
102
+ const s = toolInputRecord(toolInput)?.["status"];
103
+ return typeof s === "string" ? s : "";
104
+ }
105
+ /**
106
+ * Decide whether `toolName` (+ its `tool_input`) crosses one of the
107
+ * configured `approval_lifecycle` boundaries. Pure — no filesystem
108
+ * access. Callers branch on `.matched` before touching marker/report
109
+ * state (see `applyPostToolUseExpiry`).
110
+ */
111
+ export function matchPostToolUseBoundary(toolName, toolInput, lifecycle, bashToolNames = DEFAULT_BASH_TOOL_NAMES) {
112
+ const rawToolNameMatched = toolNameMatchesAny(toolName, lifecycle.expireOnToolMatch);
113
+ // Legacy v1 `tasks_transition`: only `status=done` releases the work
114
+ // claim (per task_finish docs: "The work claim is cleared when going
115
+ // to done and kept when going to review"). open / in_progress /
116
+ // review / missing status keep the marker. Detected via the same
117
+ // alias-aware `toolNameMatchesAny` as the general match above (not a
118
+ // raw `===`): a Codex dotted/server-variant `tasks_transition`
119
+ // tool_name must still get the status filter applied, otherwise it
120
+ // would fall through to the unconditional `true` branch below and
121
+ // clear the marker on ANY status — a worse bug than a missed match
122
+ // (review finding on task a1348c89).
123
+ const tasksTransitionStatusOk = toolNameMatchesAny(toolName, [
124
+ TASKS_TRANSITION_TOOL_NAME,
125
+ ])
126
+ ? extractTasksTransitionStatusFromToolInput(toolInput) === "done"
127
+ : true;
128
+ const toolNameMatched = rawToolNameMatched && tasksTransitionStatusOk;
129
+ // Bash check only runs when the event is actually a Bash(-alias) call;
130
+ // an MCP tool whose name happens to match a regex is not a Bash
131
+ // boundary.
132
+ const bashRegex = bashToolNames.has(toolName)
133
+ ? bashCommandMatchesAny(extractBashCommandFromToolInput(toolInput), lifecycle.expireOnBashMatch)
134
+ : undefined;
135
+ return {
136
+ matched: toolNameMatched || bashRegex !== undefined,
137
+ toolNameMatched,
138
+ rawToolNameMatched,
139
+ bashRegex,
140
+ };
141
+ }
142
+ /**
143
+ * Side-effecting: clear the session marker, the task-scoped marker
144
+ * (when `toolNameMatched` and `tool_input.taskId` names an existing
145
+ * marker), and expire the persisted report. Call ONLY after
146
+ * `matchPostToolUseBoundary(...).matched` is true — this function does
147
+ * not re-check the match itself. Mirrors the Claude hook's original
148
+ * side effects exactly so both runtimes share one clearing
149
+ * implementation (task a1348c89).
150
+ */
151
+ export function applyPostToolUseExpiry(generatedDir, sessionId, toolInput, toolNameMatched, reportsDir, now) {
152
+ const markerPath = approvalMarkerPathFor(generatedDir, sessionId);
153
+ const wasMarkerPresent = fs.existsSync(markerPath);
154
+ clearApprovalMarker(generatedDir, sessionId);
155
+ // Task-scoped marker cleanup (harness/1ee26e77). Only when the
156
+ // matched tool is an MCP task-transition verb whose tool_input.taskId
157
+ // names a specific task; Bash regex boundaries don't carry a taskId
158
+ // by design.
159
+ let taskMarkerCleared = false;
160
+ let clearedTaskId = "";
161
+ if (toolNameMatched) {
162
+ const taskId = extractTaskIdFromToolInput(toolInput);
163
+ if (taskId !== "") {
164
+ const taskMarkerPath = taskApprovalMarkerPathFor(generatedDir, taskId);
165
+ if (fs.existsSync(taskMarkerPath)) {
166
+ clearTaskApprovalMarker(generatedDir, taskId);
167
+ taskMarkerCleared = true;
168
+ clearedTaskId = taskId;
169
+ }
170
+ }
171
+ }
172
+ // Persisted-report expiry (harness/1ee26e77 follow-up). Closes the
173
+ // silent bypass that existed since PR #172: marker-deletion alone did
174
+ // not invalidate the persisted-report fallback. Best-effort.
175
+ const reportExpiry = expirePersistedReport(reportsDir, sessionId, now);
176
+ return {
177
+ wasMarkerPresent,
178
+ taskMarkerCleared,
179
+ clearedTaskId,
180
+ persistedReportExpired: reportExpiry.ok,
181
+ reportExpiry,
182
+ };
183
+ }
184
+ /**
185
+ * Compose the stderr diagnostic line for a matched-and-applied PostToolUse
186
+ * expiry. Shared by `harness pack hook post-tool-use` (Claude) and
187
+ * `harness pack hook codex-post-tool-use` (Codex) — before this extraction
188
+ * the two hooks each built this line by hand from the same four pieces
189
+ * (`matchSource`/`taskNote`/`reportNote`/the present-vs-absent branch),
190
+ * which `check:duplication` flagged as new clones the moment the Codex
191
+ * sibling landed (task a1348c89). `hookLabel` is the caller's own
192
+ * `harness pack hook ...` command string so the message still names the
193
+ * right verb.
194
+ */
195
+ export function describePostToolUseExpiry(hookLabel, sessionId, toolName, bashRegex, expiry) {
196
+ const matchSource = bashRegex !== undefined
197
+ ? `bash regex /${bashRegex.source}/`
198
+ : `tool name`;
199
+ const taskNote = expiry.taskMarkerCleared
200
+ ? `; also cleared task marker for task ${expiry.clearedTaskId}`
201
+ : "";
202
+ const reportNote = expiry.reportExpiry.ok
203
+ ? `; expired persisted report ${expiry.reportExpiry.filePath}`
204
+ : `; persisted-report expiry skipped (${expiry.reportExpiry.reason})`;
205
+ return expiry.wasMarkerPresent
206
+ ? `${hookLabel}: expired approval marker for session ${sessionId} after ${toolName} (${matchSource})${taskNote}${reportNote}`
207
+ : `${hookLabel}: ${toolName} matched ${matchSource} but no marker present for session ${sessionId}${taskNote}${reportNote}`;
208
+ }
209
+ //# sourceMappingURL=post-tool-use-boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-tool-use-boundary.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.ts"],"names":[],"mappings":"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"}