@osfactory/har 0.14.0 → 0.14.2

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 (26) hide show
  1. package/dist/index.js +829 -718
  2. package/dist/prompts/system-authoring.md +1 -1
  3. package/dist/templates/adaptation-prompt-init.md +1 -1
  4. package/dist/templates/cursor-rule.mdc.template +3 -3
  5. package/dist/templates/har-boilerplate/README.md +1 -1
  6. package/dist/templates/har-boilerplate/agent-cli.sh +1 -1
  7. package/dist/templates/har-boilerplate/agent-slot.sh +38 -1
  8. package/dist/templates/har-boilerplate/gitignore.template +15 -0
  9. package/dist/templates/har-boilerplate/harness.env +2 -1
  10. package/dist/templates/har-boilerplate/launch.sh +3 -2
  11. package/dist/templates/har-boilerplate/verify.sh +2 -1
  12. package/dist/templates/har-boilerplate-cli/agent-cli.sh +2 -2
  13. package/dist/templates/har-boilerplate-cli/agent-slot.sh +38 -1
  14. package/dist/templates/har-boilerplate-cli/gitignore.template +15 -0
  15. package/dist/templates/har-boilerplate-cli/launch.sh +3 -2
  16. package/dist/templates/har-boilerplate-cli/verify.sh +2 -1
  17. package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +57 -0
  18. package/dist/templates/har-boilerplate-ios/agent-cli.sh +2 -2
  19. package/dist/templates/har-boilerplate-ios/agent-slot.sh +38 -1
  20. package/dist/templates/har-boilerplate-ios/gitignore.template +16 -0
  21. package/dist/templates/har-boilerplate-ios/launch.sh +1 -0
  22. package/dist/templates/har-boilerplate-ios/verify.sh +2 -1
  23. package/dist/templates/stage-templates/custom-stage-skeleton.sh +2 -1
  24. package/dist/templates/stage-templates/playwright/.har/stages/browser-e2e.sh +2 -1
  25. package/dist/templates/stage-templates/rocketsim/.har/stages/rocketsim-flows.sh +2 -1
  26. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -765,14 +765,14 @@ var require_url_state_machine = __commonJS({
765
765
  return url.replace(/\u0009|\u000A|\u000D/g, "");
766
766
  }
767
767
  function shortenPath(url) {
768
- const path47 = url.path;
769
- if (path47.length === 0) {
768
+ const path48 = url.path;
769
+ if (path48.length === 0) {
770
770
  return;
771
771
  }
772
- if (url.scheme === "file" && path47.length === 1 && isNormalizedWindowsDriveLetter(path47[0])) {
772
+ if (url.scheme === "file" && path48.length === 1 && isNormalizedWindowsDriveLetter(path48[0])) {
773
773
  return;
774
774
  }
775
- path47.pop();
775
+ path48.pop();
776
776
  }
777
777
  function includesCredentials(url) {
778
778
  return url.username !== "" || url.password !== "";
@@ -6351,14 +6351,14 @@ __export(fileFromPath_exports, {
6351
6351
  fileFromPathSync: () => fileFromPathSync,
6352
6352
  isFile: () => isFile
6353
6353
  });
6354
- function createFileFromPath(path47, { mtimeMs, size }, filenameOrOptions, options = {}) {
6354
+ function createFileFromPath(path48, { mtimeMs, size }, filenameOrOptions, options = {}) {
6355
6355
  let filename;
6356
6356
  if (isPlainObject_default2(filenameOrOptions)) {
6357
6357
  [options, filename] = [filenameOrOptions, void 0];
6358
6358
  } else {
6359
6359
  filename = filenameOrOptions;
6360
6360
  }
6361
- const file = new FileFromPath({ path: path47, size, lastModified: mtimeMs });
6361
+ const file = new FileFromPath({ path: path48, size, lastModified: mtimeMs });
6362
6362
  if (!filename) {
6363
6363
  filename = file.name;
6364
6364
  }
@@ -6367,13 +6367,13 @@ function createFileFromPath(path47, { mtimeMs, size }, filenameOrOptions, option
6367
6367
  lastModified: file.lastModified
6368
6368
  });
6369
6369
  }
6370
- function fileFromPathSync(path47, filenameOrOptions, options = {}) {
6371
- const stats = (0, import_fs5.statSync)(path47);
6372
- return createFileFromPath(path47, stats, filenameOrOptions, options);
6370
+ function fileFromPathSync(path48, filenameOrOptions, options = {}) {
6371
+ const stats = (0, import_fs5.statSync)(path48);
6372
+ return createFileFromPath(path48, stats, filenameOrOptions, options);
6373
6373
  }
6374
- async function fileFromPath2(path47, filenameOrOptions, options) {
6375
- const stats = await import_fs5.promises.stat(path47);
6376
- return createFileFromPath(path47, stats, filenameOrOptions, options);
6374
+ async function fileFromPath2(path48, filenameOrOptions, options) {
6375
+ const stats = await import_fs5.promises.stat(path48);
6376
+ return createFileFromPath(path48, stats, filenameOrOptions, options);
6377
6377
  }
6378
6378
  var import_fs5, import_path5, import_node_domexception, __classPrivateFieldSet5, __classPrivateFieldGet6, _FileFromPath_path, _FileFromPath_start, MESSAGE, FileFromPath;
6379
6379
  var init_fileFromPath = __esm({
@@ -9629,8 +9629,8 @@ var require_utils2 = __commonJS({
9629
9629
  }
9630
9630
  return ind;
9631
9631
  }
9632
- function removeDotSegments(path47) {
9633
- let input = path47;
9632
+ function removeDotSegments(path48) {
9633
+ let input = path48;
9634
9634
  const output = [];
9635
9635
  let nextSlash = -1;
9636
9636
  let len = 0;
@@ -9882,8 +9882,8 @@ var require_schemes = __commonJS({
9882
9882
  wsComponent.secure = void 0;
9883
9883
  }
9884
9884
  if (wsComponent.resourceName) {
9885
- const [path47, query] = wsComponent.resourceName.split("?");
9886
- wsComponent.path = path47 && path47 !== "/" ? path47 : void 0;
9885
+ const [path48, query] = wsComponent.resourceName.split("?");
9886
+ wsComponent.path = path48 && path48 !== "/" ? path48 : void 0;
9887
9887
  wsComponent.query = query;
9888
9888
  wsComponent.resourceName = void 0;
9889
9889
  }
@@ -13276,12 +13276,12 @@ var require_dist = __commonJS({
13276
13276
  throw new Error(`Unknown format "${name}"`);
13277
13277
  return f2;
13278
13278
  };
13279
- function addFormats(ajv, list, fs40, exportName) {
13279
+ function addFormats(ajv, list, fs41, exportName) {
13280
13280
  var _a3;
13281
13281
  var _b2;
13282
13282
  (_a3 = (_b2 = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
13283
13283
  for (const f2 of list)
13284
- ajv.addFormat(f2, fs40[f2]);
13284
+ ajv.addFormat(f2, fs41[f2]);
13285
13285
  }
13286
13286
  module2.exports = exports2 = formatsPlugin;
13287
13287
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -14565,11 +14565,11 @@ var parser = new YargsParser({
14565
14565
  resolve: import_path2.resolve,
14566
14566
  // TODO: figure out a way to combine ESM and CJS coverage, such that
14567
14567
  // we can exercise all the lines below:
14568
- require: (path47) => {
14568
+ require: (path48) => {
14569
14569
  if (typeof require !== "undefined") {
14570
- return require(path47);
14571
- } else if (path47.match(/\.json$/)) {
14572
- return JSON.parse((0, import_fs2.readFileSync)(path47, "utf8"));
14570
+ return require(path48);
14571
+ } else if (path48.match(/\.json$/)) {
14572
+ return JSON.parse((0, import_fs2.readFileSync)(path48, "utf8"));
14573
14573
  } else {
14574
14574
  throw Error("only .json config files are supported in ESM");
14575
14575
  }
@@ -19250,8 +19250,8 @@ function getErrorMap() {
19250
19250
 
19251
19251
  // node_modules/zod/v3/helpers/parseUtil.js
19252
19252
  var makeIssue = (params) => {
19253
- const { data, path: path47, errorMaps, issueData } = params;
19254
- const fullPath = [...path47, ...issueData.path || []];
19253
+ const { data, path: path48, errorMaps, issueData } = params;
19254
+ const fullPath = [...path48, ...issueData.path || []];
19255
19255
  const fullIssue = {
19256
19256
  ...issueData,
19257
19257
  path: fullPath
@@ -19367,11 +19367,11 @@ var errorUtil;
19367
19367
 
19368
19368
  // node_modules/zod/v3/types.js
19369
19369
  var ParseInputLazyPath = class {
19370
- constructor(parent, value, path47, key) {
19370
+ constructor(parent, value, path48, key) {
19371
19371
  this._cachedPath = [];
19372
19372
  this.parent = parent;
19373
19373
  this.data = value;
19374
- this._path = path47;
19374
+ this._path = path48;
19375
19375
  this._key = key;
19376
19376
  }
19377
19377
  get path() {
@@ -23498,15 +23498,15 @@ function handleRemove(argv) {
23498
23498
  }
23499
23499
 
23500
23500
  // src/cli/commands/env.ts
23501
- var path40 = __toESM(require("path"));
23501
+ var path41 = __toESM(require("path"));
23502
23502
 
23503
23503
  // src/core/harness.ts
23504
- var fs19 = __toESM(require("fs"));
23505
- var path18 = __toESM(require("path"));
23504
+ var fs20 = __toESM(require("fs"));
23505
+ var path19 = __toESM(require("path"));
23506
23506
 
23507
23507
  // src/harness/generator.ts
23508
- var fs7 = __toESM(require("fs"));
23509
- var path8 = __toESM(require("path"));
23508
+ var fs10 = __toESM(require("fs"));
23509
+ var path11 = __toESM(require("path"));
23510
23510
 
23511
23511
  // src/core/gitignore.ts
23512
23512
  var fs6 = __toESM(require("fs"));
@@ -23529,6 +23529,277 @@ function ensureRootGitignorePatterns(checkoutDir) {
23529
23529
  fs6.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
23530
23530
  }
23531
23531
 
23532
+ // src/harness/gitignore-template.ts
23533
+ var fs7 = __toESM(require("fs"));
23534
+ var path8 = __toESM(require("path"));
23535
+ var HARNESS_GITIGNORE_TEMPLATE = "gitignore.template";
23536
+ var HARNESS_GITIGNORE_FILE = ".gitignore";
23537
+ function harnessFileForTemplate(templateFile) {
23538
+ return templateFile === HARNESS_GITIGNORE_TEMPLATE ? HARNESS_GITIGNORE_FILE : templateFile;
23539
+ }
23540
+ function templateFileForHarness(harnessFile) {
23541
+ return harnessFile === HARNESS_GITIGNORE_FILE ? HARNESS_GITIGNORE_TEMPLATE : harnessFile;
23542
+ }
23543
+ function resolveGitignoreTemplateSource(boilerplateDir) {
23544
+ const primary = path8.join(boilerplateDir, HARNESS_GITIGNORE_TEMPLATE);
23545
+ if (fs7.existsSync(primary)) return primary;
23546
+ const legacy = path8.join(boilerplateDir, HARNESS_GITIGNORE_FILE);
23547
+ if (fs7.existsSync(legacy)) return legacy;
23548
+ return null;
23549
+ }
23550
+ function writeHarnessGitignore(harnessDir, boilerplateDir) {
23551
+ const src = resolveGitignoreTemplateSource(boilerplateDir);
23552
+ if (!src) return;
23553
+ fs7.writeFileSync(path8.join(harnessDir, HARNESS_GITIGNORE_FILE), fs7.readFileSync(src, "utf8"));
23554
+ }
23555
+ function isExpectedHarnessOnlyFile(harnessFile, templateFiles) {
23556
+ return harnessFile === HARNESS_GITIGNORE_FILE && templateFiles.includes(HARNESS_GITIGNORE_TEMPLATE);
23557
+ }
23558
+
23559
+ // src/harness/stages.ts
23560
+ var fs9 = __toESM(require("fs"));
23561
+ var path10 = __toESM(require("path"));
23562
+
23563
+ // src/harness/env.ts
23564
+ var fs8 = __toESM(require("fs"));
23565
+ var path9 = __toESM(require("path"));
23566
+ function readHarnessEnv(repoPath) {
23567
+ const envPath = path9.join(getHarnessDir(repoPath), "harness.env");
23568
+ if (!fs8.existsSync(envPath)) return {};
23569
+ const values = {};
23570
+ for (const line of fs8.readFileSync(envPath, "utf8").split("\n")) {
23571
+ const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
23572
+ if (!match) continue;
23573
+ values[match[1]] = match[2].replace(/^"|"$/g, "");
23574
+ }
23575
+ return values;
23576
+ }
23577
+ function parseHarnessEnvInt(env3, key) {
23578
+ const raw = env3[key];
23579
+ if (raw === void 0 || raw === "") return void 0;
23580
+ const value = Number(raw);
23581
+ if (!Number.isInteger(value)) return void 0;
23582
+ return value;
23583
+ }
23584
+
23585
+ // src/harness/stages.ts
23586
+ var STAGE_REGISTRY_FILE = "stages.json";
23587
+ var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
23588
+ "launch",
23589
+ "verify",
23590
+ "test",
23591
+ "inspect",
23592
+ "reset",
23593
+ "teardown",
23594
+ "custom"
23595
+ ]);
23596
+ function getStageRegistryPath(repoPath) {
23597
+ return path10.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
23598
+ }
23599
+ function synthesizeStageRegistry(repoPath) {
23600
+ const harnessDir = getHarnessDir(repoPath);
23601
+ const stages = [];
23602
+ addStageIfRunnable(stages, harnessDir, {
23603
+ id: "setup-infra",
23604
+ kind: "setup",
23605
+ description: "Start shared harness infrastructure.",
23606
+ command: "./.har/setup-infra.sh",
23607
+ requiresAgentId: false
23608
+ });
23609
+ addStageIfRunnable(stages, harnessDir, {
23610
+ id: "launch",
23611
+ kind: "launch",
23612
+ description: "Launch an isolated agent environment slot.",
23613
+ command: "./.har/launch.sh {agentId}",
23614
+ requiresAgentId: true
23615
+ });
23616
+ addStageIfRunnable(stages, harnessDir, {
23617
+ id: "verify",
23618
+ kind: "verify",
23619
+ description: "Run the project verification pipeline.",
23620
+ command: "./.har/verify.sh {agentId}",
23621
+ requiresAgentId: true,
23622
+ acceptsArgs: ["--full"]
23623
+ });
23624
+ addStageIfRunnable(stages, harnessDir, {
23625
+ id: "status",
23626
+ kind: "inspect",
23627
+ description: "Inspect a running agent environment slot.",
23628
+ command: "./.har/agent-cli.sh {agentId} status",
23629
+ requiresAgentId: true
23630
+ });
23631
+ addStageIfRunnable(stages, harnessDir, {
23632
+ id: "logs",
23633
+ kind: "inspect",
23634
+ description: "Read recent logs for an agent slot.",
23635
+ command: "./.har/agent-cli.sh {agentId} logs",
23636
+ requiresAgentId: true
23637
+ });
23638
+ addStageIfRunnable(stages, harnessDir, {
23639
+ id: "teardown",
23640
+ kind: "teardown",
23641
+ description: "Tear down an isolated agent environment slot.",
23642
+ command: "./.har/teardown.sh {agentId}",
23643
+ requiresAgentId: true
23644
+ });
23645
+ const harnessEnv = readHarnessEnv(repoPath);
23646
+ const agentSlots = readAgentSlotsFromHarnessEnv(harnessEnv);
23647
+ if (!agentSlots) {
23648
+ throw new Error(
23649
+ "Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
23650
+ );
23651
+ }
23652
+ return {
23653
+ version: "1",
23654
+ artifactsDir: "artifacts",
23655
+ logsDir: "logs",
23656
+ agentSlots,
23657
+ verificationStages: ["typecheck", "unit-tests", "api-health"],
23658
+ stages
23659
+ };
23660
+ }
23661
+ function readAgentSlotsFromHarnessEnv(harnessEnv) {
23662
+ const max = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MAX");
23663
+ if (max === void 0) return null;
23664
+ const min = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
23665
+ if (max < min) {
23666
+ throw new Error("HARNESS_AGENT_SLOT_MAX must be >= HARNESS_AGENT_SLOT_MIN in harness.env");
23667
+ }
23668
+ return { min, max };
23669
+ }
23670
+ function stageScriptExists(harnessDir, stage) {
23671
+ if (stage.script) {
23672
+ return fs9.existsSync(path10.join(harnessDir, stage.script));
23673
+ }
23674
+ if (stage.command) {
23675
+ const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
23676
+ return fs9.existsSync(path10.join(harnessDir, scriptName));
23677
+ }
23678
+ return fs9.existsSync(path10.join(harnessDir, "stages", `${stage.id}.sh`));
23679
+ }
23680
+ function addStageIfRunnable(stages, harnessDir, stage) {
23681
+ const parsed = HarnessStageSchema.parse(stage);
23682
+ if (stageScriptExists(harnessDir, parsed)) {
23683
+ stages.push(parsed);
23684
+ }
23685
+ }
23686
+ function readStageRegistry(repoPath) {
23687
+ const registryPath = getStageRegistryPath(repoPath);
23688
+ if (!fs9.existsSync(registryPath)) {
23689
+ return synthesizeStageRegistry(repoPath);
23690
+ }
23691
+ const raw = JSON.parse(fs9.readFileSync(registryPath, "utf8"));
23692
+ const parsed = HarnessStageRegistrySchema.safeParse(raw);
23693
+ if (!parsed.success) {
23694
+ throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
23695
+ }
23696
+ return parsed.data;
23697
+ }
23698
+ function writeStageRegistry(repoPath, registry2) {
23699
+ const registryPath = getStageRegistryPath(repoPath);
23700
+ const parsed = HarnessStageRegistrySchema.safeParse(registry2);
23701
+ if (!parsed.success) {
23702
+ throw new Error(`Invalid stage registry: ${parsed.error.message}`);
23703
+ }
23704
+ fs9.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
23705
+ }
23706
+ function listStages(repoPath) {
23707
+ return readStageRegistry(repoPath).stages;
23708
+ }
23709
+ function getAgentSlotRange(repoPath) {
23710
+ const registry2 = readStageRegistry(repoPath);
23711
+ if (registry2.agentSlots) {
23712
+ return registry2.agentSlots;
23713
+ }
23714
+ const fromEnv = readAgentSlotsFromHarnessEnv(readHarnessEnv(repoPath));
23715
+ if (fromEnv) {
23716
+ return fromEnv;
23717
+ }
23718
+ throw new Error(
23719
+ "Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
23720
+ );
23721
+ }
23722
+ function getAgentSlotIds(repoPath) {
23723
+ const { min, max } = getAgentSlotRange(repoPath);
23724
+ const ids = [];
23725
+ for (let id = min; id <= max; id++) ids.push(id);
23726
+ return ids;
23727
+ }
23728
+ function detectAgentSlotEnvMismatch(repoPath) {
23729
+ const registryPath = getStageRegistryPath(repoPath);
23730
+ if (!fs9.existsSync(registryPath)) return null;
23731
+ let stagesSlots = null;
23732
+ try {
23733
+ const raw = JSON.parse(fs9.readFileSync(registryPath, "utf8"));
23734
+ if (raw.agentSlots && Number.isInteger(raw.agentSlots.min) && Number.isInteger(raw.agentSlots.max)) {
23735
+ stagesSlots = { min: raw.agentSlots.min, max: raw.agentSlots.max };
23736
+ }
23737
+ } catch {
23738
+ return null;
23739
+ }
23740
+ if (!stagesSlots) return null;
23741
+ const env3 = readHarnessEnv(repoPath);
23742
+ const envMax = parseHarnessEnvInt(env3, "HARNESS_AGENT_SLOT_MAX");
23743
+ if (envMax === void 0) return null;
23744
+ const envMin = parseHarnessEnvInt(env3, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
23745
+ if (envMin === stagesSlots.min && envMax === stagesSlots.max) return null;
23746
+ return { stages: stagesSlots, env: { min: envMin, max: envMax } };
23747
+ }
23748
+ function syncAgentSlotsToHarnessEnv(repoPath) {
23749
+ const mismatch = detectAgentSlotEnvMismatch(repoPath);
23750
+ if (!mismatch) return false;
23751
+ const envPath = path10.join(getHarnessDir(repoPath), "harness.env");
23752
+ if (!fs9.existsSync(envPath)) return false;
23753
+ let content = fs9.readFileSync(envPath, "utf8");
23754
+ const minLine = `export HARNESS_AGENT_SLOT_MIN=${mismatch.stages.min}`;
23755
+ const maxLine = `export HARNESS_AGENT_SLOT_MAX=${mismatch.stages.max}`;
23756
+ if (/^export HARNESS_AGENT_SLOT_MIN=/m.test(content)) {
23757
+ content = content.replace(/^export HARNESS_AGENT_SLOT_MIN=.*$/m, minLine);
23758
+ } else {
23759
+ content = `${content.replace(/\s*$/, "")}
23760
+ ${minLine}
23761
+ `;
23762
+ }
23763
+ if (/^export HARNESS_AGENT_SLOT_MAX=/m.test(content)) {
23764
+ content = content.replace(/^export HARNESS_AGENT_SLOT_MAX=.*$/m, maxLine);
23765
+ } else {
23766
+ content = `${content.replace(/\s*$/, "")}
23767
+ ${maxLine}
23768
+ `;
23769
+ }
23770
+ fs9.writeFileSync(envPath, content);
23771
+ return true;
23772
+ }
23773
+ function stageRequiresAgentId(stage) {
23774
+ if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
23775
+ return AGENT_REQUIRED_KINDS.has(stage.kind);
23776
+ }
23777
+ function resolveStage(repoPath, options) {
23778
+ if (!options.id && !options.kind) return null;
23779
+ const stages = listStages(repoPath);
23780
+ if (options.id) {
23781
+ return stages.find((stage) => stage.id === options.id) ?? null;
23782
+ }
23783
+ const matches = stages.filter((stage) => stage.kind === options.kind);
23784
+ if (matches.length === 1) return matches[0];
23785
+ if (matches.length > 1) {
23786
+ throw new Error(
23787
+ `Multiple stages with kind "${options.kind}". Specify stage id. Available: ${matches.map((s2) => s2.id).join(", ")}`
23788
+ );
23789
+ }
23790
+ return null;
23791
+ }
23792
+ function getVerificationStageIds(repoPath) {
23793
+ const registry2 = readStageRegistry(repoPath);
23794
+ if (Array.isArray(registry2.verificationStages) && registry2.verificationStages.length > 0) {
23795
+ return registry2.verificationStages;
23796
+ }
23797
+ return registry2.stages.filter((stage) => stage.group === "verification").map((stage) => stage.id);
23798
+ }
23799
+ function getArtifactsDir(repoPath) {
23800
+ return readStageRegistry(repoPath).artifactsDir ?? "artifacts";
23801
+ }
23802
+
23532
23803
  // src/harness/generator.ts
23533
23804
  var PROFILE_DIRS = {
23534
23805
  default: "har-boilerplate",
@@ -23542,46 +23813,48 @@ var CLI_PRUNE_FILES = [
23542
23813
  ];
23543
23814
  function pruneCliProfile(harnessDir) {
23544
23815
  for (const file of CLI_PRUNE_FILES) {
23545
- const filePath = path8.join(harnessDir, file);
23546
- if (fs7.existsSync(filePath)) {
23547
- fs7.unlinkSync(filePath);
23816
+ const filePath = path11.join(harnessDir, file);
23817
+ if (fs10.existsSync(filePath)) {
23818
+ fs10.unlinkSync(filePath);
23548
23819
  }
23549
23820
  }
23550
23821
  }
23551
23822
  function scaffoldClaudeMd(repoPath, projectName, force) {
23552
23823
  const templatePath = resolveTemplateFile("CLAUDE.md.template");
23553
23824
  if (!templatePath) return;
23554
- const dest = path8.join(repoPath, "CLAUDE.md");
23555
- if (fs7.existsSync(dest) && !force) return;
23825
+ const dest = path11.join(repoPath, "CLAUDE.md");
23826
+ if (fs10.existsSync(dest) && !force) return;
23556
23827
  const displayName = projectName.replace(/_/g, " ");
23557
- const content = fs7.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
23558
- fs7.writeFileSync(dest, content);
23828
+ const content = fs10.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
23829
+ fs10.writeFileSync(dest, content);
23559
23830
  }
23560
23831
  function scaffoldHarnessBoilerplate(repoPath, options = {}) {
23561
- const harnessDir = path8.join(repoPath, DEFAULT_HAR_DIR);
23562
- const projectName = path8.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
23832
+ const harnessDir = path11.join(repoPath, DEFAULT_HAR_DIR);
23833
+ const projectName = path11.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
23563
23834
  const profile = options.profile ?? "default";
23564
- const boilerplateDir = path8.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
23565
- if (fs7.existsSync(harnessDir) && !options.force) {
23835
+ const boilerplateDir = path11.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
23836
+ if (fs10.existsSync(harnessDir) && !options.force) {
23566
23837
  throw new Error(
23567
23838
  '.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
23568
23839
  );
23569
23840
  }
23570
- if (!fs7.existsSync(boilerplateDir)) {
23841
+ if (!fs10.existsSync(boilerplateDir)) {
23571
23842
  throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
23572
23843
  }
23573
- if (options.force && fs7.existsSync(harnessDir)) {
23574
- fs7.rmSync(harnessDir, { recursive: true, force: true });
23844
+ if (options.force && fs10.existsSync(harnessDir)) {
23845
+ fs10.rmSync(harnessDir, { recursive: true, force: true });
23575
23846
  }
23576
23847
  copyDirRecursive(boilerplateDir, harnessDir);
23848
+ writeHarnessGitignore(harnessDir, boilerplateDir);
23577
23849
  if (profile === "cli") {
23578
23850
  pruneCliProfile(harnessDir);
23579
23851
  }
23580
- const harnessEnvPath = path8.join(harnessDir, "harness.env");
23581
- if (fs7.existsSync(harnessEnvPath)) {
23582
- let content = fs7.readFileSync(harnessEnvPath, "utf8");
23852
+ syncAgentSlotsToHarnessEnv(repoPath);
23853
+ const harnessEnvPath = path11.join(harnessDir, "harness.env");
23854
+ if (fs10.existsSync(harnessEnvPath)) {
23855
+ let content = fs10.readFileSync(harnessEnvPath, "utf8");
23583
23856
  content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
23584
- fs7.writeFileSync(harnessEnvPath, content);
23857
+ fs10.writeFileSync(harnessEnvPath, content);
23585
23858
  }
23586
23859
  const manifest = createManifest(
23587
23860
  repoPath,
@@ -23604,8 +23877,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
23604
23877
  }
23605
23878
 
23606
23879
  // src/llm/authoring-agent.ts
23607
- var fs11 = __toESM(require("fs"));
23608
- var path11 = __toESM(require("path"));
23880
+ var fs14 = __toESM(require("fs"));
23881
+ var path14 = __toESM(require("path"));
23609
23882
 
23610
23883
  // node_modules/@anthropic-ai/sdk/error.mjs
23611
23884
  var error_exports = {};
@@ -24004,13 +24277,13 @@ var MultipartBody = class {
24004
24277
  // node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
24005
24278
  var import_web = require("node:stream/web");
24006
24279
  var fileFromPathWarned = false;
24007
- async function fileFromPath3(path47, ...args) {
24280
+ async function fileFromPath3(path48, ...args) {
24008
24281
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
24009
24282
  if (!fileFromPathWarned) {
24010
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path47)}) instead`);
24283
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path48)}) instead`);
24011
24284
  fileFromPathWarned = true;
24012
24285
  }
24013
- return await _fileFromPath(path47, ...args);
24286
+ return await _fileFromPath(path48, ...args);
24014
24287
  }
24015
24288
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
24016
24289
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -24607,29 +24880,29 @@ var APIClient = class {
24607
24880
  defaultIdempotencyKey() {
24608
24881
  return `stainless-node-retry-${uuid4()}`;
24609
24882
  }
24610
- get(path47, opts) {
24611
- return this.methodRequest("get", path47, opts);
24883
+ get(path48, opts) {
24884
+ return this.methodRequest("get", path48, opts);
24612
24885
  }
24613
- post(path47, opts) {
24614
- return this.methodRequest("post", path47, opts);
24886
+ post(path48, opts) {
24887
+ return this.methodRequest("post", path48, opts);
24615
24888
  }
24616
- patch(path47, opts) {
24617
- return this.methodRequest("patch", path47, opts);
24889
+ patch(path48, opts) {
24890
+ return this.methodRequest("patch", path48, opts);
24618
24891
  }
24619
- put(path47, opts) {
24620
- return this.methodRequest("put", path47, opts);
24892
+ put(path48, opts) {
24893
+ return this.methodRequest("put", path48, opts);
24621
24894
  }
24622
- delete(path47, opts) {
24623
- return this.methodRequest("delete", path47, opts);
24895
+ delete(path48, opts) {
24896
+ return this.methodRequest("delete", path48, opts);
24624
24897
  }
24625
- methodRequest(method, path47, opts) {
24898
+ methodRequest(method, path48, opts) {
24626
24899
  return this.request(Promise.resolve(opts).then(async (opts2) => {
24627
24900
  const body = opts2 && isBlobLike(opts2?.body) ? new DataView(await opts2.body.arrayBuffer()) : opts2?.body instanceof DataView ? opts2.body : opts2?.body instanceof ArrayBuffer ? new DataView(opts2.body) : opts2 && ArrayBuffer.isView(opts2?.body) ? new DataView(opts2.body.buffer) : opts2?.body;
24628
- return { method, path: path47, ...opts2, body };
24901
+ return { method, path: path48, ...opts2, body };
24629
24902
  }));
24630
24903
  }
24631
- getAPIList(path47, Page, opts) {
24632
- return this.requestAPIList(Page, { method: "get", path: path47, ...opts });
24904
+ getAPIList(path48, Page, opts) {
24905
+ return this.requestAPIList(Page, { method: "get", path: path48, ...opts });
24633
24906
  }
24634
24907
  calculateContentLength(body) {
24635
24908
  if (typeof body === "string") {
@@ -24647,10 +24920,10 @@ var APIClient = class {
24647
24920
  return null;
24648
24921
  }
24649
24922
  buildRequest(options) {
24650
- const { method, path: path47, query, headers = {} } = options;
24923
+ const { method, path: path48, query, headers = {} } = options;
24651
24924
  const body = ArrayBuffer.isView(options.body) || options.__binaryRequest && typeof options.body === "string" ? options.body : isMultipartBody(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null;
24652
24925
  const contentLength = this.calculateContentLength(body);
24653
- const url = this.buildURL(path47, query);
24926
+ const url = this.buildURL(path48, query);
24654
24927
  if ("timeout" in options)
24655
24928
  validatePositiveInteger("timeout", options.timeout);
24656
24929
  const timeout = options.timeout ?? this.timeout;
@@ -24759,8 +25032,8 @@ var APIClient = class {
24759
25032
  const request = this.makeRequest(options, null);
24760
25033
  return new PagePromise(this, request, Page);
24761
25034
  }
24762
- buildURL(path47, query) {
24763
- const url = isAbsoluteURL(path47) ? new URL(path47) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path47.startsWith("/") ? path47.slice(1) : path47));
25035
+ buildURL(path48, query) {
25036
+ const url = isAbsoluteURL(path48) ? new URL(path48) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path48.startsWith("/") ? path48.slice(1) : path48));
24764
25037
  const defaultQuery = this.defaultQuery();
24765
25038
  if (!isEmptyObj(defaultQuery)) {
24766
25039
  query = { ...defaultQuery, ...query };
@@ -26583,34 +26856,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
26583
26856
  var sdk_default = Anthropic;
26584
26857
 
26585
26858
  // src/llm/tools.ts
26586
- var fs10 = __toESM(require("fs"));
26587
- var path10 = __toESM(require("path"));
26859
+ var fs13 = __toESM(require("fs"));
26860
+ var path13 = __toESM(require("path"));
26588
26861
  var readline3 = __toESM(require("readline"));
26589
26862
 
26590
26863
  // src/harness/agent-md.ts
26591
- var fs9 = __toESM(require("fs"));
26592
- var path9 = __toESM(require("path"));
26864
+ var fs12 = __toESM(require("fs"));
26865
+ var path12 = __toESM(require("path"));
26593
26866
  var readline2 = __toESM(require("readline"));
26594
26867
  var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
26595
26868
  var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
26596
26869
  function writeAgentMdProposal(repoPath, content, rationale) {
26597
26870
  const harnessDir = getHarnessDir(repoPath);
26598
- writeFileSafe(path9.join(harnessDir, AGENT_MD_PROPOSAL), content);
26871
+ writeFileSafe(path12.join(harnessDir, AGENT_MD_PROPOSAL), content);
26599
26872
  writeFileSafe(
26600
- path9.join(harnessDir, AGENT_MD_PROPOSAL_META),
26873
+ path12.join(harnessDir, AGENT_MD_PROPOSAL_META),
26601
26874
  JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
26602
26875
  );
26603
26876
  }
26604
26877
  function readAgentMdProposal(repoPath) {
26605
26878
  const harnessDir = getHarnessDir(repoPath);
26606
- const proposalPath = path9.join(harnessDir, AGENT_MD_PROPOSAL);
26607
- const metaPath = path9.join(harnessDir, AGENT_MD_PROPOSAL_META);
26608
- if (!fs9.existsSync(proposalPath)) return null;
26609
- const content = fs9.readFileSync(proposalPath, "utf8");
26879
+ const proposalPath = path12.join(harnessDir, AGENT_MD_PROPOSAL);
26880
+ const metaPath = path12.join(harnessDir, AGENT_MD_PROPOSAL_META);
26881
+ if (!fs12.existsSync(proposalPath)) return null;
26882
+ const content = fs12.readFileSync(proposalPath, "utf8");
26610
26883
  let rationale = "";
26611
- if (fs9.existsSync(metaPath)) {
26884
+ if (fs12.existsSync(metaPath)) {
26612
26885
  try {
26613
- rationale = JSON.parse(fs9.readFileSync(metaPath, "utf8")).rationale ?? "";
26886
+ rationale = JSON.parse(fs12.readFileSync(metaPath, "utf8")).rationale ?? "";
26614
26887
  } catch {
26615
26888
  rationale = "";
26616
26889
  }
@@ -26618,21 +26891,21 @@ function readAgentMdProposal(repoPath) {
26618
26891
  return {
26619
26892
  content,
26620
26893
  rationale,
26621
- createdAt: fs9.existsSync(metaPath) ? JSON.parse(fs9.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
26894
+ createdAt: fs12.existsSync(metaPath) ? JSON.parse(fs12.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
26622
26895
  };
26623
26896
  }
26624
26897
  function clearAgentMdProposal(repoPath) {
26625
26898
  const harnessDir = getHarnessDir(repoPath);
26626
26899
  for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
26627
- const p2 = path9.join(harnessDir, file);
26628
- if (fs9.existsSync(p2)) fs9.unlinkSync(p2);
26900
+ const p2 = path12.join(harnessDir, file);
26901
+ if (fs12.existsSync(p2)) fs12.unlinkSync(p2);
26629
26902
  }
26630
26903
  }
26631
26904
  async function promptApplyAgentMdProposal(repoPath) {
26632
26905
  const proposal = readAgentMdProposal(repoPath);
26633
26906
  if (!proposal) return false;
26634
- const agentMdPath = path9.join(repoPath, "AGENT.md");
26635
- const exists = fs9.existsSync(agentMdPath);
26907
+ const agentMdPath = path12.join(repoPath, "AGENT.md");
26908
+ const exists = fs12.existsSync(agentMdPath);
26636
26909
  process.stderr.write("\n");
26637
26910
  process.stderr.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
26638
26911
  process.stderr.write("Proposed AGENT.md (repo root)\n");
@@ -26820,13 +27093,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
26820
27093
  async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
26821
27094
  switch (toolName) {
26822
27095
  case "readRepoFile": {
26823
- const filePath = path10.join(repoPath, input.path);
27096
+ const filePath = path13.join(repoPath, input.path);
26824
27097
  const content = readFile(filePath, input.maxChars ?? 8e3);
26825
27098
  info(`Read repo: ${input.path}`);
26826
27099
  return content;
26827
27100
  }
26828
27101
  case "listRepoDir": {
26829
- const dirPath = path10.join(repoPath, input.path ?? ".");
27102
+ const dirPath = path13.join(repoPath, input.path ?? ".");
26830
27103
  const entries = listDir(dirPath, input.maxFiles ?? 60);
26831
27104
  info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
26832
27105
  return entries.length > 0 ? entries.join("\n") : "(empty directory)";
@@ -26851,17 +27124,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26851
27124
  const filePath = resolveSafePath(harnessDir, relPath);
26852
27125
  writeFileSafe(filePath, input.content);
26853
27126
  if (relPath.endsWith(".sh")) {
26854
- fs10.chmodSync(filePath, 493);
27127
+ fs13.chmodSync(filePath, 493);
26855
27128
  }
26856
27129
  info(`Wrote harness: ${relPath}`);
26857
27130
  return `Written ${relPath} (${input.content.length} bytes)`;
26858
27131
  }
26859
27132
  case "editHarnessFile": {
26860
27133
  const filePath = resolveSafePath(harnessDir, input.path);
26861
- if (!fs10.existsSync(filePath)) {
27134
+ if (!fs13.existsSync(filePath)) {
26862
27135
  return `Error: file not found: ${input.path}`;
26863
27136
  }
26864
- const content = fs10.readFileSync(filePath, "utf8");
27137
+ const content = fs13.readFileSync(filePath, "utf8");
26865
27138
  const oldStr = input.old_string;
26866
27139
  const newStr = input.new_string;
26867
27140
  const count = content.split(oldStr).length - 1;
@@ -26878,10 +27151,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26878
27151
  }
26879
27152
  case "deleteHarnessFile": {
26880
27153
  const filePath = resolveSafePath(harnessDir, input.path);
26881
- if (!fs10.existsSync(filePath)) {
27154
+ if (!fs13.existsSync(filePath)) {
26882
27155
  return `File not found (already deleted): ${input.path}`;
26883
27156
  }
26884
- fs10.unlinkSync(filePath);
27157
+ fs13.unlinkSync(filePath);
26885
27158
  info(`Deleted harness: ${input.path}`);
26886
27159
  return `Deleted ${input.path}`;
26887
27160
  }
@@ -26932,29 +27205,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
26932
27205
  const mode = options.mode ?? "init";
26933
27206
  const systemPromptPath = resolvePromptPath("system-authoring.md");
26934
27207
  const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
26935
- let systemPrompt = fs11.existsSync(systemPromptPath) ? fs11.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
27208
+ let systemPrompt = fs14.existsSync(systemPromptPath) ? fs14.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
26936
27209
  if (agentMdTemplatePath) {
26937
27210
  systemPrompt += `
26938
27211
 
26939
27212
  ## AGENT.md template (starting point for proposeAgentMd)
26940
27213
 
26941
- ${fs11.readFileSync(agentMdTemplatePath, "utf8")}`;
27214
+ ${fs14.readFileSync(agentMdTemplatePath, "utf8")}`;
26942
27215
  }
26943
27216
  const tools = buildAuthoringTools();
26944
27217
  const messages = [];
26945
27218
  let rootListing;
26946
27219
  try {
26947
- rootListing = fs11.readdirSync(repoPath).slice(0, 50).join("\n");
27220
+ rootListing = fs14.readdirSync(repoPath).slice(0, 50).join("\n");
26948
27221
  } catch {
26949
27222
  throw new Error(`Cannot read repo at path: ${repoPath}`);
26950
27223
  }
26951
27224
  let harnessListing;
26952
27225
  try {
26953
- harnessListing = fs11.readdirSync(harnessDir).join("\n");
27226
+ harnessListing = fs14.readdirSync(harnessDir).join("\n");
26954
27227
  } catch {
26955
27228
  throw new Error(`Cannot read harness dir at: ${harnessDir}`);
26956
27229
  }
26957
- const existingAgentMd = fs11.existsSync(path11.join(repoPath, "AGENT.md")) ? "AGENT.md exists at repo root \u2014 read it before proposing changes via proposeAgentMd." : "No AGENT.md at repo root yet \u2014 propose one via proposeAgentMd.";
27230
+ const existingAgentMd = fs14.existsSync(path14.join(repoPath, "AGENT.md")) ? "AGENT.md exists at repo root \u2014 read it before proposing changes via proposeAgentMd." : "No AGENT.md at repo root yet \u2014 propose one via proposeAgentMd.";
26958
27231
  const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
26959
27232
  Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
26960
27233
  Keep README.md accurate \u2014 it is the index of the harness. ${existingAgentMd}` : `This is an INITIAL setup. A fresh boilerplate has been copied to .har/.
@@ -27034,8 +27307,8 @@ Try running with --verbose to see what the agent is doing.`
27034
27307
  }
27035
27308
 
27036
27309
  // src/harness/validator.ts
27037
- var fs14 = __toESM(require("fs"));
27038
- var path14 = __toESM(require("path"));
27310
+ var fs15 = __toESM(require("fs"));
27311
+ var path15 = __toESM(require("path"));
27039
27312
 
27040
27313
  // src/utils/shell.ts
27041
27314
  var import_child_process = require("child_process");
@@ -27103,205 +27376,6 @@ function runShellCommand(command2, options = {}) {
27103
27376
  });
27104
27377
  }
27105
27378
 
27106
- // src/harness/stages.ts
27107
- var fs13 = __toESM(require("fs"));
27108
- var path13 = __toESM(require("path"));
27109
-
27110
- // src/harness/env.ts
27111
- var fs12 = __toESM(require("fs"));
27112
- var path12 = __toESM(require("path"));
27113
- function readHarnessEnv(repoPath) {
27114
- const envPath = path12.join(getHarnessDir(repoPath), "harness.env");
27115
- if (!fs12.existsSync(envPath)) return {};
27116
- const values = {};
27117
- for (const line of fs12.readFileSync(envPath, "utf8").split("\n")) {
27118
- const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
27119
- if (!match) continue;
27120
- values[match[1]] = match[2].replace(/^"|"$/g, "");
27121
- }
27122
- return values;
27123
- }
27124
- function parseHarnessEnvInt(env3, key) {
27125
- const raw = env3[key];
27126
- if (raw === void 0 || raw === "") return void 0;
27127
- const value = Number(raw);
27128
- if (!Number.isInteger(value)) return void 0;
27129
- return value;
27130
- }
27131
-
27132
- // src/harness/stages.ts
27133
- var STAGE_REGISTRY_FILE = "stages.json";
27134
- var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
27135
- "launch",
27136
- "verify",
27137
- "test",
27138
- "inspect",
27139
- "reset",
27140
- "teardown",
27141
- "custom"
27142
- ]);
27143
- function getStageRegistryPath(repoPath) {
27144
- return path13.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
27145
- }
27146
- function synthesizeStageRegistry(repoPath) {
27147
- const harnessDir = getHarnessDir(repoPath);
27148
- const stages = [];
27149
- addStageIfRunnable(stages, harnessDir, {
27150
- id: "setup-infra",
27151
- kind: "setup",
27152
- description: "Start shared harness infrastructure.",
27153
- command: "./.har/setup-infra.sh",
27154
- requiresAgentId: false
27155
- });
27156
- addStageIfRunnable(stages, harnessDir, {
27157
- id: "launch",
27158
- kind: "launch",
27159
- description: "Launch an isolated agent environment slot.",
27160
- command: "./.har/launch.sh {agentId}",
27161
- requiresAgentId: true
27162
- });
27163
- addStageIfRunnable(stages, harnessDir, {
27164
- id: "verify",
27165
- kind: "verify",
27166
- description: "Run the project verification pipeline.",
27167
- command: "./.har/verify.sh {agentId}",
27168
- requiresAgentId: true,
27169
- acceptsArgs: ["--full"]
27170
- });
27171
- addStageIfRunnable(stages, harnessDir, {
27172
- id: "status",
27173
- kind: "inspect",
27174
- description: "Inspect a running agent environment slot.",
27175
- command: "./.har/agent-cli.sh {agentId} status",
27176
- requiresAgentId: true
27177
- });
27178
- addStageIfRunnable(stages, harnessDir, {
27179
- id: "logs",
27180
- kind: "inspect",
27181
- description: "Read recent logs for an agent slot.",
27182
- command: "./.har/agent-cli.sh {agentId} logs",
27183
- requiresAgentId: true
27184
- });
27185
- addStageIfRunnable(stages, harnessDir, {
27186
- id: "teardown",
27187
- kind: "teardown",
27188
- description: "Tear down an isolated agent environment slot.",
27189
- command: "./.har/teardown.sh {agentId}",
27190
- requiresAgentId: true
27191
- });
27192
- const harnessEnv = readHarnessEnv(repoPath);
27193
- const agentSlots = readAgentSlotsFromHarnessEnv(harnessEnv);
27194
- if (!agentSlots) {
27195
- throw new Error(
27196
- "Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
27197
- );
27198
- }
27199
- return {
27200
- version: "1",
27201
- artifactsDir: "artifacts",
27202
- logsDir: "logs",
27203
- agentSlots,
27204
- verificationStages: ["typecheck", "unit-tests", "api-health"],
27205
- stages
27206
- };
27207
- }
27208
- function readAgentSlotsFromHarnessEnv(harnessEnv) {
27209
- const max = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MAX");
27210
- if (max === void 0) return null;
27211
- const min = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
27212
- if (max < min) {
27213
- throw new Error("HARNESS_AGENT_SLOT_MAX must be >= HARNESS_AGENT_SLOT_MIN in harness.env");
27214
- }
27215
- return { min, max };
27216
- }
27217
- function stageScriptExists(harnessDir, stage) {
27218
- if (stage.script) {
27219
- return fs13.existsSync(path13.join(harnessDir, stage.script));
27220
- }
27221
- if (stage.command) {
27222
- const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
27223
- return fs13.existsSync(path13.join(harnessDir, scriptName));
27224
- }
27225
- return fs13.existsSync(path13.join(harnessDir, "stages", `${stage.id}.sh`));
27226
- }
27227
- function addStageIfRunnable(stages, harnessDir, stage) {
27228
- const parsed = HarnessStageSchema.parse(stage);
27229
- if (stageScriptExists(harnessDir, parsed)) {
27230
- stages.push(parsed);
27231
- }
27232
- }
27233
- function readStageRegistry(repoPath) {
27234
- const registryPath = getStageRegistryPath(repoPath);
27235
- if (!fs13.existsSync(registryPath)) {
27236
- return synthesizeStageRegistry(repoPath);
27237
- }
27238
- const raw = JSON.parse(fs13.readFileSync(registryPath, "utf8"));
27239
- const parsed = HarnessStageRegistrySchema.safeParse(raw);
27240
- if (!parsed.success) {
27241
- throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
27242
- }
27243
- return parsed.data;
27244
- }
27245
- function writeStageRegistry(repoPath, registry2) {
27246
- const registryPath = getStageRegistryPath(repoPath);
27247
- const parsed = HarnessStageRegistrySchema.safeParse(registry2);
27248
- if (!parsed.success) {
27249
- throw new Error(`Invalid stage registry: ${parsed.error.message}`);
27250
- }
27251
- fs13.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
27252
- }
27253
- function listStages(repoPath) {
27254
- return readStageRegistry(repoPath).stages;
27255
- }
27256
- function getAgentSlotRange(repoPath) {
27257
- const registry2 = readStageRegistry(repoPath);
27258
- if (registry2.agentSlots) {
27259
- return registry2.agentSlots;
27260
- }
27261
- const fromEnv = readAgentSlotsFromHarnessEnv(readHarnessEnv(repoPath));
27262
- if (fromEnv) {
27263
- return fromEnv;
27264
- }
27265
- throw new Error(
27266
- "Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
27267
- );
27268
- }
27269
- function getAgentSlotIds(repoPath) {
27270
- const { min, max } = getAgentSlotRange(repoPath);
27271
- const ids = [];
27272
- for (let id = min; id <= max; id++) ids.push(id);
27273
- return ids;
27274
- }
27275
- function stageRequiresAgentId(stage) {
27276
- if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
27277
- return AGENT_REQUIRED_KINDS.has(stage.kind);
27278
- }
27279
- function resolveStage(repoPath, options) {
27280
- if (!options.id && !options.kind) return null;
27281
- const stages = listStages(repoPath);
27282
- if (options.id) {
27283
- return stages.find((stage) => stage.id === options.id) ?? null;
27284
- }
27285
- const matches = stages.filter((stage) => stage.kind === options.kind);
27286
- if (matches.length === 1) return matches[0];
27287
- if (matches.length > 1) {
27288
- throw new Error(
27289
- `Multiple stages with kind "${options.kind}". Specify stage id. Available: ${matches.map((s2) => s2.id).join(", ")}`
27290
- );
27291
- }
27292
- return null;
27293
- }
27294
- function getVerificationStageIds(repoPath) {
27295
- const registry2 = readStageRegistry(repoPath);
27296
- if (Array.isArray(registry2.verificationStages) && registry2.verificationStages.length > 0) {
27297
- return registry2.verificationStages;
27298
- }
27299
- return registry2.stages.filter((stage) => stage.group === "verification").map((stage) => stage.id);
27300
- }
27301
- function getArtifactsDir(repoPath) {
27302
- return readStageRegistry(repoPath).artifactsDir ?? "artifacts";
27303
- }
27304
-
27305
27379
  // src/harness/validator.ts
27306
27380
  var REQUIRED_FILES_DEFAULT = [
27307
27381
  "README.md",
@@ -27354,22 +27428,22 @@ var SHELL_SCRIPTS = [
27354
27428
  function validateHarness(repoPath) {
27355
27429
  const harnessDir = getHarnessDir(repoPath);
27356
27430
  const issues = [];
27357
- if (!fs14.existsSync(harnessDir)) {
27431
+ if (!fs15.existsSync(harnessDir)) {
27358
27432
  return {
27359
27433
  pass: false,
27360
27434
  issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
27361
27435
  };
27362
27436
  }
27363
27437
  for (const file of getRequiredFiles(repoPath)) {
27364
- const filePath = path14.join(harnessDir, file);
27365
- if (!fs14.existsSync(filePath)) {
27438
+ const filePath = path15.join(harnessDir, file);
27439
+ if (!fs15.existsSync(filePath)) {
27366
27440
  issues.push({ file, message: "Required file missing", severity: "error" });
27367
27441
  }
27368
27442
  }
27369
27443
  for (const script of SHELL_SCRIPTS) {
27370
- const scriptPath = path14.join(harnessDir, script);
27371
- if (!fs14.existsSync(scriptPath)) continue;
27372
- const stat = fs14.statSync(scriptPath);
27444
+ const scriptPath = path15.join(harnessDir, script);
27445
+ if (!fs15.existsSync(scriptPath)) continue;
27446
+ const stat = fs15.statSync(scriptPath);
27373
27447
  if (!(stat.mode & 73)) {
27374
27448
  issues.push({ file: script, message: "Script is not executable", severity: "warning" });
27375
27449
  }
@@ -27384,9 +27458,9 @@ function validateHarness(repoPath) {
27384
27458
  }
27385
27459
  const manifest = readManifest(repoPath);
27386
27460
  const profile = manifest?.profile ?? "default";
27387
- const ecosystemPath = path14.join(harnessDir, "ecosystem.agent.template.cjs");
27388
- if (profile !== "cli" && fs14.existsSync(ecosystemPath)) {
27389
- const content = fs14.readFileSync(ecosystemPath, "utf8");
27461
+ const ecosystemPath = path15.join(harnessDir, "ecosystem.agent.template.cjs");
27462
+ if (profile !== "cli" && fs15.existsSync(ecosystemPath)) {
27463
+ const content = fs15.readFileSync(ecosystemPath, "utf8");
27390
27464
  if (!content.includes("module.exports")) {
27391
27465
  issues.push({
27392
27466
  file: "ecosystem.agent.template.cjs",
@@ -27395,9 +27469,9 @@ function validateHarness(repoPath) {
27395
27469
  });
27396
27470
  }
27397
27471
  }
27398
- const harnessEnvPath = path14.join(harnessDir, "harness.env");
27399
- if (fs14.existsSync(harnessEnvPath)) {
27400
- const content = fs14.readFileSync(harnessEnvPath, "utf8");
27472
+ const harnessEnvPath = path15.join(harnessDir, "harness.env");
27473
+ if (fs15.existsSync(harnessEnvPath)) {
27474
+ const content = fs15.readFileSync(harnessEnvPath, "utf8");
27401
27475
  if (content.includes("TODO: set migrate command")) {
27402
27476
  issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
27403
27477
  }
@@ -27405,17 +27479,17 @@ function validateHarness(repoPath) {
27405
27479
  issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
27406
27480
  }
27407
27481
  }
27408
- const verifyPath = path14.join(harnessDir, "verify.sh");
27409
- if (fs14.existsSync(verifyPath)) {
27410
- const content = fs14.readFileSync(verifyPath, "utf8");
27482
+ const verifyPath = path15.join(harnessDir, "verify.sh");
27483
+ if (fs15.existsSync(verifyPath)) {
27484
+ const content = fs15.readFileSync(verifyPath, "utf8");
27411
27485
  if (content.includes("echo 'TODO:")) {
27412
27486
  issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
27413
27487
  }
27414
27488
  }
27415
- const stagesPath = path14.join(harnessDir, "stages.json");
27416
- if (fs14.existsSync(stagesPath)) {
27489
+ const stagesPath = path15.join(harnessDir, "stages.json");
27490
+ if (fs15.existsSync(stagesPath)) {
27417
27491
  try {
27418
- JSON.parse(fs14.readFileSync(stagesPath, "utf8"));
27492
+ JSON.parse(fs15.readFileSync(stagesPath, "utf8"));
27419
27493
  const registry2 = readStageRegistry(repoPath);
27420
27494
  if (registry2.stages.length === 0) {
27421
27495
  issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
@@ -27430,9 +27504,9 @@ function validateHarness(repoPath) {
27430
27504
  } else {
27431
27505
  issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
27432
27506
  }
27433
- const readmePath = path14.join(harnessDir, "README.md");
27434
- if (fs14.existsSync(readmePath)) {
27435
- const content = fs14.readFileSync(readmePath, "utf8");
27507
+ const readmePath = path15.join(harnessDir, "README.md");
27508
+ if (fs15.existsSync(readmePath)) {
27509
+ const content = fs15.readFileSync(readmePath, "utf8");
27436
27510
  if (!content.includes(".har")) {
27437
27511
  issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
27438
27512
  }
@@ -27446,8 +27520,8 @@ function validateHarness(repoPath) {
27446
27520
  async function smokeTestHarness(repoPath) {
27447
27521
  const harnessDir = getHarnessDir(repoPath);
27448
27522
  const issues = [];
27449
- const setupScript = path14.join(harnessDir, "setup-infra.sh");
27450
- if (fs14.existsSync(setupScript)) {
27523
+ const setupScript = path15.join(harnessDir, "setup-infra.sh");
27524
+ if (fs15.existsSync(setupScript)) {
27451
27525
  const result = run(`bash "${setupScript}"`, { cwd: repoPath });
27452
27526
  if (result.code !== 0) {
27453
27527
  issues.push({
@@ -27462,8 +27536,8 @@ async function smokeTestHarness(repoPath) {
27462
27536
  }
27463
27537
 
27464
27538
  // src/harness/drift.ts
27465
- var fs15 = __toESM(require("fs"));
27466
- var path15 = __toESM(require("path"));
27539
+ var fs16 = __toESM(require("fs"));
27540
+ var path16 = __toESM(require("path"));
27467
27541
  var PROFILE_DIRS2 = {
27468
27542
  default: "har-boilerplate",
27469
27543
  cli: "har-boilerplate-cli",
@@ -27531,48 +27605,51 @@ function substituteProjectName(content, projectName) {
27531
27605
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
27532
27606
  }
27533
27607
  function listBoilerplateFiles(boilerplateDir) {
27534
- if (!fs15.existsSync(boilerplateDir)) return [];
27535
- return fs15.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
27608
+ if (!fs16.existsSync(boilerplateDir)) return [];
27609
+ return fs16.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
27536
27610
  }
27537
27611
  function compareHarnessToTemplate(repoPath) {
27538
- const resolved = path15.resolve(repoPath);
27612
+ const resolved = path16.resolve(repoPath);
27539
27613
  const manifest = readManifest(resolved);
27540
27614
  const profile = manifest?.profile ?? "default";
27541
27615
  const harnessDir = getHarnessDir(resolved);
27542
- const projectName = path15.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
27543
- const boilerplateDir = path15.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
27616
+ const projectName = path16.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
27617
+ const boilerplateDir = path16.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
27544
27618
  const templateFiles = listBoilerplateFiles(boilerplateDir);
27545
27619
  const missing = [];
27546
27620
  const checksumMismatch = [];
27547
27621
  const extra = [];
27548
27622
  const unchanged = [];
27549
27623
  for (const file of templateFiles) {
27550
- const templatePath = path15.join(boilerplateDir, file);
27551
- const harnessPath = path15.join(harnessDir, file);
27552
- let templateContent = fs15.readFileSync(templatePath, "utf8");
27624
+ const harnessFile = harnessFileForTemplate(file);
27625
+ const templatePath = path16.join(boilerplateDir, file);
27626
+ const harnessPath = path16.join(harnessDir, harnessFile);
27627
+ let templateContent = fs16.readFileSync(templatePath, "utf8");
27553
27628
  if (file === "harness.env") {
27554
27629
  templateContent = substituteProjectName(templateContent, projectName);
27555
27630
  }
27556
- if (!fs15.existsSync(harnessPath)) {
27557
- missing.push(file);
27631
+ if (!fs16.existsSync(harnessPath)) {
27632
+ missing.push(harnessFile);
27558
27633
  continue;
27559
27634
  }
27560
- const harnessChecksum = computeFileChecksum(fs15.readFileSync(harnessPath, "utf8"));
27635
+ const harnessChecksum = computeFileChecksum(fs16.readFileSync(harnessPath, "utf8"));
27561
27636
  const templateChecksum = computeFileChecksum(templateContent);
27562
27637
  if (harnessChecksum === templateChecksum) {
27563
- unchanged.push(file);
27638
+ unchanged.push(harnessFile);
27564
27639
  } else {
27565
- checksumMismatch.push(file);
27640
+ checksumMismatch.push(harnessFile);
27566
27641
  }
27567
27642
  }
27568
- if (fs15.existsSync(harnessDir)) {
27569
- for (const file of fs15.readdirSync(harnessDir)) {
27570
- const full = path15.join(harnessDir, file);
27571
- if (!fs15.statSync(full).isFile()) continue;
27643
+ if (fs16.existsSync(harnessDir)) {
27644
+ for (const file of fs16.readdirSync(harnessDir)) {
27645
+ const full = path16.join(harnessDir, file);
27646
+ if (!fs16.statSync(full).isFile()) continue;
27572
27647
  if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
27573
27648
  if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
27574
27649
  extra.push(file);
27575
- } else if (!templateFiles.includes(file)) {
27650
+ } else if (isExpectedHarnessOnlyFile(file, templateFiles)) {
27651
+ continue;
27652
+ } else if (!templateFiles.includes(file) && !templateFiles.includes(templateFileForHarness(file))) {
27576
27653
  extra.push(file);
27577
27654
  }
27578
27655
  }
@@ -27580,6 +27657,7 @@ function compareHarnessToTemplate(repoPath) {
27580
27657
  const installed = manifest?.generatorVersion;
27581
27658
  const harnessEnv = readHarnessEnv(resolved);
27582
27659
  const missingPortVars = missingPortDocumentationVars(profile, harnessEnv);
27660
+ const agentSlotMismatch = detectAgentSlotEnvMismatch(resolved);
27583
27661
  return {
27584
27662
  generatorVersion: {
27585
27663
  installed,
@@ -27590,14 +27668,15 @@ function compareHarnessToTemplate(repoPath) {
27590
27668
  checksumMismatch,
27591
27669
  extra,
27592
27670
  unchanged,
27593
- missingPortVars
27671
+ missingPortVars,
27672
+ agentSlotMismatch
27594
27673
  };
27595
27674
  }
27596
27675
 
27597
27676
  // src/harness/maintain-bundle.ts
27598
- var fs16 = __toESM(require("fs"));
27677
+ var fs17 = __toESM(require("fs"));
27599
27678
  var os3 = __toESM(require("os"));
27600
- var path16 = __toESM(require("path"));
27679
+ var path17 = __toESM(require("path"));
27601
27680
  var MAINTAIN_DIR = "maintain";
27602
27681
  var PROFILE_DIRS3 = {
27603
27682
  default: "har-boilerplate",
@@ -27608,14 +27687,14 @@ function substituteProjectName2(content, projectName) {
27608
27687
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
27609
27688
  }
27610
27689
  function projectNameFromRepo(repoPath) {
27611
- return path16.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
27690
+ return path17.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
27612
27691
  }
27613
27692
  function boilerplateDirForProfile(profile) {
27614
- return path16.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
27693
+ return path17.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
27615
27694
  }
27616
27695
  function readBundledTemplateContent(repoPath, profile, file) {
27617
- const templatePath = path16.join(boilerplateDirForProfile(profile), file);
27618
- let content = fs16.readFileSync(templatePath, "utf8");
27696
+ const templatePath = path17.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
27697
+ let content = fs17.readFileSync(templatePath, "utf8");
27619
27698
  if (file === "harness.env") {
27620
27699
  content = substituteProjectName2(content, projectNameFromRepo(repoPath));
27621
27700
  }
@@ -27646,12 +27725,12 @@ function actionHint(file, kind2) {
27646
27725
  return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
27647
27726
  }
27648
27727
  function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
27649
- const tmpDir = fs16.mkdtempSync(path16.join(os3.tmpdir(), "har-maintain-diff-"));
27728
+ const tmpDir = fs17.mkdtempSync(path17.join(os3.tmpdir(), "har-maintain-diff-"));
27650
27729
  try {
27651
- const installedPath = path16.join(tmpDir, "installed");
27652
- const templatePath = path16.join(tmpDir, "template");
27653
- fs16.writeFileSync(installedPath, installedContent);
27654
- fs16.writeFileSync(templatePath, templateContent);
27730
+ const installedPath = path17.join(tmpDir, "installed");
27731
+ const templatePath = path17.join(tmpDir, "template");
27732
+ fs17.writeFileSync(installedPath, installedContent);
27733
+ fs17.writeFileSync(templatePath, templateContent);
27655
27734
  const result = run(`diff -u "${installedPath}" "${templatePath}"`);
27656
27735
  const body = result.stdout.trim();
27657
27736
  if (!body) return "";
@@ -27660,7 +27739,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
27660
27739
  ${body.split("\n").slice(2).join("\n")}
27661
27740
  `;
27662
27741
  } finally {
27663
- fs16.rmSync(tmpDir, { recursive: true, force: true });
27742
+ fs17.rmSync(tmpDir, { recursive: true, force: true });
27664
27743
  }
27665
27744
  }
27666
27745
  function buildActions(repoPath, profile, drift) {
@@ -27675,7 +27754,7 @@ function buildActions(repoPath, profile, drift) {
27675
27754
  });
27676
27755
  }
27677
27756
  for (const file of drift.checksumMismatch) {
27678
- const installedPath = path16.join(harnessDir, file);
27757
+ const installedPath = path17.join(harnessDir, file);
27679
27758
  const installedRel = `maintain/installed/${file}`;
27680
27759
  const templateRel = `maintain/templates/${file}`;
27681
27760
  const diffRel = `maintain/diffs/${file}.diff`;
@@ -27683,7 +27762,7 @@ function buildActions(repoPath, profile, drift) {
27683
27762
  file,
27684
27763
  kind: "drift",
27685
27764
  template: templateRel,
27686
- installed: fs16.existsSync(installedPath) ? installedRel : void 0,
27765
+ installed: fs17.existsSync(installedPath) ? installedRel : void 0,
27687
27766
  diff: diffRel,
27688
27767
  hint: actionHint(file, "drift")
27689
27768
  });
@@ -27732,6 +27811,18 @@ function buildReadme(report) {
27732
27811
  lines.push(`- \`${v2}\` \u2014 copy from \`maintain/templates/harness.env\``);
27733
27812
  }
27734
27813
  }
27814
+ if (report.agentSlotMismatch) {
27815
+ lines.push(
27816
+ "",
27817
+ "## Agent slot limit mismatch",
27818
+ "",
27819
+ `- \`stages.json\` agentSlots: ${report.agentSlotMismatch.stages.min}\u2013${report.agentSlotMismatch.stages.max}`,
27820
+ `- \`harness.env\` HARNESS_AGENT_SLOT_*: ${report.agentSlotMismatch.env.min}\u2013${report.agentSlotMismatch.env.max}`,
27821
+ "",
27822
+ "Canonical source is `.har/stages.json`. Run `har env maintain` to sync legacy exports in `harness.env`, or edit `agentSlots` there.",
27823
+ ""
27824
+ );
27825
+ }
27735
27826
  lines.push("", "## Stale files (review)", "");
27736
27827
  if (report.stale.length === 0) {
27737
27828
  lines.push("None detected.");
@@ -27754,28 +27845,28 @@ function buildReadme(report) {
27754
27845
  }
27755
27846
  function writeBundleArtifacts(repoPath, profile, drift, report) {
27756
27847
  const harnessDir = getHarnessDir(repoPath);
27757
- const bundleDir = path16.join(harnessDir, MAINTAIN_DIR);
27758
- if (fs16.existsSync(bundleDir)) {
27759
- fs16.rmSync(bundleDir, { recursive: true, force: true });
27848
+ const bundleDir = path17.join(harnessDir, MAINTAIN_DIR);
27849
+ if (fs17.existsSync(bundleDir)) {
27850
+ fs17.rmSync(bundleDir, { recursive: true, force: true });
27760
27851
  }
27761
27852
  const dirs = [
27762
27853
  bundleDir,
27763
- path16.join(bundleDir, "templates"),
27764
- path16.join(bundleDir, "installed"),
27765
- path16.join(bundleDir, "diffs"),
27766
- path16.join(bundleDir, "stale")
27854
+ path17.join(bundleDir, "templates"),
27855
+ path17.join(bundleDir, "installed"),
27856
+ path17.join(bundleDir, "diffs"),
27857
+ path17.join(bundleDir, "stale")
27767
27858
  ];
27768
27859
  for (const dir of dirs) {
27769
- fs16.mkdirSync(dir, { recursive: true });
27860
+ fs17.mkdirSync(dir, { recursive: true });
27770
27861
  }
27771
27862
  const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
27772
27863
  for (const file of affectedFiles) {
27773
27864
  const templateContent = readBundledTemplateContent(repoPath, profile, file);
27774
- writeFileSafe(path16.join(bundleDir, "templates", file), templateContent);
27775
- const harnessPath = path16.join(harnessDir, file);
27776
- if (fs16.existsSync(harnessPath)) {
27777
- const installedContent = fs16.readFileSync(harnessPath, "utf8");
27778
- writeFileSafe(path16.join(bundleDir, "installed", file), installedContent);
27865
+ writeFileSafe(path17.join(bundleDir, "templates", file), templateContent);
27866
+ const harnessPath = path17.join(harnessDir, file);
27867
+ if (fs17.existsSync(harnessPath)) {
27868
+ const installedContent = fs17.readFileSync(harnessPath, "utf8");
27869
+ writeFileSafe(path17.join(bundleDir, "installed", file), installedContent);
27779
27870
  const diff = createUnifiedDiff(
27780
27871
  installedContent,
27781
27872
  templateContent,
@@ -27783,14 +27874,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
27783
27874
  `templates/${file}`
27784
27875
  );
27785
27876
  if (diff) {
27786
- writeFileSafe(path16.join(bundleDir, "diffs", `${file}.diff`), diff);
27877
+ writeFileSafe(path17.join(bundleDir, "diffs", `${file}.diff`), diff);
27787
27878
  }
27788
27879
  }
27789
27880
  }
27790
- writeFileSafe(path16.join(bundleDir, "README.md"), buildReadme(report));
27791
- writeFileSafe(path16.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
27881
+ writeFileSafe(path17.join(bundleDir, "README.md"), buildReadme(report));
27882
+ writeFileSafe(path17.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
27792
27883
  writeFileSafe(
27793
- path16.join(bundleDir, "validation.json"),
27884
+ path17.join(bundleDir, "validation.json"),
27794
27885
  JSON.stringify(report.validation, null, 2) + "\n"
27795
27886
  );
27796
27887
  if (report.stale.length > 0) {
@@ -27802,7 +27893,7 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
27802
27893
  ...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
27803
27894
  ""
27804
27895
  ];
27805
- writeFileSafe(path16.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
27896
+ writeFileSafe(path17.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
27806
27897
  }
27807
27898
  return bundleDir;
27808
27899
  }
@@ -27818,6 +27909,7 @@ function buildMaintainBundle(repoPath, validation2, drift) {
27818
27909
  actions: buildActions(repoPath, profile, drift),
27819
27910
  stale: buildStale(drift),
27820
27911
  missingPortVars: drift.missingPortVars,
27912
+ agentSlotMismatch: drift.agentSlotMismatch,
27821
27913
  validation: {
27822
27914
  pass: validation2.pass,
27823
27915
  errors,
@@ -27828,9 +27920,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
27828
27920
  return { bundleDir, report };
27829
27921
  }
27830
27922
  function removeMaintainBundle(repoPath) {
27831
- const bundleDir = path16.join(getHarnessDir(repoPath), MAINTAIN_DIR);
27832
- if (fs16.existsSync(bundleDir)) {
27833
- fs16.rmSync(bundleDir, { recursive: true, force: true });
27923
+ const bundleDir = path17.join(getHarnessDir(repoPath), MAINTAIN_DIR);
27924
+ if (fs17.existsSync(bundleDir)) {
27925
+ fs17.rmSync(bundleDir, { recursive: true, force: true });
27834
27926
  }
27835
27927
  }
27836
27928
  function formatMaintainBundlePromptSection(report) {
@@ -27871,20 +27963,31 @@ function formatMaintainBundlePromptSection(report) {
27871
27963
  }
27872
27964
  lines.push("");
27873
27965
  }
27874
- if (report.actions.length === 0 && report.stale.length === 0 && report.missingPortVars.length === 0 && report.validation.errors.length === 0) {
27966
+ if (report.agentSlotMismatch) {
27967
+ lines.push(
27968
+ "### Agent slot limit mismatch",
27969
+ "",
27970
+ `- \`stages.json\` agentSlots: ${report.agentSlotMismatch.stages.min}\u2013${report.agentSlotMismatch.stages.max}`,
27971
+ `- \`harness.env\` HARNESS_AGENT_SLOT_*: ${report.agentSlotMismatch.env.min}\u2013${report.agentSlotMismatch.env.max}`,
27972
+ "",
27973
+ "Canonical source is `.har/stages.json`. `har env maintain --finalize` syncs legacy exports in `harness.env`.",
27974
+ ""
27975
+ );
27976
+ }
27977
+ if (report.actions.length === 0 && report.stale.length === 0 && report.missingPortVars.length === 0 && !report.agentSlotMismatch && report.validation.errors.length === 0) {
27875
27978
  lines.push("No template drift detected. Review repo stack changes manually if needed.", "");
27876
27979
  }
27877
27980
  return lines.join("\n");
27878
27981
  }
27879
27982
 
27880
27983
  // src/harness/stage-templates.ts
27881
- var fs18 = __toESM(require("fs"));
27882
- var path17 = __toESM(require("path"));
27984
+ var fs19 = __toESM(require("fs"));
27985
+ var path18 = __toESM(require("path"));
27883
27986
 
27884
27987
  // src/harness/parser.ts
27885
- var fs17 = __toESM(require("fs"));
27988
+ var fs18 = __toESM(require("fs"));
27886
27989
  function harnessExists(repoPath) {
27887
- return fs17.existsSync(`${repoPath}/.har/setup-infra.sh`);
27990
+ return fs18.existsSync(`${repoPath}/.har/setup-infra.sh`);
27888
27991
  }
27889
27992
 
27890
27993
  // src/harness/stage-templates.ts
@@ -27907,16 +28010,16 @@ var StageTemplateManifestSchema = external_exports.object({
27907
28010
  docsPath: external_exports.string().min(1)
27908
28011
  });
27909
28012
  function resolveTemplateDir(templateId) {
27910
- const dir = path17.join(resolveTemplatesDir(), "stage-templates", templateId);
27911
- if (!fs18.existsSync(dir)) {
28013
+ const dir = path18.join(resolveTemplatesDir(), "stage-templates", templateId);
28014
+ if (!fs19.existsSync(dir)) {
27912
28015
  throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
27913
28016
  }
27914
28017
  return dir;
27915
28018
  }
27916
28019
  function readTemplateManifest(templateId) {
27917
- const manifestPath = path17.join(resolveTemplateDir(templateId), "template.manifest.json");
28020
+ const manifestPath = path18.join(resolveTemplateDir(templateId), "template.manifest.json");
27918
28021
  const parsed = StageTemplateManifestSchema.safeParse(
27919
- JSON.parse(fs18.readFileSync(manifestPath, "utf8"))
28022
+ JSON.parse(fs19.readFileSync(manifestPath, "utf8"))
27920
28023
  );
27921
28024
  if (!parsed.success) {
27922
28025
  throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
@@ -27927,40 +28030,40 @@ function readTemplateManifest(templateId) {
27927
28030
  return parsed.data;
27928
28031
  }
27929
28032
  function ensureParentDir(filePath) {
27930
- const parent = path17.dirname(filePath);
27931
- if (!fs18.existsSync(parent)) {
27932
- fs18.mkdirSync(parent, { recursive: true });
28033
+ const parent = path18.dirname(filePath);
28034
+ if (!fs19.existsSync(parent)) {
28035
+ fs19.mkdirSync(parent, { recursive: true });
27933
28036
  }
27934
28037
  }
27935
28038
  function copyTemplateFile(templateDir, file, repoPath, force) {
27936
- const srcPath = path17.join(templateDir, file.src);
27937
- const destPath = path17.join(repoPath, file.dest);
27938
- if (!fs18.existsSync(srcPath)) {
28039
+ const srcPath = path18.join(templateDir, file.src);
28040
+ const destPath = path18.join(repoPath, file.dest);
28041
+ if (!fs19.existsSync(srcPath)) {
27939
28042
  throw new Error(`Template file missing: ${file.src}`);
27940
28043
  }
27941
- if (fs18.existsSync(destPath) && !force) {
28044
+ if (fs19.existsSync(destPath) && !force) {
27942
28045
  throw new Error(
27943
28046
  `File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
27944
28047
  );
27945
28048
  }
27946
28049
  ensureParentDir(destPath);
27947
- fs18.copyFileSync(srcPath, destPath);
28050
+ fs19.copyFileSync(srcPath, destPath);
27948
28051
  if (file.executable) {
27949
- fs18.chmodSync(destPath, 493);
28052
+ fs19.chmodSync(destPath, 493);
27950
28053
  }
27951
28054
  return { written: true, path: file.dest };
27952
28055
  }
27953
28056
  function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
27954
- const packagePath = path17.join(repoPath, "package.json");
27955
- if (!fs18.existsSync(packagePath)) {
28057
+ const packagePath = path18.join(repoPath, "package.json");
28058
+ if (!fs19.existsSync(packagePath)) {
27956
28059
  throw new Error("No package.json in repo root. Add one before applying this stage template.");
27957
28060
  }
27958
- const fragmentPath = path17.join(templateDir, fragmentRelPath);
27959
- if (!fs18.existsSync(fragmentPath)) {
28061
+ const fragmentPath = path18.join(templateDir, fragmentRelPath);
28062
+ if (!fs19.existsSync(fragmentPath)) {
27960
28063
  throw new Error(`Package fragment missing: ${fragmentRelPath}`);
27961
28064
  }
27962
- const pkg = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
27963
- const fragment = JSON.parse(fs18.readFileSync(fragmentPath, "utf8"));
28065
+ const pkg = JSON.parse(fs19.readFileSync(packagePath, "utf8"));
28066
+ const fragment = JSON.parse(fs19.readFileSync(fragmentPath, "utf8"));
27964
28067
  for (const section of ["scripts", "devDependencies"]) {
27965
28068
  const existing = pkg[section] ?? {};
27966
28069
  const incoming = fragment[section] ?? {};
@@ -27973,7 +28076,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
27973
28076
  }
27974
28077
  pkg[section] = existing;
27975
28078
  }
27976
- fs18.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
28079
+ fs19.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
27977
28080
  }
27978
28081
  function patchStageRegistry(repoPath, manifest, force) {
27979
28082
  const registry2 = readStageRegistry(repoPath);
@@ -28013,8 +28116,8 @@ function assertHarnessPresent(repoPath) {
28013
28116
  }
28014
28117
  function assertStageNotPresent(repoPath, stageId, force) {
28015
28118
  if (force) return;
28016
- const scriptPath = path17.join(repoPath, ".har", "stages", `${stageId}.sh`);
28017
- if (fs18.existsSync(scriptPath)) {
28119
+ const scriptPath = path18.join(repoPath, ".har", "stages", `${stageId}.sh`);
28120
+ if (fs19.existsSync(scriptPath)) {
28018
28121
  throw new Error(
28019
28122
  `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
28020
28123
  );
@@ -28027,7 +28130,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
28027
28130
  }
28028
28131
  }
28029
28132
  function applyStageTemplate(repoPath, templateId, options = {}) {
28030
- const resolved = path17.resolve(repoPath);
28133
+ const resolved = path18.resolve(repoPath);
28031
28134
  const force = options.force ?? false;
28032
28135
  const warnings = [];
28033
28136
  const filesWritten = [];
@@ -28046,7 +28149,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
28046
28149
  if (file.skipFlag === "skipCi" && options.skipCi) {
28047
28150
  continue;
28048
28151
  }
28049
- if (fs18.existsSync(path17.join(resolved, file.dest)) && !force) {
28152
+ if (fs19.existsSync(path18.join(resolved, file.dest)) && !force) {
28050
28153
  warnings.push(`Skipped optional file (exists): ${file.dest}`);
28051
28154
  continue;
28052
28155
  }
@@ -28081,9 +28184,9 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
28081
28184
  };
28082
28185
  }
28083
28186
  function listStageTemplateIds() {
28084
- const root = path17.join(resolveTemplatesDir(), "stage-templates");
28085
- if (!fs18.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
28086
- return fs18.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
28187
+ const root = path18.join(resolveTemplatesDir(), "stage-templates");
28188
+ if (!fs19.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
28189
+ return fs19.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
28087
28190
  (name) => STAGE_TEMPLATE_IDS.includes(name)
28088
28191
  );
28089
28192
  }
@@ -28110,11 +28213,11 @@ function validateAgentId(id, repoPath) {
28110
28213
  // src/core/harness.ts
28111
28214
  function listHarnessScripts(repoPath) {
28112
28215
  const harnessDir = getHarnessDir(repoPath);
28113
- if (!fs19.existsSync(harnessDir)) return [];
28114
- return fs19.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
28216
+ if (!fs20.existsSync(harnessDir)) return [];
28217
+ return fs20.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
28115
28218
  }
28116
28219
  function describeProject(repoPath) {
28117
- const resolved = path18.resolve(repoPath);
28220
+ const resolved = path19.resolve(repoPath);
28118
28221
  const manifest = readManifest(resolved);
28119
28222
  const present = harnessExists(resolved);
28120
28223
  return {
@@ -28134,14 +28237,15 @@ function describeProject(repoPath) {
28134
28237
  };
28135
28238
  }
28136
28239
  async function initHarness(options) {
28137
- const repoPath = path18.resolve(options.repoPath);
28138
- if (!fs19.existsSync(repoPath)) {
28240
+ const repoPath = path19.resolve(options.repoPath);
28241
+ if (!fs20.existsSync(repoPath)) {
28139
28242
  throw new Error(`Path not found: ${repoPath}`);
28140
28243
  }
28141
28244
  const scaffold = scaffoldHarnessBoilerplate(repoPath, {
28142
28245
  force: options.force,
28143
28246
  profile: options.profile
28144
28247
  });
28248
+ syncAgentSlotsToHarnessEnv(repoPath);
28145
28249
  let adaptationSummary;
28146
28250
  if (options.auto) {
28147
28251
  const apiKey = requireApiKey();
@@ -28166,9 +28270,9 @@ async function initHarness(options) {
28166
28270
  };
28167
28271
  }
28168
28272
  async function maintainHarness(options) {
28169
- const repoPath = path18.resolve(options.repoPath);
28273
+ const repoPath = path19.resolve(options.repoPath);
28170
28274
  const harnessDir = getHarnessDir(repoPath);
28171
- if (!fs19.existsSync(harnessDir)) {
28275
+ if (!fs20.existsSync(harnessDir)) {
28172
28276
  throw new Error('No .har/ found. Run "har env init" first.');
28173
28277
  }
28174
28278
  if (options.auto) {
@@ -28193,6 +28297,7 @@ async function maintainHarness(options) {
28193
28297
  if (!validation2.pass) {
28194
28298
  throw new Error("Cannot finalize: harness validation has errors. Fix them first.");
28195
28299
  }
28300
+ syncAgentSlotsToHarnessEnv(repoPath);
28196
28301
  const existing = readManifest(repoPath);
28197
28302
  finalizeHarness(
28198
28303
  repoPath,
@@ -28219,11 +28324,11 @@ function addStageTemplate(repoPath, templateId, options = {}) {
28219
28324
  }
28220
28325
 
28221
28326
  // src/harness/custom-stage.ts
28222
- var fs20 = __toESM(require("fs"));
28223
- var path19 = __toESM(require("path"));
28327
+ var fs21 = __toESM(require("fs"));
28328
+ var path20 = __toESM(require("path"));
28224
28329
  var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
28225
28330
  function addCustomStage(repoPath, options) {
28226
- const resolved = path19.resolve(repoPath);
28331
+ const resolved = path20.resolve(repoPath);
28227
28332
  if (!harnessExists(resolved)) {
28228
28333
  throw new Error('No .har/ harness found. Run "har env init" first.');
28229
28334
  }
@@ -28260,20 +28365,20 @@ function addCustomStage(repoPath, options) {
28260
28365
  if (options.command) {
28261
28366
  stage.command = options.command;
28262
28367
  } else {
28263
- const scriptRel = path19.join("stages", `${id}.sh`);
28264
- const scriptAbs = path19.join(resolved, ".har", scriptRel);
28265
- if (fs20.existsSync(scriptAbs) && !options.force) {
28368
+ const scriptRel = path20.join("stages", `${id}.sh`);
28369
+ const scriptAbs = path20.join(resolved, ".har", scriptRel);
28370
+ if (fs21.existsSync(scriptAbs) && !options.force) {
28266
28371
  throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
28267
28372
  }
28268
- const skeletonPath = path19.join(
28373
+ const skeletonPath = path20.join(
28269
28374
  resolveTemplatesDir(),
28270
28375
  "stage-templates",
28271
28376
  "custom-stage-skeleton.sh"
28272
28377
  );
28273
- const skeleton = fs20.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
28274
- fs20.mkdirSync(path19.dirname(scriptAbs), { recursive: true });
28275
- fs20.writeFileSync(scriptAbs, skeleton);
28276
- fs20.chmodSync(scriptAbs, 493);
28378
+ const skeleton = fs21.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
28379
+ fs21.mkdirSync(path20.dirname(scriptAbs), { recursive: true });
28380
+ fs21.writeFileSync(scriptAbs, skeleton);
28381
+ fs21.chmodSync(scriptAbs, 493);
28277
28382
  filesWritten.push(`.har/${scriptRel}`);
28278
28383
  stage.script = scriptRel;
28279
28384
  stage.artifacts = [
@@ -28321,8 +28426,8 @@ function addCustomStage(repoPath, options) {
28321
28426
  }
28322
28427
 
28323
28428
  // src/harness/adaptation-prompt.ts
28324
- var fs21 = __toESM(require("fs"));
28325
- var path20 = __toESM(require("path"));
28429
+ var fs22 = __toESM(require("fs"));
28430
+ var path21 = __toESM(require("path"));
28326
28431
  var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
28327
28432
  var PROFILE_HINTS = {
28328
28433
  default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.",
@@ -28334,7 +28439,7 @@ function loadTemplate(name) {
28334
28439
  if (!filePath) {
28335
28440
  throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
28336
28441
  }
28337
- return fs21.readFileSync(filePath, "utf8");
28442
+ return fs22.readFileSync(filePath, "utf8");
28338
28443
  }
28339
28444
  function applyProfilePlaceholders(content, profile) {
28340
28445
  return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
@@ -28356,7 +28461,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
28356
28461
  }
28357
28462
  function writeAdaptationPrompt(repoPath, content) {
28358
28463
  const harnessDir = getHarnessDir(repoPath);
28359
- const filePath = path20.join(harnessDir, ADAPTATION_PROMPT_FILE);
28464
+ const filePath = path21.join(harnessDir, ADAPTATION_PROMPT_FILE);
28360
28465
  writeFileSafe(filePath, content);
28361
28466
  return filePath;
28362
28467
  }
@@ -28375,25 +28480,25 @@ function printAdaptationPrompt(content) {
28375
28480
  }
28376
28481
 
28377
28482
  // src/harness/cursor-rule.ts
28378
- var fs22 = __toESM(require("fs"));
28379
- var path21 = __toESM(require("path"));
28483
+ var fs23 = __toESM(require("fs"));
28484
+ var path22 = __toESM(require("path"));
28380
28485
  var readline4 = __toESM(require("readline"));
28381
28486
  var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
28382
28487
  function getCursorRulePath(repoPath) {
28383
- return path21.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
28488
+ return path22.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
28384
28489
  }
28385
28490
  function isCursorWorkspace(repoPath) {
28386
- return fs22.existsSync(path21.join(repoPath, ".cursor"));
28491
+ return fs23.existsSync(path22.join(repoPath, ".cursor"));
28387
28492
  }
28388
28493
  function cursorRuleExists(repoPath) {
28389
- return fs22.existsSync(getCursorRulePath(repoPath));
28494
+ return fs23.existsSync(getCursorRulePath(repoPath));
28390
28495
  }
28391
28496
  function scaffoldCursorRule(repoPath) {
28392
28497
  const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
28393
28498
  if (!templatePath) {
28394
28499
  throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
28395
28500
  }
28396
- const content = fs22.readFileSync(templatePath, "utf8");
28501
+ const content = fs23.readFileSync(templatePath, "utf8");
28397
28502
  writeFileSafe(getCursorRulePath(repoPath), content);
28398
28503
  }
28399
28504
  async function handleCursorRule(options) {
@@ -28519,26 +28624,26 @@ function buildStageResult(input) {
28519
28624
  }
28520
28625
 
28521
28626
  // src/core/local-executor.ts
28522
- var fs24 = __toESM(require("fs"));
28523
- var path23 = __toESM(require("path"));
28627
+ var fs25 = __toESM(require("fs"));
28628
+ var path24 = __toESM(require("path"));
28524
28629
 
28525
28630
  // src/core/slot-registry.ts
28526
- var fs23 = __toESM(require("fs"));
28527
- var path22 = __toESM(require("path"));
28631
+ var fs24 = __toESM(require("fs"));
28632
+ var path23 = __toESM(require("path"));
28528
28633
  function getSlotRegistryDir(repoPath) {
28529
- return path22.join(getHarnessDir(repoPath), "slots");
28634
+ return path23.join(getHarnessDir(repoPath), "slots");
28530
28635
  }
28531
28636
  function getSlotRegistryPath(repoPath, agentId) {
28532
- return path22.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
28637
+ return path23.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
28533
28638
  }
28534
28639
  function isSlotResumable(session) {
28535
28640
  return session?.status === "failed" || session?.status === "starting";
28536
28641
  }
28537
28642
  function readSlotRegistry(repoPath, agentId) {
28538
28643
  const file = getSlotRegistryPath(repoPath, agentId);
28539
- if (!fs23.existsSync(file)) return void 0;
28644
+ if (!fs24.existsSync(file)) return void 0;
28540
28645
  try {
28541
- const raw = JSON.parse(fs23.readFileSync(file, "utf8"));
28646
+ const raw = JSON.parse(fs24.readFileSync(file, "utf8"));
28542
28647
  const result = SlotRegistryEntrySchema.safeParse(raw);
28543
28648
  return result.success ? result.data : void 0;
28544
28649
  } catch {
@@ -28548,7 +28653,7 @@ function readSlotRegistry(repoPath, agentId) {
28548
28653
 
28549
28654
  // src/core/local-executor.ts
28550
28655
  function resolveRepoPath(repoPath) {
28551
- return path23.resolve(repoPath);
28656
+ return path24.resolve(repoPath);
28552
28657
  }
28553
28658
  function buildPreviewUrlsFromPorts(ports, env3) {
28554
28659
  const urls = {};
@@ -28600,14 +28705,14 @@ function substituteAgentId(value, agentId) {
28600
28705
  function resolveStageScriptPath(repoPath, stage) {
28601
28706
  const harnessDir = getHarnessDir(repoPath);
28602
28707
  if (stage.script) {
28603
- return path23.join(harnessDir, stage.script);
28708
+ return path24.join(harnessDir, stage.script);
28604
28709
  }
28605
28710
  if (stage.command) {
28606
28711
  const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
28607
- return path23.join(harnessDir, scriptName);
28712
+ return path24.join(harnessDir, scriptName);
28608
28713
  }
28609
- const stageScript = path23.join(harnessDir, "stages", `${stage.id}.sh`);
28610
- if (fs24.existsSync(stageScript)) {
28714
+ const stageScript = path24.join(harnessDir, "stages", `${stage.id}.sh`);
28715
+ if (fs25.existsSync(stageScript)) {
28611
28716
  return stageScript;
28612
28717
  }
28613
28718
  throw new Error(
@@ -28622,7 +28727,7 @@ function buildExecutionPlan(repoPath, stage, options) {
28622
28727
  ...harnessEnv,
28623
28728
  ...stage.env ?? {}
28624
28729
  };
28625
- const cwd = stage.cwd ? path23.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28730
+ const cwd = stage.cwd ? path24.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28626
28731
  const extraArgs = options.args ?? [];
28627
28732
  const launchFlagArgs = [];
28628
28733
  if (stage.kind === "launch" && options.launchFlags) {
@@ -28699,19 +28804,19 @@ var LocalScriptExecutor = class {
28699
28804
  const repoPath = resolveRepoPath(ctx.repoPath);
28700
28805
  const harnessDir = getHarnessDir(repoPath);
28701
28806
  const artifactsDirName = getArtifactsDir(repoPath);
28702
- const artifactsDir = path23.join(harnessDir, artifactsDirName);
28703
- if (!fs24.existsSync(artifactsDir)) return [];
28807
+ const artifactsDir = path24.join(harnessDir, artifactsDirName);
28808
+ if (!fs25.existsSync(artifactsDir)) return [];
28704
28809
  const entries = [];
28705
28810
  const walk = (dir, prefix) => {
28706
- for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
28707
- const full = path23.join(dir, entry.name);
28708
- const relative2 = path23.join(prefix, entry.name);
28811
+ for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
28812
+ const full = path24.join(dir, entry.name);
28813
+ const relative2 = path24.join(prefix, entry.name);
28709
28814
  if (entry.isDirectory()) {
28710
28815
  walk(full, relative2);
28711
28816
  continue;
28712
28817
  }
28713
28818
  if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
28714
- const stat = fs24.statSync(full);
28819
+ const stat = fs25.statSync(full);
28715
28820
  entries.push({
28716
28821
  path: full,
28717
28822
  relativePath: relative2,
@@ -28727,7 +28832,7 @@ var LocalScriptExecutor = class {
28727
28832
  var localScriptExecutor = new LocalScriptExecutor();
28728
28833
 
28729
28834
  // src/core/control-sync.ts
28730
- var path37 = __toESM(require("path"));
28835
+ var path38 = __toESM(require("path"));
28731
28836
 
28732
28837
  // src/core/control-config.ts
28733
28838
  var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
@@ -28739,32 +28844,32 @@ function isControlEnabled() {
28739
28844
  }
28740
28845
 
28741
28846
  // src/core/control-registry.ts
28742
- var fs25 = __toESM(require("fs"));
28847
+ var fs26 = __toESM(require("fs"));
28743
28848
  var os4 = __toESM(require("os"));
28744
- var path24 = __toESM(require("path"));
28849
+ var path25 = __toESM(require("path"));
28745
28850
  function getRegistryPath() {
28746
28851
  if (process.env.HAR_CONTROL_REGISTRY_PATH) {
28747
- return path24.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28852
+ return path25.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28748
28853
  }
28749
- return path24.join(os4.homedir(), ".har", "repos.json");
28854
+ return path25.join(os4.homedir(), ".har", "repos.json");
28750
28855
  }
28751
28856
  function readRegistry() {
28752
28857
  const registryPath = getRegistryPath();
28753
28858
  try {
28754
- if (!fs25.existsSync(registryPath)) return { repos: [] };
28755
- return JSON.parse(fs25.readFileSync(registryPath, "utf8"));
28859
+ if (!fs26.existsSync(registryPath)) return { repos: [] };
28860
+ return JSON.parse(fs26.readFileSync(registryPath, "utf8"));
28756
28861
  } catch {
28757
28862
  return { repos: [] };
28758
28863
  }
28759
28864
  }
28760
28865
  function writeRegistry(registry2) {
28761
28866
  const registryPath = getRegistryPath();
28762
- fs25.mkdirSync(path24.dirname(registryPath), { recursive: true });
28763
- fs25.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
28867
+ fs26.mkdirSync(path25.dirname(registryPath), { recursive: true });
28868
+ fs26.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
28764
28869
  }
28765
28870
  function recordRepoForControlSync(repoPath) {
28766
28871
  if (process.env.HAR_CONTROL_DISABLED === "true") return;
28767
- const resolved = path24.resolve(repoPath);
28872
+ const resolved = path25.resolve(repoPath);
28768
28873
  if (!readManifest(resolved)) return;
28769
28874
  const registry2 = readRegistry();
28770
28875
  if (registry2.repos.includes(resolved)) return;
@@ -28773,7 +28878,7 @@ function recordRepoForControlSync(repoPath) {
28773
28878
  }
28774
28879
  function listRegisteredRepos() {
28775
28880
  const registry2 = readRegistry();
28776
- const kept = registry2.repos.filter((repoPath) => fs25.existsSync(repoPath) && readManifest(repoPath));
28881
+ const kept = registry2.repos.filter((repoPath) => fs26.existsSync(repoPath) && readManifest(repoPath));
28777
28882
  if (kept.length !== registry2.repos.length) {
28778
28883
  writeRegistry({ repos: kept });
28779
28884
  }
@@ -28781,17 +28886,17 @@ function listRegisteredRepos() {
28781
28886
  }
28782
28887
 
28783
28888
  // src/core/slot-status.ts
28784
- var fs30 = __toESM(require("fs"));
28889
+ var fs31 = __toESM(require("fs"));
28785
28890
  var os7 = __toESM(require("os"));
28786
- var path30 = __toESM(require("path"));
28891
+ var path31 = __toESM(require("path"));
28787
28892
  var import_child_process7 = require("child_process");
28788
28893
 
28789
28894
  // src/core/runs.ts
28790
28895
  var crypto2 = __toESM(require("crypto"));
28791
28896
  var import_child_process2 = require("child_process");
28792
- var fs26 = __toESM(require("fs"));
28897
+ var fs27 = __toESM(require("fs"));
28793
28898
  var os5 = __toESM(require("os"));
28794
- var path25 = __toESM(require("path"));
28899
+ var path26 = __toESM(require("path"));
28795
28900
  var RUNS_DIR = "runs";
28796
28901
  function gitCommonDir(cwd) {
28797
28902
  try {
@@ -28800,7 +28905,7 @@ function gitCommonDir(cwd) {
28800
28905
  encoding: "utf8",
28801
28906
  stdio: ["pipe", "pipe", "ignore"]
28802
28907
  }).trim();
28803
- return out ? path25.resolve(cwd, out) : void 0;
28908
+ return out ? path26.resolve(cwd, out) : void 0;
28804
28909
  } catch {
28805
28910
  return void 0;
28806
28911
  }
@@ -28822,7 +28927,7 @@ function gitPrefix(cwd) {
28822
28927
  }
28823
28928
  }
28824
28929
  function getRunsDir(harnessRoot) {
28825
- return path25.join(getHarnessDir(harnessRoot), RUNS_DIR);
28930
+ return path26.join(getHarnessDir(harnessRoot), RUNS_DIR);
28826
28931
  }
28827
28932
  function formatLocalDate(d2) {
28828
28933
  const y2 = d2.getFullYear();
@@ -28842,24 +28947,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
28842
28947
  const timePart = formatLocalTime(started);
28843
28948
  const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
28844
28949
  let filename = `${timePart}_${stageId}${agentPart}.json`;
28845
- const fullPath = path25.join(runsDir, dateFolder, filename);
28846
- if (fs26.existsSync(fullPath)) {
28950
+ const fullPath = path26.join(runsDir, dateFolder, filename);
28951
+ if (fs27.existsSync(fullPath)) {
28847
28952
  filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
28848
28953
  }
28849
- return path25.join(dateFolder, filename);
28954
+ return path26.join(dateFolder, filename);
28850
28955
  }
28851
28956
  function resolveRunFilePath(harnessRoot, run2) {
28852
28957
  if (run2.relativePath) {
28853
- return path25.join(getRunsDir(harnessRoot), run2.relativePath);
28958
+ return path26.join(getRunsDir(harnessRoot), run2.relativePath);
28854
28959
  }
28855
- return path25.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28960
+ return path26.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28856
28961
  }
28857
28962
  function collectRunFiles(runsDir) {
28858
- if (!fs26.existsSync(runsDir)) return [];
28963
+ if (!fs27.existsSync(runsDir)) return [];
28859
28964
  const files = [];
28860
28965
  const walk = (dir) => {
28861
- for (const entry of fs26.readdirSync(dir, { withFileTypes: true })) {
28862
- const full = path25.join(dir, entry.name);
28966
+ for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
28967
+ const full = path26.join(dir, entry.name);
28863
28968
  if (entry.isDirectory()) {
28864
28969
  walk(full);
28865
28970
  } else if (entry.name.endsWith(".json")) {
@@ -28873,30 +28978,30 @@ function collectRunFiles(runsDir) {
28873
28978
  function resolveAgentWorkDir(harnessRoot, agentId) {
28874
28979
  if (agentId === void 0) return void 0;
28875
28980
  const entry = readSlotRegistry(harnessRoot, agentId);
28876
- if (entry?.workDir && fs26.existsSync(entry.workDir)) return entry.workDir;
28981
+ if (entry?.workDir && fs27.existsSync(entry.workDir)) return entry.workDir;
28877
28982
  const env3 = readHarnessEnv(harnessRoot);
28878
- const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
28879
- const worktreeDir = path25.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
28983
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path26.basename(harnessRoot);
28984
+ const worktreeDir = path26.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
28880
28985
  const relPrefix = gitPrefix(harnessRoot);
28881
28986
  const sessionEnvFiles = [];
28882
- const worktreesRoot = path25.join(os5.homedir(), "worktrees");
28883
- if (fs26.existsSync(worktreesRoot)) {
28987
+ const worktreesRoot = path26.join(os5.homedir(), "worktrees");
28988
+ if (fs27.existsSync(worktreesRoot)) {
28884
28989
  const suffix = `-har-agent-${agentId}-`;
28885
- for (const entry2 of fs26.readdirSync(worktreesRoot, { withFileTypes: true })) {
28990
+ for (const entry2 of fs27.readdirSync(worktreesRoot, { withFileTypes: true })) {
28886
28991
  if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
28887
- const sessionDir = path25.join(worktreesRoot, entry2.name);
28992
+ const sessionDir = path26.join(worktreesRoot, entry2.name);
28888
28993
  if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
28889
- sessionEnvFiles.push(path25.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
28994
+ sessionEnvFiles.push(path26.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
28890
28995
  }
28891
28996
  }
28892
28997
  const candidates = [
28893
- path25.join(worktreeDir, `.env.agent.${agentId}`),
28894
- path25.join(harnessRoot, `.env.agent.${agentId}`),
28998
+ path26.join(worktreeDir, `.env.agent.${agentId}`),
28999
+ path26.join(harnessRoot, `.env.agent.${agentId}`),
28895
29000
  ...sessionEnvFiles.sort()
28896
29001
  ];
28897
29002
  for (const envFile of candidates) {
28898
- if (!fs26.existsSync(envFile)) continue;
28899
- const content = fs26.readFileSync(envFile, "utf8");
29003
+ if (!fs27.existsSync(envFile)) continue;
29004
+ const content = fs27.readFileSync(envFile, "utf8");
28900
29005
  const match = content.match(/^REPO_ROOT=(.+)$/m);
28901
29006
  if (match) return match[1].trim();
28902
29007
  }
@@ -28908,11 +29013,11 @@ function createRun(ctx, meta) {
28908
29013
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
28909
29014
  const runId = crypto2.randomUUID();
28910
29015
  const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
28911
- const runFilePath = path25.join(runsDir, relativePath);
28912
- fs26.mkdirSync(path25.dirname(runFilePath), { recursive: true });
29016
+ const runFilePath = path26.join(runsDir, relativePath);
29017
+ fs27.mkdirSync(path26.dirname(runFilePath), { recursive: true });
28913
29018
  const run2 = RunRecordSchema.parse({
28914
29019
  runId,
28915
- repoPath: path25.resolve(ctx.repoPath),
29020
+ repoPath: path26.resolve(ctx.repoPath),
28916
29021
  harnessRoot,
28917
29022
  stageId: meta.stageId,
28918
29023
  kind: meta.kind,
@@ -28923,7 +29028,7 @@ function createRun(ctx, meta) {
28923
29028
  relativePath,
28924
29029
  trigger: ctx.trigger ?? "cli"
28925
29030
  });
28926
- fs26.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
29031
+ fs27.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
28927
29032
  return run2;
28928
29033
  }
28929
29034
  function finishRun(repoPath, runId, update) {
@@ -28942,13 +29047,13 @@ function finishRun(repoPath, runId, update) {
28942
29047
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
28943
29048
  });
28944
29049
  const runPath = resolveRunFilePath(harnessRoot, finished);
28945
- fs26.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
29050
+ fs27.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
28946
29051
  return finished;
28947
29052
  }
28948
29053
  function findRunRecord(harnessRoot, runId) {
28949
29054
  const runsDir = getRunsDir(harnessRoot);
28950
29055
  for (const filePath of collectRunFiles(runsDir)) {
28951
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
29056
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
28952
29057
  if (parsed.success && parsed.data.runId === runId) {
28953
29058
  return parsed.data;
28954
29059
  }
@@ -28960,10 +29065,10 @@ function getRun(repoPath, runId) {
28960
29065
  }
28961
29066
  function listRuns(repoPath, filter = {}) {
28962
29067
  const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
28963
- if (!fs26.existsSync(runsDir)) return [];
29068
+ if (!fs27.existsSync(runsDir)) return [];
28964
29069
  const runs = [];
28965
29070
  for (const filePath of collectRunFiles(runsDir)) {
28966
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
29071
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
28967
29072
  if (!parsed.success) continue;
28968
29073
  if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
28969
29074
  runs.push(parsed.data);
@@ -28976,17 +29081,17 @@ function listRuns(repoPath, filter = {}) {
28976
29081
  }
28977
29082
 
28978
29083
  // src/core/slot-preflight.ts
28979
- var path29 = __toESM(require("path"));
29084
+ var path30 = __toESM(require("path"));
28980
29085
 
28981
29086
  // src/core/control-port.ts
28982
29087
  var import_child_process4 = require("child_process");
28983
- var fs28 = __toESM(require("fs"));
28984
- var path27 = __toESM(require("path"));
29088
+ var fs29 = __toESM(require("fs"));
29089
+ var path28 = __toESM(require("path"));
28985
29090
 
28986
29091
  // src/core/slot-ports.ts
28987
29092
  var import_child_process3 = require("child_process");
28988
- var fs27 = __toESM(require("fs"));
28989
- var path26 = __toESM(require("path"));
29093
+ var fs28 = __toESM(require("fs"));
29094
+ var path27 = __toESM(require("path"));
28990
29095
  function portStep(env3) {
28991
29096
  return Number(env3.HARNESS_PORT_STEP ?? 10);
28992
29097
  }
@@ -29042,7 +29147,7 @@ function allocateAppPorts(repoPath, agentId) {
29042
29147
  }
29043
29148
  function harnessUsesPm2(repoPath) {
29044
29149
  const harnessRoot = resolveHarnessRoot(repoPath);
29045
- return fs27.existsSync(path26.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29150
+ return fs28.existsSync(path27.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29046
29151
  }
29047
29152
 
29048
29153
  // src/core/control-port.ts
@@ -29097,13 +29202,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
29097
29202
  return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
29098
29203
  }
29099
29204
  function inspectControlUpReadiness(repoPath) {
29100
- const resolved = path27.resolve(repoPath);
29205
+ const resolved = path28.resolve(repoPath);
29101
29206
  const port = parseControlHostPort();
29102
29207
  const containers = listDockerContainers();
29103
29208
  const control = controlContainerOnPort(containers, port);
29104
29209
  const warnings = [];
29105
- const controlHarnessDir = path27.join(resolved, "control", ".har");
29106
- const harnessSlot1Active = fs28.existsSync(controlHarnessDir) && readSlotRegistry(path27.join(resolved, "control"), 1)?.status === "active";
29210
+ const controlHarnessDir = path28.join(resolved, "control", ".har");
29211
+ const harnessSlot1Active = fs29.existsSync(controlHarnessDir) && readSlotRegistry(path28.join(resolved, "control"), 1)?.status === "active";
29107
29212
  if (control) {
29108
29213
  return {
29109
29214
  warnings,
@@ -29134,9 +29239,9 @@ function inspectControlUpReadiness(repoPath) {
29134
29239
  }
29135
29240
 
29136
29241
  // src/core/slot-launch-guard-occupied.ts
29137
- var fs29 = __toESM(require("fs"));
29242
+ var fs30 = __toESM(require("fs"));
29138
29243
  var os6 = __toESM(require("os"));
29139
- var path28 = __toESM(require("path"));
29244
+ var path29 = __toESM(require("path"));
29140
29245
  var import_child_process5 = require("child_process");
29141
29246
  function runGit(cwd, args) {
29142
29247
  try {
@@ -29155,7 +29260,7 @@ function worktreeDirty(worktreePath) {
29155
29260
  }
29156
29261
  function gitCommonDir2(cwd) {
29157
29262
  const out = runGit(cwd, "rev-parse --git-common-dir");
29158
- return out ? path28.resolve(cwd, out) : void 0;
29263
+ return out ? path29.resolve(cwd, out) : void 0;
29159
29264
  }
29160
29265
  function sameGitCheckout2(a2, b2) {
29161
29266
  const left2 = gitCommonDir2(a2);
@@ -29164,20 +29269,20 @@ function sameGitCheckout2(a2, b2) {
29164
29269
  }
29165
29270
  function discoverWorktree(harnessRoot, agentId, projectName) {
29166
29271
  const session = readSlotRegistry(harnessRoot, agentId);
29167
- if (session?.worktreePath && fs29.existsSync(session.worktreePath)) {
29272
+ if (session?.worktreePath && fs30.existsSync(session.worktreePath)) {
29168
29273
  return session.worktreePath;
29169
29274
  }
29170
- const legacy = path28.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29171
- if (fs29.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
29172
- const worktreesRoot = path28.join(os6.homedir(), "worktrees");
29173
- if (!fs29.existsSync(worktreesRoot)) return void 0;
29275
+ const legacy = path29.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29276
+ if (fs30.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
29277
+ const worktreesRoot = path29.join(os6.homedir(), "worktrees");
29278
+ if (!fs30.existsSync(worktreesRoot)) return void 0;
29174
29279
  const suffix = `-har-agent-${agentId}-`;
29175
29280
  const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
29176
- for (const name of fs29.readdirSync(worktreesRoot)) {
29281
+ for (const name of fs30.readdirSync(worktreesRoot)) {
29177
29282
  if (!name.includes(suffix)) continue;
29178
- const candidate = path28.join(worktreesRoot, name);
29283
+ const candidate = path29.join(worktreesRoot, name);
29179
29284
  if (!sameGitCheckout2(harnessRoot, candidate)) continue;
29180
- if (!fs29.existsSync(path28.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29285
+ if (!fs30.existsSync(path29.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29181
29286
  return candidate;
29182
29287
  }
29183
29288
  return void 0;
@@ -29185,13 +29290,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
29185
29290
  function collectOccupiedSlot(repoPath, agentId) {
29186
29291
  const harnessRoot = resolveHarnessRoot(repoPath);
29187
29292
  const env3 = readHarnessEnv(harnessRoot);
29188
- const projectName = env3.HARNESS_PROJECT_NAME ?? path28.basename(harnessRoot);
29293
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
29189
29294
  const session = readSlotRegistry(harnessRoot, agentId);
29190
29295
  const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
29191
29296
  const workDir = session?.workDir;
29192
- const envInWorkDir = workDir ? fs29.existsSync(path28.join(workDir, `.env.agent.${agentId}`)) : false;
29193
- const envInRoot = fs29.existsSync(path28.join(harnessRoot, `.env.agent.${agentId}`));
29194
- const envInWorktree = worktreePath ? fs29.existsSync(path28.join(worktreePath, `.env.agent.${agentId}`)) : false;
29297
+ const envInWorkDir = workDir ? fs30.existsSync(path29.join(workDir, `.env.agent.${agentId}`)) : false;
29298
+ const envInRoot = fs30.existsSync(path29.join(harnessRoot, `.env.agent.${agentId}`));
29299
+ const envInWorktree = worktreePath ? fs30.existsSync(path29.join(worktreePath, `.env.agent.${agentId}`)) : false;
29195
29300
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29196
29301
  if (!active) return void 0;
29197
29302
  const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
@@ -29344,7 +29449,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
29344
29449
  function inspectSlotReadiness(repoPath, agentId, options = {}) {
29345
29450
  const harnessRoot = resolveHarnessRoot(repoPath);
29346
29451
  const env3 = readHarnessEnv(harnessRoot);
29347
- const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
29452
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29348
29453
  const usesPm2 = harnessUsesPm2(repoPath);
29349
29454
  const blockers = [];
29350
29455
  const remediations = [];
@@ -29546,7 +29651,7 @@ function readWorktreeBranch(worktreePath) {
29546
29651
  }
29547
29652
  function gitCommonDir3(cwd) {
29548
29653
  const out = runGit2(cwd, "rev-parse --git-common-dir");
29549
- return out ? path30.resolve(cwd, out) : void 0;
29654
+ return out ? path31.resolve(cwd, out) : void 0;
29550
29655
  }
29551
29656
  function sameGitCheckout3(a2, b2) {
29552
29657
  const left2 = gitCommonDir3(a2);
@@ -29558,12 +29663,12 @@ function gitPrefix2(cwd) {
29558
29663
  return out ?? "";
29559
29664
  }
29560
29665
  function discoverSessionWorktreePath(harnessRoot, agentId) {
29561
- const worktreesRoot = path30.join(os7.homedir(), "worktrees");
29562
- if (!fs30.existsSync(worktreesRoot)) return void 0;
29666
+ const worktreesRoot = path31.join(os7.homedir(), "worktrees");
29667
+ if (!fs31.existsSync(worktreesRoot)) return void 0;
29563
29668
  const suffix = `-har-agent-${agentId}-`;
29564
29669
  const relPrefix = gitPrefix2(harnessRoot);
29565
- const matches = fs30.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path30.join(worktreesRoot, entry.name)).filter(
29566
- (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs30.existsSync(path30.join(candidate, relPrefix, `.env.agent.${agentId}`))
29670
+ const matches = fs31.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path31.join(worktreesRoot, entry.name)).filter(
29671
+ (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs31.existsSync(path31.join(candidate, relPrefix, `.env.agent.${agentId}`))
29567
29672
  );
29568
29673
  return matches.sort()[0];
29569
29674
  }
@@ -29638,18 +29743,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
29638
29743
  }
29639
29744
  function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
29640
29745
  const env3 = readHarnessEnv(harnessRoot);
29641
- const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29746
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path31.basename(harnessRoot);
29642
29747
  const session = readSlotRegistry(harnessRoot, agentId);
29643
- const legacyWorktreePath = path30.join(
29748
+ const legacyWorktreePath = path31.join(
29644
29749
  os7.homedir(),
29645
29750
  "worktrees",
29646
29751
  `${projectName}-agent-${agentId}`
29647
29752
  );
29648
- const worktreePath = session?.worktreePath && fs30.existsSync(session.worktreePath) ? session.worktreePath : fs30.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
29753
+ const worktreePath = session?.worktreePath && fs31.existsSync(session.worktreePath) ? session.worktreePath : fs31.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
29649
29754
  const workDir = resolveAgentWorkDir(harnessRoot, agentId);
29650
- const envInWorkDir = workDir ? fs30.existsSync(path30.join(workDir, `.env.agent.${agentId}`)) : false;
29651
- const envInRoot = fs30.existsSync(path30.join(harnessRoot, `.env.agent.${agentId}`));
29652
- const envInWorktree = worktreePath ? fs30.existsSync(path30.join(worktreePath, `.env.agent.${agentId}`)) : false;
29755
+ const envInWorkDir = workDir ? fs31.existsSync(path31.join(workDir, `.env.agent.${agentId}`)) : false;
29756
+ const envInRoot = fs31.existsSync(path31.join(harnessRoot, `.env.agent.${agentId}`));
29757
+ const envInWorktree = worktreePath ? fs31.existsSync(path31.join(worktreePath, `.env.agent.${agentId}`)) : false;
29653
29758
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29654
29759
  const latest = latestRunForAgent(runs, agentId);
29655
29760
  const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
@@ -29702,7 +29807,7 @@ function collectEnvironmentStatus(repoPath) {
29702
29807
  const slotIds = getAgentSlotIds(harnessRoot);
29703
29808
  const pm2Procs = listPm2Processes2();
29704
29809
  return {
29705
- repoPath: path30.resolve(repoPath),
29810
+ repoPath: path31.resolve(repoPath),
29706
29811
  harnessRoot,
29707
29812
  gitRemote: readGitRemote(harnessRoot),
29708
29813
  profile: manifest?.profile,
@@ -29713,13 +29818,13 @@ function collectEnvironmentStatus(repoPath) {
29713
29818
 
29714
29819
  // src/core/validations.ts
29715
29820
  var crypto3 = __toESM(require("crypto"));
29716
- var fs32 = __toESM(require("fs"));
29717
- var path32 = __toESM(require("path"));
29821
+ var fs33 = __toESM(require("fs"));
29822
+ var path33 = __toESM(require("path"));
29718
29823
 
29719
29824
  // src/core/change-batch.ts
29720
- var fs31 = __toESM(require("fs"));
29825
+ var fs32 = __toESM(require("fs"));
29721
29826
  var os8 = __toESM(require("os"));
29722
- var path31 = __toESM(require("path"));
29827
+ var path32 = __toESM(require("path"));
29723
29828
  function git(checkoutDir, args, env3) {
29724
29829
  const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
29725
29830
  if (result.code !== 0) {
@@ -29735,7 +29840,7 @@ function isCheckoutRoot(checkoutDir) {
29735
29840
  const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
29736
29841
  if (!toplevel) return false;
29737
29842
  try {
29738
- return fs31.realpathSync(toplevel) === fs31.realpathSync(checkoutDir);
29843
+ return fs32.realpathSync(toplevel) === fs32.realpathSync(checkoutDir);
29739
29844
  } catch {
29740
29845
  return false;
29741
29846
  }
@@ -29757,7 +29862,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
29757
29862
  if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
29758
29863
  for (const dir of ["rebase-merge", "rebase-apply"]) {
29759
29864
  const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
29760
- if (gitPath && fs31.existsSync(path31.resolve(checkoutDir, gitPath))) return true;
29865
+ if (gitPath && fs32.existsSync(path32.resolve(checkoutDir, gitPath))) return true;
29761
29866
  }
29762
29867
  return false;
29763
29868
  }
@@ -29780,8 +29885,8 @@ function parseNameStatus(output) {
29780
29885
  return files;
29781
29886
  }
29782
29887
  function computeWorktreeSnapshot(checkoutDir) {
29783
- const tmpDir = fs31.mkdtempSync(path31.join(os8.tmpdir(), "har-idx-"));
29784
- const tmpIndex = path31.join(tmpDir, "index");
29888
+ const tmpDir = fs32.mkdtempSync(path32.join(os8.tmpdir(), "har-idx-"));
29889
+ const tmpIndex = path32.join(tmpDir, "index");
29785
29890
  const env3 = { GIT_INDEX_FILE: tmpIndex };
29786
29891
  try {
29787
29892
  const headTree = getHeadTree(checkoutDir);
@@ -29804,35 +29909,35 @@ function computeWorktreeSnapshot(checkoutDir) {
29804
29909
  changedFiles: parseNameStatus(diffOutput)
29805
29910
  };
29806
29911
  } finally {
29807
- fs31.rmSync(tmpDir, { recursive: true, force: true });
29912
+ fs32.rmSync(tmpDir, { recursive: true, force: true });
29808
29913
  }
29809
29914
  }
29810
29915
 
29811
29916
  // src/core/validations.ts
29812
29917
  var VALIDATIONS_DIR = "validations";
29813
29918
  function getValidationsDir(checkoutDir) {
29814
- return path32.join(checkoutDir, ".har", VALIDATIONS_DIR);
29919
+ return path33.join(checkoutDir, ".har", VALIDATIONS_DIR);
29815
29920
  }
29816
29921
  function validationPath(checkoutDir, treeHash) {
29817
- return path32.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29922
+ return path33.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29818
29923
  }
29819
29924
  function writeRecord(checkoutDir, record2) {
29820
29925
  const dir = getValidationsDir(checkoutDir);
29821
- fs32.mkdirSync(dir, { recursive: true });
29822
- fs32.writeFileSync(
29926
+ fs33.mkdirSync(dir, { recursive: true });
29927
+ fs33.writeFileSync(
29823
29928
  validationPath(checkoutDir, record2.treeHash),
29824
29929
  `${JSON.stringify(record2, null, 2)}
29825
29930
  `
29826
29931
  );
29827
29932
  }
29828
29933
  function ensureValidationsIgnored(checkoutDir) {
29829
- const harDir = path32.join(checkoutDir, ".har");
29830
- if (!fs32.existsSync(harDir)) return;
29831
- const gitignorePath = path32.join(harDir, ".gitignore");
29832
- const content = fs32.existsSync(gitignorePath) ? fs32.readFileSync(gitignorePath, "utf8") : "";
29934
+ const harDir = path33.join(checkoutDir, ".har");
29935
+ if (!fs33.existsSync(harDir)) return;
29936
+ const gitignorePath = path33.join(harDir, ".gitignore");
29937
+ const content = fs33.existsSync(gitignorePath) ? fs33.readFileSync(gitignorePath, "utf8") : "";
29833
29938
  if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
29834
29939
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
29835
- fs32.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29940
+ fs33.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29836
29941
  `);
29837
29942
  }
29838
29943
  function recordValidation(input) {
@@ -29849,8 +29954,8 @@ function recordValidation(input) {
29849
29954
  treeHash: snapshot.treeHash,
29850
29955
  headSha: snapshot.headSha,
29851
29956
  branch: snapshot.branch,
29852
- workDir: path32.resolve(input.checkoutDir),
29853
- harnessRoot: path32.resolve(input.harnessRoot),
29957
+ workDir: path33.resolve(input.checkoutDir),
29958
+ harnessRoot: path33.resolve(input.harnessRoot),
29854
29959
  agentId: input.agentId,
29855
29960
  status: input.status,
29856
29961
  full: input.full,
@@ -29862,16 +29967,16 @@ function recordValidation(input) {
29862
29967
  committedAt: existing?.committedAt
29863
29968
  });
29864
29969
  writeRecord(input.checkoutDir, record2);
29865
- if (path32.resolve(input.harnessRoot) !== path32.resolve(input.checkoutDir)) {
29970
+ if (path33.resolve(input.harnessRoot) !== path33.resolve(input.checkoutDir)) {
29866
29971
  writeRecord(input.harnessRoot, record2);
29867
29972
  }
29868
29973
  return record2;
29869
29974
  }
29870
29975
  function findValidation(checkoutDir, treeHash) {
29871
29976
  const file = validationPath(checkoutDir, treeHash);
29872
- if (!fs32.existsSync(file)) return void 0;
29977
+ if (!fs33.existsSync(file)) return void 0;
29873
29978
  try {
29874
- return ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(file, "utf8")));
29979
+ return ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(file, "utf8")));
29875
29980
  } catch {
29876
29981
  return void 0;
29877
29982
  }
@@ -29886,19 +29991,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
29886
29991
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29887
29992
  };
29888
29993
  writeRecord(checkoutDir, updated);
29889
- if (record2.harnessRoot && path32.resolve(record2.harnessRoot) !== path32.resolve(checkoutDir) && fs32.existsSync(path32.join(record2.harnessRoot, ".har"))) {
29994
+ if (record2.harnessRoot && path33.resolve(record2.harnessRoot) !== path33.resolve(checkoutDir) && fs33.existsSync(path33.join(record2.harnessRoot, ".har"))) {
29890
29995
  writeRecord(record2.harnessRoot, updated);
29891
29996
  }
29892
29997
  return updated;
29893
29998
  }
29894
29999
  function listValidations(harnessRoot) {
29895
30000
  const dir = getValidationsDir(harnessRoot);
29896
- if (!fs32.existsSync(dir)) return [];
30001
+ if (!fs33.existsSync(dir)) return [];
29897
30002
  const records = [];
29898
- for (const entry of fs32.readdirSync(dir)) {
30003
+ for (const entry of fs33.readdirSync(dir)) {
29899
30004
  if (!entry.endsWith(".json")) continue;
29900
30005
  try {
29901
- records.push(ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(path32.join(dir, entry), "utf8"))));
30006
+ records.push(ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(path33.join(dir, entry), "utf8"))));
29902
30007
  } catch {
29903
30008
  }
29904
30009
  }
@@ -29928,9 +30033,9 @@ function createRemoteExecutor(apiUrl, apiKey) {
29928
30033
  }
29929
30034
 
29930
30035
  // src/core/telemetry-config.ts
29931
- var fs33 = __toESM(require("fs"));
30036
+ var fs34 = __toESM(require("fs"));
29932
30037
  var os9 = __toESM(require("os"));
29933
- var path33 = __toESM(require("path"));
30038
+ var path34 = __toESM(require("path"));
29934
30039
  var DEFAULT_SIGNALS_ON = {
29935
30040
  metrics: true,
29936
30041
  logs: true,
@@ -29945,9 +30050,9 @@ var DEFAULT_SIGNALS_OFF = {
29945
30050
  };
29946
30051
  function getPreferencePath() {
29947
30052
  if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
29948
- return path33.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
30053
+ return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
29949
30054
  }
29950
- return path33.join(os9.homedir(), ".har", "telemetry.json");
30055
+ return path34.join(os9.homedir(), ".har", "telemetry.json");
29951
30056
  }
29952
30057
  function parseEnvOverride(raw) {
29953
30058
  if (raw === void 0 || raw === "") return void 0;
@@ -29968,10 +30073,10 @@ function normalizeSignals(enabled, raw) {
29968
30073
  function readTelemetryPreference() {
29969
30074
  const preferencePath = getPreferencePath();
29970
30075
  try {
29971
- if (!fs33.existsSync(preferencePath)) {
30076
+ if (!fs34.existsSync(preferencePath)) {
29972
30077
  return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
29973
30078
  }
29974
- const parsed = JSON.parse(fs33.readFileSync(preferencePath, "utf8"));
30079
+ const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
29975
30080
  const enabled = parsed.enabled !== false;
29976
30081
  return {
29977
30082
  enabled,
@@ -29994,8 +30099,8 @@ function writeTelemetryPreference(enabled, signals) {
29994
30099
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29995
30100
  };
29996
30101
  const preferencePath = getPreferencePath();
29997
- fs33.mkdirSync(path33.dirname(preferencePath), { recursive: true });
29998
- fs33.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
30102
+ fs34.mkdirSync(path34.dirname(preferencePath), { recursive: true });
30103
+ fs34.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
29999
30104
  return preference;
30000
30105
  }
30001
30106
  function isTelemetryEnabled() {
@@ -30019,13 +30124,13 @@ var TELEMETRY_SIGNALS = [
30019
30124
  ];
30020
30125
 
30021
30126
  // src/core/usage-harvest/claude.ts
30022
- var fs35 = __toESM(require("fs"));
30127
+ var fs36 = __toESM(require("fs"));
30023
30128
  var os10 = __toESM(require("os"));
30024
- var path35 = __toESM(require("path"));
30129
+ var path36 = __toESM(require("path"));
30025
30130
 
30026
30131
  // src/core/telemetry-env.ts
30027
- var fs34 = __toESM(require("fs"));
30028
- var path34 = __toESM(require("path"));
30132
+ var fs35 = __toESM(require("fs"));
30133
+ var path35 = __toESM(require("path"));
30029
30134
  function escapeAttrValue(value) {
30030
30135
  return value.replace(/[,=]/g, "_");
30031
30136
  }
@@ -30092,8 +30197,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs, options) {
30092
30197
  ${TELEMETRY_MARKER_END}
30093
30198
  `;
30094
30199
  let existing = "";
30095
- if (fs34.existsSync(envFilePath)) {
30096
- existing = fs34.readFileSync(envFilePath, "utf8");
30200
+ if (fs35.existsSync(envFilePath)) {
30201
+ existing = fs35.readFileSync(envFilePath, "utf8");
30097
30202
  const start = existing.indexOf(TELEMETRY_MARKER_START);
30098
30203
  if (start >= 0) {
30099
30204
  const end = existing.indexOf(TELEMETRY_MARKER_END, start);
@@ -30104,9 +30209,9 @@ ${TELEMETRY_MARKER_END}
30104
30209
  }
30105
30210
  }
30106
30211
  }
30107
- fs34.mkdirSync(path34.dirname(envFilePath), { recursive: true });
30212
+ fs35.mkdirSync(path35.dirname(envFilePath), { recursive: true });
30108
30213
  const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
30109
- fs34.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30214
+ fs35.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30110
30215
  }
30111
30216
  function buildCodexOtelSnippet(attrs) {
30112
30217
  const apiUrl = getControlApiUrl().replace(/\/$/, "");
@@ -30127,21 +30232,21 @@ metrics_exporter = "otlp-http"
30127
30232
 
30128
30233
  // src/core/usage-harvest/claude.ts
30129
30234
  function pathsMatch(candidate, targets) {
30130
- const norm = path35.resolve(candidate);
30235
+ const norm = path36.resolve(candidate);
30131
30236
  return targets.some((t2) => {
30132
- const target = path35.resolve(t2);
30133
- return norm === target || norm.startsWith(target + path35.sep) || target.startsWith(norm + path35.sep);
30237
+ const target = path36.resolve(t2);
30238
+ return norm === target || norm.startsWith(target + path36.sep) || target.startsWith(norm + path36.sep);
30134
30239
  });
30135
30240
  }
30136
30241
  function claudeProjectsRoot() {
30137
- return process.env.HAR_CLAUDE_PROJECTS_DIR ? path35.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path35.join(os10.homedir(), ".claude", "projects");
30242
+ return process.env.HAR_CLAUDE_PROJECTS_DIR ? path36.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path36.join(os10.homedir(), ".claude", "projects");
30138
30243
  }
30139
30244
  function encodeClaudeProjectDir(cwd) {
30140
30245
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
30141
30246
  }
30142
30247
  function readJsonlRecords(filePath) {
30143
- if (!fs35.existsSync(filePath)) return [];
30144
- const lines = fs35.readFileSync(filePath, "utf8").split(/\r?\n/);
30248
+ if (!fs36.existsSync(filePath)) return [];
30249
+ const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
30145
30250
  const out = [];
30146
30251
  for (const line of lines) {
30147
30252
  const trimmed = line.trim();
@@ -30184,16 +30289,16 @@ function findMatchingClaudeTranscripts(slot) {
30184
30289
  const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
30185
30290
  if (targets.length === 0) return [];
30186
30291
  const root = claudeProjectsRoot();
30187
- if (!fs35.existsSync(root)) return [];
30292
+ if (!fs36.existsSync(root)) return [];
30188
30293
  const matches = [];
30189
- for (const entry of fs35.readdirSync(root, { withFileTypes: true })) {
30294
+ for (const entry of fs36.readdirSync(root, { withFileTypes: true })) {
30190
30295
  if (!entry.isDirectory()) continue;
30191
- const projectDir = path35.join(root, entry.name);
30296
+ const projectDir = path36.join(root, entry.name);
30192
30297
  const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
30193
- for (const file of fs35.readdirSync(projectDir)) {
30298
+ for (const file of fs36.readdirSync(projectDir)) {
30194
30299
  if (!file.endsWith(".jsonl")) continue;
30195
- const filePath = path35.join(projectDir, file);
30196
- const stat = fs35.statSync(filePath);
30300
+ const filePath = path36.join(projectDir, file);
30301
+ const stat = fs36.statSync(filePath);
30197
30302
  const records = readJsonlRecords(filePath);
30198
30303
  let cwdHit = encodedHit;
30199
30304
  for (const record2 of records) {
@@ -30304,30 +30409,30 @@ function harvestClaudeEvents(slot) {
30304
30409
  }
30305
30410
 
30306
30411
  // src/core/usage-harvest/codex.ts
30307
- var fs36 = __toESM(require("fs"));
30412
+ var fs37 = __toESM(require("fs"));
30308
30413
  var os11 = __toESM(require("os"));
30309
- var path36 = __toESM(require("path"));
30414
+ var path37 = __toESM(require("path"));
30310
30415
  function pathsMatch2(candidate, targets) {
30311
- const norm = path36.resolve(candidate);
30416
+ const norm = path37.resolve(candidate);
30312
30417
  return targets.some((t2) => {
30313
- const target = path36.resolve(t2);
30314
- return norm === target || norm.startsWith(target + path36.sep) || target.startsWith(norm + path36.sep);
30418
+ const target = path37.resolve(t2);
30419
+ return norm === target || norm.startsWith(target + path37.sep) || target.startsWith(norm + path37.sep);
30315
30420
  });
30316
30421
  }
30317
30422
  function codexSessionsRoot() {
30318
- return process.env.HAR_CODEX_SESSIONS_DIR ? path36.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path36.join(os11.homedir(), ".codex", "sessions");
30423
+ return process.env.HAR_CODEX_SESSIONS_DIR ? path37.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path37.join(os11.homedir(), ".codex", "sessions");
30319
30424
  }
30320
30425
  function walkJsonlFiles(dir, out = []) {
30321
- if (!fs36.existsSync(dir)) return out;
30322
- for (const entry of fs36.readdirSync(dir, { withFileTypes: true })) {
30323
- const full = path36.join(dir, entry.name);
30426
+ if (!fs37.existsSync(dir)) return out;
30427
+ for (const entry of fs37.readdirSync(dir, { withFileTypes: true })) {
30428
+ const full = path37.join(dir, entry.name);
30324
30429
  if (entry.isDirectory()) walkJsonlFiles(full, out);
30325
30430
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
30326
30431
  }
30327
30432
  return out;
30328
30433
  }
30329
30434
  function readJsonl(filePath) {
30330
- const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
30435
+ const lines = fs37.readFileSync(filePath, "utf8").split(/\r?\n/);
30331
30436
  const out = [];
30332
30437
  for (const line of lines) {
30333
30438
  const trimmed = line.trim();
@@ -30391,7 +30496,7 @@ function harvestCodexUsage(slot) {
30391
30496
  const extracted = extractCodexTokens(records);
30392
30497
  if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
30393
30498
  if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
30394
- const mtime = fs36.statSync(file).mtimeMs;
30499
+ const mtime = fs37.statSync(file).mtimeMs;
30395
30500
  if (mtime >= bestMtime) {
30396
30501
  bestMtime = mtime;
30397
30502
  best = extracted;
@@ -30473,7 +30578,7 @@ async function syncAllKnownReposWithControl(options) {
30473
30578
  }
30474
30579
  const repoPaths = new Set(listRegisteredRepos());
30475
30580
  if (options?.cwd) {
30476
- const cwd = path37.resolve(options.cwd);
30581
+ const cwd = path38.resolve(options.cwd);
30477
30582
  if (readManifest(cwd)) repoPaths.add(cwd);
30478
30583
  }
30479
30584
  try {
@@ -30481,7 +30586,7 @@ async function syncAllKnownReposWithControl(options) {
30481
30586
  if (listResponse.ok) {
30482
30587
  const repos = await listResponse.json();
30483
30588
  for (const repo of repos) {
30484
- const resolved = path37.resolve(repo.path);
30589
+ const resolved = path38.resolve(repo.path);
30485
30590
  if (readManifest(resolved)) repoPaths.add(resolved);
30486
30591
  }
30487
30592
  }
@@ -30500,7 +30605,7 @@ async function syncAllKnownReposWithControl(options) {
30500
30605
  return { synced, failed };
30501
30606
  }
30502
30607
  async function registerRepoWithControl(options) {
30503
- const repoPath = path37.resolve(options.repoPath);
30608
+ const repoPath = path38.resolve(options.repoPath);
30504
30609
  const apiUrl = options.apiUrl ?? getControlApiUrl();
30505
30610
  const manifest = readManifest(repoPath);
30506
30611
  const stagesRegistry = readStageRegistry(repoPath);
@@ -30513,7 +30618,7 @@ async function registerRepoWithControl(options) {
30513
30618
  return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
30514
30619
  }
30515
30620
  async function syncRepoWithControl(options) {
30516
- const repoPath = path37.resolve(options.repoPath);
30621
+ const repoPath = path38.resolve(options.repoPath);
30517
30622
  const apiUrl = options.apiUrl ?? getControlApiUrl();
30518
30623
  if (options.cloud) {
30519
30624
  const remote = createRemoteExecutor();
@@ -30541,7 +30646,7 @@ async function syncRepoWithControl(options) {
30541
30646
  const listResponse = await fetch(`${apiUrl}/api/repos`);
30542
30647
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
30543
30648
  const repos = await listResponse.json();
30544
- const existing = repos.find((r2) => path37.resolve(r2.path) === repoPath);
30649
+ const existing = repos.find((r2) => path38.resolve(r2.path) === repoPath);
30545
30650
  if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
30546
30651
  await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
30547
30652
  return;
@@ -30660,7 +30765,7 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
30660
30765
 
30661
30766
  // src/core/control-lifecycle.ts
30662
30767
  var import_child_process8 = require("child_process");
30663
- var path38 = __toESM(require("path"));
30768
+ var path39 = __toESM(require("path"));
30664
30769
 
30665
30770
  // src/core/control-image.ts
30666
30771
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -30683,10 +30788,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
30683
30788
  var CONTROL_CONTAINER_PORT = 3847;
30684
30789
  var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
30685
30790
  function resolveControlDir() {
30686
- return path38.resolve(__dirname, "..", "control");
30791
+ return path39.resolve(__dirname, "..", "control");
30687
30792
  }
30688
30793
  function resolveControlBuildContext() {
30689
- return path38.resolve(resolveControlDir(), "..");
30794
+ return path39.resolve(resolveControlDir(), "..");
30690
30795
  }
30691
30796
  function buildDockerRunArgs(options) {
30692
30797
  const name = options.containerName ?? CONTROL_CONTAINER_NAME;
@@ -30742,7 +30847,7 @@ async function startMissionControl(options) {
30742
30847
  "-t",
30743
30848
  imageRef,
30744
30849
  "-f",
30745
- path38.join(resolveControlDir(), "Dockerfile"),
30850
+ path39.join(resolveControlDir(), "Dockerfile"),
30746
30851
  resolveControlBuildContext()
30747
30852
  ]);
30748
30853
  if (buildCode !== 0) {
@@ -30884,7 +30989,7 @@ async function ensureTelemetryInfrastructure(options) {
30884
30989
  }
30885
30990
 
30886
30991
  // src/core/run-service.ts
30887
- var path39 = __toESM(require("path"));
30992
+ var path40 = __toESM(require("path"));
30888
30993
  function extractShellOutput(result) {
30889
30994
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
30890
30995
  const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
@@ -31048,14 +31153,14 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
31048
31153
  suffix: session.suffix,
31049
31154
  createdAt: session.createdAt
31050
31155
  });
31051
- const envFile = path39.join(session.workDir, `.env.agent.${options.agentId}`);
31156
+ const envFile = path40.join(session.workDir, `.env.agent.${options.agentId}`);
31052
31157
  try {
31053
31158
  appendTelemetryEnvToFile(
31054
31159
  envFile,
31055
31160
  {
31056
31161
  sessionKey,
31057
31162
  agentId: options.agentId,
31058
- repoPath: path39.resolve(options.repoPath),
31163
+ repoPath: path40.resolve(options.repoPath),
31059
31164
  workDir: session.workDir,
31060
31165
  branch: session.branch,
31061
31166
  suffix: session.suffix,
@@ -31421,7 +31526,7 @@ var envCommand = {
31421
31526
  }
31422
31527
  };
31423
31528
  async function handleInit(argv) {
31424
- const repoPath = path40.resolve(argv.repo);
31529
+ const repoPath = path41.resolve(argv.repo);
31425
31530
  header("har env init");
31426
31531
  info(`Repository: ${repoPath}`);
31427
31532
  try {
@@ -31479,7 +31584,7 @@ async function handleInit(argv) {
31479
31584
  }
31480
31585
  }
31481
31586
  async function handleMaintain(argv) {
31482
- const repoPath = path40.resolve(argv.repo);
31587
+ const repoPath = path41.resolve(argv.repo);
31483
31588
  header("har env maintain");
31484
31589
  info(`Repository: ${repoPath}`);
31485
31590
  try {
@@ -31577,7 +31682,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31577
31682
  if (autoYes) {
31578
31683
  const proposal = readAgentMdProposal(repoPath);
31579
31684
  if (proposal) {
31580
- writeFileSafe(path40.join(repoPath, "AGENT.md"), proposal.content);
31685
+ writeFileSafe(path41.join(repoPath, "AGENT.md"), proposal.content);
31581
31686
  clearAgentMdProposal(repoPath);
31582
31687
  info("Applied AGENT.md proposal (--yes)");
31583
31688
  }
@@ -31586,7 +31691,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31586
31691
  await promptApplyAgentMdProposal(repoPath);
31587
31692
  }
31588
31693
  async function handleAddStage(argv) {
31589
- const repoPath = path40.resolve(argv.repo);
31694
+ const repoPath = path41.resolve(argv.repo);
31590
31695
  const available = listStageTemplateIds();
31591
31696
  if (argv.list) {
31592
31697
  for (const id of available) {
@@ -31663,7 +31768,7 @@ async function handleAddStage(argv) {
31663
31768
  }
31664
31769
  }
31665
31770
  async function handlePreflight(argv) {
31666
- const repo = path40.resolve(argv.repo);
31771
+ const repo = path41.resolve(argv.repo);
31667
31772
  const agentId = validateAgentId(argv.id, repo);
31668
31773
  const result = await preflightEnvironment({
31669
31774
  repoPath: repo,
@@ -31681,7 +31786,7 @@ async function handlePreflight(argv) {
31681
31786
  process.exit(result.code);
31682
31787
  }
31683
31788
  async function handleLaunch(argv) {
31684
- const repo = path40.resolve(argv.repo);
31789
+ const repo = path41.resolve(argv.repo);
31685
31790
  const agentId = validateAgentId(argv.id, repo);
31686
31791
  let confirmReplace = argv.replace;
31687
31792
  let force = argv.force;
@@ -31765,7 +31870,7 @@ async function handleRecover(argv) {
31765
31870
  });
31766
31871
  }
31767
31872
  async function handleVerify(argv) {
31768
- const repo = path40.resolve(argv.repo);
31873
+ const repo = path41.resolve(argv.repo);
31769
31874
  const agentId = validateAgentId(argv.id, repo);
31770
31875
  const result = await runVerification({
31771
31876
  repoPath: repo,
@@ -31777,7 +31882,7 @@ async function handleVerify(argv) {
31777
31882
  process.exit(result.code);
31778
31883
  }
31779
31884
  async function handleTeardown(argv) {
31780
- const repo = path40.resolve(argv.repo);
31885
+ const repo = path41.resolve(argv.repo);
31781
31886
  const agentId = validateAgentId(argv.id, repo);
31782
31887
  const result = await teardownEnvironment({
31783
31888
  repoPath: repo,
@@ -31788,7 +31893,7 @@ async function handleTeardown(argv) {
31788
31893
  process.exit(result.code);
31789
31894
  }
31790
31895
  async function handleComplete(argv) {
31791
- const repo = path40.resolve(argv.repo);
31896
+ const repo = path41.resolve(argv.repo);
31792
31897
  const agentId = validateAgentId(argv.id, repo);
31793
31898
  const result = await completeEnvironment({
31794
31899
  repoPath: repo,
@@ -31809,7 +31914,7 @@ async function handleComplete(argv) {
31809
31914
  process.exit(result.code);
31810
31915
  }
31811
31916
  async function handleStatus(argv) {
31812
- const repoPath = path40.resolve(argv.repo);
31917
+ const repoPath = path41.resolve(argv.repo);
31813
31918
  if (argv.json) {
31814
31919
  const status = collectEnvironmentStatus(repoPath);
31815
31920
  const output = EnvironmentStatusSchema.parse(status);
@@ -31822,7 +31927,7 @@ async function handleStatus(argv) {
31822
31927
  });
31823
31928
  }
31824
31929
  async function handleRunsList(argv) {
31825
- const repoPath = path40.resolve(argv.repo);
31930
+ const repoPath = path41.resolve(argv.repo);
31826
31931
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
31827
31932
  if (argv.json) {
31828
31933
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -31839,7 +31944,7 @@ async function handleRunsList(argv) {
31839
31944
  }
31840
31945
  }
31841
31946
  async function handleRunsGet(argv) {
31842
- const run2 = getRun(path40.resolve(argv.repo), argv.runId);
31947
+ const run2 = getRun(path41.resolve(argv.repo), argv.runId);
31843
31948
  if (!run2) {
31844
31949
  error(`Run not found: ${argv.runId}`);
31845
31950
  process.exit(1);
@@ -31880,7 +31985,13 @@ function printDrift(drift) {
31880
31985
  );
31881
31986
  warn(" See maintain/templates/harness.env in the maintenance bundle.");
31882
31987
  }
31883
- if (!drift.generatorVersion.outdated && drift.checksumMismatch.length === 0 && drift.missing.length === 0 && drift.extra.length === 0 && drift.missingPortVars.length === 0) {
31988
+ if (drift.agentSlotMismatch) {
31989
+ warn(
31990
+ ` Agent slot limits disagree: stages.json ${drift.agentSlotMismatch.stages.min}\u2013${drift.agentSlotMismatch.stages.max}, harness.env ${drift.agentSlotMismatch.env.min}\u2013${drift.agentSlotMismatch.env.max}`
31991
+ );
31992
+ warn(" Canonical source is .har/stages.json \u2014 har env maintain --finalize syncs harness.env.");
31993
+ }
31994
+ if (!drift.generatorVersion.outdated && drift.checksumMismatch.length === 0 && drift.missing.length === 0 && drift.extra.length === 0 && drift.missingPortVars.length === 0 && !drift.agentSlotMismatch) {
31884
31995
  success(" Harness matches bundled templates");
31885
31996
  }
31886
31997
  }
@@ -31912,7 +32023,7 @@ function printNextSteps(auto2) {
31912
32023
  }
31913
32024
 
31914
32025
  // src/cli/commands/control.ts
31915
- var path41 = __toESM(require("path"));
32026
+ var path42 = __toESM(require("path"));
31916
32027
  var controlCommand = {
31917
32028
  command: "control <subcommand>",
31918
32029
  describe: "Mission Control dashboard (local)",
@@ -32015,7 +32126,7 @@ async function handleDown() {
32015
32126
  process.exit(code);
32016
32127
  }
32017
32128
  async function handleRegister(argv) {
32018
- const repoPath = path41.resolve(argv.repo);
32129
+ const repoPath = path42.resolve(argv.repo);
32019
32130
  header("har control register");
32020
32131
  info(`Repository: ${repoPath}`);
32021
32132
  try {
@@ -32035,7 +32146,7 @@ async function handleRegister(argv) {
32035
32146
  }
32036
32147
  }
32037
32148
  async function handleSync(argv) {
32038
- const repoPath = path41.resolve(argv.repo);
32149
+ const repoPath = path42.resolve(argv.repo);
32039
32150
  try {
32040
32151
  await syncRepoWithControl({
32041
32152
  repoPath,
@@ -32074,7 +32185,7 @@ async function handleWatch(argv) {
32074
32185
  };
32075
32186
  const tick = async () => {
32076
32187
  if (argv.repo) {
32077
- await syncOne(path41.resolve(argv.repo));
32188
+ await syncOne(path42.resolve(argv.repo));
32078
32189
  return;
32079
32190
  }
32080
32191
  try {
@@ -32105,12 +32216,12 @@ async function handleLogin(argv) {
32105
32216
  }
32106
32217
 
32107
32218
  // src/cli/commands/hooks.ts
32108
- var path44 = __toESM(require("path"));
32219
+ var path45 = __toESM(require("path"));
32109
32220
 
32110
32221
  // src/core/hooks.ts
32111
- var fs37 = __toESM(require("fs"));
32222
+ var fs38 = __toESM(require("fs"));
32112
32223
  var os12 = __toESM(require("os"));
32113
- var path42 = __toESM(require("path"));
32224
+ var path43 = __toESM(require("path"));
32114
32225
  var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
32115
32226
  var MARKER_END = "# <<< har commit gate <<<";
32116
32227
  var PRE_COMMIT_BLOCK = [
@@ -32135,7 +32246,7 @@ function resolveCheckoutRoot(cwd) {
32135
32246
  function resolveHooksDir(checkoutDir) {
32136
32247
  const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
32137
32248
  if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
32138
- return path42.resolve(checkoutDir, hooksPath);
32249
+ return path43.resolve(checkoutDir, hooksPath);
32139
32250
  }
32140
32251
  function getConfiguredHooksPath(checkoutDir) {
32141
32252
  return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
@@ -32143,9 +32254,9 @@ function getConfiguredHooksPath(checkoutDir) {
32143
32254
  function defaultHarInvocation() {
32144
32255
  const entry = process.argv[1];
32145
32256
  if (entry && entry.endsWith(".js")) {
32146
- return `"${process.execPath}" "${path42.resolve(entry)}"`;
32257
+ return `"${process.execPath}" "${path43.resolve(entry)}"`;
32147
32258
  }
32148
- if (entry) return `"${path42.resolve(entry)}"`;
32259
+ if (entry) return `"${path43.resolve(entry)}"`;
32149
32260
  return "har";
32150
32261
  }
32151
32262
  function buildHookScript(harInvocation, subcommand, failOpenNotice) {
@@ -32166,40 +32277,40 @@ exit 0
32166
32277
  `;
32167
32278
  }
32168
32279
  function upsertMarkedBlock(filePath, block) {
32169
- if (!fs37.existsSync(filePath)) {
32170
- fs37.writeFileSync(filePath, `#!/bin/sh
32280
+ if (!fs38.existsSync(filePath)) {
32281
+ fs38.writeFileSync(filePath, `#!/bin/sh
32171
32282
  ${block}
32172
32283
  `, { mode: 493 });
32173
32284
  return "created";
32174
32285
  }
32175
- const content = fs37.readFileSync(filePath, "utf8");
32286
+ const content = fs38.readFileSync(filePath, "utf8");
32176
32287
  if (content.includes(MARKER_START)) {
32177
32288
  const pattern = new RegExp(
32178
32289
  `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
32179
32290
  );
32180
- fs37.writeFileSync(filePath, content.replace(pattern, block));
32181
- fs37.chmodSync(filePath, 493);
32291
+ fs38.writeFileSync(filePath, content.replace(pattern, block));
32292
+ fs38.chmodSync(filePath, 493);
32182
32293
  return "updated";
32183
32294
  }
32184
32295
  const suffix = content.endsWith("\n") ? "" : "\n";
32185
- fs37.writeFileSync(filePath, `${content}${suffix}
32296
+ fs38.writeFileSync(filePath, `${content}${suffix}
32186
32297
  ${block}
32187
32298
  `);
32188
- fs37.chmodSync(filePath, 493);
32299
+ fs38.chmodSync(filePath, 493);
32189
32300
  return "appended";
32190
32301
  }
32191
32302
  function removeMarkedBlock(filePath) {
32192
- if (!fs37.existsSync(filePath)) return false;
32193
- const content = fs37.readFileSync(filePath, "utf8");
32303
+ if (!fs38.existsSync(filePath)) return false;
32304
+ const content = fs38.readFileSync(filePath, "utf8");
32194
32305
  if (!content.includes(MARKER_START)) return false;
32195
32306
  const pattern = new RegExp(
32196
32307
  `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
32197
32308
  );
32198
32309
  const stripped = content.replace(pattern, "\n");
32199
32310
  if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
32200
- fs37.rmSync(filePath);
32311
+ fs38.rmSync(filePath);
32201
32312
  } else {
32202
- fs37.writeFileSync(filePath, stripped);
32313
+ fs38.writeFileSync(filePath, stripped);
32203
32314
  }
32204
32315
  return true;
32205
32316
  }
@@ -32221,20 +32332,20 @@ Or re-run with --force to write into that directory anyway.`
32221
32332
  );
32222
32333
  }
32223
32334
  const hooksDir = resolveHooksDir(checkout);
32224
- fs37.mkdirSync(hooksDir, { recursive: true });
32335
+ fs38.mkdirSync(hooksDir, { recursive: true });
32225
32336
  const invocation = options.harInvocation ?? defaultHarInvocation();
32226
- fs37.writeFileSync(
32227
- path42.join(hooksDir, "har-pre-commit"),
32337
+ fs38.writeFileSync(
32338
+ path43.join(hooksDir, "har-pre-commit"),
32228
32339
  buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
32229
32340
  { mode: 493 }
32230
32341
  );
32231
- fs37.writeFileSync(
32232
- path42.join(hooksDir, "har-post-commit"),
32342
+ fs38.writeFileSync(
32343
+ path43.join(hooksDir, "har-post-commit"),
32233
32344
  buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
32234
32345
  { mode: 493 }
32235
32346
  );
32236
- const preCommit = upsertMarkedBlock(path42.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32237
- const postCommit = upsertMarkedBlock(path42.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32347
+ const preCommit = upsertMarkedBlock(path43.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32348
+ const postCommit = upsertMarkedBlock(path43.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32238
32349
  ensureValidationsIgnored(checkout);
32239
32350
  return { hooksDir, preCommit, postCommit };
32240
32351
  }
@@ -32243,12 +32354,12 @@ function uninstallHooks(repoPath) {
32243
32354
  if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
32244
32355
  const hooksDir = resolveHooksDir(checkout);
32245
32356
  let removed = false;
32246
- removed = removeMarkedBlock(path42.join(hooksDir, "pre-commit")) || removed;
32247
- removed = removeMarkedBlock(path42.join(hooksDir, "post-commit")) || removed;
32357
+ removed = removeMarkedBlock(path43.join(hooksDir, "pre-commit")) || removed;
32358
+ removed = removeMarkedBlock(path43.join(hooksDir, "post-commit")) || removed;
32248
32359
  for (const file of ["har-pre-commit", "har-post-commit"]) {
32249
- const full = path42.join(hooksDir, file);
32250
- if (fs37.existsSync(full)) {
32251
- fs37.rmSync(full);
32360
+ const full = path43.join(hooksDir, file);
32361
+ if (fs38.existsSync(full)) {
32362
+ fs38.rmSync(full);
32252
32363
  removed = true;
32253
32364
  }
32254
32365
  }
@@ -32265,9 +32376,9 @@ function getCommitGateConfig(checkoutDir) {
32265
32376
  function isAgentWorktree(checkoutDir) {
32266
32377
  const branch = getCurrentBranch(checkoutDir);
32267
32378
  if (branch && /^har-agent-\d+$/.test(branch)) return true;
32268
- const worktreesRoot = path42.join(os12.homedir(), "worktrees");
32269
- const resolved = path42.resolve(checkoutDir);
32270
- return resolved.startsWith(`${worktreesRoot}${path42.sep}`) && /-agent-\d+$/.test(path42.basename(resolved));
32379
+ const worktreesRoot = path43.join(os12.homedir(), "worktrees");
32380
+ const resolved = path43.resolve(checkoutDir);
32381
+ return resolved.startsWith(`${worktreesRoot}${path43.sep}`) && /-agent-\d+$/.test(path43.basename(resolved));
32271
32382
  }
32272
32383
  function resolveEffectiveMode(checkoutDir, gate) {
32273
32384
  if (!gate.enabled) return "off";
@@ -32280,15 +32391,15 @@ function getHooksStatus(repoPath) {
32280
32391
  const hooksDir = resolveHooksDir(checkout);
32281
32392
  const gate = getCommitGateConfig(checkout);
32282
32393
  const hasBlock = (name) => {
32283
- const file = path42.join(hooksDir, name);
32284
- return fs37.existsSync(file) && fs37.readFileSync(file, "utf8").includes(MARKER_START);
32394
+ const file = path43.join(hooksDir, name);
32395
+ return fs38.existsSync(file) && fs38.readFileSync(file, "utf8").includes(MARKER_START);
32285
32396
  };
32286
32397
  return {
32287
32398
  checkout,
32288
32399
  hooksDir,
32289
32400
  configuredHooksPath: getConfiguredHooksPath(checkout),
32290
- preCommitInstalled: hasBlock("pre-commit") && fs37.existsSync(path42.join(hooksDir, "har-pre-commit")),
32291
- postCommitInstalled: hasBlock("post-commit") && fs37.existsSync(path42.join(hooksDir, "har-post-commit")),
32401
+ preCommitInstalled: hasBlock("pre-commit") && fs38.existsSync(path43.join(hooksDir, "har-pre-commit")),
32402
+ postCommitInstalled: hasBlock("post-commit") && fs38.existsSync(path43.join(hooksDir, "har-post-commit")),
32292
32403
  gate,
32293
32404
  effectiveMode: resolveEffectiveMode(checkout, gate)
32294
32405
  };
@@ -32299,7 +32410,7 @@ function checkCommitGate(cwd) {
32299
32410
  }
32300
32411
  const checkout = resolveCheckoutRoot(cwd);
32301
32412
  if (!checkout) return { exitCode: 0, messages: [] };
32302
- if (!fs37.existsSync(path42.join(checkout, ".har", "stages.json"))) {
32413
+ if (!fs38.existsSync(path43.join(checkout, ".har", "stages.json"))) {
32303
32414
  return { exitCode: 0, messages: [] };
32304
32415
  }
32305
32416
  const gate = getCommitGateConfig(checkout);
@@ -32370,8 +32481,8 @@ async function recordCommitAssociation(cwd) {
32370
32481
  }
32371
32482
 
32372
32483
  // src/core/claude-hooks.ts
32373
- var fs38 = __toESM(require("fs"));
32374
- var path43 = __toESM(require("path"));
32484
+ var fs39 = __toESM(require("fs"));
32485
+ var path44 = __toESM(require("path"));
32375
32486
  var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
32376
32487
  var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
32377
32488
  var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
@@ -32382,8 +32493,8 @@ function isHarGuardEntry(entry) {
32382
32493
  );
32383
32494
  }
32384
32495
  function readSettings(settingsPath) {
32385
- if (!fs38.existsSync(settingsPath)) return {};
32386
- const raw = fs38.readFileSync(settingsPath, "utf8").trim();
32496
+ if (!fs39.existsSync(settingsPath)) return {};
32497
+ const raw = fs39.readFileSync(settingsPath, "utf8").trim();
32387
32498
  if (raw === "") return {};
32388
32499
  const parsed = JSON.parse(raw);
32389
32500
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -32392,19 +32503,19 @@ function readSettings(settingsPath) {
32392
32503
  return parsed;
32393
32504
  }
32394
32505
  function writeSettings(settingsPath, settings) {
32395
- fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
32396
- fs38.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32506
+ fs39.mkdirSync(path44.dirname(settingsPath), { recursive: true });
32507
+ fs39.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32397
32508
  }
32398
32509
  function installClaudeGuard(repoPath) {
32399
32510
  const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
32400
32511
  if (!templatePath) {
32401
32512
  throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
32402
32513
  }
32403
- const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32404
- fs38.mkdirSync(path43.dirname(guardScript), { recursive: true });
32405
- fs38.copyFileSync(templatePath, guardScript);
32406
- fs38.chmodSync(guardScript, 493);
32407
- const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32514
+ const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32515
+ fs39.mkdirSync(path44.dirname(guardScript), { recursive: true });
32516
+ fs39.copyFileSync(templatePath, guardScript);
32517
+ fs39.chmodSync(guardScript, 493);
32518
+ const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32408
32519
  const settings = readSettings(settingsPath);
32409
32520
  const hooks = settings.hooks ?? {};
32410
32521
  const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
@@ -32418,15 +32529,15 @@ function installClaudeGuard(repoPath) {
32418
32529
  }
32419
32530
  function uninstallClaudeGuard(repoPath) {
32420
32531
  let removed = false;
32421
- const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32422
- if (fs38.existsSync(guardScript)) {
32423
- fs38.rmSync(guardScript);
32532
+ const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32533
+ if (fs39.existsSync(guardScript)) {
32534
+ fs39.rmSync(guardScript);
32424
32535
  removed = true;
32425
- const parent = path43.dirname(guardScript);
32426
- if (fs38.existsSync(parent) && fs38.readdirSync(parent).length === 0) fs38.rmdirSync(parent);
32536
+ const parent = path44.dirname(guardScript);
32537
+ if (fs39.existsSync(parent) && fs39.readdirSync(parent).length === 0) fs39.rmdirSync(parent);
32427
32538
  }
32428
- const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32429
- if (fs38.existsSync(settingsPath)) {
32539
+ const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32540
+ if (fs39.existsSync(settingsPath)) {
32430
32541
  const settings = readSettings(settingsPath);
32431
32542
  const preToolUse = settings.hooks?.PreToolUse;
32432
32543
  if (preToolUse) {
@@ -32451,8 +32562,8 @@ function uninstallClaudeGuard(repoPath) {
32451
32562
  return { removed };
32452
32563
  }
32453
32564
  function claudeGuardInstalled(repoPath) {
32454
- const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32455
- if (!fs38.existsSync(settingsPath)) return false;
32565
+ const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32566
+ if (!fs39.existsSync(settingsPath)) return false;
32456
32567
  try {
32457
32568
  const settings = readSettings(settingsPath);
32458
32569
  return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
@@ -32468,7 +32579,7 @@ function repoOption(y2) {
32468
32579
  function handleInstall2(argv) {
32469
32580
  try {
32470
32581
  if (argv.claude) {
32471
- const result2 = installClaudeGuard(path44.resolve(argv.repo));
32582
+ const result2 = installClaudeGuard(path45.resolve(argv.repo));
32472
32583
  success("Claude Code worktree guard installed.");
32473
32584
  info(`Guard script: ${result2.guardScript}`);
32474
32585
  info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
@@ -32476,7 +32587,7 @@ function handleInstall2(argv) {
32476
32587
  info("Commit both files so the guard travels with the repo.");
32477
32588
  return;
32478
32589
  }
32479
- const result = installHooks({ repoPath: path44.resolve(argv.repo), force: argv.force });
32590
+ const result = installHooks({ repoPath: path45.resolve(argv.repo), force: argv.force });
32480
32591
  success(`Commit gate installed in ${result.hooksDir}`);
32481
32592
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
32482
32593
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -32489,7 +32600,7 @@ function handleInstall2(argv) {
32489
32600
  function handleUninstall(argv) {
32490
32601
  try {
32491
32602
  if (argv.claude) {
32492
- const result2 = uninstallClaudeGuard(path44.resolve(argv.repo));
32603
+ const result2 = uninstallClaudeGuard(path45.resolve(argv.repo));
32493
32604
  if (result2.removed) {
32494
32605
  success("Claude Code worktree guard removed.");
32495
32606
  } else {
@@ -32497,7 +32608,7 @@ function handleUninstall(argv) {
32497
32608
  }
32498
32609
  return;
32499
32610
  }
32500
- const result = uninstallHooks(path44.resolve(argv.repo));
32611
+ const result = uninstallHooks(path45.resolve(argv.repo));
32501
32612
  if (result.removed) {
32502
32613
  success(`Commit gate removed from ${result.hooksDir}`);
32503
32614
  } else {
@@ -32510,7 +32621,7 @@ function handleUninstall(argv) {
32510
32621
  }
32511
32622
  function handleStatus2(argv) {
32512
32623
  try {
32513
- const status = getHooksStatus(path44.resolve(argv.repo));
32624
+ const status = getHooksStatus(path45.resolve(argv.repo));
32514
32625
  if (argv.json) {
32515
32626
  process.stdout.write(`${JSON.stringify(status, null, 2)}
32516
32627
  `);
@@ -32528,7 +32639,7 @@ function handleStatus2(argv) {
32528
32639
  );
32529
32640
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
32530
32641
  info(
32531
- `Claude guard: ${claudeGuardInstalled(path44.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32642
+ `Claude guard: ${claudeGuardInstalled(path45.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32532
32643
  );
32533
32644
  } catch (err) {
32534
32645
  error(err instanceof Error ? err.message : String(err));
@@ -32789,10 +32900,10 @@ function assignProp(target, prop, value) {
32789
32900
  configurable: true
32790
32901
  });
32791
32902
  }
32792
- function getElementAtPath(obj, path47) {
32793
- if (!path47)
32903
+ function getElementAtPath(obj, path48) {
32904
+ if (!path48)
32794
32905
  return obj;
32795
- return path47.reduce((acc, key) => acc?.[key], obj);
32906
+ return path48.reduce((acc, key) => acc?.[key], obj);
32796
32907
  }
32797
32908
  function promiseAllObject(promisesObj) {
32798
32909
  const keys = Object.keys(promisesObj);
@@ -33112,11 +33223,11 @@ function aborted(x2, startIndex = 0) {
33112
33223
  }
33113
33224
  return false;
33114
33225
  }
33115
- function prefixIssues(path47, issues) {
33226
+ function prefixIssues(path48, issues) {
33116
33227
  return issues.map((iss) => {
33117
33228
  var _a3;
33118
33229
  (_a3 = iss).path ?? (_a3.path = []);
33119
- iss.path.unshift(path47);
33230
+ iss.path.unshift(path48);
33120
33231
  return iss;
33121
33232
  });
33122
33233
  }
@@ -39590,7 +39701,7 @@ var Server = class extends Protocol {
39590
39701
  };
39591
39702
 
39592
39703
  // src/mcp/server.ts
39593
- var path45 = __toESM(require("path"));
39704
+ var path46 = __toESM(require("path"));
39594
39705
 
39595
39706
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
39596
39707
  var import_node_process2 = __toESM(require("node:process"), 1);
@@ -40222,7 +40333,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
40222
40333
  const input = ControlUpInputSchema.parse({ ...args, repo });
40223
40334
  const result = await startControlAndSync({
40224
40335
  detach: input.detach,
40225
- cwd: path45.resolve(input.repo)
40336
+ cwd: path46.resolve(input.repo)
40226
40337
  });
40227
40338
  if (result.code !== 0) {
40228
40339
  return {
@@ -40285,8 +40396,8 @@ var mcpCommand = {
40285
40396
  };
40286
40397
 
40287
40398
  // src/cli/commands/telemetry.ts
40288
- var fs39 = __toESM(require("fs"));
40289
- var path46 = __toESM(require("path"));
40399
+ var fs40 = __toESM(require("fs"));
40400
+ var path47 = __toESM(require("path"));
40290
40401
  function printSignals() {
40291
40402
  info("Collected when enabled (local Mission Control only):");
40292
40403
  for (const signal of TELEMETRY_SIGNALS) {
@@ -40357,7 +40468,7 @@ async function handleOff() {
40357
40468
  info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
40358
40469
  }
40359
40470
  async function handleWriteEnv(argv) {
40360
- const repoPath = path46.resolve(argv.repo);
40471
+ const repoPath = path47.resolve(argv.repo);
40361
40472
  const harnessRoot = resolveHarnessRoot(repoPath);
40362
40473
  const session = readSlotRegistry(harnessRoot, argv.agentId);
40363
40474
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
@@ -40370,7 +40481,7 @@ async function handleWriteEnv(argv) {
40370
40481
  suffix,
40371
40482
  createdAt: session?.createdAt
40372
40483
  });
40373
- const envFile = argv.envFile ?? path46.join(workDir, `.env.agent.${argv.agentId}`);
40484
+ const envFile = argv.envFile ?? path47.join(workDir, `.env.agent.${argv.agentId}`);
40374
40485
  let otelReady = argv.otelReady;
40375
40486
  if (otelReady === void 0 && isTelemetryEnabled()) {
40376
40487
  const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
@@ -40389,7 +40500,7 @@ async function handleWriteEnv(argv) {
40389
40500
  success(`Wrote telemetry env to ${envFile}`);
40390
40501
  }
40391
40502
  function handlePrintEnv(argv) {
40392
- const repoPath = path46.resolve(argv.repo);
40503
+ const repoPath = path47.resolve(argv.repo);
40393
40504
  const workDir = argv.workDir ?? repoPath;
40394
40505
  const sessionKey = buildSessionKey({
40395
40506
  branch: argv.branch,
@@ -40411,7 +40522,7 @@ function handlePrintEnv(argv) {
40411
40522
  );
40412
40523
  }
40413
40524
  function handleCodexSnippet(argv) {
40414
- const repoPath = path46.resolve(argv.repo);
40525
+ const repoPath = path47.resolve(argv.repo);
40415
40526
  const harnessRoot = resolveHarnessRoot(repoPath);
40416
40527
  const session = readSlotRegistry(harnessRoot, argv.agentId);
40417
40528
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
@@ -40433,10 +40544,10 @@ function handleCodexSnippet(argv) {
40433
40544
  purpose: session?.purpose
40434
40545
  });
40435
40546
  if (argv.write) {
40436
- const outDir = path46.join(harnessRoot, ".har", "telemetry");
40437
- fs39.mkdirSync(outDir, { recursive: true });
40438
- const outPath = path46.join(outDir, "codex.otel.toml.snippet");
40439
- fs39.writeFileSync(outPath, snippet);
40547
+ const outDir = path47.join(harnessRoot, ".har", "telemetry");
40548
+ fs40.mkdirSync(outDir, { recursive: true });
40549
+ const outPath = path47.join(outDir, "codex.otel.toml.snippet");
40550
+ fs40.writeFileSync(outPath, snippet);
40440
40551
  success(`Wrote ${outPath}`);
40441
40552
  info("Merge the [otel] table into ~/.codex/config.toml manually.");
40442
40553
  return;