harnery 0.5.0 → 0.6.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 (68) hide show
  1. package/dist/commander.d.ts +10 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +2 -0
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +43 -29
  6. package/dist/commands/browse-ai.js +1 -1
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +41 -9
  9. package/dist/commands/cookies.js +1 -1
  10. package/dist/commands/decision.d.ts +4 -0
  11. package/dist/commands/decision.d.ts.map +1 -0
  12. package/dist/commands/decision.js +354 -0
  13. package/dist/commands/docs.d.ts.map +1 -1
  14. package/dist/commands/docs.js +5 -1
  15. package/dist/commands/fetch.js +1 -1
  16. package/dist/core/agents/events/consume.d.ts +25 -2
  17. package/dist/core/agents/events/consume.d.ts.map +1 -1
  18. package/dist/core/agents/events/consume.js +55 -7
  19. package/dist/core/agents/events/emit.d.ts +2 -1
  20. package/dist/core/agents/events/emit.d.ts.map +1 -1
  21. package/dist/core/agents/events/emit.js +2 -1
  22. package/dist/core/agents/state/scratch.d.ts +1 -1
  23. package/dist/core/agents/state/scratch.js +2 -2
  24. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  25. package/dist/core/hooks/effects/index.js +2 -1
  26. package/dist/lib/agent-browser/client.js +1 -1
  27. package/dist/lib/browser/client.d.ts +14 -0
  28. package/dist/lib/browser/client.d.ts.map +1 -1
  29. package/dist/lib/browser/client.js +20 -0
  30. package/dist/lib/browser/index.d.ts +1 -0
  31. package/dist/lib/browser/index.d.ts.map +1 -1
  32. package/dist/lib/browser/runts.d.ts +44 -0
  33. package/dist/lib/browser/runts.d.ts.map +1 -0
  34. package/dist/lib/browser/runts.js +193 -0
  35. package/dist/lib/completion/walk.js +1 -1
  36. package/dist/lib/cookies/client.d.ts +1 -1
  37. package/dist/lib/cookies/client.d.ts.map +1 -1
  38. package/dist/lib/cookies/client.js +1 -1
  39. package/dist/lib/decision/index.d.ts +212 -0
  40. package/dist/lib/decision/index.d.ts.map +1 -0
  41. package/dist/lib/decision/index.js +523 -0
  42. package/dist/lib/docs-lint.d.ts +1 -0
  43. package/dist/lib/docs-lint.d.ts.map +1 -1
  44. package/dist/lib/docs-lint.js +49 -0
  45. package/dist/lib/tunnel/gate.js +1 -1
  46. package/package.json +3 -1
  47. package/src/commander.ts +12 -0
  48. package/src/commands/agents.ts +47 -26
  49. package/src/commands/browse-ai.ts +1 -1
  50. package/src/commands/browse.ts +63 -8
  51. package/src/commands/cookies.ts +1 -1
  52. package/src/commands/decision.ts +438 -0
  53. package/src/commands/docs.ts +5 -1
  54. package/src/commands/fetch.ts +1 -1
  55. package/src/core/agents/events/consume.ts +65 -7
  56. package/src/core/agents/events/emit.ts +2 -1
  57. package/src/core/agents/state/scratch.ts +2 -2
  58. package/src/core/config.ts +1 -1
  59. package/src/core/hooks/effects/index.ts +2 -1
  60. package/src/lib/agent-browser/client.ts +1 -1
  61. package/src/lib/browser/client.ts +28 -0
  62. package/src/lib/browser/index.ts +4 -0
  63. package/src/lib/browser/runts.ts +218 -0
  64. package/src/lib/completion/walk.ts +1 -1
  65. package/src/lib/cookies/client.ts +2 -2
  66. package/src/lib/decision/index.ts +685 -0
  67. package/src/lib/docs-lint.ts +44 -0
  68. package/src/lib/tunnel/gate.ts +1 -1
@@ -91,6 +91,16 @@ export interface HarneryProgramContext {
91
91
  * exclusions (`.claude/`, `.harnery/`, `.codex/`, `.cursor/`).
92
92
  */
93
93
  extraDocsExcludedPrefixes?: readonly string[];
94
+ /**
95
+ * Filenames permitted at the host project's `docs/` root (parent repo only).
96
+ * When set, `harn docs lint` flags any other `.md`/`.json` file sitting
97
+ * loose at `docs/` root (rule `docs-root-file`) — topic docs belong in
98
+ * `docs/<topic>/` subdirs. Names are matched exactly (basename). When
99
+ * omitted or empty, the rule is a no-op, so standalone `harn` and consumers
100
+ * that don't opt in are unaffected. Submodule `docs/` roots are never
101
+ * checked (their entry tiers differ from the parent's).
102
+ */
103
+ docsRootAllowlist?: readonly string[];
94
104
  /**
95
105
  * Default Host header for `tunnel up` when `--vhost` is omitted: a literal
96
106
  * host, or a resolver evaluated at start time (e.g. read a dev stack's
@@ -1 +1 @@
1
- {"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAEhC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,MAAM,GAAG,SAAS,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACxC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,KAAK,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACpE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,WAAW,EAAE,WAkCzB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CA2C3E"}
1
+ {"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAEhC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,MAAM,GAAG,SAAS,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACxC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,KAAK,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACpE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,WAAW,EAAE,WAkCzB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CA4C3E"}
package/dist/commander.js CHANGED
@@ -26,6 +26,7 @@ import { registerCompletionCommand } from "./commands/completion.js";
26
26
  import { registerConfigGetCommand } from "./commands/config-get.js";
27
27
  import { registerContextCommand } from "./commands/context.js";
28
28
  import { registerCookiesCommand } from "./commands/cookies.js";
29
+ import { registerDecisionCommand } from "./commands/decision.js";
29
30
  import { registerDeinitCommand } from "./commands/deinit.js";
30
31
  import { registerDocsCommand } from "./commands/docs.js";
31
32
  import { registerDoctorCommand } from "./commands/doctor.js";
@@ -110,6 +111,7 @@ export function createHarneryProgram(opts = {}) {
110
111
  registerCompletionCommand(program, emit, opts.context);
111
112
  registerContextCommand(program, emit, opts.context);
112
113
  registerScratchCommand(program, emit);
114
+ registerDecisionCommand(program, emit);
113
115
  registerTunnelCommand(program, emit, opts.context);
114
116
  registerDocsCommand(program, emit, opts.context);
115
117
  registerAgentsCommand(program, emit);
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAgFnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CA6OpF"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAuFnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CA6OpF"}
@@ -16,8 +16,13 @@ import { spawnSync } from "node:child_process";
16
16
  import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
17
17
  import { homedir, tmpdir } from "node:os";
18
18
  import { basename, join, resolve } from "node:path";
19
+ import { readStreamTailBounded } from "../core/agents/events/consume.js";
19
20
  import { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, resolveOwnerWithSource, } from "../core/agents/index.js";
20
21
  import { resolveBinName } from "../core/config.js";
22
+ /** Cap for CLI scans of the unbounded event ledger (`trace` / `health`). Well
23
+ * under V8's ~512MB max string length so a `readFileSync` of the whole file can
24
+ * never throw; covers ample recent history for a diagnostic scan. */
25
+ const STREAM_SCAN_CAP_BYTES = 128 * 1024 * 1024; // 128 MiB
21
26
  import { parsePsChainLine } from "../core/hooks/resolve/anchor.js";
22
27
  import { buildCouncilId, buildInviteMarkdown, COUNCIL_SCHEMA_VERSION, contributorsInRound, councilBodyDir, councilsArchiveDir, deleteArchivedCouncil, effectiveSteward, findManifestByPartialId, listKnownAgents, listManifests, moveFromArchive, moveToArchive, normalizeAgentName, pendingCouncilsForMember, readArchivedManifest, readManifest, readRoundPrompts, roundDir, setCouncilSteward, writeContribution, writeManifest, writePrompt, } from "../lib/council/index.js";
23
28
  import { displayName as displayAgentName, ensureIdentity, listIdentities, lookupById as lookupIdentityById, lookupByName as lookupIdentityByName, } from "../lib/identities/index.js";
@@ -1717,8 +1722,12 @@ function readStreamStats(root) {
1717
1722
  let backlog = 0;
1718
1723
  let seenCursor = cursor === null; // no cursor → everything is "backlog"
1719
1724
  try {
1720
- const raw = readFileSync(streamPath, "utf8");
1721
- for (const line of raw.split("\n")) {
1725
+ // Bounded tail read: the ledger grows without bound and a whole-file
1726
+ // readFileSync throws past V8's ~512MB string limit. lines/backlog are then
1727
+ // scoped to the scanned window; `bytes` (full size, via statSync above) still
1728
+ // reflects the true stream size for the health rollup.
1729
+ const { text } = readStreamTailBounded(streamPath, STREAM_SCAN_CAP_BYTES);
1730
+ for (const line of text.split("\n")) {
1722
1731
  if (!line.trim())
1723
1732
  continue;
1724
1733
  lines++;
@@ -1835,27 +1844,32 @@ function runTrace(name, opts) {
1835
1844
  const sinceMs = opts.since ? Date.now() - (parseWindowSecs(opts.since) ?? 0) * 1000 : 0;
1836
1845
  const limit = Math.max(1, Number.parseInt(opts.limit, 10) || 200);
1837
1846
  const candidateIds = targetId.includes("\x00") ? targetId.split("\x00") : [targetId];
1838
- // Scan the full stream once, bucket events by instance_id for the candidates.
1847
+ // Scan the stream tail once, bucket events by instance_id for the candidates.
1848
+ // Bounded read: the ledger grows without bound and a whole-file readFileSync
1849
+ // throws past V8's ~512MB string limit. A trace of an agent whose events all
1850
+ // predate the window is reported as truncated rather than crashing.
1839
1851
  const streamPath = resolve(root, ".harnery", "events.ndjson");
1840
1852
  const byId = new Map();
1841
- if (existsSync(streamPath)) {
1842
- for (const line of readFileSync(streamPath, "utf8").split("\n")) {
1843
- if (!line)
1853
+ const { text: streamText, truncated: streamTruncated } = readStreamTailBounded(streamPath, STREAM_SCAN_CAP_BYTES);
1854
+ for (const line of streamText.split("\n")) {
1855
+ if (!line)
1856
+ continue;
1857
+ try {
1858
+ const ev = JSON.parse(line);
1859
+ if (!ev.instance_id || !candidateIds.includes(ev.instance_id))
1844
1860
  continue;
1845
- try {
1846
- const ev = JSON.parse(line);
1847
- if (!ev.instance_id || !candidateIds.includes(ev.instance_id))
1848
- continue;
1849
- if (sinceMs && Date.parse(ev.ts) < sinceMs)
1850
- continue;
1851
- const arr = byId.get(ev.instance_id) ?? [];
1852
- arr.push(ev);
1853
- byId.set(ev.instance_id, arr);
1854
- }
1855
- catch {
1856
- /* skip */
1857
- }
1861
+ if (sinceMs && Date.parse(ev.ts) < sinceMs)
1862
+ continue;
1863
+ const arr = byId.get(ev.instance_id) ?? [];
1864
+ arr.push(ev);
1865
+ byId.set(ev.instance_id, arr);
1858
1866
  }
1867
+ catch {
1868
+ /* skip */
1869
+ }
1870
+ }
1871
+ if (streamTruncated) {
1872
+ process.stderr.write(`note: event ledger exceeds ${Math.round(STREAM_SCAN_CAP_BYTES / 1024 / 1024)}MB; traced only the most recent window (older events omitted)\n`);
1859
1873
  }
1860
1874
  // If the name mapped to multiple instances, trace the one with the latest event.
1861
1875
  let resolvedId = candidateIds[0];
@@ -2208,7 +2222,7 @@ function renderHealthBox(report) {
2208
2222
  ];
2209
2223
  const localTime = formatLocalTime(new Date(report.generated_at));
2210
2224
  const title = `Coord Health (${localTime})`;
2211
- process.stdout.write(`${formatBox(title, rows)}\n`); // lint-ok-emission: chat-paste path; mirrors runStatus's direct write so the box surfaces in both TTY + bp-session-teed contexts
2225
+ process.stdout.write(`${formatBox(title, rows)}\n`); // lint-ok-emission: chat-paste path; mirrors runStatus's direct write so the box surfaces in both TTY + harn-session-teed contexts
2212
2226
  if (report.anomalies.length > 0) {
2213
2227
  process.stdout.write("\n"); // lint-ok-emission: same chat-paste path
2214
2228
  for (const a of report.anomalies) {
@@ -2245,7 +2259,7 @@ function runHarnessProbe(id, opts) {
2245
2259
  // and `resolveOwner` here, so the probe reports exactly what the live hot
2246
2260
  // path resolves.
2247
2261
  const anchorTokens = new Set(["claude", "claude-code", "cursor", "codex"]);
2248
- const override = process.env.BP_AGENT_COORD_TEST_ANCHOR_PID;
2262
+ const override = process.env.HARNERY_AGENT_COORD_TEST_ANCHOR_PID;
2249
2263
  let anchorPid = override && Number(override) > 0 ? override : "";
2250
2264
  const chainParts = [];
2251
2265
  let walkPid = process.pid;
@@ -2348,12 +2362,12 @@ function runHarnessProbe(id, opts) {
2348
2362
  * dispatcher in an isolated sandbox.
2349
2363
  *
2350
2364
  * Sandbox isolation strategy:
2351
- * - mkdtempSync(tmpdir(), "bp-harness-probe-") creates a non-git tmp dir.
2365
+ * - mkdtempSync(tmpdir(), "harn-harness-probe-") creates a non-git tmp dir.
2352
2366
  * - The dispatcher's coord-root resolution falls back to
2353
- * `BP_COORD_ROOT_OVERRIDE` when git rev-parse fails. We set it to the sandbox.
2367
+ * `HARNERY_COORD_ROOT_OVERRIDE` when git rev-parse fails. We set it to the sandbox.
2354
2368
  * - We rewrite the payload's `cwd` field (Cursor cds to it) to the sandbox,
2355
2369
  * so real `.harnery/` never gets touched.
2356
- * - We set `BP_AGENT_COORD_OFF=0` explicitly so any user-side off-switch in
2370
+ * - We set `HARNERY_AGENT_COORD_OFF=0` explicitly so any user-side off-switch in
2357
2371
  * the environment doesn't mask adapter crashes.
2358
2372
  *
2359
2373
  * Sample shape: probe-meta wrapped (`_probe_meta.event` + `.payload`) OR bare
@@ -2420,7 +2434,7 @@ function replayHarnessSamples(harness, root, relativeSampleDir, filter) {
2420
2434
  StopFailure: "stop-failure",
2421
2435
  };
2422
2436
  const harnessFlag = harness === "claude_code" ? "claude-code" : harness;
2423
- const sandbox = mkdtempSync(join(tmpdir(), "bp-harness-probe-"));
2437
+ const sandbox = mkdtempSync(join(tmpdir(), "harn-harness-probe-"));
2424
2438
  const results = [];
2425
2439
  try {
2426
2440
  for (const file of fixtures) {
@@ -2464,10 +2478,10 @@ function replayHarnessSamples(harness, root, relativeSampleDir, filter) {
2464
2478
  timeout: 10_000,
2465
2479
  env: {
2466
2480
  ...process.env,
2467
- BP_COORD_ROOT_OVERRIDE: sandbox,
2468
- BP_AGENT_COORD_HARNESS: harness,
2469
- BP_AGENT_COORD_PLATFORM: harness,
2470
- BP_AGENT_COORD_OFF: "0",
2481
+ HARNERY_COORD_ROOT_OVERRIDE: sandbox,
2482
+ HARNERY_AGENT_COORD_HARNESS: harness,
2483
+ HARNERY_AGENT_COORD_PLATFORM: harness,
2484
+ HARNERY_AGENT_COORD_OFF: "0",
2471
2485
  },
2472
2486
  });
2473
2487
  const exit = dispatch.status ?? -1;
@@ -62,7 +62,7 @@ export function registerBrowseAiCommand(program, emitParam) {
62
62
  function runBrowseAi(url, opts) {
63
63
  const jar = opts.cookies === false
64
64
  ? null
65
- : new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "bp-browse-ai" });
65
+ : new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse-ai" });
66
66
  const ab = new AgentBrowser({
67
67
  jar,
68
68
  timeoutMs: Number.parseInt(opts.timeout, 10),
@@ -1 +1 @@
1
- {"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA0G1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAwJN"}
1
+ {"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiH1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA0KN"}
@@ -92,6 +92,14 @@ export function registerBrowseCommand(program, emitParam, context) {
92
92
  "Catches the class of mobile-layout bug where a nav/table overflows the viewport edge.")
93
93
  .option("--check-overflow-fail", "Exit non-zero if --check-overflow detects horizontal overflow.")
94
94
  .option("--no-check-overflow-annotate", "Skip drawing overflow annotations on the screenshot (JSON still emitted).")
95
+ .option("--check-runts [selector]", "Scan text blocks for runts (a single word alone on a block's last visual line) " +
96
+ "by counting words on the last line via per-word Range rects — width thresholds " +
97
+ "miss narrow-column runts. Optional selector scopes the sweep (default: whole body). " +
98
+ "Atomic tokens (URLs, emails, phone numbers) are excluded. Reports hits in the JSON " +
99
+ "envelope under `runts` and annotates them on the screenshot.")
100
+ .option("--check-runts-min-chars <n>", "Minimum block text length to scan (default 40; smaller labels can't meaningfully wrap).", "40")
101
+ .option("--check-runts-fail", "Exit non-zero if any runt is detected.")
102
+ .option("--no-check-runts-annotate", "Skip drawing runt boxes on the screenshot (JSON still emitted).")
95
103
  .option("--baseline <name>", "Save the captured screenshot as a named baseline at " +
96
104
  "~/.cache/harnery/visual-baselines/<name>.png. Use --diff <name> later to " +
97
105
  "compare future captures against it (visual-regression check).")
@@ -117,7 +125,7 @@ async function runBrowse(url, opts, context) {
117
125
  // Commander: `--no-cookies` turns `opts.cookies` into `false`. Default is `true`.
118
126
  const jar = opts.cookies === false
119
127
  ? null
120
- : new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "bp-browse" });
128
+ : new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
121
129
  const headed = opts.login || opts.headed;
122
130
  const viewport = parseViewport(opts.viewport ?? "desktop");
123
131
  const browser = new Browser({
@@ -165,7 +173,7 @@ async function runBrowse(url, opts, context) {
165
173
  visibility = await browser.checkVisibility(opts.checkVisible, {
166
174
  sampleGrid: Number.parseInt(opts.checkVisibleSampleGrid ?? "3", 10),
167
175
  });
168
- if (opts.checkVisibleNoAnnotate !== true) {
176
+ if (opts.checkVisibleAnnotate !== false) {
169
177
  await browser.annotateVisibility(visibility);
170
178
  }
171
179
  }
@@ -177,9 +185,16 @@ async function runBrowse(url, opts, context) {
177
185
  if (opts.checkOverflow) {
178
186
  overflow = await browser.checkOverflow();
179
187
  }
188
+ let runts;
189
+ if (opts.checkRunts) {
190
+ runts = await browser.checkRunts({
191
+ scope: typeof opts.checkRunts === "string" ? opts.checkRunts : null,
192
+ minChars: Number.parseInt(opts.checkRuntsMinChars ?? "40", 10),
193
+ });
194
+ }
180
195
  const widthThreshold = Number.parseFloat(opts.checkWidthThreshold ?? "0.9");
181
- const annotateWidth = widths && opts.checkWidthNoAnnotate !== true;
182
- const annotateOverflow = overflow && opts.checkOverflowNoAnnotate !== true;
196
+ const annotateWidth = widths && opts.checkWidthAnnotate !== false;
197
+ const annotateOverflow = overflow && opts.checkOverflowAnnotate !== false;
183
198
  if (annotateWidth || annotateOverflow) {
184
199
  await browser.annotateLayout({
185
200
  widths: annotateWidth ? widths : [],
@@ -187,6 +202,10 @@ async function runBrowse(url, opts, context) {
187
202
  widthThreshold,
188
203
  });
189
204
  }
205
+ const annotateRunts = runts && runts.runts.length > 0 && opts.checkRuntsAnnotate !== false;
206
+ if (annotateRunts) {
207
+ await browser.annotateRunts(runts);
208
+ }
190
209
  if (opts.login) {
191
210
  await new Promise((res) => {
192
211
  emit.log("[--login] Headed Chromium is open. Walk through your auth flow now. Press Enter here to close + persist cookies into the profile.", "info");
@@ -203,17 +222,20 @@ async function runBrowse(url, opts, context) {
203
222
  if (opts.baseline || opts.diff) {
204
223
  throw new Error("--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.");
205
224
  }
206
- await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult);
225
+ await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult);
207
226
  }
208
227
  else {
209
- await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult);
228
+ await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult);
210
229
  }
211
- if (visibility && opts.checkVisibleNoAnnotate !== true) {
230
+ if (visibility && opts.checkVisibleAnnotate !== false) {
212
231
  await browser.clearVisibilityAnnotations();
213
232
  }
214
233
  if (annotateWidth || annotateOverflow) {
215
234
  await browser.clearLayoutAnnotations();
216
235
  }
236
+ if (annotateRunts) {
237
+ await browser.clearRuntsAnnotations();
238
+ }
217
239
  if (opts.checkVisibleFail && visibility) {
218
240
  const threshold = Number.parseFloat(opts.checkVisibleThreshold ?? "0.9");
219
241
  const failed = visibility.filter((r) => !r.found || !r.cssVisible || r.visibleRatio < threshold);
@@ -249,6 +271,12 @@ async function runBrowse(url, opts, context) {
249
271
  process.exitCode = 2;
250
272
  }
251
273
  }
274
+ if (opts.checkRuntsFail && runts && runts.runts.length > 0) {
275
+ for (const hit of runts.runts) {
276
+ emit.log(`check-runts FAIL ${hit.block}: last line is a lone "${hit.word}" ("…${hit.snippet.slice(-40)}")`, "warn");
277
+ }
278
+ process.exitCode = 2;
279
+ }
252
280
  if (opts.checkOverflowFail && overflow?.hasHorizontalOverflow) {
253
281
  const culprit = overflow.widerThanViewport[0] ?? overflow.rightOverflow[0] ?? null;
254
282
  const detail = culprit
@@ -356,7 +384,7 @@ function splitBatchSteps(input) {
356
384
  // ---------------------------------------------------------------------------
357
385
  // Print mode (--snapshot / --html / --json): stdout, no file writes
358
386
  // ---------------------------------------------------------------------------
359
- async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult) {
387
+ async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult) {
360
388
  let body = null;
361
389
  if (opts.html) {
362
390
  body = await browser.htmlContent(opts.selector);
@@ -385,6 +413,8 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
385
413
  result.width = widths;
386
414
  if (overflow)
387
415
  result.overflow = overflow;
416
+ if (runts)
417
+ result.runts = runts;
388
418
  if (devOverlay)
389
419
  result.devOverlay = devOverlay;
390
420
  if (batchResult && batchResult.clipboardReads.length > 0) {
@@ -408,7 +438,7 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
408
438
  // ---------------------------------------------------------------------------
409
439
  // Trio mode (default): write <prefix>.{png,html,json}
410
440
  // ---------------------------------------------------------------------------
411
- async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult) {
441
+ async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult) {
412
442
  const prefix = resolveOutPrefix(opts.out);
413
443
  mkdirSync(dirname(prefix), { recursive: true });
414
444
  const written = [];
@@ -449,6 +479,8 @@ async function runTrioMode(browser, navResult, opts, evalResult, visibility, wid
449
479
  envelope.width = widths;
450
480
  if (overflow)
451
481
  envelope.overflow = overflow;
482
+ if (runts)
483
+ envelope.runts = runts;
452
484
  if (devOverlay)
453
485
  envelope.devOverlay = devOverlay;
454
486
  if (batchResult && batchResult.clipboardReads.length > 0) {
@@ -12,7 +12,7 @@ const DEFAULT_STORE = resolve(homedir(), ".cache", "harnery", "cookies.json");
12
12
  function jarFrom(opts) {
13
13
  return new CookieJar({
14
14
  path: opts.store ?? DEFAULT_STORE,
15
- source: "bp-cookies",
15
+ source: "harn-cookies",
16
16
  });
17
17
  }
18
18
  // Module-scoped emit assigned by registerCookiesCommand; the module-level
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ export declare function registerDecisionCommand(program: Command, emitParam: EmitContext): void;
4
+ //# sourceMappingURL=decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/commands/decision.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA6CnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CAgStF"}