@lannguyensi/harness 0.51.0 → 0.53.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 (95) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/dist/cli/approve/understanding.d.ts +46 -0
  3. package/dist/cli/approve/understanding.js +50 -22
  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/bypass-without-auto-approve.d.ts +38 -0
  15. package/dist/cli/doctor/bypass-without-auto-approve.js +89 -0
  16. package/dist/cli/doctor/bypass-without-auto-approve.js.map +1 -0
  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 +39 -1
  21. package/dist/cli/doctor/format.js.map +1 -1
  22. package/dist/cli/doctor/index.js +68 -7
  23. package/dist/cli/doctor/index.js.map +1 -1
  24. package/dist/cli/doctor/settings-drift.d.ts +6 -0
  25. package/dist/cli/doctor/settings-drift.js +20 -6
  26. package/dist/cli/doctor/settings-drift.js.map +1 -1
  27. package/dist/cli/doctor/types.d.ts +54 -0
  28. package/dist/cli/doctor/types.js.map +1 -1
  29. package/dist/cli/doctor/ug-auto-approvals.js +18 -43
  30. package/dist/cli/doctor/ug-auto-approvals.js.map +1 -1
  31. package/dist/cli/doctor/ug-delegations.d.ts +26 -0
  32. package/dist/cli/doctor/ug-delegations.js +112 -0
  33. package/dist/cli/doctor/ug-delegations.js.map +1 -0
  34. package/dist/cli/doctor/understanding-mode-env.d.ts +2 -0
  35. package/dist/cli/doctor/understanding-mode-env.js +5 -2
  36. package/dist/cli/doctor/understanding-mode-env.js.map +1 -1
  37. package/dist/cli/gc/index.d.ts +13 -2
  38. package/dist/cli/gc/index.js +203 -1
  39. package/dist/cli/gc/index.js.map +1 -1
  40. package/dist/cli/index.js +133 -7
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/cli/init/composer.js +9 -0
  43. package/dist/cli/init/composer.js.map +1 -1
  44. package/dist/cli/init/profiles.d.ts +2 -2
  45. package/dist/cli/init/profiles.js +11 -0
  46. package/dist/cli/init/profiles.js.map +1 -1
  47. package/dist/cli/init/templates.d.ts +1 -1
  48. package/dist/cli/init/templates.js +11 -0
  49. package/dist/cli/init/templates.js.map +1 -1
  50. package/dist/cli/pack/approve-escape.js +16 -7
  51. package/dist/cli/pack/approve-escape.js.map +1 -1
  52. package/dist/cli/pack/auto-approve-path.d.ts +17 -0
  53. package/dist/cli/pack/auto-approve-path.js +56 -3
  54. package/dist/cli/pack/auto-approve-path.js.map +1 -1
  55. package/dist/cli/pack/hook-pre-tool-use.d.ts +25 -0
  56. package/dist/cli/pack/hook-pre-tool-use.js +335 -3
  57. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  58. package/dist/cli/pack/index.d.ts +1 -0
  59. package/dist/cli/pack/index.js +1 -0
  60. package/dist/cli/pack/index.js.map +1 -1
  61. package/dist/cli/pack/transcript-report-scan.d.ts +71 -0
  62. package/dist/cli/pack/transcript-report-scan.js +359 -0
  63. package/dist/cli/pack/transcript-report-scan.js.map +1 -0
  64. package/dist/cli/pack/upgrade.d.ts +32 -0
  65. package/dist/cli/pack/upgrade.js +273 -0
  66. package/dist/cli/pack/upgrade.js.map +1 -0
  67. package/dist/cli/smoke/index.d.ts +18 -1
  68. package/dist/cli/smoke/index.js +54 -0
  69. package/dist/cli/smoke/index.js.map +1 -1
  70. package/dist/io/read-json-dir-entries.d.ts +36 -0
  71. package/dist/io/read-json-dir-entries.js +38 -0
  72. package/dist/io/read-json-dir-entries.js.map +1 -0
  73. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.d.ts +35 -0
  74. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js +86 -0
  75. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js.map +1 -0
  76. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +76 -2
  77. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +139 -2
  78. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +1 -1
  79. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +237 -0
  80. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +647 -0
  81. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -0
  82. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +4 -1
  83. package/dist/policy-packs/builtin/understanding-before-execution/index.js +26 -1
  84. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
  85. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.d.ts +71 -0
  86. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +169 -0
  87. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +1 -0
  88. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +19 -0
  89. package/dist/policy-packs/builtin/understanding-before-execution.js +29 -0
  90. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  91. package/dist/runtime/approval-signing.js +28 -0
  92. package/dist/runtime/approval-signing.js.map +1 -1
  93. package/dist/runtime/git-context.js +7 -1
  94. package/dist/runtime/git-context.js.map +1 -1
  95. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -27,7 +27,7 @@ import { add } from "./add/index.js";
27
27
  import { adopt } from "./adopt/index.js";
28
28
  import { apply, CODEX_CONFIG_BASENAME, DRIFT_HINT_MESSAGE, formatNextSteps, OPENCODE_CONFIG_BASENAME, SETTINGS_BASENAME, } from "./apply/index.js";
29
29
  import { isRemoveType, KNOWN_REMOVE_TYPES, remove } from "./remove/index.js";
30
- import { packAdd, packList, packRemove, packReseed } from "./pack/index.js";
30
+ import { packAdd, packList, packRemove, packReseed, packUpgrade } from "./pack/index.js";
31
31
  import { runPackHookPreToolUseCli } from "./pack/hook-pre-tool-use.js";
32
32
  import { runPackHookPostToolUseCli } from "./pack/hook-post-tool-use.js";
33
33
  import { runPackHookTrackActiveClaimCli } from "./pack/hook-track-active-claim.js";
@@ -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";
@@ -860,6 +862,35 @@ export function buildProgram(opts = {}) {
860
862
  }
861
863
  stdout(`reseeded ${result.fieldsChanged.map((f) => `config.${f}`).join(", ")} for policy_packs entry ${JSON.stringify(result.name)} in ${result.path}\n`);
862
864
  });
865
+ // `harness pack upgrade <name>` (task 8f637efd, D-004): text-level
866
+ // insertion of a pack's missing default config block into an existing
867
+ // manifest, idempotent, never rewrites content outside the inserted
868
+ // block. Today's only wired upgrade is `understanding-before-
869
+ // execution`'s `auto_approve` default; see upgrade.ts's module header
870
+ // for why this is text-level rather than a Document-API mutation like
871
+ // `add` / `reseed`.
872
+ packCmd
873
+ .command("upgrade <name>")
874
+ .description("Insert a pack's missing default config block into an existing manifest (today: " +
875
+ "understanding-before-execution's auto_approve default). Idempotent; " +
876
+ "refuses on ambiguity rather than guessing where to insert.")
877
+ .option("--config <path>", "manifest path (default: ~/.harness/harness.yaml; legacy fallback ~/.claude/harness.yaml)")
878
+ .option("--dry-run", "print the unified diff and exit without writing")
879
+ .action(async (name, options) => {
880
+ const result = await packUpgrade(name, {
881
+ configPath: options.config,
882
+ dryRun: options.dryRun,
883
+ });
884
+ if (result.alreadyPresent) {
885
+ stdout(`policy_packs entry ${JSON.stringify(result.name)} already carries this upgrade; nothing to insert (${result.path} unchanged).\n`);
886
+ return;
887
+ }
888
+ if (options.dryRun) {
889
+ stdout(result.diff);
890
+ return;
891
+ }
892
+ stdout(`upgraded policy_packs entry ${JSON.stringify(result.name)} in ${result.path}\n`);
893
+ });
863
894
  // `harness pack hook` runtime sub-tree (Phase 6 #4): wired by the
864
895
  // pack's PreToolUse hook contribution; reads PreToolUse JSON from
865
896
  // stdin, consults the signed approval marker (the persisted report and
@@ -1516,6 +1547,86 @@ export function buildProgram(opts = {}) {
1516
1547
  }
1517
1548
  stdout(`${lines.join("\n")}\n`);
1518
1549
  });
1550
+ // `harness delegate` (Slice 3 of docs/decisions/2026-08-27-ug-auto-mode-approval.md,
1551
+ // agent-tasks 37ad0b05): issue a signed pre-authorization for a
1552
+ // headless `claude -p` child session, bound to an already-approved
1553
+ // PARENT session. NOT an approval: the child still writes and gets its
1554
+ // own Understanding Report checked by its own PreToolUse hook before
1555
+ // its auto-marker is minted. Writes harness.generated/.delegations/,
1556
+ // never .approvals/.
1557
+ program
1558
+ .command("delegate")
1559
+ .description("Issue a signed delegation for a headless `claude -p` child session, bound to " +
1560
+ "an already-approved parent session (pre-authorization, not an approval: the " +
1561
+ "child still writes and gets its own Understanding Report checked). Writes " +
1562
+ "harness.generated/.delegations/<child-sid>, never .approvals/. See " +
1563
+ "docs/decisions/2026-08-27-ug-auto-mode-approval.md.")
1564
+ .requiredOption("--child-session <uuid>", "session id of the claude -p child this delegation authorizes (UUID)")
1565
+ .option("--cwd <path>", "bind the delegation to this working directory; at least one of --cwd/--task is required")
1566
+ .option("--task <id>", "bind the delegation to this agent-tasks task id; at least one of --cwd/--task is required")
1567
+ .option("--ttl <duration>", "delegation lifetime (e.g. 30m, 1h); default: the pack's approval_lifecycle.max_age when set, else 1h")
1568
+ .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")
1569
+ .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)")
1570
+ .option("--config <path>", "manifest path (default: ~/.harness/harness.yaml; legacy fallback ~/.claude/harness.yaml)")
1571
+ .option("--project <name>", "apply per-project overrides")
1572
+ .action(async (options) => {
1573
+ // Usage-level check ahead of every deeper resolution, with the
1574
+ // exact message the ADR slice 3 acceptance criterion pins.
1575
+ // `issueDelegation` carries the identical check (reason
1576
+ // "no-binding") for callers that invoke it directly (the smoke
1577
+ // runner, tests) without going through commander's flag parsing.
1578
+ if (options.cwd === undefined && options.task === undefined) {
1579
+ throw new HarnessExitError("a delegation must bind a cwd or a task", EX_FAIL);
1580
+ }
1581
+ let ttlSeconds;
1582
+ if (options.ttl) {
1583
+ try {
1584
+ ttlSeconds = parseDurationSeconds(options.ttl);
1585
+ }
1586
+ catch (err) {
1587
+ if (err instanceof InvalidDurationError) {
1588
+ throw new HarnessExitError(`--ttl: ${err.message}`, EX_USAGE);
1589
+ }
1590
+ throw err;
1591
+ }
1592
+ }
1593
+ const cliOpts = {
1594
+ childSessionId: options.childSession,
1595
+ };
1596
+ // `!== undefined` (not truthy), agreeing with the usage check
1597
+ // above: a truthy check would silently drop an explicit `--cwd
1598
+ // ''` / `--task ''` instead of letting `issueDelegation` refuse
1599
+ // it with `invalid-cwd` / `invalid-task` (L3 fix, agent-tasks
1600
+ // 37ad0b05).
1601
+ if (options.cwd !== undefined)
1602
+ cliOpts.cwd = options.cwd;
1603
+ if (options.task !== undefined)
1604
+ cliOpts.taskId = options.task;
1605
+ if (ttlSeconds !== undefined)
1606
+ cliOpts.ttlSeconds = ttlSeconds;
1607
+ if (options.report)
1608
+ cliOpts.reportPath = options.report;
1609
+ if (options.sessionId)
1610
+ cliOpts.parentSessionId = options.sessionId;
1611
+ if (options.config)
1612
+ cliOpts.configPath = options.config;
1613
+ if (options.project)
1614
+ cliOpts.project = options.project;
1615
+ const result = await issueDelegation(cliOpts);
1616
+ if (!result.ok) {
1617
+ throw new HarnessExitError(`delegate refused (${result.reason}): ${result.detail}`, EX_FAIL);
1618
+ }
1619
+ const lines = [
1620
+ `delegation: ✓ ${result.filePath} (child ${result.childSessionId}, parent ${result.parentSessionId}, expires ${result.expiresAt})`,
1621
+ ];
1622
+ if (result.ledgerFact.written) {
1623
+ lines.push(`ledger: ✓ wrote understanding-delegated:${result.childSessionId}:${result.parentSessionId} (audit only)`);
1624
+ }
1625
+ else {
1626
+ lines.push(`ledger: ⚠ skipped (${result.ledgerFact.reason ?? "unknown"}) (audit only)`);
1627
+ }
1628
+ stdout(`${lines.join("\n")}\n`);
1629
+ });
1519
1630
  const VALID_DECISION_FILTERS = [
1520
1631
  "allow",
1521
1632
  "warn",
@@ -1641,7 +1752,7 @@ export function buildProgram(opts = {}) {
1641
1752
  });
1642
1753
  program
1643
1754
  .command("audit")
1644
- .description("Replay policy decisions from the evidence ledger for a time window")
1755
+ .description("Replay policy decisions from the evidence ledger for a time window, plus an approvals section listing raw understanding-gate approval facts")
1645
1756
  .option("--since <duration>", "time window (default: 24h)")
1646
1757
  .option("--policy <name>", "filter to a single policy by name")
1647
1758
  .option("--outcome <outcome>", "filter by decision outcome (allow / warn / require_approval / deny / warn-degraded / deny-degraded)")
@@ -1738,6 +1849,8 @@ export function buildProgram(opts = {}) {
1738
1849
  .option("--expect-no-hook <names>", "comma-separated list of hook names / events that MUST NOT fire (repeatable)", (value, prev = []) => prev.concat(splitCommaList(value)), [])
1739
1850
  .option("--expect-exit <n>", "expected result.is_error: 0 ⇒ false, !=0 ⇒ true")
1740
1851
  .option("--expect-decision <kind>", "policy decision must be one of allow|deny|warn")
1852
+ .option("--no-delegate", "do not pre-authorize the spawned child (docs/decisions/2026-08-27-ug-auto-mode-approval.md); " +
1853
+ "it then has no delegation binding it to this approved session and falls back to the plain opt-in path")
1741
1854
  .action(async (options) => {
1742
1855
  const expectations = {};
1743
1856
  if (options.expectHook && options.expectHook.length > 0) {
@@ -1761,6 +1874,10 @@ export function buildProgram(opts = {}) {
1761
1874
  outputDir: options.outputDir,
1762
1875
  expectations,
1763
1876
  };
1877
+ // commander's `--no-delegate` negates `options.delegate` (default
1878
+ // true); only an explicit `--no-delegate` flips it to `false`.
1879
+ if (options.delegate === false)
1880
+ smokeOpts.noDelegate = true;
1764
1881
  if (options.config)
1765
1882
  smokeOpts.configPath = options.config;
1766
1883
  if (options.project)
@@ -2168,11 +2285,13 @@ export function buildProgram(opts = {}) {
2168
2285
  program
2169
2286
  .command("gc")
2170
2287
  .description("Retention-based cleanup of harness-owned gate state: terminal " +
2171
- "(approved/expired) understanding-gate reports, parse-error logs, and " +
2172
- "approval markers older than the retention window. Pending reports and " +
2173
- "anything outside the enumerated harness-owned dirs are never touched " +
2174
- "(the evidence ledger and solution-acceptance verdict dirs are owned by " +
2175
- "their producers). Dry-run by default; pass --apply to delete.")
2288
+ "(approved/expired) understanding-gate reports, parse-error logs, " +
2289
+ "approval markers, expired delegation markers, orphaned delegation " +
2290
+ "adoption ledgers, and stale permission-mode observations older than " +
2291
+ "the retention window. Pending reports and anything outside the " +
2292
+ "enumerated harness-owned dirs are never touched (the evidence " +
2293
+ "ledger and solution-acceptance verdict dirs are owned by their " +
2294
+ "producers). Dry-run by default; pass --apply to delete.")
2176
2295
  .option("--config <path>", "manifest path (default: ~/.harness/harness.yaml; legacy fallback ~/.claude/harness.yaml)")
2177
2296
  .option("--retention-days <n>", `delete artifacts older than this many days (default: ${DEFAULT_RETENTION_DAYS})`)
2178
2297
  .option("--apply", "delete the listed artifacts (default: dry-run listing only)")
@@ -2195,10 +2314,17 @@ export function buildProgram(opts = {}) {
2195
2314
  result.reportsDir,
2196
2315
  ...(result.parseErrorsDir !== null ? [result.parseErrorsDir] : []),
2197
2316
  result.approvalsDir,
2317
+ result.delegationsDir,
2318
+ result.adoptionLedgerDir,
2319
+ result.permissionModeObservationsDir,
2198
2320
  ];
2199
2321
  if (result.parseErrorsDir === null) {
2200
2322
  stderr("gc: skipping the parse-errors sweep (reports dir does not have the conventional .understanding-gate/reports shape)\n");
2201
2323
  }
2324
+ if (result.unparseable.length > 0) {
2325
+ stderr(`gc: ${result.unparseable.length} delegation file(s) could not be parsed and were left in place:\n` +
2326
+ result.unparseable.map((u) => ` [${u.category}] ${u.filePath} (${u.reason})\n`).join(""));
2327
+ }
2202
2328
  if (result.candidates.length === 0) {
2203
2329
  stdout(`gc: nothing older than ${result.retentionDays}d (cutoff ${result.cutoffIso}) under\n` +
2204
2330
  sweptDirs.map((d) => ` ${d}\n`).join("") +