@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,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
|
package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map
ADDED
|
@@ -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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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ApprovalMarker, type CheckApprovalMarkerOptions, type MarkerCheck } from "./markers.js";
|
|
2
|
+
export declare const APPROVAL_MARKER_TASK_PREFIX = "task-";
|
|
3
|
+
/** Filesystem path of a per-task approval marker. */
|
|
4
|
+
export declare function taskApprovalMarkerPathFor(generatedDir: string, taskId: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Operator-side: write a task-scoped marker file. Atomic. Caller is
|
|
7
|
+
* `harness approve understanding --task <id>`. The session marker is
|
|
8
|
+
* written separately by the same caller for back-compat.
|
|
9
|
+
*
|
|
10
|
+
* Signed the same way as `writeApprovalMarker` (harness/f9485cc7), using
|
|
11
|
+
* `task-<id>` (the exact string `checkActiveClaimApprovalMarker` looks
|
|
12
|
+
* up) as the signed markerId — so a validly-signed task marker cannot be
|
|
13
|
+
* copied onto a different task id (or the session marker's id) and still
|
|
14
|
+
* verify.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeTaskApprovalMarker(generatedDir: string, taskId: string, marker: ApprovalMarker): string;
|
|
17
|
+
/**
|
|
18
|
+
* Gate-side: resolve the active agent-tasks claim (via `active-claim`)
|
|
19
|
+
* and check ONLY that task's approval marker. When no active claim is
|
|
20
|
+
* recorded, this returns `matched:false` so the caller falls through
|
|
21
|
+
* to the session marker — preserving the legacy contract for solo /
|
|
22
|
+
* non-agent-tasks workflows that never call `task_start`.
|
|
23
|
+
*
|
|
24
|
+
* Replaces the v1 "any task marker satisfies the gate" behaviour
|
|
25
|
+
* (PR #198): a stale approval from a different, already-completed task
|
|
26
|
+
* was silently authorising every Edit/Write/Bash in the next session
|
|
27
|
+
* because the scan returned the first existing marker regardless of
|
|
28
|
+
* which task the agent had actually claimed.
|
|
29
|
+
*
|
|
30
|
+
* Same safety filters as `checkApprovalMarker` (signature verification,
|
|
31
|
+
* symlink rejection, optional freshness via `maxAgeMs`); the only
|
|
32
|
+
* difference is the filename suffix derived from `active-claim`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function checkActiveClaimApprovalMarker(generatedDir: string, opts?: CheckApprovalMarkerOptions): MarkerCheck;
|
|
35
|
+
export interface OperatorMarkerApproval {
|
|
36
|
+
matched: boolean;
|
|
37
|
+
/** Which marker satisfied the gate; null when neither matched. */
|
|
38
|
+
source: "task" | "session" | null;
|
|
39
|
+
/** Detail of the decisive check (the match, or the session-scoped miss). */
|
|
40
|
+
detail: string;
|
|
41
|
+
/** Task-scoped check detail, for callers that trace the fall-through. */
|
|
42
|
+
taskCheckDetail: string;
|
|
43
|
+
/**
|
|
44
|
+
* True when EITHER the task-scoped or the session-scoped marker
|
|
45
|
+
* existed but aged past `approval_lifecycle.max_age` (task 6e888423).
|
|
46
|
+
* False when `matched` is true, and false when a marker was simply
|
|
47
|
+
* absent (never approved, or cleared by a task-completion boundary
|
|
48
|
+
* tool) — distinguishing "this identity had a real approval that
|
|
49
|
+
* merely expired" from "this identity was never approved" / "a new
|
|
50
|
+
* task just started". PreToolUse hooks use this (never on its own —
|
|
51
|
+
* always alongside `isRecoveryGitCommit`) to decide whether a bare
|
|
52
|
+
* recovery `git commit` may proceed without a fresh Understanding
|
|
53
|
+
* Report; see src/runtime/recovery-git-commit.ts.
|
|
54
|
+
*/
|
|
55
|
+
expired: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* True when EITHER the task-scoped or the session-scoped marker
|
|
58
|
+
* existed but FAILED signature verification (harness/f9485cc7) —
|
|
59
|
+
* missing/invalid signature, wrong `alg`, or tampered payload. Distinct
|
|
60
|
+
* from a marker simply being absent, so a caller can log a forgery
|
|
61
|
+
* attempt distinctly from the routine "not approved yet" case.
|
|
62
|
+
*/
|
|
63
|
+
forged: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Shared marker resolution for the Claude and Codex understanding-gate
|
|
67
|
+
* PreToolUse hooks (task e7c2ec3c): parse `approval_lifecycle` from the
|
|
68
|
+
* pack config, consult the task-scoped (active-claim) marker first and
|
|
69
|
+
* the session-scoped marker second, both under the same TTL. One code
|
|
70
|
+
* path on purpose — the Codex hook previously called the bare session
|
|
71
|
+
* check, so `max_age` and task-scoping silently applied only to Claude
|
|
72
|
+
* sessions.
|
|
73
|
+
*/
|
|
74
|
+
export declare function checkOperatorApprovalMarkers(generatedDir: string, sessionId: string, packConfig: unknown, stderr?: {
|
|
75
|
+
write: (s: string) => void;
|
|
76
|
+
} | null): OperatorMarkerApproval;
|
|
77
|
+
/** Clear a specific task-scoped marker. Used by the post-tool-use hook. */
|
|
78
|
+
export declare function clearTaskApprovalMarker(generatedDir: string, taskId: string): void;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Task-scoped approval markers (harness/1ee26e77), split out of the
|
|
2
|
+
// former monolithic understanding-before-execution-runtime.ts (structural
|
|
3
|
+
// 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 * as path from "node:path";
|
|
8
|
+
import { atomicWriteFile } from "../../../io/atomic-write.js";
|
|
9
|
+
import { signMarker } from "../../../runtime/approval-signing.js";
|
|
10
|
+
import { readActiveClaim } from "./active-claim.js";
|
|
11
|
+
import { parseApprovalLifecycle } from "./lifecycle.js";
|
|
12
|
+
import { APPROVAL_MARKER_DIRNAME, checkApprovalMarker, } from "./markers.js";
|
|
13
|
+
// Task-scoped approval markers (harness/1ee26e77). When the operator
|
|
14
|
+
// passes `--task <id>` to `harness approve understanding`, a second
|
|
15
|
+
// marker file is written next to the session marker, keyed by the
|
|
16
|
+
// agent-tasks task id and prefixed `task-` so a directory scan can
|
|
17
|
+
// distinguish them from session-keyed markers (UUIDs do not start with
|
|
18
|
+
// `task-`). Either marker satisfies the gate; the session marker
|
|
19
|
+
// remains for back-compat with operators who don't pass --task and for
|
|
20
|
+
// non-agent-tasks workflows.
|
|
21
|
+
//
|
|
22
|
+
// Why task-scope: a session can span many distinct tasks; the operator's
|
|
23
|
+
// Understanding Report for task A doesn't transfer trust to task B
|
|
24
|
+
// (different surface, different acceptance criteria). Without per-task
|
|
25
|
+
// markers a multi-task session re-uses the first task's approval for
|
|
26
|
+
// every subsequent task. The expire_on_tool_match PostToolUse hook
|
|
27
|
+
// already deletes the session marker on `task_finish`, but if delivery
|
|
28
|
+
// is unreliable (e.g. PostToolUse skipped for an MCP tool) the marker
|
|
29
|
+
// silently persists. A task-keyed marker side-steps that delivery
|
|
30
|
+
// concern because the next task's id is different even if the previous
|
|
31
|
+
// marker file outlives its scope.
|
|
32
|
+
export const APPROVAL_MARKER_TASK_PREFIX = "task-";
|
|
33
|
+
/**
|
|
34
|
+
* Reject taskIds that would escape the approvals/ namespace via path
|
|
35
|
+
* traversal or directory separators. The operator's --task flag value
|
|
36
|
+
* lands here verbatim; an accidental shell-expanded `..` or `/` would
|
|
37
|
+
* otherwise write to a sibling directory. This is defensive — the
|
|
38
|
+
* caller is the operator's own shell — but pins the trust boundary.
|
|
39
|
+
*/
|
|
40
|
+
function rejectMalformedTaskId(taskId) {
|
|
41
|
+
if (taskId.length === 0) {
|
|
42
|
+
throw new Error("taskId is empty");
|
|
43
|
+
}
|
|
44
|
+
if (taskId.includes("/") || taskId.includes("\\") || taskId.includes("..")) {
|
|
45
|
+
throw new Error(`taskId contains path-separator or traversal characters: ${JSON.stringify(taskId)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Filesystem path of a per-task approval marker. */
|
|
49
|
+
export function taskApprovalMarkerPathFor(generatedDir, taskId) {
|
|
50
|
+
rejectMalformedTaskId(taskId);
|
|
51
|
+
return path.join(generatedDir, APPROVAL_MARKER_DIRNAME, `${APPROVAL_MARKER_TASK_PREFIX}${taskId}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Operator-side: write a task-scoped marker file. Atomic. Caller is
|
|
55
|
+
* `harness approve understanding --task <id>`. The session marker is
|
|
56
|
+
* written separately by the same caller for back-compat.
|
|
57
|
+
*
|
|
58
|
+
* Signed the same way as `writeApprovalMarker` (harness/f9485cc7), using
|
|
59
|
+
* `task-<id>` (the exact string `checkActiveClaimApprovalMarker` looks
|
|
60
|
+
* up) as the signed markerId — so a validly-signed task marker cannot be
|
|
61
|
+
* copied onto a different task id (or the session marker's id) and still
|
|
62
|
+
* verify.
|
|
63
|
+
*/
|
|
64
|
+
export function writeTaskApprovalMarker(generatedDir, taskId, marker) {
|
|
65
|
+
const filePath = taskApprovalMarkerPathFor(generatedDir, taskId);
|
|
66
|
+
const markerId = `${APPROVAL_MARKER_TASK_PREFIX}${taskId}`;
|
|
67
|
+
const signed = signMarker(generatedDir, markerId, marker);
|
|
68
|
+
atomicWriteFile(filePath, `${JSON.stringify(signed, null, 2)}\n`, { mode: 0o600 });
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Gate-side: resolve the active agent-tasks claim (via `active-claim`)
|
|
73
|
+
* and check ONLY that task's approval marker. When no active claim is
|
|
74
|
+
* recorded, this returns `matched:false` so the caller falls through
|
|
75
|
+
* to the session marker — preserving the legacy contract for solo /
|
|
76
|
+
* non-agent-tasks workflows that never call `task_start`.
|
|
77
|
+
*
|
|
78
|
+
* Replaces the v1 "any task marker satisfies the gate" behaviour
|
|
79
|
+
* (PR #198): a stale approval from a different, already-completed task
|
|
80
|
+
* was silently authorising every Edit/Write/Bash in the next session
|
|
81
|
+
* because the scan returned the first existing marker regardless of
|
|
82
|
+
* which task the agent had actually claimed.
|
|
83
|
+
*
|
|
84
|
+
* Same safety filters as `checkApprovalMarker` (signature verification,
|
|
85
|
+
* symlink rejection, optional freshness via `maxAgeMs`); the only
|
|
86
|
+
* difference is the filename suffix derived from `active-claim`.
|
|
87
|
+
*/
|
|
88
|
+
export function checkActiveClaimApprovalMarker(generatedDir, opts = {}) {
|
|
89
|
+
const claim = readActiveClaim(generatedDir);
|
|
90
|
+
if (claim === null) {
|
|
91
|
+
return {
|
|
92
|
+
matched: false,
|
|
93
|
+
detail: `no active-claim recorded; task-scoped check skipped`,
|
|
94
|
+
marker: null,
|
|
95
|
+
expired: false,
|
|
96
|
+
forged: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const markerName = `${APPROVAL_MARKER_TASK_PREFIX}${claim}`;
|
|
100
|
+
const check = checkApprovalMarker(generatedDir, markerName, opts);
|
|
101
|
+
if (check.matched) {
|
|
102
|
+
return {
|
|
103
|
+
matched: true,
|
|
104
|
+
detail: `task-scoped marker for active-claim ${claim}: ${check.detail}`,
|
|
105
|
+
marker: check.marker,
|
|
106
|
+
expired: false,
|
|
107
|
+
forged: false,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
matched: false,
|
|
112
|
+
detail: `active-claim ${claim} has no fresh task marker (${check.detail})`,
|
|
113
|
+
marker: null,
|
|
114
|
+
expired: check.expired,
|
|
115
|
+
forged: check.forged,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Shared marker resolution for the Claude and Codex understanding-gate
|
|
120
|
+
* PreToolUse hooks (task e7c2ec3c): parse `approval_lifecycle` from the
|
|
121
|
+
* pack config, consult the task-scoped (active-claim) marker first and
|
|
122
|
+
* the session-scoped marker second, both under the same TTL. One code
|
|
123
|
+
* path on purpose — the Codex hook previously called the bare session
|
|
124
|
+
* check, so `max_age` and task-scoping silently applied only to Claude
|
|
125
|
+
* sessions.
|
|
126
|
+
*/
|
|
127
|
+
export function checkOperatorApprovalMarkers(generatedDir, sessionId, packConfig, stderr) {
|
|
128
|
+
const lifecycleRaw = packConfig !== null && typeof packConfig === "object" && !Array.isArray(packConfig)
|
|
129
|
+
? packConfig["approval_lifecycle"]
|
|
130
|
+
: undefined;
|
|
131
|
+
const lifecycle = parseApprovalLifecycle(lifecycleRaw, stderr);
|
|
132
|
+
const ageOpts = lifecycle.maxAgeMs !== undefined ? { maxAgeMs: lifecycle.maxAgeMs } : {};
|
|
133
|
+
const taskMarker = checkActiveClaimApprovalMarker(generatedDir, ageOpts);
|
|
134
|
+
if (taskMarker.matched) {
|
|
135
|
+
return {
|
|
136
|
+
matched: true,
|
|
137
|
+
source: "task",
|
|
138
|
+
detail: taskMarker.detail,
|
|
139
|
+
taskCheckDetail: taskMarker.detail,
|
|
140
|
+
expired: false,
|
|
141
|
+
forged: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const sessionMarker = checkApprovalMarker(generatedDir, sessionId, ageOpts);
|
|
145
|
+
if (sessionMarker.matched) {
|
|
146
|
+
return {
|
|
147
|
+
matched: true,
|
|
148
|
+
source: "session",
|
|
149
|
+
detail: sessionMarker.detail,
|
|
150
|
+
taskCheckDetail: taskMarker.detail,
|
|
151
|
+
expired: false,
|
|
152
|
+
forged: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
matched: false,
|
|
157
|
+
source: null,
|
|
158
|
+
detail: sessionMarker.detail,
|
|
159
|
+
taskCheckDetail: taskMarker.detail,
|
|
160
|
+
// `expired` is computed ONLY on this non-matched path, preserving the
|
|
161
|
+
// "false when matched is true" invariant (task 6e888423 review):
|
|
162
|
+
// e.g. a FRESH session marker (matched:true, returned above) must not
|
|
163
|
+
// read expired:true just because a STALE sibling task marker also
|
|
164
|
+
// exists — that sibling is irrelevant once the session marker itself
|
|
165
|
+
// satisfied the gate. Either marker aged out counts here: a
|
|
166
|
+
// task-scoped approval that expired (even though the session-scoped
|
|
167
|
+
// check may simply have never existed) is just as legitimate a "this
|
|
168
|
+
// had approval, it lapsed" signal as a session-scoped expiry.
|
|
169
|
+
expired: taskMarker.expired || sessionMarker.expired,
|
|
170
|
+
// Surfaced so a forgery attempt against EITHER marker is visible to
|
|
171
|
+
// the caller even though the overall result is (correctly) unmatched
|
|
172
|
+
// — mirrors the `expired` OR-merge above.
|
|
173
|
+
forged: taskMarker.forged || sessionMarker.forged,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** Clear a specific task-scoped marker. Used by the post-tool-use hook. */
|
|
177
|
+
export function clearTaskApprovalMarker(generatedDir, taskId) {
|
|
178
|
+
try {
|
|
179
|
+
fs.rmSync(taskApprovalMarkerPathFor(generatedDir, taskId));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* already gone */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=task-markers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-markers.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/task-markers.ts"],"names":[],"mappings":"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"}
|