@osfactory/har 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,18 +2,22 @@
2
2
  <img src="assets/logo.png" alt="HAR logo" width="120">
3
3
  </p>
4
4
 
5
- # HAR — Harnesses for coding agents
5
+ # HAR: The open harness for multi-agent coding workflows
6
6
 
7
7
  [![Release](https://img.shields.io/github/v/release/os-factory/har)](https://github.com/os-factory/har/releases)
8
8
  [![CI](https://github.com/os-factory/har/actions/workflows/test.yml/badge.svg)](https://github.com/os-factory/har/actions/workflows/test.yml)
9
9
  [![Documentation](https://img.shields.io/badge/docs-harproject.dev-38c976)](https://harproject.dev/)
10
10
 
11
- **Give coding agents a real place to work.**
12
-
13
- HAR is an open-source, agent-agnostic standard for building multi-agent coding workflows. It makes your repository agent-ready, so you can run a whole fleet of coding agents in parallel, with deterministic validation gates, verifiable proof, and full observability across every agent.
11
+ HAR is an open-source, agent-agnostic framework for building multi-agent coding workflows. Run a fleet of coding agents in parallel on any repository, with deterministic validation gates, verifiable proof, and full observability across every agent, all extensible and customizable to your own workflow and tooling.
14
12
 
15
13
  Works with **Claude Code** · **Cursor** · **Codex** · any MCP agent.
16
14
 
15
+ ## What's included out of the box
16
+
17
+ - **HAR.** The core harness, available as both a CLI and an MCP server. It turns any repository into isolated worktrees with deterministic launch, verify, and teardown stages for coding agents to work in.
18
+ - **Mission Control.** HAR's open-source local dashboard. It gives you one place to keep track of every repository, worktree, run, validation, and artifact across your projects.
19
+ - **Plugins.** A growing ecosystem of open source plugins that further expand HAR's customizability and functionality.
20
+
17
21
  ## Install
18
22
 
19
23
  ```bash
@@ -29,40 +33,59 @@ har env launch 1 # isolated worktree + running stack for agent slot 1
29
33
  har env verify 1 --full # run the project's real checks, record what passed
30
34
  ```
31
35
 
32
- That’s it. Your agents discover launch, verify, and teardown through the project’s `.har/` contract — not by guessing shell commands.
36
+ Full walkthrough: [Quickstart](https://harproject.dev/docs/getting-started/quick-start/).
37
+
38
+ ## Why HAR
33
39
 
34
- Full walkthrough: [Quick start](https://harproject.dev/docs/getting-started/quick-start/).
40
+ Getting a single coding agent to work in a repo is easy. Scaling that into a real multi-agent workflow, where several agents run at once and humans still trust the output, is where it breaks down. HAR was built to close those gaps:
35
41
 
36
- ## How it works
42
+ 1. **No standard way to run or verify a repo.** That knowledge is scattered across a README, a CLAUDE.md, Cursor rules, and CI yaml today, drifting out of sync with each other and the actual codebase. HAR replaces all of that with one machine-readable contract (.har/) that Claude Code, Cursor, Codex, or any MCP agent reads the same way.
37
43
 
38
- 1. **Discover** — The agent reads one stable interface instead of guessing project-specific shell commands.
39
- 2. **Isolate** — Every task gets a clean slot: dedicated session worktree, ports, and local services.
40
- 3. **Build** — The agent edits inside the harness, without touching the main checkout.
41
- 4. **Verify** — Project checks become a deterministic pipeline with normalized status, logs, and artifacts.
42
- 5. **Hand off** — Reviewers get a branch plus evidence: what ran and the exact validated tree.
44
+ 2. **Multiple agents on one repo collide.** Shared dev server, shared database, shared ports, conflicting git state. HAR gives each agent its own worktree, ports, and database per slot, so a fleet can genuinely run concurrently.
43
45
 
44
- ## Why HAR
46
+ 3. **Trusting an agent's change means re-verifying it yourself.** Every task runs the same deterministic verify step and leaves an evidence trail, logs, artifacts, a validated tree hash, so a reviewer can check proof of what ran instead of relying on the agent's self-report.
47
+
48
+ 4. **One platform's sandbox locks you in.** If the contract lives inside a vendor's hosted dashboard, switching coding agents later means rebuilding the whole verification setup. HAR's contract is an open standard living in the repo itself, portable across whichever agent or tool you adopt.
49
+
50
+ 5. **Hand-rolled scripts rot as the stack changes.** A new dependency, a new service, a new env var, and nobody updates the script until an agent's run fails for a confusing reason. `har env maintain` diffs your installed harness against current templates and flags drift before it causes a silent failure.
45
51
 
46
- - **Project-owned** — The workflow lives with the code, not inside a vendor dashboard.
47
- - **Agent-agnostic** — One stable contract for CLI users, MCP agents, and future tools.
48
- - **Evidence-first** — Every run can leave logs, artifacts, status, and a validated tree hash.
52
+ HAR coordinates the work around the model, so agents can focus on the code and reviewers can trust the result.
49
53
 
50
- ## What HAR is not
54
+ ## How HAR works
51
55
 
52
- - A coding model
53
- - A replacement for CI/CD
54
- - A new test runner
55
- - A hosted platform lock-in
56
+ ```mermaid
57
+ flowchart LR
58
+ D["Discover<br/>reads the harness contract"] --> I["Isolate<br/>one slot per agent"]
56
59
 
57
- HAR coordinates the work around the model so agents can focus on the code — and reviewers can trust the result.
60
+ I --> A1["Agent 1<br/>own worktree, branch, ports, DB"] --> B1["Build"] --> V1["Verify"]
61
+ I --> A2["Agent 2<br/>own worktree, branch, ports, DB"] --> B2["Build"] --> V2["Verify"]
62
+
63
+ V1 --> H["Hand off<br/>branch + evidence"]
64
+ V2 --> H
65
+ ```
66
+
67
+ 1. **Discover.** The agent asks the harness what this project looks like, including its stack, its scripts, and what checks are available.
68
+
69
+ 2. **Isolate.** Every task gets its own slot. That means a fresh copy of the repo on its own branch, with its own ports and, where the project needs it, its own database. Nothing is shared with the main checkout or with any other agent's slot.
70
+
71
+ 3. **Build.** The agent edits and tests its work entirely inside that isolated copy. The main checkout stays untouched the whole time.
72
+
73
+ 4. **Verify.** The project's own checks, whatever they are, run through the same pipeline every time and produce a consistent result. A full verification goes further and captures the state of the entire codebase at that moment, so a pass is tied to the exact code that was checked.
74
+
75
+ 5. **Hand off.** Once verification passes, the session is torn down, but the branch and the proof of what ran are kept. A reviewer gets the code plus the evidence that it was checked.
58
76
 
59
77
  ## Documentation
60
78
 
61
- Everything beyond install and first commands lives at **[https://harproject.dev/](https://harproject.dev/)**:
79
+ Everything beyond install and first commands lives at [harproject.dev](https://harproject.dev/).
62
80
 
63
- - [Introduction](https://harproject.dev/docs/getting-started/introduction/) · [Core concepts](https://harproject.dev/docs/getting-started/concepts/)
64
- - [CLI reference](https://harproject.dev/docs/reference/cli/) · [MCP tools](https://harproject.dev/docs/reference/mcp/)
65
- - [Mission Control](https://harproject.dev/docs/guides/mission-control/) · [Architecture](https://harproject.dev/docs/project/architecture/)
81
+ - **[Core concepts](https://harproject.dev/docs/getting-started/concepts/).** Defines the terms the rest of the docs rely on, things like harness, slot, worktree, stage, run, and validation.
82
+ - **[Agent integrations](https://harproject.dev/docs/guides/agent-integrations/).** How to install HAR workflows for Cursor, Claude Code, Codex, and other MCP clients.
83
+ - **[Verification and commit gate](https://harproject.dev/docs/guides/verification/).** How HAR binds a successful check to exact code and enforces that result at commit time.
84
+ - **[Plugins](https://harproject.dev/docs/guides/plugins/).** How to install framework-specific verification bundles, like Playwright and RocketSim, that register stages in your harness.
85
+ - **[CLI reference](https://harproject.dev/docs/reference/cli/).** Every command and option the `har` executable exposes.
86
+ - **[MCP tools](https://harproject.dev/docs/reference/mcp/).** The structured tools an MCP-connected agent calls directly, for discovery, session control, verification, and evidence.
87
+ - **[Mission Control](https://harproject.dev/docs/guides/mission-control/).** How to run the local dashboard that tracks repositories, worktrees, runs, validations, and artifacts.
88
+ - **[Architecture](https://harproject.dev/docs/project/architecture/).** HAR's internal layers, contracts, and extension points, for anyone building a plugin or contributing to the core.
66
89
 
67
90
  ## Contributing
68
91
 
@@ -70,9 +93,9 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for local setup, the dogfood harness lo
70
93
 
71
94
  ## Sponsors
72
95
 
73
- HAR is sponsored by [Kerno](https://kerno.io) — runtime code and security review for coding agents.
96
+ HAR is sponsored by [Kerno](https://kerno.io), runtime code and security review for coding agents.
74
97
 
75
- <p align="center">
98
+ <p align="left">
76
99
  <a href="https://kerno.io">
77
100
  <picture>
78
101
  <source media="(prefers-color-scheme: dark)" srcset="assets/kerno-logo.svg">
package/dist/index.js CHANGED
@@ -31068,6 +31068,27 @@ function writePortalWatermark(repoPath, portalUrl, lastSyncedAt) {
31068
31068
  }
31069
31069
  writeState(state);
31070
31070
  }
31071
+ function readRunsWatermarkEntry(repoPath, target) {
31072
+ const canonical = canonicalizeControlRepoPath(repoPath);
31073
+ const entry = readState().states.find(
31074
+ (state) => state.repoPath === canonical && state.portalUrl === target
31075
+ );
31076
+ return entry ? { lastSyncedAt: entry.lastSyncedAt, repoId: entry.repoId } : null;
31077
+ }
31078
+ function writeRunsWatermark(repoPath, target, repoId, lastSyncedAt) {
31079
+ const canonical = canonicalizeControlRepoPath(repoPath);
31080
+ const state = readState();
31081
+ const existing = state.states.find(
31082
+ (entry) => entry.repoPath === canonical && entry.portalUrl === target
31083
+ );
31084
+ if (existing) {
31085
+ existing.lastSyncedAt = lastSyncedAt;
31086
+ existing.repoId = repoId;
31087
+ } else {
31088
+ state.states.push({ repoPath: canonical, portalUrl: target, lastSyncedAt, repoId });
31089
+ }
31090
+ writeState(state);
31091
+ }
31071
31092
  function selectSince(rows, since, getTimestamp) {
31072
31093
  let maxSyncedAt = null;
31073
31094
  const selected = [];
@@ -31275,6 +31296,7 @@ var init_portal_usage_merge = __esm({
31275
31296
  // src/core/control-sync.ts
31276
31297
  var control_sync_exports = {};
31277
31298
  __export(control_sync_exports, {
31299
+ chunkBySerializedSize: () => chunkBySerializedSize,
31278
31300
  discoverHarRepos: () => discoverHarRepos,
31279
31301
  ensureRepoRegisteredWithControl: () => ensureRepoRegisteredWithControl,
31280
31302
  isControlApiReachable: () => isControlApiReachable,
@@ -31286,13 +31308,80 @@ __export(control_sync_exports, {
31286
31308
  syncRunWithControlAsync: () => syncRunWithControlAsync,
31287
31309
  waitForControlApi: () => waitForControlApi
31288
31310
  });
31311
+ function maxSyncBatchBytes() {
31312
+ const raw = Number(process.env.HAR_SYNC_MAX_BATCH_BYTES);
31313
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MAX_BATCH_BYTES;
31314
+ }
31315
+ function chunkBySerializedSize(items, maxBytes) {
31316
+ const batches = [];
31317
+ let current = [];
31318
+ let currentBytes = 0;
31319
+ for (const item of items) {
31320
+ const itemBytes = Buffer.byteLength(JSON.stringify(item), "utf8");
31321
+ if (current.length > 0 && currentBytes + itemBytes > maxBytes) {
31322
+ batches.push(current);
31323
+ current = [];
31324
+ currentBytes = 0;
31325
+ }
31326
+ current.push(item);
31327
+ currentBytes += itemBytes;
31328
+ }
31329
+ if (current.length > 0) batches.push(current);
31330
+ return batches;
31331
+ }
31332
+ function runTimestamp(run2) {
31333
+ return run2.finishedAt ?? run2.startedAt;
31334
+ }
31335
+ function rewindSince(since) {
31336
+ if (!since) return null;
31337
+ const raw = Number(process.env.HAR_SYNC_OVERLAP_MS);
31338
+ const overlap = Number.isFinite(raw) && raw >= 0 ? raw : DEFAULT_SYNC_OVERLAP_MS;
31339
+ return new Date(new Date(since).getTime() - overlap).toISOString();
31340
+ }
31341
+ function runsWatermarkTarget(target) {
31342
+ return `runs:${target}`;
31343
+ }
31344
+ async function syncRunBatches(newRuns, repoPath, runsTarget, getRepoId, dryRun, send) {
31345
+ const ordered = [...newRuns].sort((a2, b3) => runTimestamp(a2).localeCompare(runTimestamp(b3)));
31346
+ const batches = chunkBySerializedSize(ordered, maxSyncBatchBytes());
31347
+ const toSend = batches.length > 0 ? batches : [[]];
31348
+ for (let i2 = 0; i2 < toSend.length; i2++) {
31349
+ const batch = toSend[i2];
31350
+ await send(batch, i2);
31351
+ if (!dryRun && batch.length > 0) {
31352
+ writeRunsWatermark(repoPath, runsTarget, getRepoId(), runTimestamp(batch[batch.length - 1]));
31353
+ }
31354
+ }
31355
+ }
31356
+ function describeFetchFailure(url, bodyBytes, err) {
31357
+ const base = err instanceof Error ? err.message : String(err);
31358
+ const cause = err.cause;
31359
+ const code = cause?.code ? ` [${cause.code}]` : "";
31360
+ const detail = cause?.message && cause.message !== base ? `: ${cause.message}` : "";
31361
+ const size = bodyBytes > 0 ? ` (request body ${(bodyBytes / 1024 / 1024).toFixed(2)} MB)` : "";
31362
+ return `${base}${code}${detail}${size} \u2014 POST ${url}`;
31363
+ }
31364
+ async function postForResponse(url, headers, payload) {
31365
+ let lastErr;
31366
+ for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS; attempt++) {
31367
+ try {
31368
+ return await fetch(url, { method: "POST", headers, body: payload });
31369
+ } catch (err) {
31370
+ lastErr = err;
31371
+ if (attempt < MAX_FETCH_ATTEMPTS) {
31372
+ await new Promise((resolve47) => setTimeout(resolve47, attempt * 100));
31373
+ }
31374
+ }
31375
+ }
31376
+ throw new Error(describeFetchFailure(url, Buffer.byteLength(payload, "utf8"), lastErr));
31377
+ }
31289
31378
  async function postJson(url, body, dryRun) {
31290
31379
  if (dryRun) return null;
31291
- const response = await fetch(url, {
31292
- method: "POST",
31293
- headers: { "Content-Type": "application/json" },
31294
- body: JSON.stringify(body)
31295
- });
31380
+ const response = await postForResponse(
31381
+ url,
31382
+ { "Content-Type": "application/json" },
31383
+ JSON.stringify(body)
31384
+ );
31296
31385
  if (!response.ok) {
31297
31386
  const text = await response.text().catch(() => "");
31298
31387
  throw new Error(`Control API ${response.status}: ${text || response.statusText}`);
@@ -31300,14 +31389,14 @@ async function postJson(url, body, dryRun) {
31300
31389
  return await response.json();
31301
31390
  }
31302
31391
  function postPortalOnce(target, endpoint, body) {
31303
- return fetch(`${target.url}${endpoint}`, {
31304
- method: "POST",
31305
- headers: {
31392
+ return postForResponse(
31393
+ `${target.url}${endpoint}`,
31394
+ {
31306
31395
  "Content-Type": "application/json",
31307
31396
  Authorization: `Bearer ${target.token}`
31308
31397
  },
31309
- body: JSON.stringify(body)
31310
- });
31398
+ JSON.stringify(body)
31399
+ );
31311
31400
  }
31312
31401
  async function refreshPortalToken(target) {
31313
31402
  if (!target.refreshToken) return false;
@@ -31331,7 +31420,7 @@ async function refreshPortalToken(target) {
31331
31420
  return true;
31332
31421
  }
31333
31422
  async function postPortal(target, endpoint, body, dryRun) {
31334
- if (dryRun) return;
31423
+ if (dryRun) return null;
31335
31424
  let response = await postPortalOnce(target, endpoint, body);
31336
31425
  if ((response.status === 401 || response.status === 403) && await refreshPortalToken(target)) {
31337
31426
  response = await postPortalOnce(target, endpoint, body);
@@ -31345,6 +31434,7 @@ async function postPortal(target, endpoint, body, dryRun) {
31345
31434
  const text = await response.text().catch(() => "");
31346
31435
  throw new Error(`har-portal sync failed: HTTP ${response.status}${text ? ` \u2014 ${text}` : ""}`);
31347
31436
  }
31437
+ return await response.json().catch(() => null);
31348
31438
  }
31349
31439
  function resolvePortalUserEmail() {
31350
31440
  return readPortalCredentials()?.email || void 0;
@@ -31441,7 +31531,6 @@ async function buildPortalPayload(repoPath, controlApiUrl, since) {
31441
31531
  path: repoPath,
31442
31532
  ...manifest ? { manifest } : {},
31443
31533
  ...stagesRegistry ? { stagesRegistry } : {},
31444
- runs,
31445
31534
  slots: status.slots,
31446
31535
  generatedAt: status.generatedAt,
31447
31536
  ...validations.length > 0 ? { validations } : {},
@@ -31450,7 +31539,7 @@ async function buildPortalPayload(repoPath, controlApiUrl, since) {
31450
31539
  ...validationBindings.length > 0 ? { validationBindings } : {},
31451
31540
  ...usage2.length > 0 ? { usage: usage2 } : {}
31452
31541
  };
31453
- return { syncBody, events, maxSyncedAt };
31542
+ return { syncBody, runs, events, maxSyncedAt };
31454
31543
  }
31455
31544
  async function isControlApiReachable(apiUrl = getControlApiUrl()) {
31456
31545
  try {
@@ -31488,7 +31577,7 @@ async function syncReposWithControl(options) {
31488
31577
  try {
31489
31578
  await withTimeout(
31490
31579
  syncRepoWithControl({ repoPath, apiUrl, dryRun: options.dryRun, cloud: options.cloud, full: options.full }),
31491
- SYNC_TIMEOUT_MS,
31580
+ EXPLICIT_SYNC_TIMEOUT_MS,
31492
31581
  "control sync"
31493
31582
  );
31494
31583
  synced++;
@@ -31551,7 +31640,7 @@ async function ensureRepoRegisteredWithControl(repoPath, apiUrl) {
31551
31640
  }
31552
31641
  async function syncRepoWithLocalControl(options) {
31553
31642
  const apiUrl = options.apiUrl ?? getControlApiUrl();
31554
- const { repoPath, dryRun } = options;
31643
+ const { repoPath, dryRun, full } = options;
31555
31644
  const registerResult = await registerRepoWithControl({ ...options, repoPath });
31556
31645
  const repoId = registerResult?.id;
31557
31646
  if (!repoId && !dryRun) {
@@ -31562,11 +31651,11 @@ async function syncRepoWithLocalControl(options) {
31562
31651
  if (!existing) {
31563
31652
  return;
31564
31653
  }
31565
- await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, dryRun);
31654
+ await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, dryRun, full);
31566
31655
  return;
31567
31656
  }
31568
31657
  if (repoId) {
31569
- await syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun);
31658
+ await syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun, full);
31570
31659
  }
31571
31660
  }
31572
31661
  async function syncRepoWithPortal(options, controlApiUrl) {
@@ -31574,10 +31663,28 @@ async function syncRepoWithPortal(options, controlApiUrl) {
31574
31663
  if (!portal) return;
31575
31664
  const { repoPath, dryRun, full } = options;
31576
31665
  const since = full ? null : readPortalWatermark(repoPath, portal.url);
31577
- const { syncBody, events, maxSyncedAt } = await buildPortalPayload(repoPath, controlApiUrl, since);
31578
- await postPortal(portal, "/api/sync", syncBody, dryRun);
31579
- if (events.length > 0) {
31580
- await postPortal(portal, "/api/otel", { path: repoPath, events, spans: [] }, dryRun);
31666
+ const runsTarget = runsWatermarkTarget(portal.url);
31667
+ const stored = full ? null : readRunsWatermarkEntry(repoPath, runsTarget);
31668
+ const runsSince = rewindSince(stored?.lastSyncedAt ?? null);
31669
+ const { syncBody, runs, events, maxSyncedAt } = await buildPortalPayload(
31670
+ repoPath,
31671
+ controlApiUrl,
31672
+ since
31673
+ );
31674
+ const { selected: newRuns } = selectSince(runs, runsSince, runTimestamp);
31675
+ let repoId;
31676
+ await syncRunBatches(newRuns, repoPath, runsTarget, () => repoId, dryRun, async (batch, i2) => {
31677
+ const body = i2 === 0 ? { ...syncBody, runs: batch } : { path: repoPath, runs: batch };
31678
+ const res = await postPortal(portal, "/api/sync", body, dryRun);
31679
+ if (i2 === 0 && typeof res?.repositoryId === "string") repoId = res.repositoryId;
31680
+ });
31681
+ if (!full && !dryRun && stored?.repoId && repoId && stored.repoId !== repoId) {
31682
+ await syncRunBatches(listRuns(repoPath), repoPath, runsTarget, () => repoId, dryRun, async (batch) => {
31683
+ await postPortal(portal, "/api/sync", { path: repoPath, runs: batch }, dryRun);
31684
+ });
31685
+ }
31686
+ for (const batch of chunkBySerializedSize(events, maxSyncBatchBytes())) {
31687
+ await postPortal(portal, "/api/otel", { path: repoPath, events: batch, spans: [] }, dryRun);
31581
31688
  }
31582
31689
  if (!dryRun && maxSyncedAt) {
31583
31690
  writePortalWatermark(repoPath, portal.url, maxSyncedAt);
@@ -31618,10 +31725,15 @@ async function syncRepoWithControl(options) {
31618
31725
  await syncRepoWithLocalControl({ ...options, repoPath, apiUrl });
31619
31726
  await syncRepoWithPortal({ ...options, repoPath }, apiUrl);
31620
31727
  }
31621
- async function syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun) {
31728
+ async function syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun, full) {
31729
+ const runsTarget = runsWatermarkTarget(apiUrl);
31730
+ const stored = full ? null : readRunsWatermarkEntry(repoPath, runsTarget);
31731
+ const runsSince = rewindSince(stored?.repoId === repoId ? stored?.lastSyncedAt ?? null : null);
31622
31732
  const runs = listRuns(repoPath);
31623
- const runsBody = SyncRunsInputSchema.parse({ runs });
31624
- await postJson(`${apiUrl}/api/repos/${repoId}/runs`, runsBody, dryRun);
31733
+ const { selected: newRuns } = selectSince(runs, runsSince, runTimestamp);
31734
+ await syncRunBatches(newRuns, repoPath, runsTarget, () => repoId, dryRun, async (batch) => {
31735
+ await postJson(`${apiUrl}/api/repos/${repoId}/runs`, SyncRunsInputSchema.parse({ runs: batch }), dryRun);
31736
+ });
31625
31737
  const status = collectEnvironmentStatus(repoPath);
31626
31738
  const slotsBody = SyncSlotsInputSchema.parse({
31627
31739
  slots: status.slots,
@@ -31776,7 +31888,7 @@ async function syncRunWithControlAsync(repoPath, run2) {
31776
31888
  `);
31777
31889
  }
31778
31890
  }
31779
- var path27, SYNC_TIMEOUT_MS;
31891
+ var path27, DEFAULT_MAX_BATCH_BYTES, DEFAULT_SYNC_OVERLAP_MS, MAX_FETCH_ATTEMPTS, SYNC_TIMEOUT_MS, EXPLICIT_SYNC_TIMEOUT_MS;
31780
31892
  var init_control_sync = __esm({
31781
31893
  "src/core/control-sync.ts"() {
31782
31894
  "use strict";
@@ -31800,7 +31912,11 @@ var init_control_sync = __esm({
31800
31912
  init_portal_usage_merge();
31801
31913
  init_schema2();
31802
31914
  init_portal_watermark();
31915
+ DEFAULT_MAX_BATCH_BYTES = 4 * 1024 * 1024;
31916
+ DEFAULT_SYNC_OVERLAP_MS = 6e4;
31917
+ MAX_FETCH_ATTEMPTS = 3;
31803
31918
  SYNC_TIMEOUT_MS = 15e3;
31919
+ EXPLICIT_SYNC_TIMEOUT_MS = 12e4;
31804
31920
  }
31805
31921
  });
31806
31922
 
@@ -89812,7 +89928,7 @@ var HAR_MCP_TOOLS = [
89812
89928
  force: { type: "boolean" },
89813
89929
  auto: { type: "boolean" },
89814
89930
  smoke: { type: "boolean" },
89815
- profile: { type: "string", enum: ["default", "cli"] }
89931
+ profile: { type: "string", enum: ["default", "cli", "ios"] }
89816
89932
  })
89817
89933
  },
89818
89934
  {
@@ -5,32 +5,31 @@ alwaysApply: true
5
5
 
6
6
  # HAR Harness Workflow
7
7
 
8
- This repository uses a `.har/` agent harness. Follow this workflow on every change.
8
+ This repository uses a `.har/` agent harness. Follow this on every change.
9
9
 
10
- The harness is **how you run the project**, not only how you verify it. To see the app
11
- live (manual testing, browser sessions, screenshots), launch a slot — never hand-roll
12
- docker/dev-server startup; the harness already encodes DB setup, ports, and env.
13
- If a harness command fails, fix the harness or surface the failure — do not quietly
14
- work around it with ad-hoc commands.
10
+ The harness is **how you run the project**, not only how you verify it. Launch a
11
+ slot for live apps, browsers, and screenshots — never hand-roll docker/dev-server
12
+ startup. If a harness command fails, fix the harness or surface the failure.
15
13
 
16
14
  ## Before making changes
17
15
 
18
- 1. **On the main checkout, switch to the intended base** (usually `main`) — every launch creates a worktree from that HEAD.
19
- 2. **Launch your slot FIRST — before editing any file.** Use `har_launch_environment` with `agentId: 1` (MCP) or `har env launch 1`. Launch always creates a **fresh session worktree** and returns its **work dir**. If the task binds to a durable external work item (a GitHub issue, a Linear ticket, a task from the user's tracker), pass `workUnitId` + `title` on launch (`har_launch_environment` `workUnitId`/`title`, or CLI `--work-id`/`--work-title`) so Mission Control attributes usage and events to that work item instead of leaving them session-only.
20
- 3. **Make ALL file edits under the returned work dir — never in the main checkout.** The work dir looks like `~/worktrees/<base-branch>-<sha4>-har-agent-<id>-<rand4>` (recorded in `.har/slots/agent-<id>.json`). Edits there hot-reload in the running slot; use `har env restart <id>` or `./.har/agent-cli.sh <id> restart` if a change doesn't take.
21
- 4. Read [AGENT.md](./AGENT.md) at the repo root
22
- 5. Read [.har/README.md](./.har/README.md) and [.har/stages.json](./.har/stages.json)
23
- 6. After launch, read [.har/CLAUDE.agent.md](./.har/CLAUDE.agent.md) for slot URLs and definition of done
24
-
25
- When a slot is occupied: it always blocks a new launch. Run `complete` / `teardown` on that slot id first, then `launch` again. Occupied slots do not choose the base branch either way — switch the main checkout to your intended base before launching. Failed/starting launches can be resumed instead: `--resume` / `recover`.
26
-
27
- ### Slot safety (read before launch)
28
-
29
- - **One slot ≈ one task** — do not share slot 1 across unrelated chats; use slot 2+ for parallel work.
30
- - **Call `har_get_status` / `har env status`** before launch when a slot may be occupied.
31
- - **Commit early and often** in the session worktree — teardown keeps the branch, not uncommitted work.
32
- - **Gitignored paths are ephemeral** (`state/`, `runs/`, local clones) — they are lost when the worktree is removed.
33
- - With telemetry on (default, including prompts), Mission Control derives session purpose from the first captured user prompt.
16
+ 1. On the **main checkout**, switch to the intended base (usually `main`) — launch
17
+ creates a worktree from that HEAD.
18
+ 2. **Launch first** — MCP `har_launch_environment` / `har env launch 1`. Use the
19
+ returned **work dir** for ALL edits (never the main checkout). Path looks like
20
+ `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>` (see `.har/slots/agent-<id>.json`).
21
+ Bind tracker work with `workUnitId`/`title` or `--work-id`/`--work-title` when needed.
22
+ 3. Read `AGENT.md`, `.har/README.md`, `.har/stages.json`, then
23
+ `.har/CLAUDE.agent.md` (slot URLs / definition of done).
24
+ 4. Hot-reload usually applies; if not, `./.har/agent-cli.sh <id> restart` (no-op on
25
+ cli/ios profiles without managed processes).
26
+
27
+ **Occupied slots always block.** Run `complete` / `teardown`, then `launch`. Resume
28
+ failed/starting launches with `--resume` / `recover`. Prefer a free slot (2+) over
29
+ sharing slot 1 across unrelated chats. Check `har_get_status` / `har env status`
30
+ first. Commit early — teardown keeps the branch, not uncommitted work. Gitignored
31
+ paths are ephemeral. With telemetry on, Mission Control derives session purpose from
32
+ the first captured user prompt.
34
33
 
35
34
  <!-- Monorepo: if this repository contains multiple .har harnesses, list them here
36
35
  and pick by the files you are changing. Example:
@@ -45,102 +44,33 @@ When a slot is occupied: it always blocks a new launch. Run `complete` / `teardo
45
44
  Run harness commands from the directory that owns the harness (e.g. `cd apps/admin`).
46
45
  -->
47
46
 
48
-
49
47
  ## After making changes
50
48
 
51
- Validate through the harness — do not skip verification or substitute ad-hoc npm/test commands.
52
-
53
- ### 1. HAR MCP (preferred in Cursor when `.cursor/mcp.json` has the har server)
54
-
55
- - `har_run_verification` with `agentId: 1` — fast loop (typecheck + unit tests)
56
- - `har_run_verification` with `agentId: 1, full: true` — required before declaring work done
57
- - `har_complete_environment` with `agentId: 1` — **recommended finish** (full verify + validation record + teardown, branch kept). **Propose and wait for user approval** before invoking — never run autonomously
58
- - `har_teardown_environment` with `agentId: 1` — plain cleanup only (also keeps the branch; `deleteBranch: true` to drop it). Prefer `complete` when the work succeeded
59
-
60
- ### 2. HAR CLI (when `har` is installed)
61
-
62
- ```bash
63
- har env launch 1 # FIRST, before editing — prints the work dir
64
- har env verify 1
65
- har env verify 1 --full
66
- har env complete 1 # done: verify + validate + teardown, branch kept for PR
67
- har env teardown 1 # plain cleanup (--delete-branch to drop the branch)
68
- ```
69
-
70
- ### 3. Shell fallback (no CLI/MCP installed)
71
-
72
- ```bash
73
- ./.har/launch.sh 1
74
- ./.har/verify.sh 1
75
- ./.har/verify.sh 1 --full
76
- ./.har/teardown.sh 1
77
- ```
78
-
79
- Commit your work **inside the session worktree** on its session branch. After completion the branch survives teardown — the user decides whether to push it and open a PR. Run JSON stays in the main checkout `.har/runs/`.
49
+ Prefer MCP → CLI → shell. Quick verify for the loop; **full verify before done**.
80
50
 
81
- ## UI changes
51
+ - MCP: `har_run_verification` / `full: true`; finish with `har_complete_environment`
52
+ (propose; wait for approval) or `har_teardown_environment`
53
+ - CLI: `har env verify 1`, `har env verify 1 --full`, `complete 1`, `teardown 1`
54
+ - Shell: `./.har/verify.sh 1`, `./.har/verify.sh 1 --full`, `./.har/teardown.sh 1`
82
55
 
83
- When the Playwright stage is installed (`.har/stages/browser-e2e.sh` exists), add or update specs under `tests/`. Full verify includes browser-e2e.
56
+ Commit in the session worktree. Run JSON stays in the main checkout `.har/runs/`.
57
+ UI changes with Playwright: update specs under `tests/` (full verify includes browser-e2e).
84
58
 
85
59
  ## Definition of done
86
60
 
87
- - Full verification returns pass (`har env verify 1 --full`, MCP `har_run_verification` with `full: true`, or `./.har/verify.sh 1 --full`)
88
- - All edits live in the session worktree (main checkout untouched: `git status` clean there)
89
- - New behavior has automated test coverage
90
- - Changes committed in the session worktree
91
- - Show the user the slot preview URLs so they can test the app themselves
92
- - Do not declare work complete without running full verify
93
- - **Present session handoff and wait for the user's next instruction** — do not end the session without the handoff prompt
61
+ - Full verify passes; edits only in the session worktree; tests cover new behavior;
62
+ changes committed; show preview URLs; then **session handoff** (below).
94
63
 
95
64
  ## Session handoff (required)
96
65
 
97
- After full verify passes and changes are committed, **stop and hand off**. Never
98
- autonomously run `complete`, `teardown`, `git push`, or create a pull request.
99
-
100
- Present a handoff that includes:
101
-
102
- 1. **Summary** of what changed
103
- 2. **Session branch** name (from launch / `.har/slots/agent-<id>.json`)
104
- 3. **Preview URLs** when the slot has a running app
105
- 4. **Numbered next-step options** — then wait for the user
106
-
107
- Options to offer:
108
-
109
- 1. **Complete the slot** (recommended) — `har env complete <id>` / MCP
110
- `har_complete_environment`: full verify + validation record + worktree/runtime
111
- teardown; **branch kept**. Prefer this over bare `teardown` when the work
112
- succeeded.
113
- 2. **Open a PR** — only if `gh` CLI or GitHub MCP is available. Create the PR
114
- **only after explicit user approval**.
115
- 3. **Keep the branch only** — if PR tooling is unavailable, report the session
116
- branch and how to push manually. The branch already exists from launch.
117
-
118
- Canonical handoff shape:
119
-
120
- ```markdown
121
- ## Session handoff
122
-
123
- **Summary:** …
124
- **Branch:** `<session-branch>` (session worktree)
125
- **Preview:** … (if applicable)
126
-
127
- Next steps — tell me which you want:
128
- 1. **Complete the slot** — I'll run `har env complete <id>` (full verify + validation record + teardown; branch kept)
129
- 2. **Open a PR** — I can create one with `gh`/GitHub MCP (requires your approval)
130
- 3. **Something else** — e.g. keep the slot running, more changes, or push only
131
-
132
- I'll wait for your instruction before running complete, teardown, push, or PR.
133
- ```
134
-
135
- Omit option 2 when neither `gh` nor GitHub MCP is available; replace it with push
136
- instructions for the session branch.
66
+ After full verify and commit, stop. Include summary, session branch
67
+ (`.har/slots/agent-<id>.json`), and preview URLs. Propose: (1) complete the slot
68
+ (`har env complete` / `har_complete_environment` — branch kept), (2) open a PR if
69
+ `gh`/GitHub MCP is available, or (3) something else. Wait for the user — never
70
+ autonomously complete, teardown, push, or open a PR.
137
71
 
138
72
  ## Commit gate
139
73
 
140
- Each full verify records the exact code state it validated (a git tree hash under
141
- `.har/validations/`). When the har commit gate is installed (`har hooks install`),
142
- `git commit` is blocked unless the staged changes match a state that passed full verify.
143
-
144
- - Any edit after verify — even one character — requires re-running `har env verify <id> --full`
145
- - Stage everything you verified (`git add -A`); partially staged trees were never verified and will be rejected
146
- - Do not bypass the gate (`--no-verify`, `HAR_SKIP_GATE=1`) — bypasses are recorded and visible in Mission Control
74
+ Full verify records a tree hash under `.har/validations/`. With `har hooks install`,
75
+ commits must match a passing full verify. Re-verify after any edit; `git add -A` the
76
+ verified tree. Do not bypass (`--no-verify`, `HAR_SKIP_GATE=1`).
@@ -52,8 +52,7 @@ After full verify and commit, stop and propose next steps. Never autonomously ru
52
52
  `complete`, `teardown`, `git push`, or open a PR. Prefer `complete` over bare
53
53
  `teardown` when the work succeeded. Offer a PR only if `gh` or GitHub MCP is
54
54
  available (and only after explicit approval); otherwise report the session branch
55
- for a manual push. See `.cursor/rules/har-workflow.mdc` for the canonical handoff
56
- template.
55
+ for a manual push. See `.cursor/rules/har-workflow.mdc` for the handoff shape.
57
56
 
58
57
  Quick loop during development: MCP `har_run_verification`, `har env verify ${AGENT_ID}`, or `./.har/verify.sh ${AGENT_ID}` (smoke + health only; `--full` adds the registered verification stages).
59
58
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  This directory is the **agent harness** for this repository. It lets AI coding agents (Cursor, Claude Code, etc.) run the project in isolated environments with their own ports, database, and verification workflow.
4
4
 
5
- Generated and maintained by [`har`](https://github.com/antoineFrau/har). Run `har env maintain` when the repo stack changes.
5
+ Generated and maintained by [`har`](https://github.com/os-factory/har). Run `har env maintain` when the repo stack changes.
6
6
 
7
7
  **The harness is how you run this project.** Need the app live — manual testing, a browser session, screenshots? `launch` a slot; don't hand-roll docker/dev-server startup. If a harness command fails, fix the harness or report it — don't silently fall back to ad-hoc commands.
8
8
 
@@ -38,8 +38,7 @@ After full verify and commit, stop and propose next steps. Never autonomously ru
38
38
  `complete`, `teardown`, `git push`, or open a PR. Prefer `complete` over bare
39
39
  `teardown` when the work succeeded. Offer a PR only if `gh` or GitHub MCP is
40
40
  available (and only after explicit approval); otherwise report the session branch
41
- for a manual push. See `.cursor/rules/har-workflow.mdc` for the canonical handoff
42
- template.
41
+ for a manual push. See `.cursor/rules/har-workflow.mdc` for the handoff shape.
43
42
 
44
43
  Quick loop: MCP `har_run_verification`, `har env verify ${AGENT_ID}`, or `./.har/verify.sh ${AGENT_ID}`
45
44
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  This directory is the **agent harness** for this repository. It lets AI coding agents run the project in isolated git worktrees with optional Docker-backed shared infra.
4
4
 
5
- Generated and maintained by [`har`](https://github.com/antoineFrau/har). Run `har env maintain` when the repo stack changes.
5
+ Generated and maintained by [`har`](https://github.com/os-factory/har). Run `har env maintain` when the repo stack changes.
6
6
 
7
7
  **The harness is how you run this project.** Launch a slot to exercise the code in isolation; don't hand-roll setup. If a harness command fails, fix the harness or report it — don't silently fall back to ad-hoc commands.
8
8
 
@@ -40,8 +40,7 @@ After full verify and commit, stop and propose next steps. Never autonomously ru
40
40
  `complete`, `teardown`, `git push`, or open a PR. Prefer `complete` over bare
41
41
  `teardown` when the work succeeded. Offer a PR only if `gh` or GitHub MCP is
42
42
  available (and only after explicit approval); otherwise report the session branch
43
- for a manual push. See `.cursor/rules/har-workflow.mdc` for the canonical handoff
44
- template.
43
+ for a manual push. See `.cursor/rules/har-workflow.mdc` for the handoff shape.
45
44
 
46
45
  Quick loop: MCP `har_run_verification`, `har env verify ${AGENT_ID}`, or `./.har/verify.sh ${AGENT_ID}`
47
46
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  This directory is the **agent harness** for this iOS mobile app repository. It lets AI coding agents build, test, and validate the app in isolated git worktrees against a running iOS Simulator.
4
4
 
5
- Generated and maintained by [`har`](https://github.com/antoineFrau/har). Run `har env maintain` when the repo stack changes.
5
+ Generated and maintained by [`har`](https://github.com/os-factory/har). Run `har env maintain` when the repo stack changes.
6
6
 
7
7
  **The harness is how you run this project.** Launch a slot to exercise the code in isolation; don't hand-roll Xcode/simulator setup. If a harness command fails, fix the harness or report it — don't silently fall back to ad-hoc commands.
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfactory/har",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "LLM-powered CLI for reproducible agent development environments",
5
5
  "keywords": [
6
6
  "har",