@lannguyensi/harness 0.43.0 → 0.45.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.
- package/CHANGELOG.md +1248 -0
- package/dist/cli/adopt/derive.d.ts +16 -7
- package/dist/cli/adopt/derive.js +19 -2
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/adopt/index.js +18 -3
- package/dist/cli/adopt/index.js.map +1 -1
- package/dist/cli/apply/apply.d.ts +1 -0
- package/dist/cli/apply/apply.js +62 -6
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.js +5 -6
- package/dist/cli/apply/generate-codex-config.js.map +1 -1
- package/dist/cli/apply/generate-opencode-config.d.ts +58 -0
- package/dist/cli/apply/generate-opencode-config.js +210 -0
- package/dist/cli/apply/generate-opencode-config.js.map +1 -0
- package/dist/cli/apply/generate-settings.d.ts +90 -32
- package/dist/cli/apply/generate-settings.js +133 -10
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/apply/index.d.ts +2 -1
- package/dist/cli/apply/index.js +2 -1
- package/dist/cli/apply/index.js.map +1 -1
- package/dist/cli/approve/stdin-report.d.ts +15 -0
- package/dist/cli/approve/stdin-report.js +1 -1
- package/dist/cli/approve/stdin-report.js.map +1 -1
- package/dist/cli/approve/understanding.d.ts +75 -0
- package/dist/cli/approve/understanding.js +150 -2
- package/dist/cli/approve/understanding.js.map +1 -1
- package/dist/cli/audit.d.ts +1 -1
- package/dist/cli/audit.js +3 -2
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/doctor/claude-mcp.d.ts +19 -1
- package/dist/cli/doctor/claude-mcp.js +36 -10
- package/dist/cli/doctor/claude-mcp.js.map +1 -1
- package/dist/cli/doctor/codex.d.ts +3 -1
- package/dist/cli/doctor/codex.js +6 -9
- package/dist/cli/doctor/codex.js.map +1 -1
- package/dist/cli/doctor/format.js +94 -8
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +23 -0
- package/dist/cli/doctor/index.js +106 -2
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/opencode.d.ts +24 -0
- package/dist/cli/doctor/opencode.js +107 -0
- package/dist/cli/doctor/opencode.js.map +1 -0
- package/dist/cli/doctor/target-checks.d.ts +11 -0
- package/dist/cli/doctor/target-checks.js +25 -0
- package/dist/cli/doctor/target-checks.js.map +1 -0
- package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
- package/dist/cli/doctor/toolchain-parity.js +154 -0
- package/dist/cli/doctor/toolchain-parity.js.map +1 -0
- package/dist/cli/doctor/types.d.ts +85 -1
- package/dist/cli/doctor/types.js +1 -1
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
- package/dist/cli/doctor/understanding-mode-env.js +83 -0
- package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
- package/dist/cli/dry-run.js +34 -13
- package/dist/cli/dry-run.js.map +1 -1
- package/dist/cli/explain.d.ts +1 -1
- package/dist/cli/explain.js +1 -1
- package/dist/cli/explain.js.map +1 -1
- package/dist/cli/index.js +62 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.js +36 -13
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/interactive.d.ts +7 -4
- package/dist/cli/init/interactive.js +79 -27
- package/dist/cli/init/interactive.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +32 -1
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -1
- package/dist/cli/init/templates.js +136 -24
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/pack/approve-escape.js +69 -8
- package/dist/cli/pack/approve-escape.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.js +1 -1
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-post-merge-gate.js +44 -26
- package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +91 -4
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-solution-acceptance.js +19 -2
- package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
- package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
- package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
- package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
- package/dist/cli/pause/index.js +1 -1
- package/dist/cli/pause/index.js.map +1 -1
- package/dist/cli/policy/intercept.d.ts +148 -0
- package/dist/cli/policy/intercept.js +538 -79
- package/dist/cli/policy/intercept.js.map +1 -1
- package/dist/cli/session-start/index.d.ts +12 -3
- package/dist/cli/session-start/index.js +67 -11
- package/dist/cli/session-start/index.js.map +1 -1
- package/dist/cli/session-start/stale-base-check.d.ts +71 -0
- package/dist/cli/session-start/stale-base-check.js +379 -0
- package/dist/cli/session-start/stale-base-check.js.map +1 -0
- package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
- package/dist/cli/session-start/toolchain-parity.js +244 -36
- package/dist/cli/session-start/toolchain-parity.js.map +1 -1
- package/dist/cli/smoke/assertions.d.ts +9 -2
- package/dist/cli/smoke/assertions.js +9 -2
- package/dist/cli/smoke/assertions.js.map +1 -1
- package/dist/cli/uninstall/index.js +18 -2
- package/dist/cli/uninstall/index.js.map +1 -1
- package/dist/cli/validate/checks.d.ts +3 -1
- package/dist/cli/validate/checks.js +298 -3
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/io/claude-mcp.d.ts +122 -1
- package/dist/io/claude-mcp.js +214 -12
- package/dist/io/claude-mcp.js.map +1 -1
- package/dist/io/duration.js.map +1 -0
- package/dist/io/expand-home.js.map +1 -0
- package/dist/io/extract.js.map +1 -0
- package/dist/io/harness-lock.js +1 -1
- package/dist/io/harness-lock.js.map +1 -1
- package/dist/{runtime → io}/ledger-record.d.ts +1 -1
- package/dist/io/ledger-record.js.map +1 -0
- package/dist/policies/index.d.ts +2 -2
- package/dist/policies/index.js +2 -2
- package/dist/policies/index.js.map +1 -1
- package/dist/policies/ledger-client.js +10 -6
- package/dist/policies/ledger-client.js.map +1 -1
- package/dist/policies/producers.js +1 -1
- package/dist/policies/producers.js.map +1 -1
- package/dist/policies/requires.js +2 -2
- package/dist/policies/requires.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.js +28 -3
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +90 -10
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js +112 -16
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/post-merge-gate.js +84 -31
- package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +152 -8
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +260 -13
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
- package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/policy-packs/registry.d.ts +11 -0
- package/dist/policy-packs/registry.js +1 -1
- package/dist/policy-packs/registry.js.map +1 -1
- package/dist/policy-packs/runtime.d.ts +1 -1
- package/dist/policy-packs/runtime.js +10 -1
- package/dist/policy-packs/runtime.js.map +1 -1
- package/dist/runtime/agent-facing.js +1 -1
- package/dist/runtime/agent-facing.js.map +1 -1
- package/dist/runtime/bash-match-facts.d.ts +9 -7
- package/dist/runtime/bash-match-facts.js +9 -7
- package/dist/runtime/bash-match-facts.js.map +1 -1
- package/dist/runtime/bash-match-registry.d.ts +75 -38
- package/dist/runtime/bash-match-registry.js +279 -232
- package/dist/runtime/bash-match-registry.js.map +1 -1
- package/dist/runtime/bash-prefix-parse.d.ts +7 -0
- package/dist/runtime/bash-prefix-parse.js +205 -17
- package/dist/runtime/bash-prefix-parse.js.map +1 -1
- package/dist/runtime/command-normalize.d.ts +429 -4
- package/dist/runtime/command-normalize.js +1146 -67
- package/dist/runtime/command-normalize.js.map +1 -1
- package/dist/runtime/hermetic-spawn-guard.js +6 -0
- package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/intercept.d.ts +247 -4
- package/dist/runtime/intercept.js +553 -61
- package/dist/runtime/intercept.js.map +1 -1
- package/dist/runtime/ledger-add.js +1 -1
- package/dist/runtime/ledger-add.js.map +1 -1
- package/dist/runtime/read-only-bash.js +356 -26
- package/dist/runtime/read-only-bash.js.map +1 -1
- package/dist/runtime/shell-word.d.ts +21 -0
- package/dist/runtime/shell-word.js +242 -0
- package/dist/runtime/shell-word.js.map +1 -0
- package/dist/schema/doctor.d.ts +25 -0
- package/dist/schema/doctor.js +23 -0
- package/dist/schema/doctor.js.map +1 -0
- package/dist/schema/extract.js +1 -1
- package/dist/schema/extract.js.map +1 -1
- package/dist/schema/index.d.ts +76 -0
- package/dist/schema/index.js +9 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/requires.js +2 -2
- package/dist/schema/requires.js.map +1 -1
- package/dist/schema/risk.d.ts +7 -0
- package/dist/schema/risk.js +23 -0
- package/dist/schema/risk.js.map +1 -1
- package/dist/schema/stale-base-check.d.ts +18 -0
- package/dist/schema/stale-base-check.js +37 -0
- package/dist/schema/stale-base-check.js.map +1 -0
- package/dist/schema/toolchain-parity.d.ts +3 -0
- package/dist/schema/toolchain-parity.js +13 -0
- package/dist/schema/toolchain-parity.js.map +1 -1
- package/package.json +5 -2
- package/dist/policies/duration.js.map +0 -1
- package/dist/policies/extract.js.map +0 -1
- package/dist/runtime/expand-home.js.map +0 -1
- package/dist/runtime/ledger-record.js.map +0 -1
- /package/dist/{policies → io}/duration.d.ts +0 -0
- /package/dist/{policies → io}/duration.js +0 -0
- /package/dist/{runtime → io}/expand-home.d.ts +0 -0
- /package/dist/{runtime → io}/expand-home.js +0 -0
- /package/dist/{policies → io}/extract.d.ts +0 -0
- /package/dist/{policies → io}/extract.js +0 -0
- /package/dist/{runtime → io}/ledger-record.js +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export interface PersistedReport {
|
|
2
|
+
filePath: string;
|
|
3
|
+
sessionId: string | null;
|
|
4
|
+
approvalStatus: string | null;
|
|
5
|
+
approvedAt: string | null;
|
|
6
|
+
/**
|
|
7
|
+
* ISO timestamp the producer stamped when it wrote the report; null
|
|
8
|
+
* for legacy reports without the field.
|
|
9
|
+
*/
|
|
10
|
+
createdAt: string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Effective creation time in epoch ms, resolved `createdAt` →
|
|
13
|
+
* filename ISO prefix → file mtime. Unlike mtime alone this survives
|
|
14
|
+
* the approval rewrite (which bumps mtime and would otherwise make a
|
|
15
|
+
* weeks-old report sort as the freshest, harness-discovery C1).
|
|
16
|
+
*/
|
|
17
|
+
createdAtMs: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Env var the persisted-report directory can be set from. Honored by
|
|
21
|
+
* harness (`defaultReportsDir` below + emitted by `harness apply` onto
|
|
22
|
+
* the pack-contributed hook commands) AND by `@lannguyensi/understanding-gate`
|
|
23
|
+
* (its `core/persistence.js:resolveReportDir` reads the same name), so
|
|
24
|
+
* the three actors that touch the directory — Stop hook (package),
|
|
25
|
+
* PreToolUse blocker (harness), `harness approve understanding` — can
|
|
26
|
+
* agree on the path regardless of each process's cwd.
|
|
27
|
+
*/
|
|
28
|
+
export declare const REPORTS_DIR_ENV = "UNDERSTANDING_GATE_REPORT_DIR";
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the persisted-report directory. Precedence:
|
|
31
|
+
* 1. `UNDERSTANDING_GATE_REPORT_DIR` (taken verbatim — apply emits an
|
|
32
|
+
* absolute path, operator-exported values are shell-expanded before
|
|
33
|
+
* we see them).
|
|
34
|
+
* 2. `<cwd>/.understanding-gate/reports` — backward-compat fallback.
|
|
35
|
+
* Callers that have a stable anchor (the manifest directory) pass
|
|
36
|
+
* it as `cwd` so the fallback agrees with whatever path apply
|
|
37
|
+
* baked into the hook commands.
|
|
38
|
+
*/
|
|
39
|
+
export declare function defaultReportsDir(cwd?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Project root anchor for the reports directory: `<dir-of-manifest>/.understanding-gate/reports`.
|
|
42
|
+
* Used by `harness apply` to bake an absolute, manifest-anchored value into
|
|
43
|
+
* the pack-contributed hook commands' env, and by `harness approve` as the
|
|
44
|
+
* fallback when `UNDERSTANDING_GATE_REPORT_DIR` is unset.
|
|
45
|
+
*/
|
|
46
|
+
export declare function reportsDirForManifest(manifestPath: string): string;
|
|
47
|
+
export declare function safeJsonParse(text: string): unknown;
|
|
48
|
+
/**
|
|
49
|
+
* List persisted reports under `dir`, newest-first by creation time
|
|
50
|
+
* (JSON `createdAt`, falling back to the filename ISO prefix, falling
|
|
51
|
+
* back to mtime). Missing directory returns []. Any I/O error on a
|
|
52
|
+
* single file is silently skipped; the caller falls through to the
|
|
53
|
+
* ledger result.
|
|
54
|
+
*
|
|
55
|
+
* Creation time, NOT mtime, is the sort key: `harness approve
|
|
56
|
+
* understanding` rewrites the report it flips, which bumps mtime and
|
|
57
|
+
* made an old just-approved report sort as the freshest
|
|
58
|
+
* (harness-discovery C1). mtime survives only as the last-resort
|
|
59
|
+
* fallback for files that carry neither timestamp.
|
|
60
|
+
*/
|
|
61
|
+
export declare function listPersistedReports(dir: string): PersistedReport[];
|
|
62
|
+
/**
|
|
63
|
+
* Maximum age a sessionId-null report may have for the tolerant
|
|
64
|
+
* fallback to adopt it on the `harness approve understanding` path.
|
|
65
|
+
* Sized for the real flow (Stop hook persists at turn end, operator
|
|
66
|
+
* approves within minutes) with slack for a slow read-through. Live
|
|
67
|
+
* repro that motivated it: a 17-day-old pending report got adopted,
|
|
68
|
+
* validated, and stamped for a fresh session because the producer had
|
|
69
|
+
* silently failed to persist the fresh report (harness-discovery C1,
|
|
70
|
+
* friction-log #67).
|
|
71
|
+
*/
|
|
72
|
+
export declare const TOLERANT_FALLBACK_MAX_AGE_MS: number;
|
|
73
|
+
/**
|
|
74
|
+
* Tolerance for a sessionId-less candidate whose `createdAt` lies in
|
|
75
|
+
* the FUTURE relative to the approve-time clock. A future creation
|
|
76
|
+
* time is suspect either way: a forged `createdAt` (the producer's
|
|
77
|
+
* Metadata block lets the agent author it) or serious clock skew.
|
|
78
|
+
* Beyond this skew the fallback rejects the candidate just like a
|
|
79
|
+
* stale one rather than trusting a timestamp that cannot be right.
|
|
80
|
+
*/
|
|
81
|
+
export declare const TOLERANT_FALLBACK_FUTURE_SKEW_MS: number;
|
|
82
|
+
export interface FindReportOptions {
|
|
83
|
+
/**
|
|
84
|
+
* Behaviour of the sessionId-null tolerant fallback (older Stop-hook
|
|
85
|
+
* package versions write reports without a `sessionId` field):
|
|
86
|
+
* - `"any"` (default): adopt the freshest sessionId-null report
|
|
87
|
+
* regardless of its `approvalStatus`. The gate read path
|
|
88
|
+
* (`checkPersistedReport`) and post-tool-use expiry
|
|
89
|
+
* (`expirePersistedReport`) rely on this so they keep finding the
|
|
90
|
+
* session's own report.
|
|
91
|
+
* - `"uncompleted"`: skip sessionId-null reports whose
|
|
92
|
+
* `approvalStatus` is a terminal `approved` / `expired`. Such a
|
|
93
|
+
* report belongs to a prior, finished approval cycle (often from
|
|
94
|
+
* a different task days ago) and must not be silently re-adopted
|
|
95
|
+
* as the current session's approval. `harness approve
|
|
96
|
+
* understanding` passes this so it never flips a stale unrelated
|
|
97
|
+
* report into the live session (harness/0dce3880 friction #1).
|
|
98
|
+
*/
|
|
99
|
+
tolerantFallback?: "any" | "uncompleted";
|
|
100
|
+
/**
|
|
101
|
+
* Maximum age (relative to `now`) of a sessionId-null candidate the
|
|
102
|
+
* tolerant fallback may adopt; older candidates are skipped and
|
|
103
|
+
* surfaced via `FindReportSelection.staleRejected`. Strict sessionId
|
|
104
|
+
* matches are never age-limited. Unset means no limit (the legacy
|
|
105
|
+
* gate-read / expiry contract).
|
|
106
|
+
*/
|
|
107
|
+
maxFallbackAgeMs?: number;
|
|
108
|
+
/** Clock anchor for the age computation; defaults to the wall clock. */
|
|
109
|
+
now?: Date;
|
|
110
|
+
}
|
|
111
|
+
export interface FindReportSelection {
|
|
112
|
+
report: PersistedReport | null;
|
|
113
|
+
/**
|
|
114
|
+
* True when `report` was adopted via the sessionId-null tolerant
|
|
115
|
+
* fallback rather than a strict sessionId match. Callers that bind
|
|
116
|
+
* the report to a session (the approve flow) surface this loudly so
|
|
117
|
+
* the operator can verify the adoption.
|
|
118
|
+
*/
|
|
119
|
+
fallbackAdopted: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* sessionId-null candidates skipped for exceeding `maxFallbackAgeMs`,
|
|
122
|
+
* newest first. Lets the caller distinguish "no report at all" from
|
|
123
|
+
* "only stale candidates existed", which are different failures: the
|
|
124
|
+
* latter usually means the producer failed to persist the fresh
|
|
125
|
+
* report (harness-discovery C1).
|
|
126
|
+
*/
|
|
127
|
+
staleRejected: PersistedReport[];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Select the freshest report for a given session_id, or the freshest
|
|
131
|
+
* applicable report when the persisted file lacks a sessionId field
|
|
132
|
+
* (older package versions). `report: null` when nothing matches.
|
|
133
|
+
*
|
|
134
|
+
* The strict (sessionId-equals) match always wins. The tolerant
|
|
135
|
+
* fallback's appetite is controlled by `opts.tolerantFallback` and
|
|
136
|
+
* `opts.maxFallbackAgeMs` — see `FindReportOptions`. The selection
|
|
137
|
+
* result carries enough context (`fallbackAdopted`, `staleRejected`)
|
|
138
|
+
* for the caller to be loud about non-strict adoptions.
|
|
139
|
+
*/
|
|
140
|
+
export declare function selectReportForSession(reports: PersistedReport[], sessionId: string, opts?: FindReportOptions): FindReportSelection;
|
|
141
|
+
/**
|
|
142
|
+
* Back-compat wrapper around `selectReportForSession` for callers that
|
|
143
|
+
* only need the report (the gate read and expiry paths).
|
|
144
|
+
*/
|
|
145
|
+
export declare function findLatestReportForSession(reports: PersistedReport[], sessionId: string, opts?: FindReportOptions): PersistedReport | null;
|
|
146
|
+
export interface PersistedReportApprovalCheck {
|
|
147
|
+
approved: boolean;
|
|
148
|
+
detail: string;
|
|
149
|
+
report: PersistedReport | null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Flip the latest matching persisted report's approvalStatus to
|
|
153
|
+
* `expired` so it no longer satisfies the gate's persisted-report
|
|
154
|
+
* fallback (harness/1ee26e77 follow-up: post-tool-use expiry was
|
|
155
|
+
* marker-only; the persisted report at .understanding-gate/reports/
|
|
156
|
+
* silently kept satisfying the gate even after task_finish deleted
|
|
157
|
+
* the marker).
|
|
158
|
+
*
|
|
159
|
+
* Atomic rewrite. Preserves the rest of the report body so the audit
|
|
160
|
+
* trail (the operator's actual Understanding text + previous approval
|
|
161
|
+
* timestamps) stays intact; only the status fields change.
|
|
162
|
+
*
|
|
163
|
+
* Returns `{ ok: true, filePath, previousStatus }` on success,
|
|
164
|
+
* `{ ok: false, reason }` when no matching report exists or rewrite
|
|
165
|
+
* failed. Non-throwing: caller (post-tool-use hook) uses this as a
|
|
166
|
+
* best-effort cleanup so a missing report dir or unrelated I/O issue
|
|
167
|
+
* does not escalate into a session-breaking hook failure.
|
|
168
|
+
*/
|
|
169
|
+
export declare function expirePersistedReport(reportsDir: string, sessionId: string, now?: Date): {
|
|
170
|
+
ok: true;
|
|
171
|
+
filePath: string;
|
|
172
|
+
previousStatus: string | null;
|
|
173
|
+
} | {
|
|
174
|
+
ok: false;
|
|
175
|
+
reason: string;
|
|
176
|
+
};
|
|
177
|
+
export declare function checkPersistedReport(reportsDir: string, sessionId: string): PersistedReportApprovalCheck;
|
|
@@ -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;
|