@lannguyensi/harness 0.50.0 → 0.52.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 (81) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/dist/cli/approve/understanding.d.ts +97 -0
  3. package/dist/cli/approve/understanding.js +67 -24
  4. package/dist/cli/approve/understanding.js.map +1 -1
  5. package/dist/cli/audit.d.ts +29 -0
  6. package/dist/cli/audit.js +130 -17
  7. package/dist/cli/audit.js.map +1 -1
  8. package/dist/cli/delegate/index.d.ts +63 -0
  9. package/dist/cli/delegate/index.js +313 -0
  10. package/dist/cli/delegate/index.js.map +1 -0
  11. package/dist/cli/doctor/auto-approve-mode.d.ts +37 -0
  12. package/dist/cli/doctor/auto-approve-mode.js +80 -0
  13. package/dist/cli/doctor/auto-approve-mode.js.map +1 -0
  14. package/dist/cli/doctor/claude-mcp.d.ts +14 -0
  15. package/dist/cli/doctor/claude-mcp.js +7 -1
  16. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  17. package/dist/cli/doctor/codex-config-drift.d.ts +28 -0
  18. package/dist/cli/doctor/codex-config-drift.js +240 -0
  19. package/dist/cli/doctor/codex-config-drift.js.map +1 -0
  20. package/dist/cli/doctor/format.js +67 -1
  21. package/dist/cli/doctor/format.js.map +1 -1
  22. package/dist/cli/doctor/index.d.ts +20 -0
  23. package/dist/cli/doctor/index.js +84 -1
  24. package/dist/cli/doctor/index.js.map +1 -1
  25. package/dist/cli/doctor/settings-drift.d.ts +25 -0
  26. package/dist/cli/doctor/settings-drift.js +203 -0
  27. package/dist/cli/doctor/settings-drift.js.map +1 -0
  28. package/dist/cli/doctor/types.d.ts +69 -0
  29. package/dist/cli/doctor/types.js.map +1 -1
  30. package/dist/cli/doctor/ug-auto-approvals.d.ts +40 -0
  31. package/dist/cli/doctor/ug-auto-approvals.js +116 -0
  32. package/dist/cli/doctor/ug-auto-approvals.js.map +1 -0
  33. package/dist/cli/doctor/ug-delegations.d.ts +26 -0
  34. package/dist/cli/doctor/ug-delegations.js +112 -0
  35. package/dist/cli/doctor/ug-delegations.js.map +1 -0
  36. package/dist/cli/doctor/understanding-mode-env.d.ts +12 -0
  37. package/dist/cli/doctor/understanding-mode-env.js +16 -1
  38. package/dist/cli/doctor/understanding-mode-env.js.map +1 -1
  39. package/dist/cli/index.js +100 -1
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/pack/approve-escape.js +16 -7
  42. package/dist/cli/pack/approve-escape.js.map +1 -1
  43. package/dist/cli/pack/auto-approve-path.d.ts +135 -0
  44. package/dist/cli/pack/auto-approve-path.js +400 -0
  45. package/dist/cli/pack/auto-approve-path.js.map +1 -0
  46. package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +9 -0
  47. package/dist/cli/pack/hook-codex-pre-tool-use.js +118 -4
  48. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  49. package/dist/cli/pack/hook-pre-tool-use.d.ts +36 -0
  50. package/dist/cli/pack/hook-pre-tool-use.js +402 -3
  51. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  52. package/dist/cli/pack/transcript-report-scan.d.ts +71 -0
  53. package/dist/cli/pack/transcript-report-scan.js +359 -0
  54. package/dist/cli/pack/transcript-report-scan.js.map +1 -0
  55. package/dist/cli/smoke/index.d.ts +18 -1
  56. package/dist/cli/smoke/index.js +54 -0
  57. package/dist/cli/smoke/index.js.map +1 -1
  58. package/dist/cli/validate/checks.js +81 -0
  59. package/dist/cli/validate/checks.js.map +1 -1
  60. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +210 -0
  61. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +361 -0
  62. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +1 -0
  63. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +223 -0
  64. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +633 -0
  65. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -0
  66. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +3 -1
  67. package/dist/policy-packs/builtin/understanding-before-execution/index.js +10 -1
  68. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
  69. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.d.ts +29 -0
  70. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js +82 -0
  71. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js.map +1 -0
  72. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +23 -0
  73. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +29 -0
  74. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -1
  75. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +42 -0
  76. package/dist/policy-packs/builtin/understanding-before-execution.js +56 -1
  77. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  78. package/dist/runtime/approval-signing.d.ts +22 -0
  79. package/dist/runtime/approval-signing.js +83 -1
  80. package/dist/runtime/approval-signing.js.map +1 -1
  81. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-drift.js","sourceRoot":"","sources":["../../../src/cli/doctor/settings-drift.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,11 @@ import type { NpmBinReport } from "./npm-bin-path.js";
9
9
  import type { RogueLedgerDb } from "./rogue-ledger.js";
10
10
  import type { UnderstandingModeEnvDivergence } from "./understanding-mode-env.js";
11
11
  import type { ToolchainParitySection } from "./toolchain-parity.js";
12
+ import type { UgAutoApprovalsSection } from "./ug-auto-approvals.js";
13
+ import type { UgDelegationsSection } from "./ug-delegations.js";
14
+ import type { SettingsDriftSection } from "./settings-drift.js";
15
+ import type { AutoApproveModeWarning } from "./auto-approve-mode.js";
16
+ import type { CodexConfigDriftSection } from "./codex-config-drift.js";
12
17
  /**
13
18
  * Phase 6 #6 follow-up — doctor target identifier. Distinct from
14
19
  * `Runtime` (which gates `harness apply --runtime`): doctor only adds
@@ -379,6 +384,65 @@ export interface DoctorReport {
379
384
  * `understanding-mode-env.ts` for the full rationale.
380
385
  */
381
386
  understandingModeEnv?: UnderstandingModeEnvDivergence;
387
+ /**
388
+ * Auto-approval listing + last-N metric (ADR
389
+ * docs/decisions/2026-08-27-ug-auto-mode-approval.md slice 1,
390
+ * agent-tasks 74b4b17d, "Audit and doctor"). Present only when the
391
+ * `understanding-before-execution` pack is declared and enabled
392
+ * (mirrors `understandingModeEnv`'s gate) — a manifest that never
393
+ * uses the pack has no `.approvals/` markers this listing owns an
394
+ * opinion about. Purely informational (`ℹ`); never rolls into
395
+ * `warningCount`.
396
+ */
397
+ ugAutoApprovals?: UgAutoApprovalsSection;
398
+ /**
399
+ * Delegations-on-disk metric (same ADR, slice 3, "Audit and doctor",
400
+ * agent-tasks 37ad0b05): `total`/`expired`/`unreadable` counts
401
+ * from `harness.generated/.delegations/`, distinct from
402
+ * `ugAutoApprovals` (which reads `.approvals/` only). Present only
403
+ * when the `understanding-before-execution` pack is declared and
404
+ * enabled (mirrors `ugAutoApprovals`'s gate). `delegationsDirPresent`
405
+ * mirrors `ugAutoApprovals`'s `approvalsDirPresent`: the rendered line
406
+ * stays silent when `.delegations/` does not exist at all, and shows
407
+ * the zero-count line when it exists but is empty (both cases resolve
408
+ * `total`/`expired`/`unreadable` to `0`). Informational (`ℹ`) unless
409
+ * `unreadable > 0`, in which case it rolls one warning into
410
+ * `warningCount`.
411
+ */
412
+ ugDelegations?: UgDelegationsSection;
413
+ /**
414
+ * `auto_approve` configured without `mode: grill_me` (agent-tasks
415
+ * abfad738, follow-up of ADR
416
+ * docs/decisions/2026-08-27-ug-auto-mode-approval.md slice 1).
417
+ * Present only when the pack is declared and enabled AND
418
+ * `config.auto_approve` parses as a valid opt-in block. Always
419
+ * advisory (rolls into `warningCount`, never `errorCount`). See
420
+ * `auto-approve-mode.ts` for the full rationale.
421
+ */
422
+ ugAutoApproveMode?: AutoApproveModeWarning;
423
+ /**
424
+ * Settings-drift compensating control (same ADR, threat model (c)): a
425
+ * `permissions.defaultMode` or hook entry present in a live Claude
426
+ * Code settings file but absent from harness's own last-apply
427
+ * snapshot. Present only when the understanding-gate pack is enabled
428
+ * AND `harness apply` has run at least once for this manifest
429
+ * (`harness.generated/` exists) — otherwise there is no baseline to
430
+ * compare against and nothing to say. `warnings` roll into
431
+ * `warningCount`; `notes` never do.
432
+ */
433
+ settingsDrift?: SettingsDriftSection;
434
+ /**
435
+ * Codex counterpart of `settingsDrift` (follow-up of slice 2 of the
436
+ * same ADR, agent-tasks f59ea0eb): a live `approval_policy = "never"`
437
+ * or full-access `default_permissions` selection in `$CODEX_HOME
438
+ * /config.toml` or `<repo>/.codex/config.toml`. Present only when the
439
+ * understanding-gate pack's `auto_approve.harnesses` lists `codex`;
440
+ * a repo that never opted Codex into the auto path has no live signal
441
+ * for this key to pre-set. Unlike `settingsDrift`, this always warns
442
+ * while the key is present and the repo is opted in (not gated on
443
+ * drift since the last apply); `warnings` roll into `warningCount`.
444
+ */
445
+ codexConfigDrift?: CodexConfigDriftSection;
382
446
  memory: MemoryReport;
383
447
  hooks: HookEntryReport[];
384
448
  policies: PolicyEntryReport[];
@@ -478,5 +542,10 @@ export interface DoctorReport {
478
542
  export type { NpmBinReport } from "./npm-bin-path.js";
479
543
  export type { ClaudeMcpRegistrationSection, ClaudeMcpEntryReport } from "./claude-mcp.js";
480
544
  export type { UnderstandingModeEnvDivergence } from "./understanding-mode-env.js";
545
+ export type { UgAutoApprovalsSection, AutoApprovalListingEntry } from "./ug-auto-approvals.js";
546
+ export type { UgDelegationsSection } from "./ug-delegations.js";
547
+ export type { AutoApproveModeWarning } from "./auto-approve-mode.js";
548
+ export type { SettingsDriftSection } from "./settings-drift.js";
549
+ export type { CodexConfigDriftSection } from "./codex-config-drift.js";
481
550
  export type { ToolchainParitySection, ToolchainParityPeerReport } from "./toolchain-parity.js";
482
551
  export type { Manifest, McpProbeResult, MemoryReport, StaleMemory, CodexTargetReport, RogueLedgerDb, };
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/cli/doctor/types.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAC;AAGnE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,oBAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClG,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/cli/doctor/types.ts"],"names":[],"mappings":"AAiBA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAC;AAGnE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,oBAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClG,CAAC"}
@@ -0,0 +1,40 @@
1
+ /** Default doctor window when `--recent-sessions` is not passed. */
2
+ export declare const DEFAULT_RECENT_SESSIONS = 20;
3
+ export interface AutoApprovalListingEntry {
4
+ sessionId: string;
5
+ mode: string;
6
+ harness: string;
7
+ approvedAt: string;
8
+ }
9
+ export interface UgAutoApprovalsSection {
10
+ /** Whether `<generatedDir>/.approvals/` exists at all. */
11
+ approvalsDirPresent: boolean;
12
+ /** The `recentSessions` window that was applied. */
13
+ windowSize: number;
14
+ /** Auto approvals among the newest `windowSize` session markers. */
15
+ autoApprovedCount: number;
16
+ /** Per-mode breakdown of `autoApprovedCount` (e.g. `{bypassPermissions: 3}`). */
17
+ byMode: Record<string, number>;
18
+ /** Per-harness breakdown of `autoApprovedCount` (e.g. `{"claude-code": 3}`). */
19
+ byHarness: Record<string, number>;
20
+ /** `(sessionId, mode, approvedAt)` for each auto approval in the window, newest first. */
21
+ entries: AutoApprovalListingEntry[];
22
+ /**
23
+ * Session markers whose body could not be read as JSON with a
24
+ * parseable `approvedAt` (corrupt file, non-JSON content, missing
25
+ * `approvedAt`). Excluded from the window (there is no timestamp to
26
+ * sort them by) and never counted as auto approvals; surfaced as a
27
+ * single summary count instead of per-file noise.
28
+ */
29
+ unreadableCount: number;
30
+ }
31
+ /**
32
+ * Build the auto-approval listing + last-N metric. Pure filesystem read;
33
+ * never throws for ordinary "nothing to see" states (missing
34
+ * `.approvals/`, empty directory, every marker unreadable) — those all
35
+ * resolve to an empty-ish section rather than an exception, mirroring
36
+ * the rest of doctor's read-only checks.
37
+ */
38
+ export declare function buildUgAutoApprovals(generatedDir: string, opts: {
39
+ recentSessions: number;
40
+ }): UgAutoApprovalsSection;
@@ -0,0 +1,116 @@
1
+ // Doctor visibility half of ADR docs/decisions/2026-08-27-ug-auto-mode-approval.md
2
+ // slice 1 (agent-tasks 74b4b17d): the "auto approvals in the last N
3
+ // sessions" listing named in the ADR's "Audit and doctor" section and
4
+ // slice 1 acceptance criterion 7.
5
+ //
6
+ // Scans `<generatedDir>/.approvals/` for SESSION markers (skips the
7
+ // per-task markers named `task-<id>` and the branch-protection markers
8
+ // named `branch-protection-<sid>`, neither of which is a session
9
+ // approval), sorts the readable ones newest-first by `approvedAt`,
10
+ // takes the newest `recentSessions` of them, and reports how many of
11
+ // THOSE were minted by the auto path (`approvedBy` starting with
12
+ // `auto-mode:`, see auto-approve.ts's `parseAutoApprovedBy`).
13
+ //
14
+ // This is an AUDIT SURFACE, not a security check: it never verifies a
15
+ // marker's HMAC signature (that is `checkApprovalMarker`'s job at gate
16
+ // time; a forged marker still gets rejected there regardless of what
17
+ // this listing shows) and it never blocks or writes anything.
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ import { readRegularFileRejectingSymlink } from "../../io/read-regular-file.js";
21
+ import { safeJsonParse } from "../../io/safe-json-parse.js";
22
+ import { BRANCH_PROTECTION_MARKER_PREFIX } from "../../policy-packs/builtin/branch-protection-runtime.js";
23
+ import { APPROVAL_MARKER_DIRNAME, APPROVAL_MARKER_TASK_PREFIX, parseAutoApprovedBy, } from "../../policy-packs/builtin/understanding-before-execution/index.js";
24
+ /** Default doctor window when `--recent-sessions` is not passed. */
25
+ export const DEFAULT_RECENT_SESSIONS = 20;
26
+ function emptySection(windowSize) {
27
+ return {
28
+ approvalsDirPresent: false,
29
+ windowSize,
30
+ autoApprovedCount: 0,
31
+ byMode: {},
32
+ byHarness: {},
33
+ entries: [],
34
+ unreadableCount: 0,
35
+ };
36
+ }
37
+ /**
38
+ * Build the auto-approval listing + last-N metric. Pure filesystem read;
39
+ * never throws for ordinary "nothing to see" states (missing
40
+ * `.approvals/`, empty directory, every marker unreadable) — those all
41
+ * resolve to an empty-ish section rather than an exception, mirroring
42
+ * the rest of doctor's read-only checks.
43
+ */
44
+ export function buildUgAutoApprovals(generatedDir, opts) {
45
+ const windowSize = opts.recentSessions;
46
+ const dir = path.join(generatedDir, APPROVAL_MARKER_DIRNAME);
47
+ let dirents;
48
+ try {
49
+ dirents = fs.readdirSync(dir, { withFileTypes: true });
50
+ }
51
+ catch {
52
+ return emptySection(windowSize);
53
+ }
54
+ const readable = [];
55
+ let unreadableCount = 0;
56
+ for (const d of dirents) {
57
+ const name = d.name;
58
+ if (name.startsWith(APPROVAL_MARKER_TASK_PREFIX))
59
+ continue;
60
+ if (name.startsWith(BRANCH_PROTECTION_MARKER_PREFIX))
61
+ continue;
62
+ const full = path.join(dir, name);
63
+ const read = readRegularFileRejectingSymlink(full);
64
+ // A symlink / non-regular / raced-away entry is not a session marker
65
+ // this listing owns an opinion about; skip silently rather than
66
+ // counting it as "unreadable" (that count is for a FILE we expected
67
+ // to be a marker but could not parse).
68
+ if (read.kind === "symlink" || read.kind === "not-regular" || read.kind === "missing") {
69
+ continue;
70
+ }
71
+ if (read.kind === "unreadable") {
72
+ unreadableCount++;
73
+ continue;
74
+ }
75
+ const parsed = safeJsonParse(read.content);
76
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
77
+ unreadableCount++;
78
+ continue;
79
+ }
80
+ const obj = parsed;
81
+ const approvedAt = obj["approvedAt"];
82
+ if (typeof approvedAt !== "string" || Number.isNaN(Date.parse(approvedAt))) {
83
+ unreadableCount++;
84
+ continue;
85
+ }
86
+ readable.push({ sessionId: name, approvedAt, approvedBy: obj["approvedBy"] });
87
+ }
88
+ readable.sort((a, b) => Date.parse(b.approvedAt) - Date.parse(a.approvedAt));
89
+ const windowed = readable.slice(0, windowSize);
90
+ const byMode = {};
91
+ const byHarness = {};
92
+ const entries = [];
93
+ for (const m of windowed) {
94
+ const parsedAuto = parseAutoApprovedBy(m.approvedBy);
95
+ if (parsedAuto === null)
96
+ continue;
97
+ byMode[parsedAuto.mode] = (byMode[parsedAuto.mode] ?? 0) + 1;
98
+ byHarness[parsedAuto.harness] = (byHarness[parsedAuto.harness] ?? 0) + 1;
99
+ entries.push({
100
+ sessionId: m.sessionId,
101
+ mode: parsedAuto.mode,
102
+ harness: parsedAuto.harness,
103
+ approvedAt: m.approvedAt,
104
+ });
105
+ }
106
+ return {
107
+ approvalsDirPresent: true,
108
+ windowSize,
109
+ autoApprovedCount: entries.length,
110
+ byMode,
111
+ byHarness,
112
+ entries,
113
+ unreadableCount,
114
+ };
115
+ }
116
+ //# sourceMappingURL=ug-auto-approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ug-auto-approvals.js","sourceRoot":"","sources":["../../../src/cli/doctor/ug-auto-approvals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface UgDelegationsSection {
2
+ /** Whether `<generatedDir>/.delegations/` exists at all. */
3
+ delegationsDirPresent: boolean;
4
+ /** Delegation files found in `.delegations/`, readable or not. */
5
+ total: number;
6
+ /** Of `total`, how many parsed with an `expires` segment in the past. */
7
+ expired: number;
8
+ /** Of `total`, how many were not valid JSON, or failed `parseDelegationApprovedBy`. */
9
+ unreadable: number;
10
+ }
11
+ /**
12
+ * Build the delegations-on-disk doctor metric. Pure filesystem read;
13
+ * never throws for "nothing to see" states (missing `.delegations/`,
14
+ * empty directory, every file unreadable), those all resolve to a
15
+ * zero-ish section, mirroring `buildUgAutoApprovals`. `total`/`expired`/
16
+ * `unreadable` read `0` for both an EMPTY and a MISSING directory; only
17
+ * `delegationsDirPresent` tells them apart, the same way
18
+ * `approvalsDirPresent` does for `buildUgAutoApprovals`, doctor's
19
+ * render layer uses it to stay silent when there is truly nothing on
20
+ * disk (`format.ts`'s own "no line for a check that found nothing"
21
+ * convention) while still rendering the zero-count line for a
22
+ * `harness delegate`-touched, now-empty directory.
23
+ */
24
+ export declare function buildUgDelegations(generatedDir: string, opts?: {
25
+ now?: Date;
26
+ }): UgDelegationsSection;
@@ -0,0 +1,112 @@
1
+ // Doctor visibility for slice 3 delegations (ADR
2
+ // docs/decisions/2026-08-27-ug-auto-mode-approval.md, "Audit and
3
+ // doctor", agent-tasks 37ad0b05): "delegations on disk: <count>
4
+ // (<count> expired)" from `<generatedDir>/.delegations/`, distinct from
5
+ // the `.approvals/` auto-approval listing in `ug-auto-approvals.ts`.
6
+ //
7
+ // SEPARATE DIRECTORY, DELIBERATELY: a delegation never lands in
8
+ // `.approvals/` (delegation-markers.ts's module header), so this scan
9
+ // reads `DELEGATION_MARKER_DIRNAME` (".delegations") only and never
10
+ // touches the approvals directory `ug-auto-approvals.ts` owns. Mirrors
11
+ // that module's read-only-audit shape (no signature verification here
12
+ // either; a forged delegation still gets rejected by the dedicated
13
+ // verifier at gate time, `delegation-markers.ts`'s `verifyDelegation`)
14
+ // but does not share its code: the two scans read different
15
+ // directories, filter differently (no task-marker / branch-protection
16
+ // prefix skip here), and count differently (an approval count vs. an
17
+ // expiry count), so a shared helper would mostly be parameters threading
18
+ // through an otherwise-empty shell. `npm run check:duplication` is the
19
+ // backstop if that stops being true.
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+ import { readRegularFileRejectingSymlink } from "../../io/read-regular-file.js";
23
+ import { safeJsonParse } from "../../io/safe-json-parse.js";
24
+ import { DELEGATION_MARKER_DIRNAME, parseDelegationApprovedBy, } from "../../policy-packs/builtin/understanding-before-execution/index.js";
25
+ function emptySection() {
26
+ return { delegationsDirPresent: false, total: 0, expired: 0, unreadable: 0 };
27
+ }
28
+ /**
29
+ * A delegation filename is always a session id: a UUID handed out by
30
+ * Claude Code or Codex, or (in this repo's own test fixtures) a short
31
+ * alnum/hyphen placeholder. Neither ever contains a literal `.` or
32
+ * starts with one. Filtering on that BEFORE the read is what keeps
33
+ * filesystem debris that happens to sit next to `.delegations/` files
34
+ * (macOS's `.DS_Store`, a stray `foo.txt`) from counting toward `total`
35
+ * at all: without it, such a file reads as valid-but-not-JSON and rolls
36
+ * into `unreadable`, which flips this metric's doctor line from
37
+ * informational (ℹ) straight to a WARNING (⚠) for debris that was never
38
+ * a delegation to begin with. Mirrors the same non-empty/no-traversal
39
+ * shape family `rejectMalformedSessionId` pins for a delegation's own
40
+ * filename on the write side (`delegationMarkerPathFor`), tightened to
41
+ * additionally exclude a dot, which is the part that actually
42
+ * discriminates a stray dotfile from a real session id.
43
+ */
44
+ const SESSION_ID_BASENAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
45
+ /**
46
+ * Build the delegations-on-disk doctor metric. Pure filesystem read;
47
+ * never throws for "nothing to see" states (missing `.delegations/`,
48
+ * empty directory, every file unreadable), those all resolve to a
49
+ * zero-ish section, mirroring `buildUgAutoApprovals`. `total`/`expired`/
50
+ * `unreadable` read `0` for both an EMPTY and a MISSING directory; only
51
+ * `delegationsDirPresent` tells them apart, the same way
52
+ * `approvalsDirPresent` does for `buildUgAutoApprovals`, doctor's
53
+ * render layer uses it to stay silent when there is truly nothing on
54
+ * disk (`format.ts`'s own "no line for a check that found nothing"
55
+ * convention) while still rendering the zero-count line for a
56
+ * `harness delegate`-touched, now-empty directory.
57
+ */
58
+ export function buildUgDelegations(generatedDir, opts = {}) {
59
+ const now = opts.now ?? new Date();
60
+ const dir = path.join(generatedDir, DELEGATION_MARKER_DIRNAME);
61
+ let dirents;
62
+ try {
63
+ dirents = fs.readdirSync(dir, { withFileTypes: true });
64
+ }
65
+ catch {
66
+ return emptySection();
67
+ }
68
+ let total = 0;
69
+ let expired = 0;
70
+ let unreadable = 0;
71
+ for (const d of dirents) {
72
+ // Not a session-id-shaped basename: never a delegation file (a
73
+ // stray dotfile, an extensioned file, a directory, ...), skip
74
+ // before even reading it. The once-per-session adoption ledger is
75
+ // never nested under here: `.delegation-adoptions/` is a SIBLING of
76
+ // `.delegations/` (see delegation-markers.ts's module header), so
77
+ // this filter has no ledger-shaped entry to reason about; an
78
+ // ordinary session-id-named directory would still fall through to
79
+ // the `not-regular` skip below, same as any other directory would.
80
+ if (!SESSION_ID_BASENAME_RE.test(d.name))
81
+ continue;
82
+ const full = path.join(dir, d.name);
83
+ const read = readRegularFileRejectingSymlink(full);
84
+ // A symlink / non-regular / raced-away entry is not a delegation
85
+ // file this metric owns an opinion about; skip silently, same
86
+ // reasoning as `buildUgAutoApprovals`.
87
+ if (read.kind === "symlink" || read.kind === "not-regular" || read.kind === "missing") {
88
+ continue;
89
+ }
90
+ total++;
91
+ if (read.kind === "unreadable") {
92
+ unreadable++;
93
+ continue;
94
+ }
95
+ const parsed = safeJsonParse(read.content);
96
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
97
+ unreadable++;
98
+ continue;
99
+ }
100
+ const obj = parsed;
101
+ const segments = parseDelegationApprovedBy(obj["approvedBy"]);
102
+ if (!segments.ok) {
103
+ unreadable++;
104
+ continue;
105
+ }
106
+ if (Date.parse(segments.value.expiresAt) <= now.getTime()) {
107
+ expired++;
108
+ }
109
+ }
110
+ return { delegationsDirPresent: true, total, expired, unreadable };
111
+ }
112
+ //# sourceMappingURL=ug-delegations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ug-delegations.js","sourceRoot":"","sources":["../../../src/cli/doctor/ug-delegations.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { type Mode } from "../../policy-packs/builtin/understanding-before-execution.js";
2
2
  import type { Manifest } from "../../schema/index.js";
3
+ import type { PolicyPack } from "../../schema/policy-packs.js";
3
4
  export interface UnderstandingModeEnvDivergence {
4
5
  /** `UNDERSTANDING_GATE_MODE`, normalised (trim + lowercase, mirrors `resolveMode`'s own normalisation). */
5
6
  envMode: Mode;
@@ -36,4 +37,15 @@ export interface UnderstandingModeEnvDivergence {
36
37
  * warn about.
37
38
  * - the normalised env mode differs from the resolved `config.mode`.
38
39
  */
40
+ /**
41
+ * Shared gate for the understanding-gate doctor sub-checks that only
42
+ * make sense when the pack is actually live: this divergence advisory,
43
+ * and (ADR docs/decisions/2026-08-27-ug-auto-mode-approval.md slice 1,
44
+ * agent-tasks 74b4b17d) the auto-approval listing and settings-drift
45
+ * checks. Declared-but-disabled counts as not enabled, mirroring the
46
+ * `p.enabled !== false` gate `checkUnderstandingModeEnvDivergence` uses
47
+ * inline below.
48
+ */
49
+ export declare function findEnabledUnderstandingPack(manifest: Manifest): PolicyPack | undefined;
50
+ export declare function isUnderstandingPackEnabled(manifest: Manifest): boolean;
39
51
  export declare function checkUnderstandingModeEnvDivergence(manifest: Manifest, env: NodeJS.ProcessEnv): UnderstandingModeEnvDivergence | undefined;
@@ -48,8 +48,23 @@ import { isMode, MODE_ENV, PACK_NAME as UNDERSTANDING_PACK_NAME, resolveModeFrom
48
48
  * warn about.
49
49
  * - the normalised env mode differs from the resolved `config.mode`.
50
50
  */
51
+ /**
52
+ * Shared gate for the understanding-gate doctor sub-checks that only
53
+ * make sense when the pack is actually live: this divergence advisory,
54
+ * and (ADR docs/decisions/2026-08-27-ug-auto-mode-approval.md slice 1,
55
+ * agent-tasks 74b4b17d) the auto-approval listing and settings-drift
56
+ * checks. Declared-but-disabled counts as not enabled, mirroring the
57
+ * `p.enabled !== false` gate `checkUnderstandingModeEnvDivergence` uses
58
+ * inline below.
59
+ */
60
+ export function findEnabledUnderstandingPack(manifest) {
61
+ return manifest.policy_packs.find((p) => p.name === UNDERSTANDING_PACK_NAME && p.enabled !== false);
62
+ }
63
+ export function isUnderstandingPackEnabled(manifest) {
64
+ return findEnabledUnderstandingPack(manifest) !== undefined;
65
+ }
51
66
  export function checkUnderstandingModeEnvDivergence(manifest, env) {
52
- const pack = manifest.policy_packs.find((p) => p.name === UNDERSTANDING_PACK_NAME && p.enabled !== false);
67
+ const pack = findEnabledUnderstandingPack(manifest);
53
68
  if (!pack)
54
69
  return undefined;
55
70
  const envRaw = env[MODE_ENV];
@@ -1 +1 @@
1
- {"version":3,"file":"understanding-mode-env.js","sourceRoot":"","sources":["../../../src/cli/doctor/understanding-mode-env.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,mEAAmE;AACnE,EAAE;AACF,iEAAiE;AACjE,0EAA0E;AAC1E,kEAAkE;AAClE,wEAAwE;AACxE,kEAAkE;AAClE,sEAAsE;AACtE,oEAAoE;AACpE,sEAAsE;AACtE,yEAAyE;AACzE,wEAAwE;AACxE,yEAAyE;AACzE,uDAAuD;AACvD,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,4DAA4D;AAC5D,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,qEAAqE;AACrE,yEAAyE;AACzE,+DAA+D;AAC/D,EAAE;AACF,+DAA+D;AAC/D,0EAA0E;AAC1E,kEAAkE;AAClE,2BAA2B;AAE3B,OAAO,EACL,MAAM,EACN,QAAQ,EACR,SAAS,IAAI,uBAAuB,EACpC,qBAAqB,GAEtB,MAAM,8DAA8D,CAAC;AAwBtE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mCAAmC,CACjD,QAAkB,EAClB,GAAsB;IAEtB,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAC,IAAI,CACrC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,uBAAuB,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,CACjE,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC5D,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IAE7C,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,aAAa,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAEnD,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,uEAAuE;IACvE,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAEhE,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,UAAU;QACV,OAAO,EAAE,GAAG,QAAQ,IAAI,aAAa,8BAA8B,UAAU,EAAE;QAC/E,MAAM,EAAE;YACN,uFAAuF;gBACrF,iFAAiF,aAAa,GAAG;YACnG,yBAAyB,UAAU,GAAG,WAAW,EAAE;YACnD,uDAAuD,QAAQ,wBAAwB;SACxF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"understanding-mode-env.js","sourceRoot":"","sources":["../../../src/cli/doctor/understanding-mode-env.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,mEAAmE;AACnE,EAAE;AACF,iEAAiE;AACjE,0EAA0E;AAC1E,kEAAkE;AAClE,wEAAwE;AACxE,kEAAkE;AAClE,sEAAsE;AACtE,oEAAoE;AACpE,sEAAsE;AACtE,yEAAyE;AACzE,wEAAwE;AACxE,yEAAyE;AACzE,uDAAuD;AACvD,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,4DAA4D;AAC5D,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,qEAAqE;AACrE,yEAAyE;AACzE,+DAA+D;AAC/D,EAAE;AACF,+DAA+D;AAC/D,0EAA0E;AAC1E,kEAAkE;AAClE,2BAA2B;AAE3B,OAAO,EACL,MAAM,EACN,QAAQ,EACR,SAAS,IAAI,uBAAuB,EACpC,qBAAqB,GAEtB,MAAM,8DAA8D,CAAC;AAyBtE;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B,CAAC,QAAkB;IAC7D,OAAO,QAAQ,CAAC,YAAY,CAAC,IAAI,CAC/B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,uBAAuB,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,CACjE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAkB;IAC3D,OAAO,4BAA4B,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,mCAAmC,CACjD,QAAkB,EAClB,GAAsB;IAEtB,MAAM,IAAI,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC5D,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IAE7C,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,aAAa,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAEnD,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,uEAAuE;IACvE,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAEhE,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,UAAU;QACV,OAAO,EAAE,GAAG,QAAQ,IAAI,aAAa,8BAA8B,UAAU,EAAE;QAC/E,MAAM,EAAE;YACN,uFAAuF;gBACrF,iFAAiF,aAAa,GAAG;YACnG,yBAAyB,UAAU,GAAG,WAAW,EAAE;YACnD,uDAAuD,QAAQ,wBAAwB;SACxF;KACF,CAAC;AACJ,CAAC"}
package/dist/cli/index.js CHANGED
@@ -41,6 +41,8 @@ import { approveBranchProtection } from "./approve/branch-protection.js";
41
41
  import { approveRisk } from "./approve/risk.js";
42
42
  import { approveUnderstanding } from "./approve/understanding.js";
43
43
  import { readPipedStdin } from "./approve/stdin-report.js";
44
+ import { issueDelegation } from "./delegate/index.js";
45
+ import { InvalidDurationError, parseDurationSeconds } from "../policies/index.js";
44
46
  import { runRecordDogfood, runRecordReview, runRecordReviewSubagent, } from "./record/index.js";
45
47
  import { describe, isPillar } from "./describe.js";
46
48
  import { diff as diffRun } from "./diff/index.js";
@@ -175,6 +177,7 @@ export function buildProgram(opts = {}) {
175
177
  .option("--project <name>", "apply per-project overrides for this project name")
176
178
  .option("--shallow", "skip MCP probes (CLI --version probes still run); report manifest-reference state only")
177
179
  .option("--target <runtime>", `additionally evaluate the harness-side adapter health for a runtime (allowed: ${KNOWN_DOCTOR_TARGETS.join(", ")})`)
180
+ .option("--recent-sessions <n>", "understanding-gate auto-approval doctor window: how many of the newest .approvals/ session markers to scan (default: 20)")
178
181
  .option("--json", "emit a structured JSON DoctorReport instead of prose (--rm-rogue-ledgers is ignored with --json)")
179
182
  .option("--rm-rogue-ledgers", "after reporting, delete each rogue evidence-ledger directory found (prompts per hit; combine with --yes to skip prompts)")
180
183
  .option("--yes", "with --rm-rogue-ledgers, skip per-hit confirmation prompts")
@@ -187,12 +190,22 @@ export function buildProgram(opts = {}) {
187
190
  }
188
191
  target = options.target;
189
192
  }
193
+ let recentSessions;
194
+ if (options.recentSessions !== undefined) {
195
+ const parsed = Number(options.recentSessions);
196
+ if (!Number.isInteger(parsed) || parsed < 1) {
197
+ stderr(`--recent-sessions must be an integer >= 1, got ${JSON.stringify(options.recentSessions)}\n`);
198
+ throw new HarnessExitError("", EX_USAGE);
199
+ }
200
+ recentSessions = parsed;
201
+ }
190
202
  const report = await doctor({
191
203
  configPath: options.config,
192
204
  project: options.project,
193
205
  shallow: options.shallow,
194
206
  versionProbe: defaultVersionProbe,
195
207
  ...(target !== undefined ? { target } : {}),
208
+ ...(recentSessions !== undefined ? { recentSessions } : {}),
196
209
  ...(opts.rogueLedgerScanOptions !== undefined
197
210
  ? { rogueLedgerScanOptions: opts.rogueLedgerScanOptions }
198
211
  : {}),
@@ -1505,6 +1518,86 @@ export function buildProgram(opts = {}) {
1505
1518
  }
1506
1519
  stdout(`${lines.join("\n")}\n`);
1507
1520
  });
1521
+ // `harness delegate` (Slice 3 of docs/decisions/2026-08-27-ug-auto-mode-approval.md,
1522
+ // agent-tasks 37ad0b05): issue a signed pre-authorization for a
1523
+ // headless `claude -p` child session, bound to an already-approved
1524
+ // PARENT session. NOT an approval: the child still writes and gets its
1525
+ // own Understanding Report checked by its own PreToolUse hook before
1526
+ // its auto-marker is minted. Writes harness.generated/.delegations/,
1527
+ // never .approvals/.
1528
+ program
1529
+ .command("delegate")
1530
+ .description("Issue a signed delegation for a headless `claude -p` child session, bound to " +
1531
+ "an already-approved parent session (pre-authorization, not an approval: the " +
1532
+ "child still writes and gets its own Understanding Report checked). Writes " +
1533
+ "harness.generated/.delegations/<child-sid>, never .approvals/. See " +
1534
+ "docs/decisions/2026-08-27-ug-auto-mode-approval.md.")
1535
+ .requiredOption("--child-session <uuid>", "session id of the claude -p child this delegation authorizes (UUID)")
1536
+ .option("--cwd <path>", "bind the delegation to this working directory; at least one of --cwd/--task is required")
1537
+ .option("--task <id>", "bind the delegation to this agent-tasks task id; at least one of --cwd/--task is required")
1538
+ .option("--ttl <duration>", "delegation lifetime (e.g. 30m, 1h); default: the pack's approval_lifecycle.max_age when set, else 1h")
1539
+ .option("--report <path>", "fallback shape: bind the child's Understanding Report by content + path hash at spawn time; not yet consumed by the child hook, see the pack doc")
1540
+ .option("--session-id <id>", "explicit parent session id (default: $CLAUDE_CODE_SESSION_ID, then $CLAUDE_SESSION_ID, then $CODEX_SESSION_ID, then staged .pending-approval)")
1541
+ .option("--config <path>", "manifest path (default: ~/.harness/harness.yaml; legacy fallback ~/.claude/harness.yaml)")
1542
+ .option("--project <name>", "apply per-project overrides")
1543
+ .action(async (options) => {
1544
+ // Usage-level check ahead of every deeper resolution, with the
1545
+ // exact message the ADR slice 3 acceptance criterion pins.
1546
+ // `issueDelegation` carries the identical check (reason
1547
+ // "no-binding") for callers that invoke it directly (the smoke
1548
+ // runner, tests) without going through commander's flag parsing.
1549
+ if (options.cwd === undefined && options.task === undefined) {
1550
+ throw new HarnessExitError("a delegation must bind a cwd or a task", EX_FAIL);
1551
+ }
1552
+ let ttlSeconds;
1553
+ if (options.ttl) {
1554
+ try {
1555
+ ttlSeconds = parseDurationSeconds(options.ttl);
1556
+ }
1557
+ catch (err) {
1558
+ if (err instanceof InvalidDurationError) {
1559
+ throw new HarnessExitError(`--ttl: ${err.message}`, EX_USAGE);
1560
+ }
1561
+ throw err;
1562
+ }
1563
+ }
1564
+ const cliOpts = {
1565
+ childSessionId: options.childSession,
1566
+ };
1567
+ // `!== undefined` (not truthy), agreeing with the usage check
1568
+ // above: a truthy check would silently drop an explicit `--cwd
1569
+ // ''` / `--task ''` instead of letting `issueDelegation` refuse
1570
+ // it with `invalid-cwd` / `invalid-task` (L3 fix, agent-tasks
1571
+ // 37ad0b05).
1572
+ if (options.cwd !== undefined)
1573
+ cliOpts.cwd = options.cwd;
1574
+ if (options.task !== undefined)
1575
+ cliOpts.taskId = options.task;
1576
+ if (ttlSeconds !== undefined)
1577
+ cliOpts.ttlSeconds = ttlSeconds;
1578
+ if (options.report)
1579
+ cliOpts.reportPath = options.report;
1580
+ if (options.sessionId)
1581
+ cliOpts.parentSessionId = options.sessionId;
1582
+ if (options.config)
1583
+ cliOpts.configPath = options.config;
1584
+ if (options.project)
1585
+ cliOpts.project = options.project;
1586
+ const result = await issueDelegation(cliOpts);
1587
+ if (!result.ok) {
1588
+ throw new HarnessExitError(`delegate refused (${result.reason}): ${result.detail}`, EX_FAIL);
1589
+ }
1590
+ const lines = [
1591
+ `delegation: ✓ ${result.filePath} (child ${result.childSessionId}, parent ${result.parentSessionId}, expires ${result.expiresAt})`,
1592
+ ];
1593
+ if (result.ledgerFact.written) {
1594
+ lines.push(`ledger: ✓ wrote understanding-delegated:${result.childSessionId}:${result.parentSessionId} (audit only)`);
1595
+ }
1596
+ else {
1597
+ lines.push(`ledger: ⚠ skipped (${result.ledgerFact.reason ?? "unknown"}) (audit only)`);
1598
+ }
1599
+ stdout(`${lines.join("\n")}\n`);
1600
+ });
1508
1601
  const VALID_DECISION_FILTERS = [
1509
1602
  "allow",
1510
1603
  "warn",
@@ -1630,7 +1723,7 @@ export function buildProgram(opts = {}) {
1630
1723
  });
1631
1724
  program
1632
1725
  .command("audit")
1633
- .description("Replay policy decisions from the evidence ledger for a time window")
1726
+ .description("Replay policy decisions from the evidence ledger for a time window, plus an approvals section listing raw understanding-gate approval facts")
1634
1727
  .option("--since <duration>", "time window (default: 24h)")
1635
1728
  .option("--policy <name>", "filter to a single policy by name")
1636
1729
  .option("--outcome <outcome>", "filter by decision outcome (allow / warn / require_approval / deny / warn-degraded / deny-degraded)")
@@ -1727,6 +1820,8 @@ export function buildProgram(opts = {}) {
1727
1820
  .option("--expect-no-hook <names>", "comma-separated list of hook names / events that MUST NOT fire (repeatable)", (value, prev = []) => prev.concat(splitCommaList(value)), [])
1728
1821
  .option("--expect-exit <n>", "expected result.is_error: 0 ⇒ false, !=0 ⇒ true")
1729
1822
  .option("--expect-decision <kind>", "policy decision must be one of allow|deny|warn")
1823
+ .option("--no-delegate", "do not pre-authorize the spawned child (docs/decisions/2026-08-27-ug-auto-mode-approval.md); " +
1824
+ "it then has no delegation binding it to this approved session and falls back to the plain opt-in path")
1730
1825
  .action(async (options) => {
1731
1826
  const expectations = {};
1732
1827
  if (options.expectHook && options.expectHook.length > 0) {
@@ -1750,6 +1845,10 @@ export function buildProgram(opts = {}) {
1750
1845
  outputDir: options.outputDir,
1751
1846
  expectations,
1752
1847
  };
1848
+ // commander's `--no-delegate` negates `options.delegate` (default
1849
+ // true); only an explicit `--no-delegate` flips it to `false`.
1850
+ if (options.delegate === false)
1851
+ smokeOpts.noDelegate = true;
1753
1852
  if (options.config)
1754
1853
  smokeOpts.configPath = options.config;
1755
1854
  if (options.project)