@lannguyensi/harness 0.10.1 → 0.11.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 (47) hide show
  1. package/CHANGELOG.md +78 -7
  2. package/README.md +1 -1
  3. package/dist/cli/approve/understanding.d.ts +8 -0
  4. package/dist/cli/approve/understanding.js +56 -7
  5. package/dist/cli/approve/understanding.js.map +1 -1
  6. package/dist/cli/audit.d.ts +8 -0
  7. package/dist/cli/audit.js +2 -2
  8. package/dist/cli/audit.js.map +1 -1
  9. package/dist/cli/doctor/format.js +7 -1
  10. package/dist/cli/doctor/format.js.map +1 -1
  11. package/dist/cli/doctor/index.js +62 -5
  12. package/dist/cli/doctor/index.js.map +1 -1
  13. package/dist/cli/doctor/types.d.ts +15 -0
  14. package/dist/cli/dry-run.js +9 -3
  15. package/dist/cli/dry-run.js.map +1 -1
  16. package/dist/cli/explain.d.ts +8 -0
  17. package/dist/cli/explain.js +6 -4
  18. package/dist/cli/explain.js.map +1 -1
  19. package/dist/cli/index.js +40 -1
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/cli/init/dependencies.js +17 -7
  22. package/dist/cli/init/dependencies.js.map +1 -1
  23. package/dist/cli/init/templates.d.ts +1 -1
  24. package/dist/cli/init/templates.js +14 -5
  25. package/dist/cli/init/templates.js.map +1 -1
  26. package/dist/cli/pack/hook-pre-tool-use.d.ts +2 -0
  27. package/dist/cli/pack/hook-pre-tool-use.js +34 -2
  28. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  29. package/dist/cli/policy/intercept.d.ts +7 -1
  30. package/dist/cli/policy/intercept.js +28 -6
  31. package/dist/cli/policy/intercept.js.map +1 -1
  32. package/dist/cli/session-start/index.d.ts +52 -0
  33. package/dist/cli/session-start/index.js +195 -0
  34. package/dist/cli/session-start/index.js.map +1 -0
  35. package/dist/runtime/git-context.d.ts +16 -0
  36. package/dist/runtime/git-context.js +97 -0
  37. package/dist/runtime/git-context.js.map +1 -0
  38. package/dist/runtime/index.d.ts +1 -0
  39. package/dist/runtime/index.js +1 -0
  40. package/dist/runtime/index.js.map +1 -1
  41. package/dist/runtime/pending-approval.d.ts +31 -0
  42. package/dist/runtime/pending-approval.js +80 -0
  43. package/dist/runtime/pending-approval.js.map +1 -0
  44. package/dist/runtime/session-id.d.ts +40 -1
  45. package/dist/runtime/session-id.js +99 -8
  46. package/dist/runtime/session-id.js.map +1 -1
  47. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { type LedgerEntry } from "../../policies/index.js";
2
2
  import { type LedgerClient, type PolicyDecision } from "../../runtime/index.js";
3
- import type { Manifest } from "../../schema/index.js";
3
+ import type { Manifest, McpServer } from "../../schema/index.js";
4
4
  import { type LoaderOptions } from "../loader.js";
5
5
  export interface InterceptCliOptions extends LoaderOptions {
6
6
  /** Defaults to process.stdin. */
@@ -30,5 +30,11 @@ export interface InterceptCliResult {
30
30
  decisions: PolicyDecision[];
31
31
  blocked: boolean;
32
32
  }
33
+ /**
34
+ * Real grounding-mcp-backed ledger client. Exported for testing: the
35
+ * `record` adapter's failure-surfacing path is exercised against a
36
+ * bogus mcpCommand in tests/runtime/intercept-cli.test.ts.
37
+ */
38
+ export declare function realLedgerClient(server: McpServer, opts: InterceptCliOptions): LedgerClient;
33
39
  export declare function runInterceptCli(opts?: InterceptCliOptions): Promise<InterceptCliResult>;
34
40
  export type { LedgerEntry };
@@ -4,7 +4,7 @@
4
4
  // the PreToolUse hook. Reads the event JSON from stdin, runs the runtime
5
5
  // interceptor, writes Claude Code's deny JSON to stdout on block.
6
6
  import { queryLedgerByTag, } from "../../policies/index.js";
7
- import { intercept, recordPolicyDecision, } from "../../runtime/index.js";
7
+ import { intercept, recordPolicyDecision, resolveGitContext, } from "../../runtime/index.js";
8
8
  import { loadManifest } from "../loader.js";
9
9
  async function readStdin(stream) {
10
10
  return new Promise((resolve, reject) => {
@@ -55,12 +55,18 @@ function isVerboseEnabled(opts) {
55
55
  // Accept anything truthy except literal disable-words (case-insensitive).
56
56
  return !/^(0|false|no|off)$/i.test(env.trim());
57
57
  }
58
- function realLedgerClient(server, opts) {
58
+ /**
59
+ * Real grounding-mcp-backed ledger client. Exported for testing: the
60
+ * `record` adapter's failure-surfacing path is exercised against a
61
+ * bogus mcpCommand in tests/runtime/intercept-cli.test.ts.
62
+ */
63
+ export function realLedgerClient(server, opts) {
59
64
  const command = Array.isArray(server.command)
60
65
  ? server.command
61
66
  : server.command.trim().split(/\s+/);
62
67
  const env = server.env ?? undefined;
63
68
  const timeoutMs = opts.ledgerTimeoutMs ?? server.health?.timeout_ms ?? 5_000;
69
+ const stderr = opts.stderr ?? process.stderr;
64
70
  return {
65
71
  async query(_tag, sessionId) {
66
72
  return queryLedgerByTag({
@@ -71,11 +77,19 @@ function realLedgerClient(server, opts) {
71
77
  });
72
78
  },
73
79
  async record(decision, sessionId) {
74
- await recordPolicyDecision(decision, sessionId, {
80
+ const result = await recordPolicyDecision(decision, sessionId, {
75
81
  mcpCommand: command,
76
82
  ...(env && { mcpEnv: env }),
77
83
  timeoutMs,
78
84
  });
85
+ // A failed audit-write must not block the tool — the decision is
86
+ // still applied — but it must not be silent either: an unrecorded
87
+ // decision is invisible to `harness audit` / `explain --trace`.
88
+ // Goes to stderr so Claude Code's stdout deny-JSON contract holds.
89
+ if (!result.ok) {
90
+ stderr.write(`harness policy intercept: audit-write failed for ` +
91
+ `${decision.policyName}: ${result.reason ?? "unknown error"}\n`);
92
+ }
79
93
  },
80
94
  };
81
95
  }
@@ -138,12 +152,20 @@ export async function runInterceptCli(opts = {}) {
138
152
  // a stripped event can pick up the active session.
139
153
  const eventSessionId = typeof event.session_id === "string" ? event.session_id : undefined;
140
154
  const builtinSessionId = eventSessionId ?? process.env.CLAUDE_SESSION_ID ?? "";
155
+ // REPO / BRANCH are derived from the tool event's cwd so that per-repo
156
+ // and per-branch ledger tags (`preflight:${REPO}`, `preflight:${BRANCH}`)
157
+ // actually namespace — they were previously read from HARNESS_REPO /
158
+ // HARNESS_BRANCH env vars that nothing sets, collapsing every tag to the
159
+ // literal `preflight:`. An explicit env var still wins: it is the
160
+ // operator's deliberate override of the derived value.
161
+ const cwd = typeof event.cwd === "string" ? event.cwd : process.cwd();
162
+ const gitContext = resolveGitContext(cwd);
141
163
  const builtins = {
142
164
  SESSION_ID: builtinSessionId,
143
- REPO: process.env.HARNESS_REPO ?? "",
144
- BRANCH: process.env.HARNESS_BRANCH ?? "",
165
+ REPO: process.env.HARNESS_REPO ?? gitContext.repo,
166
+ BRANCH: process.env.HARNESS_BRANCH ?? gitContext.branch,
145
167
  TOOL_NAME: typeof event.tool_name === "string" ? event.tool_name : "",
146
- CWD: typeof event.cwd === "string" ? event.cwd : process.cwd(),
168
+ CWD: cwd,
147
169
  };
148
170
  const result = await intercept({
149
171
  manifest,
@@ -1 +1 @@
1
- {"version":3,"file":"intercept.js","sourceRoot":"","sources":["../../../src/cli/policy/intercept.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"intercept.js","sourceRoot":"","sources":["../../../src/cli/policy/intercept.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type LoaderOptions } from "../loader.js";
2
+ /** The slice of `preflight run --json` output this producer reads. */
3
+ export interface PreflightJson {
4
+ ready?: boolean;
5
+ confidence?: number;
6
+ checks?: Array<{
7
+ name?: string;
8
+ status?: string;
9
+ message?: string;
10
+ }>;
11
+ }
12
+ export type RunPreflightResult = {
13
+ ok: true;
14
+ json: PreflightJson;
15
+ } | {
16
+ ok: false;
17
+ reason: string;
18
+ };
19
+ export interface SessionStartPreflightOptions extends LoaderOptions {
20
+ /** Defaults to process.stdin. */
21
+ stdin?: NodeJS.ReadableStream;
22
+ /** Defaults to process.stderr. stdout is never written (SessionStart). */
23
+ stderr?: NodeJS.WritableStream;
24
+ /** `preflight` subprocess timeout in ms. */
25
+ preflightTimeoutMs?: number;
26
+ /** Per-call ledger timeout in ms. */
27
+ ledgerTimeoutMs?: number;
28
+ /** Inject the preflight runner (tests). */
29
+ runPreflight?: (cwd: string, timeoutMs: number) => Promise<RunPreflightResult>;
30
+ /** Inject the ledger writer (tests). */
31
+ writeLedger?: (args: {
32
+ sessionId: string;
33
+ content: string;
34
+ source: string;
35
+ }) => Promise<{
36
+ ok: boolean;
37
+ reason?: string;
38
+ }>;
39
+ }
40
+ export interface SessionStartPreflightResult {
41
+ /** Always 0 — a SessionStart hook must never break the session loop. */
42
+ exitCode: number;
43
+ /** Whether the `preflight:` ledger fact was written. */
44
+ wrote: boolean;
45
+ /** Resolved repo name (the `${REPO}` a tag is namespaced by). */
46
+ repo: string;
47
+ /** Resolved branch (the `${BRANCH}` a tag is namespaced by; "" if detached). */
48
+ branch: string;
49
+ /** Human-readable explanation of a non-write outcome, for diagnostics. */
50
+ reason?: string;
51
+ }
52
+ export declare function runSessionStartPreflight(opts?: SessionStartPreflightOptions): Promise<SessionStartPreflightResult>;
@@ -0,0 +1,195 @@
1
+ // `harness session-start preflight` — SessionStart hook entrypoint.
2
+ //
3
+ // Wired by the Full template's `git-preflight` SessionStart hook. Reads
4
+ // the SessionStart event JSON from stdin, runs `agent-preflight`
5
+ // (`preflight run --json <cwd>`), and on a `ready:true` result writes a
6
+ // `preflight:${REPO}` fact to the evidence ledger so the
7
+ // `preflight-before-investigation` / `preflight-before-push` policies
8
+ // have a fresh tag to match within their `within` windows.
9
+ //
10
+ // SessionStart hooks are `blocking:false`: this command MUST NOT break
11
+ // the session loop. Every failure path — `preflight` not on PATH, a
12
+ // timeout, a non-`ready` result, an unreachable ledger — logs one line
13
+ // to stderr and exits 0. The only observable effect of a failure is
14
+ // that the preflight policies stay closed (which is the safe default).
15
+ //
16
+ // `ready:false` deliberately does NOT write the tag: the policy intent
17
+ // is "block investigative git reads until agent-preflight ran cleanly",
18
+ // so a failing preflight must leave the gate shut, not satisfy it.
19
+ import { execFile } from "node:child_process";
20
+ import { addLedgerFact, resolveGitContext, resolveSessionId, } from "../../runtime/index.js";
21
+ import { loadManifest } from "../loader.js";
22
+ const PREFLIGHT_BIN = "preflight";
23
+ const DEFAULT_PREFLIGHT_TIMEOUT_MS = 25_000;
24
+ const LEDGER_SOURCE = "harness-session-start-preflight";
25
+ async function readStdin(stream) {
26
+ return new Promise((resolve, reject) => {
27
+ let data = "";
28
+ stream.setEncoding("utf8");
29
+ stream.on("data", (chunk) => {
30
+ data += chunk;
31
+ });
32
+ stream.on("end", () => resolve(data));
33
+ stream.on("error", reject);
34
+ });
35
+ }
36
+ function findGroundingMcp(manifest) {
37
+ return manifest.tools.mcp.find((m) => m.name === "grounding-mcp") ?? null;
38
+ }
39
+ function mcpCommandList(server) {
40
+ return Array.isArray(server.command)
41
+ ? server.command
42
+ : server.command.trim().split(/\s+/);
43
+ }
44
+ /**
45
+ * Default `preflight` runner: spawn `preflight run --json <cwd>` and
46
+ * parse its stdout. Resolves `{ ok: false }` (never throws) for the
47
+ * not-installed / timeout / unparseable cases so the caller can degrade.
48
+ */
49
+ function spawnPreflight(cwd, timeoutMs) {
50
+ return new Promise((resolve) => {
51
+ execFile(PREFLIGHT_BIN, ["run", "--json", cwd], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024, encoding: "utf8" }, (err, stdout) => {
52
+ // `preflight` may exit non-zero on a not-ready result while still
53
+ // emitting valid JSON, so a parseable stdout wins over the exit
54
+ // code. Only a missing binary / timeout / unparseable output is a
55
+ // genuine "could not run".
56
+ const text = (stdout ?? "").trim();
57
+ if (text.length > 0) {
58
+ try {
59
+ return resolve({ ok: true, json: JSON.parse(text) });
60
+ }
61
+ catch {
62
+ /* fall through to the error path */
63
+ }
64
+ }
65
+ if (err) {
66
+ const e = err;
67
+ if (e.code === "ENOENT") {
68
+ return resolve({
69
+ ok: false,
70
+ reason: `\`${PREFLIGHT_BIN}\` not on PATH (npm i -g @lannguyensi/agent-preflight)`,
71
+ });
72
+ }
73
+ // maxBuffer overflow also sets `killed:true`; check it first so
74
+ // an over-budget output is not mis-reported as a timeout.
75
+ if (e.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
76
+ return resolve({
77
+ ok: false,
78
+ reason: `\`${PREFLIGHT_BIN} run --json\` output exceeded the read buffer`,
79
+ });
80
+ }
81
+ if (e.killed) {
82
+ return resolve({
83
+ ok: false,
84
+ reason: `\`${PREFLIGHT_BIN} run\` timed out after ${timeoutMs}ms`,
85
+ });
86
+ }
87
+ return resolve({ ok: false, reason: `\`${PREFLIGHT_BIN} run\` failed: ${e.message}` });
88
+ }
89
+ return resolve({
90
+ ok: false,
91
+ reason: `\`${PREFLIGHT_BIN} run --json\` produced no parseable JSON`,
92
+ });
93
+ });
94
+ });
95
+ }
96
+ function describeNotReady(json) {
97
+ const failing = (json.checks ?? [])
98
+ .filter((c) => c.status === "fail" || c.status === "error")
99
+ .map((c) => c.name ?? "(unnamed)");
100
+ const confidence = typeof json.confidence === "number" ? json.confidence.toFixed(2) : "?";
101
+ const failSuffix = failing.length > 0 ? `; failing: ${failing.join(", ")}` : "";
102
+ return `preflight not ready (confidence ${confidence})${failSuffix}`;
103
+ }
104
+ export async function runSessionStartPreflight(opts = {}) {
105
+ const stdin = opts.stdin ?? process.stdin;
106
+ const stderr = opts.stderr ?? process.stderr;
107
+ const preflightTimeoutMs = opts.preflightTimeoutMs ?? DEFAULT_PREFLIGHT_TIMEOUT_MS;
108
+ const note = (msg) => {
109
+ stderr.write(`harness session-start preflight: ${msg}\n`);
110
+ };
111
+ const done = (wrote, repo, branch, reason) => ({
112
+ exitCode: 0,
113
+ wrote,
114
+ repo,
115
+ branch,
116
+ ...(reason !== undefined && { reason }),
117
+ });
118
+ let event;
119
+ try {
120
+ event = JSON.parse((await readStdin(stdin)).trim() || "{}");
121
+ }
122
+ catch (err) {
123
+ const reason = `malformed event JSON: ${err.message}`;
124
+ note(reason);
125
+ return done(false, "", "", reason);
126
+ }
127
+ const cwd = typeof event.cwd === "string" && event.cwd.length > 0 ? event.cwd : process.cwd();
128
+ const { repo, branch } = resolveGitContext(cwd);
129
+ if (repo === "") {
130
+ const reason = `cwd is not inside a git work tree (${cwd}); nothing to preflight`;
131
+ note(reason);
132
+ return done(false, "", "", reason);
133
+ }
134
+ const sessionId = resolveSessionId(typeof event.session_id === "string" ? event.session_id : undefined);
135
+ const runPreflight = opts.runPreflight ?? spawnPreflight;
136
+ const preflight = await runPreflight(cwd, preflightTimeoutMs);
137
+ if (!preflight.ok) {
138
+ note(preflight.reason);
139
+ return done(false, repo, branch, preflight.reason);
140
+ }
141
+ if (preflight.json.ready !== true) {
142
+ const reason = describeNotReady(preflight.json);
143
+ note(`${reason} — leaving the preflight tag unwritten so the gate stays closed`);
144
+ return done(false, repo, branch, reason);
145
+ }
146
+ const confidence = typeof preflight.json.confidence === "number"
147
+ ? preflight.json.confidence.toFixed(2)
148
+ : "?";
149
+ // Emit BOTH per-repo and per-branch tags in one fact: the requires
150
+ // evaluator substring-matches, so a single entry containing
151
+ // `preflight:${REPO}` and `preflight:${BRANCH}` satisfies both
152
+ // `preflight-before-investigation` (REPO, within 1h) and
153
+ // `preflight-before-push` (BRANCH, within 10m). Caveat: a SessionStart
154
+ // producer cannot keep the 10m push window fresh through a long
155
+ // session — a push-time refresh is a separate concern (see task notes).
156
+ // On a detached HEAD `branch` is "" — only the REPO tag is written.
157
+ const tags = branch.length > 0 ? `preflight:${repo} preflight:${branch}` : `preflight:${repo}`;
158
+ const content = `${tags} ready:true confidence:${confidence}`;
159
+ let writeLedger = opts.writeLedger;
160
+ if (!writeLedger) {
161
+ let manifest;
162
+ try {
163
+ manifest = loadManifest(opts).manifest;
164
+ }
165
+ catch (err) {
166
+ const reason = `manifest load failed: ${err.message}`;
167
+ note(reason);
168
+ return done(false, repo, branch, reason);
169
+ }
170
+ const server = findGroundingMcp(manifest);
171
+ if (!server) {
172
+ const reason = "grounding-mcp not declared in manifest; cannot record preflight tag";
173
+ note(reason);
174
+ return done(false, repo, branch, reason);
175
+ }
176
+ const command = mcpCommandList(server);
177
+ const env = server.env ?? undefined;
178
+ const timeoutMs = opts.ledgerTimeoutMs ?? server.health?.timeout_ms ?? 5_000;
179
+ writeLedger = (args) => addLedgerFact({
180
+ mcpCommand: command,
181
+ ...(env && { mcpEnv: env }),
182
+ timeoutMs,
183
+ ...args,
184
+ });
185
+ }
186
+ const result = await writeLedger({ sessionId, content, source: LEDGER_SOURCE });
187
+ if (!result.ok) {
188
+ const reason = `ledger write failed: ${result.reason ?? "unknown error"}`;
189
+ note(reason);
190
+ return done(false, repo, branch, reason);
191
+ }
192
+ note(`recorded ${content} for session ${sessionId}`);
193
+ return done(true, repo, branch);
194
+ }
195
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/session-start/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface GitRepoContext {
2
+ /** Basename of the work-tree root, or "" when `cwd` is not in a repo. */
3
+ repo: string;
4
+ /**
5
+ * Current branch name, or "" when not in a repo or HEAD is detached
6
+ * (a raw SHA — there is no branch to name).
7
+ */
8
+ branch: string;
9
+ }
10
+ /**
11
+ * Resolve `{ repo, branch }` for a working directory. Returns empty
12
+ * strings (never throws) when `cwd` is not inside a git work tree, or
13
+ * when any individual lookup fails — callers treat "" as "unknown" and
14
+ * fall through to their own behaviour.
15
+ */
16
+ export declare function resolveGitContext(cwd: string): GitRepoContext;
@@ -0,0 +1,97 @@
1
+ // Resolves the `REPO` and `BRANCH` policy builtins from a working
2
+ // directory.
3
+ //
4
+ // The intercept engine exposes `${REPO}` / `${BRANCH}` as `ledger_tag`
5
+ // template builtins, but they were only ever populated from the
6
+ // `HARNESS_REPO` / `HARNESS_BRANCH` env vars — which nothing sets — so
7
+ // every `preflight:${REPO}` tag collapsed to the literal `preflight:`.
8
+ // That silently degraded the founding-incident policies to one global
9
+ // tag: a preflight done in repo A satisfied the gate in repo B.
10
+ //
11
+ // This module derives both values from the filesystem, not a `git`
12
+ // subprocess: the intercept hook runs on every Bash / Edit / Write
13
+ // tool call, so the resolution must stay cheap. A bounded walk up the
14
+ // directory tree to find `.git`, plus one small `HEAD` read, is
15
+ // microseconds and spawns no process.
16
+ //
17
+ // It is a deliberate approximation of `git rev-parse`: it reads the
18
+ // work tree's basename and `.git/HEAD` directly and does NOT consult
19
+ // `GIT_DIR` / `GIT_WORK_TREE` / `core.worktree`. For namespacing a
20
+ // ledger tag, the on-disk layout is the right (and more stable)
21
+ // signal; those exotic overrides are out of scope.
22
+ import * as fs from "node:fs";
23
+ import * as path from "node:path";
24
+ const EMPTY = { repo: "", branch: "" };
25
+ // A `.git` *file* (linked worktree / submodule) points at the real git
26
+ // dir: `gitdir: <path>`.
27
+ const GITDIR_RE = /^gitdir:\s*(.+)$/;
28
+ // `.git/HEAD` on a branch: `ref: refs/heads/<branch>`. A detached HEAD
29
+ // holds a raw SHA instead and matches nothing here.
30
+ const HEAD_REF_RE = /^ref:\s*refs\/heads\/(.+)$/;
31
+ /**
32
+ * Walk up from `startDir` looking for a `.git` entry. Handles both the
33
+ * common `.git` directory and the `.git` *file* form used by linked
34
+ * worktrees and submodules. The walk is bounded so a pathologically
35
+ * deep cwd cannot spin.
36
+ */
37
+ function findGitEntry(startDir) {
38
+ let dir = path.resolve(startDir);
39
+ for (let depth = 0; depth < 128; depth++) {
40
+ const dotGit = path.join(dir, ".git");
41
+ let stat;
42
+ try {
43
+ stat = fs.statSync(dotGit);
44
+ }
45
+ catch {
46
+ stat = undefined;
47
+ }
48
+ if (stat?.isDirectory()) {
49
+ return { worktreeRoot: dir, gitDir: dotGit };
50
+ }
51
+ if (stat?.isFile()) {
52
+ let gitDir = "";
53
+ try {
54
+ const match = GITDIR_RE.exec(fs.readFileSync(dotGit, "utf8").trim());
55
+ if (match)
56
+ gitDir = path.resolve(dir, match[1].trim());
57
+ }
58
+ catch {
59
+ /* unreadable `.git` file — leave gitDir empty, repo still resolves */
60
+ }
61
+ return { worktreeRoot: dir, gitDir };
62
+ }
63
+ const parent = path.dirname(dir);
64
+ if (parent === dir)
65
+ return null; // hit the filesystem root
66
+ dir = parent;
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Resolve `{ repo, branch }` for a working directory. Returns empty
72
+ * strings (never throws) when `cwd` is not inside a git work tree, or
73
+ * when any individual lookup fails — callers treat "" as "unknown" and
74
+ * fall through to their own behaviour.
75
+ */
76
+ export function resolveGitContext(cwd) {
77
+ if (typeof cwd !== "string" || cwd.length === 0)
78
+ return EMPTY;
79
+ const entry = findGitEntry(cwd);
80
+ if (!entry)
81
+ return EMPTY;
82
+ const repo = path.basename(entry.worktreeRoot);
83
+ let branch = "";
84
+ if (entry.gitDir) {
85
+ try {
86
+ const head = fs.readFileSync(path.join(entry.gitDir, "HEAD"), "utf8").trim();
87
+ const match = HEAD_REF_RE.exec(head);
88
+ if (match)
89
+ branch = match[1].trim();
90
+ }
91
+ catch {
92
+ /* unreadable HEAD — branch stays "" */
93
+ }
94
+ }
95
+ return { repo, branch };
96
+ }
97
+ //# sourceMappingURL=git-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-context.js","sourceRoot":"","sources":["../../src/runtime/git-context.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export { intercept, type ClaudeDenyJson, type InterceptOptions, type InterceptResult, type LedgerClient, type PolicyDecision, type PolicyOutcome, type ToolEvent, } from "./intercept.js";
2
2
  export { recordPolicyDecision, payloadFromDecision, encodeLedgerContent, decodeLedgerContent, decisionSortKey, type LedgerRecordOptions, type PolicyDecisionPayload, } from "./ledger-record.js";
3
3
  export { resolveSessionId } from "./session-id.js";
4
+ export { resolveGitContext, type GitRepoContext } from "./git-context.js";
4
5
  export { addLedgerFact, type AddLedgerFactOptions, type AddLedgerFactResult, } from "./ledger-add.js";
@@ -1,5 +1,6 @@
1
1
  export { intercept, } from "./intercept.js";
2
2
  export { recordPolicyDecision, payloadFromDecision, encodeLedgerContent, decodeLedgerContent, decisionSortKey, } from "./ledger-record.js";
3
3
  export { resolveSessionId } from "./session-id.js";
4
+ export { resolveGitContext } from "./git-context.js";
4
5
  export { addLedgerFact, } from "./ledger-add.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAQV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAGhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,aAAa,GAGd,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAQV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAGhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAuB,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,aAAa,GAGd,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,31 @@
1
+ export declare const GENERATED_DIRNAME = "harness.generated";
2
+ export declare const PENDING_APPROVAL_BASENAME = ".pending-approval";
3
+ /**
4
+ * Resolve `harness.generated/` the way `harness apply` does: a sibling of
5
+ * the manifest, unless an explicit `homeDir` override is in play (tests,
6
+ * non-default home).
7
+ */
8
+ export declare function resolveGeneratedDir(opts: {
9
+ homeDir?: string;
10
+ manifestPath: string;
11
+ }): string;
12
+ export declare function pendingApprovalPath(generatedDir: string): string;
13
+ /**
14
+ * Producer: stage `sessionId` for a later `harness approve`. `atomicWriteFile`
15
+ * creates `generatedDir` if missing, so a hand-wired hook with no prior
16
+ * apply still benefits. Callers treat this as best-effort — a write
17
+ * failure must never escalate a gate block into a thrown hook error.
18
+ */
19
+ export declare function writePendingApproval(generatedDir: string, sessionId: string): void;
20
+ /**
21
+ * Consumer: read the staged session id, or null when the file is absent,
22
+ * empty, whitespace-only, or unreadable. Trims the trailing newline the
23
+ * producer writes plus any surrounding whitespace.
24
+ */
25
+ export declare function readPendingApproval(generatedDir: string): string | null;
26
+ /**
27
+ * Consumer: drop the staging file once its id has been consumed, so a
28
+ * later arg-less `harness approve` cannot revive a stale session id.
29
+ * Best-effort — a missing file counts as success.
30
+ */
31
+ export declare function clearPendingApproval(generatedDir: string): void;
@@ -0,0 +1,80 @@
1
+ // Task 33abc147 — `.pending-approval` session-id staging file.
2
+ //
3
+ // The understanding-gate PreToolUse hook knows the running session's
4
+ // exact `session_id` (it arrives on the hook event's stdin). `harness
5
+ // approve`, run from the operator's `!`-shell, does NOT: $CLAUDE_SESSION_ID
6
+ // is unset in that shell, and guessing the id from the newest project
7
+ // transcript is a heuristic that breaks on subagent / parallel-session
8
+ // transcripts (the approve error message warns about exactly that).
9
+ //
10
+ // So the producer hands the id off instead of making the consumer guess:
11
+ // on every block / ask the gate hook writes the `session_id` to
12
+ // `<generatedDir>/.pending-approval`, and `harness approve` reads it when
13
+ // no `--session` flag and no `$CLAUDE_SESSION_ID` are given. Deterministic,
14
+ // not a guess.
15
+ //
16
+ // `harness apply` only writes its own known files into harness.generated/
17
+ // (it never wipes the directory), so the staging file survives applies.
18
+ // `harness approve` deletes it after a successful resolve so a later
19
+ // arg-less invocation cannot revive a stale session id.
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+ import { atomicWriteFile } from "../io/atomic-write.js";
23
+ // Mirrors `GENERATED_DIRNAME` in cli/apply/apply.ts — also independently
24
+ // duplicated in cli/pack/remove.ts and cli/diff/since-apply.ts. The
25
+ // constant has no single home yet; consolidating the four copies is a
26
+ // separate cleanup.
27
+ export const GENERATED_DIRNAME = "harness.generated";
28
+ export const PENDING_APPROVAL_BASENAME = ".pending-approval";
29
+ /**
30
+ * Resolve `harness.generated/` the way `harness apply` does: a sibling of
31
+ * the manifest, unless an explicit `homeDir` override is in play (tests,
32
+ * non-default home).
33
+ */
34
+ export function resolveGeneratedDir(opts) {
35
+ if (opts.homeDir !== undefined)
36
+ return path.join(opts.homeDir, GENERATED_DIRNAME);
37
+ return path.join(path.dirname(opts.manifestPath), GENERATED_DIRNAME);
38
+ }
39
+ export function pendingApprovalPath(generatedDir) {
40
+ return path.join(generatedDir, PENDING_APPROVAL_BASENAME);
41
+ }
42
+ /**
43
+ * Producer: stage `sessionId` for a later `harness approve`. `atomicWriteFile`
44
+ * creates `generatedDir` if missing, so a hand-wired hook with no prior
45
+ * apply still benefits. Callers treat this as best-effort — a write
46
+ * failure must never escalate a gate block into a thrown hook error.
47
+ */
48
+ export function writePendingApproval(generatedDir, sessionId) {
49
+ atomicWriteFile(pendingApprovalPath(generatedDir), `${sessionId}\n`);
50
+ }
51
+ /**
52
+ * Consumer: read the staged session id, or null when the file is absent,
53
+ * empty, whitespace-only, or unreadable. Trims the trailing newline the
54
+ * producer writes plus any surrounding whitespace.
55
+ */
56
+ export function readPendingApproval(generatedDir) {
57
+ let raw;
58
+ try {
59
+ raw = fs.readFileSync(pendingApprovalPath(generatedDir), "utf8");
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ const trimmed = raw.trim();
65
+ return trimmed.length > 0 ? trimmed : null;
66
+ }
67
+ /**
68
+ * Consumer: drop the staging file once its id has been consumed, so a
69
+ * later arg-less `harness approve` cannot revive a stale session id.
70
+ * Best-effort — a missing file counts as success.
71
+ */
72
+ export function clearPendingApproval(generatedDir) {
73
+ try {
74
+ fs.rmSync(pendingApprovalPath(generatedDir));
75
+ }
76
+ catch {
77
+ /* already gone (or never written) — nothing to clean up */
78
+ }
79
+ }
80
+ //# sourceMappingURL=pending-approval.js.map