@osfactory/har 0.2.0 → 0.4.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 (28) hide show
  1. package/README.md +29 -3
  2. package/dist/index.js +81 -9
  3. package/dist/prompts/system-authoring.md +26 -0
  4. package/dist/templates/adaptation-prompt-init.md +37 -1
  5. package/dist/templates/adaptation-prompt-maintain.md +30 -0
  6. package/dist/templates/har-boilerplate/CLAUDE.agent.md +14 -0
  7. package/dist/templates/har-boilerplate/README.md +18 -1
  8. package/dist/templates/har-boilerplate/agent-cli.sh +7 -1
  9. package/dist/templates/har-boilerplate/agent-slot.sh +60 -7
  10. package/dist/templates/har-boilerplate/harness.env +15 -0
  11. package/dist/templates/har-boilerplate/launch.sh +50 -0
  12. package/dist/templates/har-boilerplate/stages.json +1 -1
  13. package/dist/templates/har-boilerplate/verify.sh +9 -0
  14. package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +8 -0
  15. package/dist/templates/har-boilerplate-cli/README.md +6 -1
  16. package/dist/templates/har-boilerplate-cli/agent-slot.sh +60 -7
  17. package/dist/templates/har-boilerplate-cli/harness.env +8 -0
  18. package/dist/templates/har-boilerplate-cli/launch.sh +41 -0
  19. package/dist/templates/har-boilerplate-cli/stages.json +1 -1
  20. package/dist/templates/har-boilerplate-cli/verify.sh +9 -0
  21. package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +8 -0
  22. package/dist/templates/har-boilerplate-ios/README.md +5 -1
  23. package/dist/templates/har-boilerplate-ios/agent-slot.sh +60 -7
  24. package/dist/templates/har-boilerplate-ios/harness.env +4 -0
  25. package/dist/templates/har-boilerplate-ios/launch.sh +41 -0
  26. package/dist/templates/har-boilerplate-ios/stages.json +1 -1
  27. package/dist/templates/har-boilerplate-ios/verify.sh +10 -0
  28. package/package.json +1 -1
package/README.md CHANGED
@@ -39,11 +39,37 @@ npm install && npm run build && npm link
39
39
 
40
40
  See [AGENT.md](./AGENT.md) for architecture and coding-agent guidance, and [CONTRIBUTING.md](./CONTRIBUTING.md) for the full development workflow, testing on sample projects, and project layout. To report security issues, see [SECURITY.md](./SECURITY.md).
41
41
 
42
+ ## Harness profiles
43
+
44
+ `har env init` scaffolds `.har/` from a boilerplate profile. Pick the one that matches what agents need to run — you only choose this once at init time.
45
+
46
+ | Profile | Best for | What you get |
47
+ |---------|----------|--------------|
48
+ | `default` | Web apps (Next.js, Rails, Django, etc.) | Docker Compose for shared infra, PM2 for the primary app, per-slot ports and preview URLs |
49
+ | `cli` | CLI tools, libraries, npm packages | No PM2 or port wiring — agents run project commands in an isolated git worktree; optional Docker for databases |
50
+ | `ios` | iOS / Swift mobile apps | xcodebuild + iOS Simulator; configure scheme, project, and simulator in `harness.env` |
51
+
52
+ ```bash
53
+ # Web app (default — omit --profile)
54
+ cd my-web-app && har env init
55
+
56
+ # CLI tool or library
57
+ cd my-cli && har env init --profile cli
58
+
59
+ # iOS app
60
+ cd MyApp && har env init --profile ios
61
+ har env add-stage rocketsim # optional: UI flow validation on the simulator
62
+ ```
63
+
64
+ After init, paste the printed adaptation prompt into your coding agent to tailor scripts to your stack. For built-in Claude adaptation: `har env init --auto` (requires `ANTHROPIC_API_KEY`).
65
+
66
+ Optional stage templates depend on the profile: `har env add-stage playwright` for browser E2E on web apps, `har env add-stage rocketsim` for simulator user-flow checks on iOS.
67
+
42
68
  ## Quick start
43
69
 
44
70
  ```bash
45
71
  cd my-app
46
- har env init
72
+ har env init # or --profile cli / --profile ios
47
73
 
48
74
  # Paste the printed prompt into your coding agent to adapt .har/ and AGENT.md
49
75
  git add .har/ AGENT.md
@@ -55,7 +81,7 @@ har env verify 1
55
81
 
56
82
  Shell fallback when the CLI is not installed: `./.har/setup-infra.sh`, `./.har/launch.sh 1`, `./.har/verify.sh 1`.
57
83
 
58
- For built-in Claude adaptation (requires `ANTHROPIC_API_KEY`): `har env init --auto`.
84
+ See [Harness profiles](#harness-profiles) above if your repo is a CLI/library or iOS app rather than a web app.
59
85
 
60
86
  ## Repo layout after init
61
87
 
@@ -114,7 +140,7 @@ See `.har/stages/PLAYWRIGHT.md` in the target repo after applying the template.
114
140
  | `har control watch` | Continuously sync registered repos |
115
141
  | `har mcp` | Start the HAR MCP server (stdio) |
116
142
 
117
- Options: `--force`, `--auto` (built-in Claude adaptation), `--smoke`, `--yes` (auto-apply AGENT.md with `--auto`), `--verbose`, `--profile cli`
143
+ Options: `--force`, `--auto` (built-in Claude adaptation), `--smoke`, `--yes` (auto-apply AGENT.md with `--auto`), `--verbose`, `--profile <default|cli|ios>`
118
144
 
119
145
  ## MCP Surface
120
146
 
package/dist/index.js CHANGED
@@ -22989,7 +22989,8 @@ var SlotRegistryEntrySchema = external_exports.object({
22989
22989
  purpose: external_exports.string().optional(),
22990
22990
  ports: external_exports.record(external_exports.number()).optional(),
22991
22991
  previewUrls: external_exports.record(external_exports.string()).optional(),
22992
- status: external_exports.enum(["active", "completed"]).default("active")
22992
+ status: external_exports.enum(["starting", "active", "failed", "completed"]).default("active"),
22993
+ lastError: external_exports.string().optional()
22993
22994
  }).passthrough();
22994
22995
  var AgentSlotHarnessUsageSchema = external_exports.enum([
22995
22996
  "mcp",
@@ -23016,6 +23017,8 @@ var AgentSlotStatusSchema = external_exports.object({
23016
23017
  baseCommit: external_exports.string().optional(),
23017
23018
  sessionCreatedAt: external_exports.string().optional(),
23018
23019
  purpose: external_exports.string().optional(),
23020
+ sessionStatus: external_exports.enum(["starting", "active", "failed", "completed"]).optional(),
23021
+ lastError: external_exports.string().optional(),
23019
23022
  /** Worktree checked out to no branch (legacy failure mode; should not happen with sessions). */
23020
23023
  detachedHead: external_exports.boolean().optional(),
23021
23024
  /** Worktree has uncommitted changes. */
@@ -27864,10 +27867,11 @@ function listRegisteredRepos() {
27864
27867
  var fs23 = __toESM(require("fs"));
27865
27868
  var os4 = __toESM(require("os"));
27866
27869
  var path21 = __toESM(require("path"));
27867
- var import_child_process2 = require("child_process");
27870
+ var import_child_process3 = require("child_process");
27868
27871
 
27869
27872
  // src/core/runs.ts
27870
27873
  var crypto2 = __toESM(require("crypto"));
27874
+ var import_child_process2 = require("child_process");
27871
27875
  var fs22 = __toESM(require("fs"));
27872
27876
  var os3 = __toESM(require("os"));
27873
27877
  var path20 = __toESM(require("path"));
@@ -27895,6 +27899,34 @@ function readSlotRegistry(repoPath, agentId) {
27895
27899
 
27896
27900
  // src/core/runs.ts
27897
27901
  var RUNS_DIR = "runs";
27902
+ function gitCommonDir(cwd) {
27903
+ try {
27904
+ const out = (0, import_child_process2.execSync)("git rev-parse --git-common-dir", {
27905
+ cwd,
27906
+ encoding: "utf8",
27907
+ stdio: ["pipe", "pipe", "ignore"]
27908
+ }).trim();
27909
+ return out ? path20.resolve(cwd, out) : void 0;
27910
+ } catch {
27911
+ return void 0;
27912
+ }
27913
+ }
27914
+ function sameGitCheckout(a2, b2) {
27915
+ const left2 = gitCommonDir(a2);
27916
+ const right2 = gitCommonDir(b2);
27917
+ return left2 !== void 0 && right2 !== void 0 && left2 === right2;
27918
+ }
27919
+ function gitPrefix(cwd) {
27920
+ try {
27921
+ return (0, import_child_process2.execSync)("git rev-parse --show-prefix", {
27922
+ cwd,
27923
+ encoding: "utf8",
27924
+ stdio: ["pipe", "pipe", "ignore"]
27925
+ }).trim();
27926
+ } catch {
27927
+ return "";
27928
+ }
27929
+ }
27898
27930
  function getRunsDir(harnessRoot) {
27899
27931
  return path20.join(getHarnessDir(harnessRoot), RUNS_DIR);
27900
27932
  }
@@ -27951,9 +27983,22 @@ function resolveAgentWorkDir(harnessRoot, agentId) {
27951
27983
  const env3 = readHarnessEnv(harnessRoot);
27952
27984
  const projectName = env3.HARNESS_PROJECT_NAME ?? path20.basename(harnessRoot);
27953
27985
  const worktreeDir = path20.join(os3.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
27986
+ const relPrefix = gitPrefix(harnessRoot);
27987
+ const sessionEnvFiles = [];
27988
+ const worktreesRoot = path20.join(os3.homedir(), "worktrees");
27989
+ if (fs22.existsSync(worktreesRoot)) {
27990
+ const suffix = `-har-agent-${agentId}-`;
27991
+ for (const entry2 of fs22.readdirSync(worktreesRoot, { withFileTypes: true })) {
27992
+ if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
27993
+ const sessionDir = path20.join(worktreesRoot, entry2.name);
27994
+ if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
27995
+ sessionEnvFiles.push(path20.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
27996
+ }
27997
+ }
27954
27998
  const candidates = [
27955
27999
  path20.join(worktreeDir, `.env.agent.${agentId}`),
27956
- path20.join(harnessRoot, `.env.agent.${agentId}`)
28000
+ path20.join(harnessRoot, `.env.agent.${agentId}`),
28001
+ ...sessionEnvFiles.sort()
27957
28002
  ];
27958
28003
  for (const envFile of candidates) {
27959
28004
  if (!fs22.existsSync(envFile)) continue;
@@ -28040,7 +28085,7 @@ function listRuns(repoPath, filter = {}) {
28040
28085
  var BYPASS_WARNING_MS = 15 * 60 * 1e3;
28041
28086
  function readGitRemote(repoPath) {
28042
28087
  try {
28043
- const remote = (0, import_child_process2.execSync)("git remote get-url origin", {
28088
+ const remote = (0, import_child_process3.execSync)("git remote get-url origin", {
28044
28089
  cwd: repoPath,
28045
28090
  encoding: "utf8",
28046
28091
  stdio: ["pipe", "pipe", "ignore"]
@@ -28052,7 +28097,7 @@ function readGitRemote(repoPath) {
28052
28097
  }
28053
28098
  function runGit(cwd, args) {
28054
28099
  try {
28055
- return (0, import_child_process2.execSync)(`git ${args}`, {
28100
+ return (0, import_child_process3.execSync)(`git ${args}`, {
28056
28101
  cwd,
28057
28102
  encoding: "utf8",
28058
28103
  stdio: ["pipe", "pipe", "ignore"]
@@ -28064,6 +28109,29 @@ function runGit(cwd, args) {
28064
28109
  function readWorktreeBranch(worktreePath) {
28065
28110
  return runGit(worktreePath, "rev-parse --abbrev-ref HEAD");
28066
28111
  }
28112
+ function gitCommonDir2(cwd) {
28113
+ const out = runGit(cwd, "rev-parse --git-common-dir");
28114
+ return out ? path21.resolve(cwd, out) : void 0;
28115
+ }
28116
+ function sameGitCheckout2(a2, b2) {
28117
+ const left2 = gitCommonDir2(a2);
28118
+ const right2 = gitCommonDir2(b2);
28119
+ return left2 !== void 0 && right2 !== void 0 && left2 === right2;
28120
+ }
28121
+ function gitPrefix2(cwd) {
28122
+ const out = runGit(cwd, "rev-parse --show-prefix");
28123
+ return out ?? "";
28124
+ }
28125
+ function discoverSessionWorktreePath(harnessRoot, agentId) {
28126
+ const worktreesRoot = path21.join(os4.homedir(), "worktrees");
28127
+ if (!fs23.existsSync(worktreesRoot)) return void 0;
28128
+ const suffix = `-har-agent-${agentId}-`;
28129
+ const relPrefix = gitPrefix2(harnessRoot);
28130
+ const matches = fs23.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path21.join(worktreesRoot, entry.name)).filter(
28131
+ (candidate) => sameGitCheckout2(harnessRoot, candidate) && fs23.existsSync(path21.join(candidate, relPrefix, `.env.agent.${agentId}`))
28132
+ );
28133
+ return matches.sort()[0];
28134
+ }
28067
28135
  function collectWorktreeDrift(harnessRoot, worktreePath, baseCommit) {
28068
28136
  const drift = {};
28069
28137
  drift.detachedHead = runGit(worktreePath, "symbolic-ref -q HEAD") === void 0;
@@ -28112,12 +28180,12 @@ function collectSlotStatus(harnessRoot, agentId, runs) {
28112
28180
  "worktrees",
28113
28181
  `${projectName}-agent-${agentId}`
28114
28182
  );
28115
- const worktreePath = session?.worktreePath && fs23.existsSync(session.worktreePath) ? session.worktreePath : fs23.existsSync(legacyWorktreePath) ? legacyWorktreePath : void 0;
28183
+ const worktreePath = session?.worktreePath && fs23.existsSync(session.worktreePath) ? session.worktreePath : fs23.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
28116
28184
  const workDir = resolveAgentWorkDir(harnessRoot, agentId);
28117
28185
  const envInWorkDir = workDir ? fs23.existsSync(path21.join(workDir, `.env.agent.${agentId}`)) : false;
28118
28186
  const envInRoot = fs23.existsSync(path21.join(harnessRoot, `.env.agent.${agentId}`));
28119
28187
  const envInWorktree = worktreePath ? fs23.existsSync(path21.join(worktreePath, `.env.agent.${agentId}`)) : false;
28120
- const active = session !== void 0 && session.status === "active" || envInWorkDir || envInRoot || envInWorktree;
28188
+ const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
28121
28189
  const latest = latestRunForAgent(runs, agentId);
28122
28190
  const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
28123
28191
  let previewUrls = session?.previewUrls;
@@ -28147,6 +28215,8 @@ function collectSlotStatus(harnessRoot, agentId, runs) {
28147
28215
  baseCommit: session?.baseCommit,
28148
28216
  sessionCreatedAt: session?.createdAt,
28149
28217
  purpose: session?.purpose,
28218
+ sessionStatus: session?.status,
28219
+ lastError: session?.lastError,
28150
28220
  ...drift
28151
28221
  };
28152
28222
  }
@@ -28552,6 +28622,8 @@ function formatOccupiedSlot(slot) {
28552
28622
  slot.worktreePath ? ` Worktree: ${slot.worktreePath}` : void 0,
28553
28623
  slot.branch ? ` Branch: ${slot.branch}` : void 0,
28554
28624
  slot.workDir ? ` Work dir: ${slot.workDir}` : void 0,
28625
+ slot.sessionStatus ? ` Status: ${slot.sessionStatus}` : void 0,
28626
+ slot.lastError ? ` Error: ${slot.lastError}` : void 0,
28555
28627
  slot.sessionCreatedAt ? ` Since: ${slot.sessionCreatedAt}` : void 0,
28556
28628
  slot.dirty ? " Git: dirty (uncommitted changes \u2014 commit or use force to discard)" : " Git: clean",
28557
28629
  "",
@@ -29379,7 +29451,7 @@ function printNextSteps(auto2) {
29379
29451
  var path28 = __toESM(require("path"));
29380
29452
 
29381
29453
  // src/core/control-lifecycle.ts
29382
- var import_child_process3 = require("child_process");
29454
+ var import_child_process4 = require("child_process");
29383
29455
  var path27 = __toESM(require("path"));
29384
29456
 
29385
29457
  // src/core/package-version.ts
@@ -29439,7 +29511,7 @@ function runDockerCompose(args, options) {
29439
29511
  const controlDir = resolveControlDir();
29440
29512
  const composeFiles = resolveControlComposeFiles(options);
29441
29513
  const composeArgs = composeFiles.flatMap((file) => ["-f", file]);
29442
- const result = (0, import_child_process3.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
29514
+ const result = (0, import_child_process4.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
29443
29515
  cwd: controlDir,
29444
29516
  stdio: "inherit",
29445
29517
  env: buildDockerComposeEnv()
@@ -58,6 +58,29 @@ PM2 processes for the primary application only, matching how it runs in dev.
58
58
  ### `.har/verify.sh`
59
59
  Real typecheck, lint, test, and health check commands — replace all TODOs.
60
60
 
61
+ ## Readiness vs liveness (required)
62
+ Do not treat a passing health check as adaptation complete. A usable agent
63
+ environment has layered readiness:
64
+
65
+ 1. **Infra ready** — shared services and template data stores exist.
66
+ 2. **Slot data ready** — every per-slot data store is created or cloned.
67
+ 3. **Process ready** — primary app processes are online and health passes.
68
+ 4. **Agent usable** — documented credentials/workflows work, required default
69
+ data exists, and UI/API smoke is not blocked by asset/dev-server issues.
70
+
71
+ Compare the harness against the repository's full local-dev setup. If you skip
72
+ heavy steps such as full seed, optional services, asset compilation modes, or
73
+ background daemons, add the minimum substitute in `.har/` scripts or document
74
+ why no substitute is needed. If the app has multiple databases/stores, provision
75
+ all per-slot state. If launch generates config, validate the nested keys the app
76
+ actually reads. Put agent-usability checks in full verify, a project-owned
77
+ readiness script, or documented smoke URLs. Health alone is not sufficient for
78
+ UI/auth apps.
79
+
80
+ Ensure `launch.sh` writes the slot registry before slow or fragile steps, and
81
+ `verify.sh` resolves env/work dir through `agent-slot.sh` so partial launches are
82
+ recoverable.
83
+
61
84
  ### `.har/CLAUDE.agent.md`
62
85
  Detailed agent instructions: commands, credentials, architecture, definition of done.
63
86
 
@@ -101,4 +124,7 @@ Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HA
101
124
  - unused harness files deleted (`deleteHarnessFile`), e.g. `attach.sh` when unused
102
125
  - `.har/README.md` file table matches the files that actually exist
103
126
  - `CLAUDE.agent.md` shows only real URLs/ports and commands that run
127
+ - skipped full-dev setup has a minimal bootstrap or clearly documented limitation
128
+ - all per-slot data stores are provisioned, not only the primary database
129
+ - `CLAUDE.agent.md` defines what "agent usable" means, including credentials or smoke checks when applicable
104
130
  8. **Call finishAuthoring** when complete
@@ -44,6 +44,39 @@ PM2 processes for the primary application only, matching how it runs in dev. Ski
44
44
  ### `.har/verify.sh`
45
45
  Real typecheck, lint, test, and health check commands — replace all TODOs.
46
46
 
47
+ ### Readiness vs liveness (required)
48
+ Do not treat a passing health check as adaptation complete. Before finishing,
49
+ make the harness explicit about the layers that apply to this repository:
50
+
51
+ 1. **Infra ready** — shared services are running and template data stores exist.
52
+ 2. **Slot data ready** — every per-slot data store is created or cloned, not only
53
+ the primary database.
54
+ 3. **Process ready** — the primary app processes are online and
55
+ `HARNESS_HEALTH_CHECK_PATH` passes.
56
+ 4. **Agent usable** — a real workflow an agent needs is possible: documented
57
+ credentials work, a tenant/org/project exists when the app requires one, the
58
+ UI is not blocked by dev-server overlays, or an authenticated API smoke works.
59
+
60
+ Compare the harness against the project's full local-dev setup. If the harness
61
+ intentionally skips slow or heavy steps (full seed, optional services, asset
62
+ mode, background daemons), add the minimum substitute directly in `.har/`
63
+ scripts or document why no substitute is needed. In particular:
64
+
65
+ - If `HARNESS_DB_SEED_CMD` is empty or schema-only, add an idempotent minimal
66
+ bootstrap step for required tenants/users/settings, or document why schema-only
67
+ is enough.
68
+ - If the app has multiple databases, schemas, queues, object stores, search
69
+ indexes, or other per-slot state, provision all of them in `setup-infra.sh`
70
+ and `launch.sh`.
71
+ - If launch generates config, validate the nested keys the app actually reads
72
+ against upstream examples/defaults, not only top-level keys.
73
+ - If the dev server can block agents with overlays or noisy HMR failures,
74
+ choose and document an agent-friendly asset mode.
75
+ - Put Layer 3 checks in `verify --full`, a project-owned readiness script, or
76
+ documented smoke URLs. Health alone is not sufficient for UI/auth apps.
77
+ - Ensure `launch.sh` writes the slot registry before slow or fragile steps, and
78
+ `verify.sh` resolves env/work dir through `agent-slot.sh`.
79
+
47
80
  ### Optional Playwright stage
48
81
  If the user ran `har env add-stage playwright` (or `@playwright/test` is in package.json):
49
82
 
@@ -65,7 +98,7 @@ Agents run in parallel on configurable slot ids. Ports: `BASE + (AGENT_ID × 10)
65
98
  Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`) based on machine capacity.
66
99
 
67
100
  ### Git worktree
68
- `launch.sh` creates an isolated worktree at `~/worktrees/<project>-agent-<id>` by default (`HARNESS_USE_WORKTREE=true`). Agents should commit from that worktree, not the main checkout.
101
+ `launch.sh` creates an isolated session worktree at `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>` by default (`HARNESS_USE_WORKTREE=true`) and records it in `.har/slots/agent-<id>.json`. Agents should commit from that worktree, not the main checkout.
69
102
 
70
103
  ## Step 3 — Update repo-root `AGENT.md`
71
104
 
@@ -112,6 +145,9 @@ The boilerplate ships more than any single repository needs. Strip it down to st
112
145
  - [ ] Unused harness files are deleted (e.g. `attach.sh` when tmux isn't part of the workflow; CLI profile: `ecosystem.agent.template.cjs`, `env.template`)
113
146
  - [ ] `.har/README.md` file table lists exactly the files that exist — no more, no less
114
147
  - [ ] `.har/CLAUDE.agent.md` shows only real URLs/ports/credentials (e.g. drop the Frontend row for an API-only project) and project commands that actually run
148
+ - [ ] If full seed/setup is skipped, `.har/` provides a minimal bootstrap or clearly documents why none is needed
149
+ - [ ] All per-slot data stores are provisioned, not just the primary database
150
+ - [ ] `.har/CLAUDE.agent.md` defines what "agent usable" means for this repo: login/API smoke, credentials, required default data, and known skipped dev-setup steps
115
151
 
116
152
  ## Rules
117
153
 
@@ -28,6 +28,36 @@ Align commands and instructions with the current stack.
28
28
  ### `.har/env.template`, `setup-infra.sh`, `docker-compose.agent.yml`
29
29
  Update only if infra changed.
30
30
 
31
+ ### Readiness vs liveness regression check
32
+ Do not treat a passing health check as proof that the harness is still usable.
33
+ When maintaining an existing harness, re-check the layers that apply:
34
+
35
+ 1. **Infra ready** — shared services and template data stores still match the app.
36
+ 2. **Slot data ready** — every per-slot data store is created or cloned, not only
37
+ the primary database.
38
+ 3. **Process ready** — app processes are online and `HARNESS_HEALTH_CHECK_PATH`
39
+ passes.
40
+ 4. **Agent usable** — documented credentials/workflows still work, required
41
+ default data exists, UI/API smoke is not blocked by asset/dev-server issues,
42
+ and any skipped full-dev setup has a minimal substitute or clear limitation.
43
+
44
+ Look specifically for drift introduced since the last adaptation:
45
+
46
+ - A seed command was removed or made schema-only without a minimal bootstrap.
47
+ - A new database, schema, queue, object store, search index, or other per-slot
48
+ dependency was added but launch only provisions the original primary store.
49
+ - Config generation writes plausible top-level keys while the app reads nested
50
+ defaults from another file.
51
+ - The dev server mode is fine for humans but blocks browser automation or agents
52
+ with overlays/noisy HMR.
53
+ - `verify.sh` became health-only and no longer checks the key workflow that makes
54
+ the slot usable.
55
+ - `launch.sh` writes the slot registry only after fragile late steps; partial
56
+ launches must remain discoverable by verify/status/teardown.
57
+
58
+ Update `.har/CLAUDE.agent.md` with skipped setup steps, substitutes, credentials,
59
+ and the repo-specific definition of "agent usable."
60
+
31
61
  ### HAR platform upgrades checklist
32
62
 
33
63
  When upgrading `@osfactory/har` or adopting new harness standards:
@@ -22,9 +22,23 @@ This slot runs **only the primary application** (`HARNESS_PRIMARY_APP`). Externa
22
22
  ./.har/agent-cli.sh ${AGENT_ID} health
23
23
  ```
24
24
 
25
+ ## Readiness
26
+
27
+ Adapt this section for the repository. A passing health check means the process
28
+ is alive; it does not automatically mean an agent can use the app.
29
+
30
+ - **Health**: `./.har/agent-cli.sh ${AGENT_ID} health`
31
+ - **Agent-usable smoke**: document the login/API/UI workflow agents should try,
32
+ or wire it into `HARNESS_READINESS_CMD` / full verify.
33
+ - **Credentials/default data**: document any test users, tenants, projects, or
34
+ settings created by the harness.
35
+ - **Skipped full-dev setup**: document anything intentionally omitted from the
36
+ upstream developer setup and the minimal substitute in `.har/`.
37
+
25
38
  ## Definition of done
26
39
 
27
40
  - [ ] Full verification returns `"status": "pass"` (`har env verify ${AGENT_ID} --full`, MCP `har_run_verification` with `full: true`, or `./.har/verify.sh ${AGENT_ID} --full`)
41
+ - [ ] The slot is agent-usable for this repo's documented smoke workflow, not only health-check green
28
42
  - [ ] When `stages/browser-e2e.sh` exists, full verify includes Playwright — adapt specs under `tests/` for UI changes
29
43
  - [ ] New behavior has automated test coverage (unit and/or browser as appropriate)
30
44
  - [ ] Changes committed **in the session worktree** with a clear message
@@ -61,10 +61,27 @@ Read **`stages.json`** for registered stages and **`verificationStages`** for th
61
61
  | Mode | Command | Typical steps |
62
62
  |------|---------|---------------|
63
63
  | Quick | `har env verify <id>` or `verify.sh <id>` | Project checks in `verify.sh` (stops early on failure) |
64
- | Full | `har env verify <id> --full` or `verify.sh <id> --full` | Quick steps + lint + **`browser-e2e`** when `.har/stages/browser-e2e.sh` exists |
64
+ | Full | `har env verify <id> --full` or `verify.sh <id> --full` | Quick steps + lint + optional readiness smoke + **`browser-e2e`** when `.har/stages/browser-e2e.sh` exists |
65
65
 
66
66
  Install Playwright stage: `har env add-stage playwright` (optional). UI changes should add or update specs under `tests/`.
67
67
 
68
+ ## Readiness layers
69
+
70
+ Do not treat a health endpoint as the whole definition of success. Adapt this
71
+ section for the repository:
72
+
73
+ | Layer | What it means | Where to encode it |
74
+ |-------|---------------|--------------------|
75
+ | Infra ready | Shared services and template data stores exist | `setup-infra.sh`, `docker-compose.agent.yml` |
76
+ | Slot data ready | Every per-slot data store is created/cloned | `launch.sh` |
77
+ | Process ready | Primary app is running and health passes | `launch.sh`, `verify.sh` |
78
+ | Agent usable | Login/API/UI smoke works with documented data | `HARNESS_READINESS_CMD`, browser-e2e, `CLAUDE.agent.md` |
79
+
80
+ If full local-dev setup is too heavy to run in the harness, document the skipped
81
+ steps and add the minimum substitute directly in `.har/` scripts (for example an
82
+ idempotent bootstrap for required users/tenants/settings). Health alone is not
83
+ enough for UI/auth apps.
84
+
68
85
  ## Run history
69
86
 
70
87
  | Entry point | Writes `.har/runs/`? |
@@ -56,7 +56,13 @@ process.stdin.on('end', () => {
56
56
  fi
57
57
 
58
58
  if [ "$PM2_FOUND" = true ]; then
59
- :
59
+ REG_FILE="$(slot_registry_file "$AGENT_ID")"
60
+ if [ ! -f "$REG_FILE" ]; then
61
+ echo "Warning: slot registry missing at $REG_FILE — verify/teardown may not resolve this slot."
62
+ fi
63
+ if [ -z "$ENV_FILE" ]; then
64
+ echo "Warning: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready."
65
+ fi
60
66
  elif [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
61
67
  echo "Agent ${AGENT_ID}: active (no PM2 processes)"
62
68
  # shellcheck source=/dev/null
@@ -50,7 +50,8 @@ try {
50
50
  # Writes the registry entry from SLOT_* variables:
51
51
  # required: SLOT_AGENT_ID, SLOT_MODE (worktree|root), SLOT_WORK_DIR
52
52
  # optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
53
- # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON, SLOT_PURPOSE
53
+ # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
54
+ # SLOT_PURPOSE, SLOT_STATUS, SLOT_LAST_ERROR
54
55
  write_slot_registry() {
55
56
  local file
56
57
  file="$(slot_registry_file "$SLOT_AGENT_ID")"
@@ -65,7 +66,7 @@ const entry = {
65
66
  mode: e.SLOT_MODE,
66
67
  workDir: e.SLOT_WORK_DIR,
67
68
  createdAt: new Date().toISOString(),
68
- status: "active",
69
+ status: e.SLOT_STATUS || "active",
69
70
  };
70
71
  if (e.SLOT_SUFFIX) entry.suffix = e.SLOT_SUFFIX;
71
72
  if (e.SLOT_WORKTREE_PATH) entry.worktreePath = e.SLOT_WORKTREE_PATH;
@@ -73,6 +74,7 @@ if (e.SLOT_BRANCH) entry.branch = e.SLOT_BRANCH;
73
74
  if (e.SLOT_BASE_BRANCH) entry.baseBranch = e.SLOT_BASE_BRANCH;
74
75
  if (e.SLOT_BASE_COMMIT) entry.baseCommit = e.SLOT_BASE_COMMIT;
75
76
  if (e.SLOT_PURPOSE) entry.purpose = e.SLOT_PURPOSE;
77
+ if (e.SLOT_LAST_ERROR) entry.lastError = e.SLOT_LAST_ERROR;
76
78
  for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PREVIEW_URLS_JSON"]]) {
77
79
  if (e[env]) try { entry[key] = JSON.parse(e[env]); } catch {}
78
80
  }
@@ -115,13 +117,15 @@ slot_dirty_summary() {
115
117
  # Print a warning before replacing an occupied slot (stdout — visible to agents).
116
118
  print_slot_replace_warning() {
117
119
  local agent_id="$1"
118
- local reg wt branch work_dir purpose created dirty_summary head
120
+ local reg wt branch work_dir purpose created status last_error dirty_summary head
119
121
  reg="$(slot_registry_file "$agent_id")"
120
122
  wt="$(existing_slot_worktree "$agent_id")"
121
123
  branch="$(read_slot_field "$reg" branch || true)"
122
124
  work_dir="$(read_slot_field "$reg" workDir || true)"
123
125
  purpose="$(read_slot_field "$reg" purpose || true)"
124
126
  created="$(read_slot_field "$reg" createdAt || true)"
127
+ status="$(read_slot_field "$reg" status || true)"
128
+ last_error="$(read_slot_field "$reg" lastError || true)"
125
129
  dirty_summary="$(slot_dirty_summary "$wt")"
126
130
  if [ -n "$wt" ] && [ -d "$wt" ]; then
127
131
  head="$(git -C "$wt" rev-parse --short HEAD 2>/dev/null || true)"
@@ -133,6 +137,8 @@ print_slot_replace_warning() {
133
137
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
134
138
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
135
139
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
140
+ [ -n "$status" ] && echo " Status: ${status}" >&2
141
+ [ -n "$last_error" ] && echo " Error: ${last_error}" >&2
136
142
  [ -n "$created" ] && echo " Since: ${created}" >&2
137
143
  echo " Git: ${dirty_summary}" >&2
138
144
  echo "" >&2
@@ -182,11 +188,30 @@ require_slot_replace_confirm() {
182
188
  exit 2
183
189
  }
184
190
 
191
+ git_common_dir() {
192
+ local cwd="$1"
193
+ local out
194
+ out="$(git -C "$cwd" rev-parse --git-common-dir 2>/dev/null)" || return 1
195
+ case "$out" in
196
+ /*) echo "$out" ;;
197
+ *) (cd "$cwd" && cd "$out" && pwd) ;;
198
+ esac
199
+ }
200
+
201
+ same_git_checkout() {
202
+ local left right
203
+ left="$(git_common_dir "$1" || true)"
204
+ right="$(git_common_dir "$2" || true)"
205
+ [ -n "$left" ] && [ -n "$right" ] && [ "$left" = "$right" ]
206
+ }
207
+
185
208
  # Echo the previous session's worktree path for a slot: registry first, then
186
- # the legacy fixed path (pre-registry sessions). Empty output when none exists.
209
+ # legacy and randomized session worktree fallbacks. Empty output when none exists.
187
210
  existing_slot_worktree() {
188
211
  local agent_id="$1"
189
- local reg path
212
+ local reg path candidate repo_root rel_prefix
213
+ repo_root="$(cd "$SCRIPT_DIR/.." && pwd)"
214
+ rel_prefix="$(git -C "$repo_root" rev-parse --show-prefix 2>/dev/null || true)"
190
215
  reg="$(slot_registry_file "$agent_id")"
191
216
  if [ -f "$reg" ]; then
192
217
  path="$(read_slot_field "$reg" worktreePath || true)"
@@ -198,12 +223,20 @@ existing_slot_worktree() {
198
223
  path="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${agent_id}"
199
224
  if [ -d "$path" ]; then
200
225
  echo "$path"
226
+ return 0
201
227
  fi
228
+ for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*; do
229
+ [ -d "$candidate" ] || continue
230
+ same_git_checkout "$repo_root" "$candidate" || continue
231
+ [ -f "$candidate/${rel_prefix}.env.agent.${agent_id}" ] || continue
232
+ echo "$candidate"
233
+ return 0
234
+ done
202
235
  return 0
203
236
  }
204
237
 
205
- # Resolve .env.agent.<id> — registry work dir first, then legacy locations
206
- # (repo root, pre-registry fixed worktree path).
238
+ # Resolve .env.agent.<id> — registry work dir first, then legacy and
239
+ # randomized session worktree fallbacks.
207
240
  resolve_agent_env_file() {
208
241
  local agent_id="$1"
209
242
  local repo_root="$2"
@@ -229,6 +262,15 @@ resolve_agent_env_file() {
229
262
  return 0
230
263
  fi
231
264
  done
265
+ local candidate_dir
266
+ for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*/${rel_prefix}.env.agent."${agent_id}"; do
267
+ if [ -f "$candidate" ]; then
268
+ candidate_dir="$(cd "$(dirname "$candidate")" && pwd)"
269
+ same_git_checkout "$repo_root" "$candidate_dir" || continue
270
+ echo "$candidate"
271
+ return 0
272
+ fi
273
+ done
232
274
  return 1
233
275
  }
234
276
 
@@ -278,3 +320,14 @@ run_browser_e2e_if_present() {
278
320
  "$e2e" "$agent_id"
279
321
  fi
280
322
  }
323
+
324
+ # Optional project-owned "agent usable" smoke beyond health.
325
+ run_readiness_if_configured() {
326
+ local agent_id="$1"
327
+ if [ -z "${HARNESS_READINESS_CMD:-}" ]; then
328
+ echo "No HARNESS_READINESS_CMD configured; skipping readiness smoke."
329
+ return 0
330
+ fi
331
+ local cmd="${HARNESS_READINESS_CMD//\{agentId\}/$agent_id}"
332
+ eval "$cmd"
333
+ }
@@ -48,3 +48,18 @@ har_pg() {
48
48
  # Database setup commands (run once against template DB in setup-infra.sh)
49
49
  export HARNESS_DB_MIGRATE_CMD="echo 'TODO: set migrate command'"
50
50
  export HARNESS_DB_SEED_CMD="echo 'TODO: set seed command'"
51
+
52
+ # Optional readiness/data hooks for larger apps. Enable only when this project
53
+ # needs them, and keep the scripts idempotent.
54
+ #
55
+ # Multiple per-slot data stores can be represented as logical_name:template_db
56
+ # pairs and handled directly in setup-infra.sh / launch.sh.
57
+ # export HARNESS_TEMPLATE_DBS="main:template___PROJECT_NAME__"
58
+ #
59
+ # Use a minimal bootstrap when full seed is too slow/heavy but the app still
60
+ # needs default tenants, users, settings, feature flags, or similar data.
61
+ # export HARNESS_DB_MINIMAL_BOOTSTRAP_CMD="./.har/bootstrap-data.sh"
62
+ #
63
+ # Use readiness checks for "agent usable" smoke beyond a health endpoint:
64
+ # login/API auth, representative UI page, required data, secondary stores.
65
+ # export HARNESS_READINESS_CMD="./.har/readiness.sh"
@@ -147,6 +147,55 @@ REPO_ROOT="$WORK_DIR" \
147
147
  envsubst '${AGENT_ID} ${API_PORT} ${FE_PORT} ${DEBUG_PORT} ${DB_PORT} ${MINIO_PORT} ${BROWSER_PORT} ${REPO_ROOT}' \
148
148
  < "$SCRIPT_DIR/env.template" > "$ENV_FILE"
149
149
 
150
+ REGISTRY_WRITTEN=false
151
+ mark_slot_failed() {
152
+ local exit_code="$?"
153
+ if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
154
+ log "Launch failed after creating the session. Recording failed slot state..."
155
+ set +e
156
+ SLOT_AGENT_ID="$AGENT_ID" \
157
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
158
+ SLOT_WORK_DIR="$WORK_DIR" \
159
+ SLOT_SUFFIX="${SUFFIX:-}" \
160
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
161
+ SLOT_BRANCH="${BRANCH:-}" \
162
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
163
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
164
+ SLOT_PURPOSE="${PURPOSE}" \
165
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
166
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
167
+ SLOT_STATUS="failed" \
168
+ SLOT_LAST_ERROR="launch.sh exited with code ${exit_code}" \
169
+ write_slot_registry
170
+ log " Work dir: ${WORK_DIR}"
171
+ log " Env file: ${ENV_FILE}"
172
+ log " Recovery: fix the failure, then relaunch with --replace when ready."
173
+ fi
174
+ }
175
+ trap mark_slot_failed EXIT
176
+
177
+ # Record the session before slow or fragile setup so verify/status/teardown can
178
+ # recover partial launches that already created a worktree and env file.
179
+ SLOT_AGENT_ID="$AGENT_ID" \
180
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
181
+ SLOT_WORK_DIR="$WORK_DIR" \
182
+ SLOT_SUFFIX="${SUFFIX:-}" \
183
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
184
+ SLOT_BRANCH="${BRANCH:-}" \
185
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
186
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
187
+ SLOT_PURPOSE="${PURPOSE}" \
188
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
189
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
190
+ SLOT_STATUS="starting" \
191
+ write_slot_registry
192
+ REGISTRY_WRITTEN=true
193
+
194
+ if [ -n "${HARNESS_DB_MINIMAL_BOOTSTRAP_CMD:-}" ]; then
195
+ log "Running minimal data bootstrap..."
196
+ (cd "$WORK_DIR" && set -a && . "$ENV_FILE" && set +a && eval "$HARNESS_DB_MINIMAL_BOOTSTRAP_CMD")
197
+ fi
198
+
150
199
  # Generate PM2 ecosystem config
151
200
  ECOSYSTEM_FILE="$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
152
201
  log "Generating $ECOSYSTEM_FILE..."
@@ -199,6 +248,7 @@ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
199
248
  SLOT_PURPOSE="${PURPOSE}" \
200
249
  SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
201
250
  SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
251
+ SLOT_STATUS="active" \
202
252
  write_slot_registry
203
253
 
204
254
  # Launch Claude Code in tmux (optional)
@@ -20,7 +20,7 @@
20
20
  {
21
21
  "path": ".env.agent.{agentId}",
22
22
  "kind": "file",
23
- "description": "Generated per-agent environment file"
23
+ "description": "Generated per-agent environment file in the session work dir"
24
24
  }
25
25
  ]
26
26
  },
@@ -38,6 +38,14 @@ WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
38
38
  API_PORT="${API_PORT:-$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))}"
39
39
 
40
40
  echo "==> Verifying agent ${AGENT_ID} (work dir: ${WORK_DIR})..." >&2
41
+ REG_FILE="$(slot_registry_file "$AGENT_ID")"
42
+ echo " Work dir: ${WORK_DIR}" >&2
43
+ echo " Env file: ${ENV_FILE}" >&2
44
+ if [ -f "$REG_FILE" ]; then
45
+ echo " Registry: ${REG_FILE}" >&2
46
+ else
47
+ echo " Registry: missing (${REG_FILE})" >&2
48
+ fi
41
49
 
42
50
  OVERALL_PASS=true
43
51
  START_TOTAL=$(now_ms)
@@ -134,6 +142,7 @@ run_http_step "api-health" "http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_P
134
142
 
135
143
  if [ -n "$FULL" ]; then
136
144
  run_step "lint" "echo 'TODO: npm run lint'" || true
145
+ run_step "readiness" "run_readiness_if_configured \"$AGENT_ID\"" || true
137
146
  run_step "browser-e2e" "run_browser_e2e_if_present \"$SCRIPT_DIR\" \"$AGENT_ID\"" || true
138
147
  fi
139
148
 
@@ -15,9 +15,17 @@
15
15
  ./.har/agent-cli.sh ${AGENT_ID} status
16
16
  ```
17
17
 
18
+ ## Readiness
19
+
20
+ Adapt this section for the repository. For pure CLI/library repos, full verify
21
+ may be enough. If the project needs services, auth, seeded data, or a sample
22
+ workflow, document the required credentials/default data and wire a smoke into
23
+ `HARNESS_READINESS_CMD` or full verify.
24
+
18
25
  ## Definition of done
19
26
 
20
27
  - [ ] Full verification returns `"status": "pass"` (`har env verify ${AGENT_ID} --full`, MCP `har_run_verification` with `full: true`, or `./.har/verify.sh ${AGENT_ID} --full`)
28
+ - [ ] The slot is agent-usable for this repo's documented smoke workflow when runtime services are involved
21
29
  - [ ] When `stages/browser-e2e.sh` exists, full verify includes Playwright — adapt specs under `tests/` for UI changes
22
30
  - [ ] New behavior has automated test coverage
23
31
  - [ ] Changes committed **in the session worktree** with a clear message
@@ -59,7 +59,12 @@ Read **`stages.json`** and **`verificationStages`**. Optional: `har env add-stag
59
59
  | Mode | Command | Typical steps |
60
60
  |------|---------|---------------|
61
61
  | Quick | `har env verify <id>` or `verify.sh <id>` | typecheck, unit tests |
62
- | Full | `har env verify <id> --full` or `verify.sh <id> --full` | + lint, build, **browser-e2e** when `stages/browser-e2e.sh` exists |
62
+ | Full | `har env verify <id> --full` or `verify.sh <id> --full` | + lint, optional readiness smoke, **browser-e2e** when `stages/browser-e2e.sh` exists |
63
+
64
+ For repos that need runtime services, distinguish health from usability. If the
65
+ harness skips slow local-dev setup, document the skipped steps and add a minimal
66
+ bootstrap/readiness check when agents need default data, credentials, or an
67
+ authenticated workflow.
63
68
 
64
69
  Use `har env launch 1 --no-worktree` or `./.har/launch.sh 1 --no-worktree` only when working in the repo root.
65
70
 
@@ -50,7 +50,8 @@ try {
50
50
  # Writes the registry entry from SLOT_* variables:
51
51
  # required: SLOT_AGENT_ID, SLOT_MODE (worktree|root), SLOT_WORK_DIR
52
52
  # optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
53
- # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON, SLOT_PURPOSE
53
+ # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
54
+ # SLOT_PURPOSE, SLOT_STATUS, SLOT_LAST_ERROR
54
55
  write_slot_registry() {
55
56
  local file
56
57
  file="$(slot_registry_file "$SLOT_AGENT_ID")"
@@ -65,7 +66,7 @@ const entry = {
65
66
  mode: e.SLOT_MODE,
66
67
  workDir: e.SLOT_WORK_DIR,
67
68
  createdAt: new Date().toISOString(),
68
- status: "active",
69
+ status: e.SLOT_STATUS || "active",
69
70
  };
70
71
  if (e.SLOT_SUFFIX) entry.suffix = e.SLOT_SUFFIX;
71
72
  if (e.SLOT_WORKTREE_PATH) entry.worktreePath = e.SLOT_WORKTREE_PATH;
@@ -73,6 +74,7 @@ if (e.SLOT_BRANCH) entry.branch = e.SLOT_BRANCH;
73
74
  if (e.SLOT_BASE_BRANCH) entry.baseBranch = e.SLOT_BASE_BRANCH;
74
75
  if (e.SLOT_BASE_COMMIT) entry.baseCommit = e.SLOT_BASE_COMMIT;
75
76
  if (e.SLOT_PURPOSE) entry.purpose = e.SLOT_PURPOSE;
77
+ if (e.SLOT_LAST_ERROR) entry.lastError = e.SLOT_LAST_ERROR;
76
78
  for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PREVIEW_URLS_JSON"]]) {
77
79
  if (e[env]) try { entry[key] = JSON.parse(e[env]); } catch {}
78
80
  }
@@ -115,13 +117,15 @@ slot_dirty_summary() {
115
117
  # Print a warning before replacing an occupied slot (stdout — visible to agents).
116
118
  print_slot_replace_warning() {
117
119
  local agent_id="$1"
118
- local reg wt branch work_dir purpose created dirty_summary head
120
+ local reg wt branch work_dir purpose created status last_error dirty_summary head
119
121
  reg="$(slot_registry_file "$agent_id")"
120
122
  wt="$(existing_slot_worktree "$agent_id")"
121
123
  branch="$(read_slot_field "$reg" branch || true)"
122
124
  work_dir="$(read_slot_field "$reg" workDir || true)"
123
125
  purpose="$(read_slot_field "$reg" purpose || true)"
124
126
  created="$(read_slot_field "$reg" createdAt || true)"
127
+ status="$(read_slot_field "$reg" status || true)"
128
+ last_error="$(read_slot_field "$reg" lastError || true)"
125
129
  dirty_summary="$(slot_dirty_summary "$wt")"
126
130
  if [ -n "$wt" ] && [ -d "$wt" ]; then
127
131
  head="$(git -C "$wt" rev-parse --short HEAD 2>/dev/null || true)"
@@ -133,6 +137,8 @@ print_slot_replace_warning() {
133
137
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
134
138
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
135
139
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
140
+ [ -n "$status" ] && echo " Status: ${status}" >&2
141
+ [ -n "$last_error" ] && echo " Error: ${last_error}" >&2
136
142
  [ -n "$created" ] && echo " Since: ${created}" >&2
137
143
  echo " Git: ${dirty_summary}" >&2
138
144
  echo "" >&2
@@ -182,11 +188,30 @@ require_slot_replace_confirm() {
182
188
  exit 2
183
189
  }
184
190
 
191
+ git_common_dir() {
192
+ local cwd="$1"
193
+ local out
194
+ out="$(git -C "$cwd" rev-parse --git-common-dir 2>/dev/null)" || return 1
195
+ case "$out" in
196
+ /*) echo "$out" ;;
197
+ *) (cd "$cwd" && cd "$out" && pwd) ;;
198
+ esac
199
+ }
200
+
201
+ same_git_checkout() {
202
+ local left right
203
+ left="$(git_common_dir "$1" || true)"
204
+ right="$(git_common_dir "$2" || true)"
205
+ [ -n "$left" ] && [ -n "$right" ] && [ "$left" = "$right" ]
206
+ }
207
+
185
208
  # Echo the previous session's worktree path for a slot: registry first, then
186
- # the legacy fixed path (pre-registry sessions). Empty output when none exists.
209
+ # legacy and randomized session worktree fallbacks. Empty output when none exists.
187
210
  existing_slot_worktree() {
188
211
  local agent_id="$1"
189
- local reg path
212
+ local reg path candidate repo_root rel_prefix
213
+ repo_root="$(cd "$SCRIPT_DIR/.." && pwd)"
214
+ rel_prefix="$(git -C "$repo_root" rev-parse --show-prefix 2>/dev/null || true)"
190
215
  reg="$(slot_registry_file "$agent_id")"
191
216
  if [ -f "$reg" ]; then
192
217
  path="$(read_slot_field "$reg" worktreePath || true)"
@@ -198,12 +223,20 @@ existing_slot_worktree() {
198
223
  path="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${agent_id}"
199
224
  if [ -d "$path" ]; then
200
225
  echo "$path"
226
+ return 0
201
227
  fi
228
+ for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*; do
229
+ [ -d "$candidate" ] || continue
230
+ same_git_checkout "$repo_root" "$candidate" || continue
231
+ [ -f "$candidate/${rel_prefix}.env.agent.${agent_id}" ] || continue
232
+ echo "$candidate"
233
+ return 0
234
+ done
202
235
  return 0
203
236
  }
204
237
 
205
- # Resolve .env.agent.<id> — registry work dir first, then legacy locations
206
- # (repo root, pre-registry fixed worktree path).
238
+ # Resolve .env.agent.<id> — registry work dir first, then legacy and
239
+ # randomized session worktree fallbacks.
207
240
  resolve_agent_env_file() {
208
241
  local agent_id="$1"
209
242
  local repo_root="$2"
@@ -229,6 +262,15 @@ resolve_agent_env_file() {
229
262
  return 0
230
263
  fi
231
264
  done
265
+ local candidate_dir
266
+ for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*/${rel_prefix}.env.agent."${agent_id}"; do
267
+ if [ -f "$candidate" ]; then
268
+ candidate_dir="$(cd "$(dirname "$candidate")" && pwd)"
269
+ same_git_checkout "$repo_root" "$candidate_dir" || continue
270
+ echo "$candidate"
271
+ return 0
272
+ fi
273
+ done
232
274
  return 1
233
275
  }
234
276
 
@@ -278,3 +320,14 @@ run_browser_e2e_if_present() {
278
320
  "$e2e" "$agent_id"
279
321
  fi
280
322
  }
323
+
324
+ # Optional project-owned "agent usable" smoke beyond health.
325
+ run_readiness_if_configured() {
326
+ local agent_id="$1"
327
+ if [ -z "${HARNESS_READINESS_CMD:-}" ]; then
328
+ echo "No HARNESS_READINESS_CMD configured; skipping readiness smoke."
329
+ return 0
330
+ fi
331
+ local cmd="${HARNESS_READINESS_CMD//\{agentId\}/$agent_id}"
332
+ eval "$cmd"
333
+ }
@@ -38,3 +38,11 @@ har_pg() {
38
38
 
39
39
  export HARNESS_DB_MIGRATE_CMD="true"
40
40
  export HARNESS_DB_SEED_CMD="true"
41
+
42
+ # Optional readiness/data hooks for repositories that need more than static
43
+ # CLI/library checks. Enable only when this project needs them, and keep scripts
44
+ # idempotent.
45
+ #
46
+ # export HARNESS_TEMPLATE_DBS="main:template___PROJECT_NAME__"
47
+ # export HARNESS_DB_MINIMAL_BOOTSTRAP_CMD="./.har/bootstrap-data.sh"
48
+ # export HARNESS_READINESS_CMD="./.har/readiness.sh"
@@ -94,6 +94,46 @@ WORKTREE_DIR=${WORKTREE_DIR}
94
94
  NODE_ENV=test
95
95
  EOF
96
96
 
97
+ REGISTRY_WRITTEN=false
98
+ mark_slot_failed() {
99
+ local exit_code="$?"
100
+ if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
101
+ log "Launch failed after creating the session. Recording failed slot state..."
102
+ set +e
103
+ SLOT_AGENT_ID="$AGENT_ID" \
104
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
105
+ SLOT_WORK_DIR="$WORK_DIR" \
106
+ SLOT_SUFFIX="${SUFFIX:-}" \
107
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
108
+ SLOT_BRANCH="${BRANCH:-}" \
109
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
110
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
111
+ SLOT_PURPOSE="${PURPOSE}" \
112
+ SLOT_STATUS="failed" \
113
+ SLOT_LAST_ERROR="launch.sh exited with code ${exit_code}" \
114
+ write_slot_registry
115
+ log " Work dir: ${WORK_DIR}"
116
+ log " Env file: ${ENV_FILE}"
117
+ log " Recovery: fix the failure, then relaunch with --replace when ready."
118
+ fi
119
+ }
120
+ trap mark_slot_failed EXIT
121
+
122
+ # Record the session before slow or fragile setup so verify/status/teardown can
123
+ # recover partial launches that already created a worktree and env file.
124
+ SLOT_AGENT_ID="$AGENT_ID" \
125
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
126
+ SLOT_WORK_DIR="$WORK_DIR" \
127
+ SLOT_SUFFIX="${SUFFIX:-}" \
128
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
129
+ SLOT_BRANCH="${BRANCH:-}" \
130
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
131
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
132
+ SLOT_PURPOSE="${PURPOSE}" \
133
+ SLOT_STATUS="starting" \
134
+ write_slot_registry
135
+ REGISTRY_WRITTEN=true
136
+
97
137
  if [ -f "$WORK_DIR/package.json" ]; then
98
138
  log "Installing dependencies..."
99
139
  (cd "$WORK_DIR" && npm install --silent)
@@ -118,6 +158,7 @@ SLOT_BRANCH="${BRANCH:-}" \
118
158
  SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
119
159
  SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
120
160
  SLOT_PURPOSE="${PURPOSE}" \
161
+ SLOT_STATUS="active" \
121
162
  write_slot_registry
122
163
 
123
164
  log "Agent $AGENT_ID is ready."
@@ -20,7 +20,7 @@
20
20
  {
21
21
  "path": ".env.agent.{agentId}",
22
22
  "kind": "file",
23
- "description": "Generated per-agent environment file"
23
+ "description": "Generated per-agent environment file in the session work dir"
24
24
  }
25
25
  ]
26
26
  },
@@ -35,6 +35,14 @@ set +a
35
35
  WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
36
36
 
37
37
  echo "==> Verifying agent ${AGENT_ID} in ${WORK_DIR}..." >&2
38
+ REG_FILE="$(slot_registry_file "$AGENT_ID")"
39
+ echo " Work dir: ${WORK_DIR}" >&2
40
+ echo " Env file: ${ENV_FILE}" >&2
41
+ if [ -f "$REG_FILE" ]; then
42
+ echo " Registry: ${REG_FILE}" >&2
43
+ else
44
+ echo " Registry: missing (${REG_FILE})" >&2
45
+ fi
38
46
 
39
47
  OVERALL_PASS=true
40
48
  START_TOTAL=$(now_ms)
@@ -88,6 +96,7 @@ run_step "unit-tests" "npm test" || { [ -z "$FULL" ] && true; }
88
96
 
89
97
  if [ -n "$FULL" ]; then
90
98
  run_step "lint" "npm run lint" || true
99
+ run_step "readiness" "run_readiness_if_configured \"$AGENT_ID\"" || true
91
100
  run_step "browser-e2e" "run_browser_e2e_if_present \"$SCRIPT_DIR\" \"$AGENT_ID\"" || true
92
101
  fi
93
102
 
@@ -17,9 +17,17 @@
17
17
  ./.har/agent-cli.sh ${AGENT_ID} status
18
18
  ```
19
19
 
20
+ ## Readiness
21
+
22
+ Adapt this section for the repository. A successful build/test run does not
23
+ always mean an agent can use the app. If the app needs a backend, credentials,
24
+ seeded data, or a simulator flow, document it here and wire the smoke into
25
+ `HARNESS_READINESS_CMD`, RocketSim flows, or full verify.
26
+
20
27
  ## Definition of done
21
28
 
22
29
  - [ ] Full verification returns `"status": "pass"` (`har env verify ${AGENT_ID} --full`, MCP `har_run_verification` with `full: true`, or `./.har/verify.sh ${AGENT_ID} --full`)
30
+ - [ ] The slot is agent-usable for this repo's documented smoke workflow when runtime services are involved
23
31
  - [ ] When `stages/rocketsim-flows.sh` exists, full verify includes user-flow validation — add or update flow scripts in `flows/` for UI changes
24
32
  - [ ] New behavior has automated test coverage (unit tests via XCTest)
25
33
  - [ ] Changes committed **in the session worktree** with a clear message
@@ -67,7 +67,11 @@ This installs `.har/stages/rocketsim-flows.sh` and a `flows/` directory. Add flo
67
67
  | Mode | Command | Typical steps |
68
68
  |------|---------|---------------|
69
69
  | Quick | `har env verify <id>` | build, unit-tests |
70
- | Full | `har env verify <id> --full` | + lint, **rocketsim-flows** when installed |
70
+ | Full | `har env verify <id> --full` | + lint, optional readiness smoke, **rocketsim-flows** when installed |
71
+
72
+ For apps that depend on local backends, auth, seeded state, or simulator flows,
73
+ distinguish build/test health from agent usability. Document any skipped full
74
+ dev setup and add a readiness command when agents need a real workflow to pass.
71
75
 
72
76
  ## Configuration
73
77
 
@@ -50,7 +50,8 @@ try {
50
50
  # Writes the registry entry from SLOT_* variables:
51
51
  # required: SLOT_AGENT_ID, SLOT_MODE (worktree|root), SLOT_WORK_DIR
52
52
  # optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
53
- # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON, SLOT_PURPOSE
53
+ # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
54
+ # SLOT_PURPOSE, SLOT_STATUS, SLOT_LAST_ERROR
54
55
  write_slot_registry() {
55
56
  local file
56
57
  file="$(slot_registry_file "$SLOT_AGENT_ID")"
@@ -65,7 +66,7 @@ const entry = {
65
66
  mode: e.SLOT_MODE,
66
67
  workDir: e.SLOT_WORK_DIR,
67
68
  createdAt: new Date().toISOString(),
68
- status: "active",
69
+ status: e.SLOT_STATUS || "active",
69
70
  };
70
71
  if (e.SLOT_SUFFIX) entry.suffix = e.SLOT_SUFFIX;
71
72
  if (e.SLOT_WORKTREE_PATH) entry.worktreePath = e.SLOT_WORKTREE_PATH;
@@ -73,6 +74,7 @@ if (e.SLOT_BRANCH) entry.branch = e.SLOT_BRANCH;
73
74
  if (e.SLOT_BASE_BRANCH) entry.baseBranch = e.SLOT_BASE_BRANCH;
74
75
  if (e.SLOT_BASE_COMMIT) entry.baseCommit = e.SLOT_BASE_COMMIT;
75
76
  if (e.SLOT_PURPOSE) entry.purpose = e.SLOT_PURPOSE;
77
+ if (e.SLOT_LAST_ERROR) entry.lastError = e.SLOT_LAST_ERROR;
76
78
  for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PREVIEW_URLS_JSON"]]) {
77
79
  if (e[env]) try { entry[key] = JSON.parse(e[env]); } catch {}
78
80
  }
@@ -115,13 +117,15 @@ slot_dirty_summary() {
115
117
  # Print a warning before replacing an occupied slot (stdout — visible to agents).
116
118
  print_slot_replace_warning() {
117
119
  local agent_id="$1"
118
- local reg wt branch work_dir purpose created dirty_summary head
120
+ local reg wt branch work_dir purpose created status last_error dirty_summary head
119
121
  reg="$(slot_registry_file "$agent_id")"
120
122
  wt="$(existing_slot_worktree "$agent_id")"
121
123
  branch="$(read_slot_field "$reg" branch || true)"
122
124
  work_dir="$(read_slot_field "$reg" workDir || true)"
123
125
  purpose="$(read_slot_field "$reg" purpose || true)"
124
126
  created="$(read_slot_field "$reg" createdAt || true)"
127
+ status="$(read_slot_field "$reg" status || true)"
128
+ last_error="$(read_slot_field "$reg" lastError || true)"
125
129
  dirty_summary="$(slot_dirty_summary "$wt")"
126
130
  if [ -n "$wt" ] && [ -d "$wt" ]; then
127
131
  head="$(git -C "$wt" rev-parse --short HEAD 2>/dev/null || true)"
@@ -133,6 +137,8 @@ print_slot_replace_warning() {
133
137
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
134
138
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
135
139
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
140
+ [ -n "$status" ] && echo " Status: ${status}" >&2
141
+ [ -n "$last_error" ] && echo " Error: ${last_error}" >&2
136
142
  [ -n "$created" ] && echo " Since: ${created}" >&2
137
143
  echo " Git: ${dirty_summary}" >&2
138
144
  echo "" >&2
@@ -182,11 +188,30 @@ require_slot_replace_confirm() {
182
188
  exit 2
183
189
  }
184
190
 
191
+ git_common_dir() {
192
+ local cwd="$1"
193
+ local out
194
+ out="$(git -C "$cwd" rev-parse --git-common-dir 2>/dev/null)" || return 1
195
+ case "$out" in
196
+ /*) echo "$out" ;;
197
+ *) (cd "$cwd" && cd "$out" && pwd) ;;
198
+ esac
199
+ }
200
+
201
+ same_git_checkout() {
202
+ local left right
203
+ left="$(git_common_dir "$1" || true)"
204
+ right="$(git_common_dir "$2" || true)"
205
+ [ -n "$left" ] && [ -n "$right" ] && [ "$left" = "$right" ]
206
+ }
207
+
185
208
  # Echo the previous session's worktree path for a slot: registry first, then
186
- # the legacy fixed path (pre-registry sessions). Empty output when none exists.
209
+ # legacy and randomized session worktree fallbacks. Empty output when none exists.
187
210
  existing_slot_worktree() {
188
211
  local agent_id="$1"
189
- local reg path
212
+ local reg path candidate repo_root rel_prefix
213
+ repo_root="$(cd "$SCRIPT_DIR/.." && pwd)"
214
+ rel_prefix="$(git -C "$repo_root" rev-parse --show-prefix 2>/dev/null || true)"
190
215
  reg="$(slot_registry_file "$agent_id")"
191
216
  if [ -f "$reg" ]; then
192
217
  path="$(read_slot_field "$reg" worktreePath || true)"
@@ -198,12 +223,20 @@ existing_slot_worktree() {
198
223
  path="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${agent_id}"
199
224
  if [ -d "$path" ]; then
200
225
  echo "$path"
226
+ return 0
201
227
  fi
228
+ for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*; do
229
+ [ -d "$candidate" ] || continue
230
+ same_git_checkout "$repo_root" "$candidate" || continue
231
+ [ -f "$candidate/${rel_prefix}.env.agent.${agent_id}" ] || continue
232
+ echo "$candidate"
233
+ return 0
234
+ done
202
235
  return 0
203
236
  }
204
237
 
205
- # Resolve .env.agent.<id> — registry work dir first, then legacy locations
206
- # (repo root, pre-registry fixed worktree path).
238
+ # Resolve .env.agent.<id> — registry work dir first, then legacy and
239
+ # randomized session worktree fallbacks.
207
240
  resolve_agent_env_file() {
208
241
  local agent_id="$1"
209
242
  local repo_root="$2"
@@ -229,6 +262,15 @@ resolve_agent_env_file() {
229
262
  return 0
230
263
  fi
231
264
  done
265
+ local candidate_dir
266
+ for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*/${rel_prefix}.env.agent."${agent_id}"; do
267
+ if [ -f "$candidate" ]; then
268
+ candidate_dir="$(cd "$(dirname "$candidate")" && pwd)"
269
+ same_git_checkout "$repo_root" "$candidate_dir" || continue
270
+ echo "$candidate"
271
+ return 0
272
+ fi
273
+ done
232
274
  return 1
233
275
  }
234
276
 
@@ -278,3 +320,14 @@ run_browser_e2e_if_present() {
278
320
  "$e2e" "$agent_id"
279
321
  fi
280
322
  }
323
+
324
+ # Optional project-owned "agent usable" smoke beyond health.
325
+ run_readiness_if_configured() {
326
+ local agent_id="$1"
327
+ if [ -z "${HARNESS_READINESS_CMD:-}" ]; then
328
+ echo "No HARNESS_READINESS_CMD configured; skipping readiness smoke."
329
+ return 0
330
+ fi
331
+ local cmd="${HARNESS_READINESS_CMD//\{agentId\}/$agent_id}"
332
+ eval "$cmd"
333
+ }
@@ -40,3 +40,7 @@ har_infra_enabled() {
40
40
  *) return 1 ;;
41
41
  esac
42
42
  }
43
+
44
+ # Optional "agent usable" smoke beyond build/test, such as a simulator flow or
45
+ # backend-auth check. Enable only when this project needs it.
46
+ # export HARNESS_READINESS_CMD="./.har/readiness.sh"
@@ -94,6 +94,46 @@ WORKTREE_DIR=${WORKTREE_DIR}
94
94
  NODE_ENV=test
95
95
  EOF
96
96
 
97
+ REGISTRY_WRITTEN=false
98
+ mark_slot_failed() {
99
+ local exit_code="$?"
100
+ if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
101
+ log "Launch failed after creating the session. Recording failed slot state..."
102
+ set +e
103
+ SLOT_AGENT_ID="$AGENT_ID" \
104
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
105
+ SLOT_WORK_DIR="$WORK_DIR" \
106
+ SLOT_SUFFIX="${SUFFIX:-}" \
107
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
108
+ SLOT_BRANCH="${BRANCH:-}" \
109
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
110
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
111
+ SLOT_PURPOSE="${PURPOSE}" \
112
+ SLOT_STATUS="failed" \
113
+ SLOT_LAST_ERROR="launch.sh exited with code ${exit_code}" \
114
+ write_slot_registry
115
+ log " Work dir: ${WORK_DIR}"
116
+ log " Env file: ${ENV_FILE}"
117
+ log " Recovery: fix the failure, then relaunch with --replace when ready."
118
+ fi
119
+ }
120
+ trap mark_slot_failed EXIT
121
+
122
+ # Record the session before slow or fragile setup so verify/status/teardown can
123
+ # recover partial launches that already created a worktree and env file.
124
+ SLOT_AGENT_ID="$AGENT_ID" \
125
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
126
+ SLOT_WORK_DIR="$WORK_DIR" \
127
+ SLOT_SUFFIX="${SUFFIX:-}" \
128
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
129
+ SLOT_BRANCH="${BRANCH:-}" \
130
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
131
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
132
+ SLOT_PURPOSE="${PURPOSE}" \
133
+ SLOT_STATUS="starting" \
134
+ write_slot_registry
135
+ REGISTRY_WRITTEN=true
136
+
97
137
  if [ -f "$WORK_DIR/package.json" ]; then
98
138
  log "Installing dependencies..."
99
139
  (cd "$WORK_DIR" && npm install --silent)
@@ -118,6 +158,7 @@ SLOT_BRANCH="${BRANCH:-}" \
118
158
  SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
119
159
  SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
120
160
  SLOT_PURPOSE="${PURPOSE}" \
161
+ SLOT_STATUS="active" \
121
162
  write_slot_registry
122
163
 
123
164
  log "Agent $AGENT_ID is ready."
@@ -20,7 +20,7 @@
20
20
  {
21
21
  "path": ".env.agent.{agentId}",
22
22
  "kind": "file",
23
- "description": "Generated per-agent environment file"
23
+ "description": "Generated per-agent environment file in the session work dir"
24
24
  }
25
25
  ]
26
26
  },
@@ -38,6 +38,14 @@ set +a
38
38
  WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
39
39
 
40
40
  echo "==> Verifying agent ${AGENT_ID} in ${WORK_DIR}..." >&2
41
+ REG_FILE="$(slot_registry_file "$AGENT_ID")"
42
+ echo " Work dir: ${WORK_DIR}" >&2
43
+ echo " Env file: ${ENV_FILE}" >&2
44
+ if [ -f "$REG_FILE" ]; then
45
+ echo " Registry: ${REG_FILE}" >&2
46
+ else
47
+ echo " Registry: missing (${REG_FILE})" >&2
48
+ fi
41
49
 
42
50
  # Build xcodebuild project/workspace flags
43
51
  xc_target_flags() {
@@ -139,6 +147,8 @@ if [ -n "$FULL" ]; then
139
147
  run_step "lint" "command -v \"${HARNESS_SWIFTLINT_CMD:-swiftlint}\" >/dev/null 2>&1 && \
140
148
  \"${HARNESS_SWIFTLINT_CMD:-swiftlint}\" --quiet 2>&1 || echo 'swiftlint not installed — skipping'" || true
141
149
 
150
+ run_step "readiness" "run_readiness_if_configured \"$AGENT_ID\"" || true
151
+
142
152
  # RocketSim user-flow validation — installed via: har env add-stage rocketsim
143
153
  run_rocketsim_flows_if_present "$SCRIPT_DIR" "$AGENT_ID" || true
144
154
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfactory/har",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "LLM-powered CLI for reproducible agent development environments",
5
5
  "keywords": [
6
6
  "har",