@osfactory/har 0.13.1 → 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 +1032 -751
  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() {
@@ -23129,6 +23129,22 @@ var AgentSessionUsageSchema = external_exports.object({
23129
23129
  var SyncUsageInputSchema = external_exports.object({
23130
23130
  usage: external_exports.array(AgentSessionUsageSchema)
23131
23131
  });
23132
+ var AgentSessionEventSchema = external_exports.object({
23133
+ sessionKey: external_exports.string().min(1),
23134
+ agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
23135
+ agentTool: AgentToolSchema,
23136
+ eventName: external_exports.string().min(1),
23137
+ sequence: external_exports.number().int().nonnegative().default(0),
23138
+ timestamp: external_exports.string(),
23139
+ attributes: external_exports.record(external_exports.unknown()).optional(),
23140
+ promptText: external_exports.string().nullable().optional(),
23141
+ responseText: external_exports.string().nullable().optional(),
23142
+ rawTruncated: external_exports.string().nullable().optional(),
23143
+ source: external_exports.enum(["otel", "harvest"]).default("otel")
23144
+ });
23145
+ var SyncSessionEventsInputSchema = external_exports.object({
23146
+ events: external_exports.array(AgentSessionEventSchema)
23147
+ });
23132
23148
 
23133
23149
  // src/harness/manifest.ts
23134
23150
  var GENERATOR_VERSION = "0.4.0";
@@ -23482,15 +23498,15 @@ function handleRemove(argv) {
23482
23498
  }
23483
23499
 
23484
23500
  // src/cli/commands/env.ts
23485
- var path40 = __toESM(require("path"));
23501
+ var path41 = __toESM(require("path"));
23486
23502
 
23487
23503
  // src/core/harness.ts
23488
- var fs19 = __toESM(require("fs"));
23489
- var path18 = __toESM(require("path"));
23504
+ var fs20 = __toESM(require("fs"));
23505
+ var path19 = __toESM(require("path"));
23490
23506
 
23491
23507
  // src/harness/generator.ts
23492
- var fs7 = __toESM(require("fs"));
23493
- var path8 = __toESM(require("path"));
23508
+ var fs10 = __toESM(require("fs"));
23509
+ var path11 = __toESM(require("path"));
23494
23510
 
23495
23511
  // src/core/gitignore.ts
23496
23512
  var fs6 = __toESM(require("fs"));
@@ -23513,6 +23529,277 @@ function ensureRootGitignorePatterns(checkoutDir) {
23513
23529
  fs6.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
23514
23530
  }
23515
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
+
23516
23803
  // src/harness/generator.ts
23517
23804
  var PROFILE_DIRS = {
23518
23805
  default: "har-boilerplate",
@@ -23526,46 +23813,48 @@ var CLI_PRUNE_FILES = [
23526
23813
  ];
23527
23814
  function pruneCliProfile(harnessDir) {
23528
23815
  for (const file of CLI_PRUNE_FILES) {
23529
- const filePath = path8.join(harnessDir, file);
23530
- if (fs7.existsSync(filePath)) {
23531
- fs7.unlinkSync(filePath);
23816
+ const filePath = path11.join(harnessDir, file);
23817
+ if (fs10.existsSync(filePath)) {
23818
+ fs10.unlinkSync(filePath);
23532
23819
  }
23533
23820
  }
23534
23821
  }
23535
23822
  function scaffoldClaudeMd(repoPath, projectName, force) {
23536
23823
  const templatePath = resolveTemplateFile("CLAUDE.md.template");
23537
23824
  if (!templatePath) return;
23538
- const dest = path8.join(repoPath, "CLAUDE.md");
23539
- if (fs7.existsSync(dest) && !force) return;
23825
+ const dest = path11.join(repoPath, "CLAUDE.md");
23826
+ if (fs10.existsSync(dest) && !force) return;
23540
23827
  const displayName = projectName.replace(/_/g, " ");
23541
- const content = fs7.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
23542
- fs7.writeFileSync(dest, content);
23828
+ const content = fs10.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
23829
+ fs10.writeFileSync(dest, content);
23543
23830
  }
23544
23831
  function scaffoldHarnessBoilerplate(repoPath, options = {}) {
23545
- const harnessDir = path8.join(repoPath, DEFAULT_HAR_DIR);
23546
- 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, "_");
23547
23834
  const profile = options.profile ?? "default";
23548
- const boilerplateDir = path8.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
23549
- if (fs7.existsSync(harnessDir) && !options.force) {
23835
+ const boilerplateDir = path11.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
23836
+ if (fs10.existsSync(harnessDir) && !options.force) {
23550
23837
  throw new Error(
23551
23838
  '.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
23552
23839
  );
23553
23840
  }
23554
- if (!fs7.existsSync(boilerplateDir)) {
23841
+ if (!fs10.existsSync(boilerplateDir)) {
23555
23842
  throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
23556
23843
  }
23557
- if (options.force && fs7.existsSync(harnessDir)) {
23558
- fs7.rmSync(harnessDir, { recursive: true, force: true });
23844
+ if (options.force && fs10.existsSync(harnessDir)) {
23845
+ fs10.rmSync(harnessDir, { recursive: true, force: true });
23559
23846
  }
23560
23847
  copyDirRecursive(boilerplateDir, harnessDir);
23848
+ writeHarnessGitignore(harnessDir, boilerplateDir);
23561
23849
  if (profile === "cli") {
23562
23850
  pruneCliProfile(harnessDir);
23563
23851
  }
23564
- const harnessEnvPath = path8.join(harnessDir, "harness.env");
23565
- if (fs7.existsSync(harnessEnvPath)) {
23566
- 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");
23567
23856
  content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
23568
- fs7.writeFileSync(harnessEnvPath, content);
23857
+ fs10.writeFileSync(harnessEnvPath, content);
23569
23858
  }
23570
23859
  const manifest = createManifest(
23571
23860
  repoPath,
@@ -23588,8 +23877,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
23588
23877
  }
23589
23878
 
23590
23879
  // src/llm/authoring-agent.ts
23591
- var fs11 = __toESM(require("fs"));
23592
- var path11 = __toESM(require("path"));
23880
+ var fs14 = __toESM(require("fs"));
23881
+ var path14 = __toESM(require("path"));
23593
23882
 
23594
23883
  // node_modules/@anthropic-ai/sdk/error.mjs
23595
23884
  var error_exports = {};
@@ -23988,13 +24277,13 @@ var MultipartBody = class {
23988
24277
  // node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
23989
24278
  var import_web = require("node:stream/web");
23990
24279
  var fileFromPathWarned = false;
23991
- async function fileFromPath3(path47, ...args) {
24280
+ async function fileFromPath3(path48, ...args) {
23992
24281
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
23993
24282
  if (!fileFromPathWarned) {
23994
- 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`);
23995
24284
  fileFromPathWarned = true;
23996
24285
  }
23997
- return await _fileFromPath(path47, ...args);
24286
+ return await _fileFromPath(path48, ...args);
23998
24287
  }
23999
24288
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
24000
24289
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -24591,29 +24880,29 @@ var APIClient = class {
24591
24880
  defaultIdempotencyKey() {
24592
24881
  return `stainless-node-retry-${uuid4()}`;
24593
24882
  }
24594
- get(path47, opts) {
24595
- return this.methodRequest("get", path47, opts);
24883
+ get(path48, opts) {
24884
+ return this.methodRequest("get", path48, opts);
24596
24885
  }
24597
- post(path47, opts) {
24598
- return this.methodRequest("post", path47, opts);
24886
+ post(path48, opts) {
24887
+ return this.methodRequest("post", path48, opts);
24599
24888
  }
24600
- patch(path47, opts) {
24601
- return this.methodRequest("patch", path47, opts);
24889
+ patch(path48, opts) {
24890
+ return this.methodRequest("patch", path48, opts);
24602
24891
  }
24603
- put(path47, opts) {
24604
- return this.methodRequest("put", path47, opts);
24892
+ put(path48, opts) {
24893
+ return this.methodRequest("put", path48, opts);
24605
24894
  }
24606
- delete(path47, opts) {
24607
- return this.methodRequest("delete", path47, opts);
24895
+ delete(path48, opts) {
24896
+ return this.methodRequest("delete", path48, opts);
24608
24897
  }
24609
- methodRequest(method, path47, opts) {
24898
+ methodRequest(method, path48, opts) {
24610
24899
  return this.request(Promise.resolve(opts).then(async (opts2) => {
24611
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;
24612
- return { method, path: path47, ...opts2, body };
24901
+ return { method, path: path48, ...opts2, body };
24613
24902
  }));
24614
24903
  }
24615
- getAPIList(path47, Page, opts) {
24616
- return this.requestAPIList(Page, { method: "get", path: path47, ...opts });
24904
+ getAPIList(path48, Page, opts) {
24905
+ return this.requestAPIList(Page, { method: "get", path: path48, ...opts });
24617
24906
  }
24618
24907
  calculateContentLength(body) {
24619
24908
  if (typeof body === "string") {
@@ -24631,10 +24920,10 @@ var APIClient = class {
24631
24920
  return null;
24632
24921
  }
24633
24922
  buildRequest(options) {
24634
- const { method, path: path47, query, headers = {} } = options;
24923
+ const { method, path: path48, query, headers = {} } = options;
24635
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;
24636
24925
  const contentLength = this.calculateContentLength(body);
24637
- const url = this.buildURL(path47, query);
24926
+ const url = this.buildURL(path48, query);
24638
24927
  if ("timeout" in options)
24639
24928
  validatePositiveInteger("timeout", options.timeout);
24640
24929
  const timeout = options.timeout ?? this.timeout;
@@ -24743,8 +25032,8 @@ var APIClient = class {
24743
25032
  const request = this.makeRequest(options, null);
24744
25033
  return new PagePromise(this, request, Page);
24745
25034
  }
24746
- buildURL(path47, query) {
24747
- 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));
24748
25037
  const defaultQuery = this.defaultQuery();
24749
25038
  if (!isEmptyObj(defaultQuery)) {
24750
25039
  query = { ...defaultQuery, ...query };
@@ -26567,34 +26856,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
26567
26856
  var sdk_default = Anthropic;
26568
26857
 
26569
26858
  // src/llm/tools.ts
26570
- var fs10 = __toESM(require("fs"));
26571
- var path10 = __toESM(require("path"));
26859
+ var fs13 = __toESM(require("fs"));
26860
+ var path13 = __toESM(require("path"));
26572
26861
  var readline3 = __toESM(require("readline"));
26573
26862
 
26574
26863
  // src/harness/agent-md.ts
26575
- var fs9 = __toESM(require("fs"));
26576
- var path9 = __toESM(require("path"));
26864
+ var fs12 = __toESM(require("fs"));
26865
+ var path12 = __toESM(require("path"));
26577
26866
  var readline2 = __toESM(require("readline"));
26578
26867
  var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
26579
26868
  var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
26580
26869
  function writeAgentMdProposal(repoPath, content, rationale) {
26581
26870
  const harnessDir = getHarnessDir(repoPath);
26582
- writeFileSafe(path9.join(harnessDir, AGENT_MD_PROPOSAL), content);
26871
+ writeFileSafe(path12.join(harnessDir, AGENT_MD_PROPOSAL), content);
26583
26872
  writeFileSafe(
26584
- path9.join(harnessDir, AGENT_MD_PROPOSAL_META),
26873
+ path12.join(harnessDir, AGENT_MD_PROPOSAL_META),
26585
26874
  JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
26586
26875
  );
26587
26876
  }
26588
26877
  function readAgentMdProposal(repoPath) {
26589
26878
  const harnessDir = getHarnessDir(repoPath);
26590
- const proposalPath = path9.join(harnessDir, AGENT_MD_PROPOSAL);
26591
- const metaPath = path9.join(harnessDir, AGENT_MD_PROPOSAL_META);
26592
- if (!fs9.existsSync(proposalPath)) return null;
26593
- 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");
26594
26883
  let rationale = "";
26595
- if (fs9.existsSync(metaPath)) {
26884
+ if (fs12.existsSync(metaPath)) {
26596
26885
  try {
26597
- rationale = JSON.parse(fs9.readFileSync(metaPath, "utf8")).rationale ?? "";
26886
+ rationale = JSON.parse(fs12.readFileSync(metaPath, "utf8")).rationale ?? "";
26598
26887
  } catch {
26599
26888
  rationale = "";
26600
26889
  }
@@ -26602,21 +26891,21 @@ function readAgentMdProposal(repoPath) {
26602
26891
  return {
26603
26892
  content,
26604
26893
  rationale,
26605
- 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()
26606
26895
  };
26607
26896
  }
26608
26897
  function clearAgentMdProposal(repoPath) {
26609
26898
  const harnessDir = getHarnessDir(repoPath);
26610
26899
  for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
26611
- const p2 = path9.join(harnessDir, file);
26612
- if (fs9.existsSync(p2)) fs9.unlinkSync(p2);
26900
+ const p2 = path12.join(harnessDir, file);
26901
+ if (fs12.existsSync(p2)) fs12.unlinkSync(p2);
26613
26902
  }
26614
26903
  }
26615
26904
  async function promptApplyAgentMdProposal(repoPath) {
26616
26905
  const proposal = readAgentMdProposal(repoPath);
26617
26906
  if (!proposal) return false;
26618
- const agentMdPath = path9.join(repoPath, "AGENT.md");
26619
- const exists = fs9.existsSync(agentMdPath);
26907
+ const agentMdPath = path12.join(repoPath, "AGENT.md");
26908
+ const exists = fs12.existsSync(agentMdPath);
26620
26909
  process.stderr.write("\n");
26621
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");
26622
26911
  process.stderr.write("Proposed AGENT.md (repo root)\n");
@@ -26804,13 +27093,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
26804
27093
  async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
26805
27094
  switch (toolName) {
26806
27095
  case "readRepoFile": {
26807
- const filePath = path10.join(repoPath, input.path);
27096
+ const filePath = path13.join(repoPath, input.path);
26808
27097
  const content = readFile(filePath, input.maxChars ?? 8e3);
26809
27098
  info(`Read repo: ${input.path}`);
26810
27099
  return content;
26811
27100
  }
26812
27101
  case "listRepoDir": {
26813
- const dirPath = path10.join(repoPath, input.path ?? ".");
27102
+ const dirPath = path13.join(repoPath, input.path ?? ".");
26814
27103
  const entries = listDir(dirPath, input.maxFiles ?? 60);
26815
27104
  info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
26816
27105
  return entries.length > 0 ? entries.join("\n") : "(empty directory)";
@@ -26835,17 +27124,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26835
27124
  const filePath = resolveSafePath(harnessDir, relPath);
26836
27125
  writeFileSafe(filePath, input.content);
26837
27126
  if (relPath.endsWith(".sh")) {
26838
- fs10.chmodSync(filePath, 493);
27127
+ fs13.chmodSync(filePath, 493);
26839
27128
  }
26840
27129
  info(`Wrote harness: ${relPath}`);
26841
27130
  return `Written ${relPath} (${input.content.length} bytes)`;
26842
27131
  }
26843
27132
  case "editHarnessFile": {
26844
27133
  const filePath = resolveSafePath(harnessDir, input.path);
26845
- if (!fs10.existsSync(filePath)) {
27134
+ if (!fs13.existsSync(filePath)) {
26846
27135
  return `Error: file not found: ${input.path}`;
26847
27136
  }
26848
- const content = fs10.readFileSync(filePath, "utf8");
27137
+ const content = fs13.readFileSync(filePath, "utf8");
26849
27138
  const oldStr = input.old_string;
26850
27139
  const newStr = input.new_string;
26851
27140
  const count = content.split(oldStr).length - 1;
@@ -26862,10 +27151,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26862
27151
  }
26863
27152
  case "deleteHarnessFile": {
26864
27153
  const filePath = resolveSafePath(harnessDir, input.path);
26865
- if (!fs10.existsSync(filePath)) {
27154
+ if (!fs13.existsSync(filePath)) {
26866
27155
  return `File not found (already deleted): ${input.path}`;
26867
27156
  }
26868
- fs10.unlinkSync(filePath);
27157
+ fs13.unlinkSync(filePath);
26869
27158
  info(`Deleted harness: ${input.path}`);
26870
27159
  return `Deleted ${input.path}`;
26871
27160
  }
@@ -26916,29 +27205,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
26916
27205
  const mode = options.mode ?? "init";
26917
27206
  const systemPromptPath = resolvePromptPath("system-authoring.md");
26918
27207
  const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
26919
- 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.";
26920
27209
  if (agentMdTemplatePath) {
26921
27210
  systemPrompt += `
26922
27211
 
26923
27212
  ## AGENT.md template (starting point for proposeAgentMd)
26924
27213
 
26925
- ${fs11.readFileSync(agentMdTemplatePath, "utf8")}`;
27214
+ ${fs14.readFileSync(agentMdTemplatePath, "utf8")}`;
26926
27215
  }
26927
27216
  const tools = buildAuthoringTools();
26928
27217
  const messages = [];
26929
27218
  let rootListing;
26930
27219
  try {
26931
- rootListing = fs11.readdirSync(repoPath).slice(0, 50).join("\n");
27220
+ rootListing = fs14.readdirSync(repoPath).slice(0, 50).join("\n");
26932
27221
  } catch {
26933
27222
  throw new Error(`Cannot read repo at path: ${repoPath}`);
26934
27223
  }
26935
27224
  let harnessListing;
26936
27225
  try {
26937
- harnessListing = fs11.readdirSync(harnessDir).join("\n");
27226
+ harnessListing = fs14.readdirSync(harnessDir).join("\n");
26938
27227
  } catch {
26939
27228
  throw new Error(`Cannot read harness dir at: ${harnessDir}`);
26940
27229
  }
26941
- 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.";
26942
27231
  const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
26943
27232
  Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
26944
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/.
@@ -27018,8 +27307,8 @@ Try running with --verbose to see what the agent is doing.`
27018
27307
  }
27019
27308
 
27020
27309
  // src/harness/validator.ts
27021
- var fs14 = __toESM(require("fs"));
27022
- var path14 = __toESM(require("path"));
27310
+ var fs15 = __toESM(require("fs"));
27311
+ var path15 = __toESM(require("path"));
27023
27312
 
27024
27313
  // src/utils/shell.ts
27025
27314
  var import_child_process = require("child_process");
@@ -27087,205 +27376,6 @@ function runShellCommand(command2, options = {}) {
27087
27376
  });
27088
27377
  }
27089
27378
 
27090
- // src/harness/stages.ts
27091
- var fs13 = __toESM(require("fs"));
27092
- var path13 = __toESM(require("path"));
27093
-
27094
- // src/harness/env.ts
27095
- var fs12 = __toESM(require("fs"));
27096
- var path12 = __toESM(require("path"));
27097
- function readHarnessEnv(repoPath) {
27098
- const envPath = path12.join(getHarnessDir(repoPath), "harness.env");
27099
- if (!fs12.existsSync(envPath)) return {};
27100
- const values = {};
27101
- for (const line of fs12.readFileSync(envPath, "utf8").split("\n")) {
27102
- const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
27103
- if (!match) continue;
27104
- values[match[1]] = match[2].replace(/^"|"$/g, "");
27105
- }
27106
- return values;
27107
- }
27108
- function parseHarnessEnvInt(env3, key) {
27109
- const raw = env3[key];
27110
- if (raw === void 0 || raw === "") return void 0;
27111
- const value = Number(raw);
27112
- if (!Number.isInteger(value)) return void 0;
27113
- return value;
27114
- }
27115
-
27116
- // src/harness/stages.ts
27117
- var STAGE_REGISTRY_FILE = "stages.json";
27118
- var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
27119
- "launch",
27120
- "verify",
27121
- "test",
27122
- "inspect",
27123
- "reset",
27124
- "teardown",
27125
- "custom"
27126
- ]);
27127
- function getStageRegistryPath(repoPath) {
27128
- return path13.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
27129
- }
27130
- function synthesizeStageRegistry(repoPath) {
27131
- const harnessDir = getHarnessDir(repoPath);
27132
- const stages = [];
27133
- addStageIfRunnable(stages, harnessDir, {
27134
- id: "setup-infra",
27135
- kind: "setup",
27136
- description: "Start shared harness infrastructure.",
27137
- command: "./.har/setup-infra.sh",
27138
- requiresAgentId: false
27139
- });
27140
- addStageIfRunnable(stages, harnessDir, {
27141
- id: "launch",
27142
- kind: "launch",
27143
- description: "Launch an isolated agent environment slot.",
27144
- command: "./.har/launch.sh {agentId}",
27145
- requiresAgentId: true
27146
- });
27147
- addStageIfRunnable(stages, harnessDir, {
27148
- id: "verify",
27149
- kind: "verify",
27150
- description: "Run the project verification pipeline.",
27151
- command: "./.har/verify.sh {agentId}",
27152
- requiresAgentId: true,
27153
- acceptsArgs: ["--full"]
27154
- });
27155
- addStageIfRunnable(stages, harnessDir, {
27156
- id: "status",
27157
- kind: "inspect",
27158
- description: "Inspect a running agent environment slot.",
27159
- command: "./.har/agent-cli.sh {agentId} status",
27160
- requiresAgentId: true
27161
- });
27162
- addStageIfRunnable(stages, harnessDir, {
27163
- id: "logs",
27164
- kind: "inspect",
27165
- description: "Read recent logs for an agent slot.",
27166
- command: "./.har/agent-cli.sh {agentId} logs",
27167
- requiresAgentId: true
27168
- });
27169
- addStageIfRunnable(stages, harnessDir, {
27170
- id: "teardown",
27171
- kind: "teardown",
27172
- description: "Tear down an isolated agent environment slot.",
27173
- command: "./.har/teardown.sh {agentId}",
27174
- requiresAgentId: true
27175
- });
27176
- const harnessEnv = readHarnessEnv(repoPath);
27177
- const agentSlots = readAgentSlotsFromHarnessEnv(harnessEnv);
27178
- if (!agentSlots) {
27179
- throw new Error(
27180
- "Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
27181
- );
27182
- }
27183
- return {
27184
- version: "1",
27185
- artifactsDir: "artifacts",
27186
- logsDir: "logs",
27187
- agentSlots,
27188
- verificationStages: ["typecheck", "unit-tests", "api-health"],
27189
- stages
27190
- };
27191
- }
27192
- function readAgentSlotsFromHarnessEnv(harnessEnv) {
27193
- const max = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MAX");
27194
- if (max === void 0) return null;
27195
- const min = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
27196
- if (max < min) {
27197
- throw new Error("HARNESS_AGENT_SLOT_MAX must be >= HARNESS_AGENT_SLOT_MIN in harness.env");
27198
- }
27199
- return { min, max };
27200
- }
27201
- function stageScriptExists(harnessDir, stage) {
27202
- if (stage.script) {
27203
- return fs13.existsSync(path13.join(harnessDir, stage.script));
27204
- }
27205
- if (stage.command) {
27206
- const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
27207
- return fs13.existsSync(path13.join(harnessDir, scriptName));
27208
- }
27209
- return fs13.existsSync(path13.join(harnessDir, "stages", `${stage.id}.sh`));
27210
- }
27211
- function addStageIfRunnable(stages, harnessDir, stage) {
27212
- const parsed = HarnessStageSchema.parse(stage);
27213
- if (stageScriptExists(harnessDir, parsed)) {
27214
- stages.push(parsed);
27215
- }
27216
- }
27217
- function readStageRegistry(repoPath) {
27218
- const registryPath = getStageRegistryPath(repoPath);
27219
- if (!fs13.existsSync(registryPath)) {
27220
- return synthesizeStageRegistry(repoPath);
27221
- }
27222
- const raw = JSON.parse(fs13.readFileSync(registryPath, "utf8"));
27223
- const parsed = HarnessStageRegistrySchema.safeParse(raw);
27224
- if (!parsed.success) {
27225
- throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
27226
- }
27227
- return parsed.data;
27228
- }
27229
- function writeStageRegistry(repoPath, registry2) {
27230
- const registryPath = getStageRegistryPath(repoPath);
27231
- const parsed = HarnessStageRegistrySchema.safeParse(registry2);
27232
- if (!parsed.success) {
27233
- throw new Error(`Invalid stage registry: ${parsed.error.message}`);
27234
- }
27235
- fs13.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
27236
- }
27237
- function listStages(repoPath) {
27238
- return readStageRegistry(repoPath).stages;
27239
- }
27240
- function getAgentSlotRange(repoPath) {
27241
- const registry2 = readStageRegistry(repoPath);
27242
- if (registry2.agentSlots) {
27243
- return registry2.agentSlots;
27244
- }
27245
- const fromEnv = readAgentSlotsFromHarnessEnv(readHarnessEnv(repoPath));
27246
- if (fromEnv) {
27247
- return fromEnv;
27248
- }
27249
- throw new Error(
27250
- "Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
27251
- );
27252
- }
27253
- function getAgentSlotIds(repoPath) {
27254
- const { min, max } = getAgentSlotRange(repoPath);
27255
- const ids = [];
27256
- for (let id = min; id <= max; id++) ids.push(id);
27257
- return ids;
27258
- }
27259
- function stageRequiresAgentId(stage) {
27260
- if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
27261
- return AGENT_REQUIRED_KINDS.has(stage.kind);
27262
- }
27263
- function resolveStage(repoPath, options) {
27264
- if (!options.id && !options.kind) return null;
27265
- const stages = listStages(repoPath);
27266
- if (options.id) {
27267
- return stages.find((stage) => stage.id === options.id) ?? null;
27268
- }
27269
- const matches = stages.filter((stage) => stage.kind === options.kind);
27270
- if (matches.length === 1) return matches[0];
27271
- if (matches.length > 1) {
27272
- throw new Error(
27273
- `Multiple stages with kind "${options.kind}". Specify stage id. Available: ${matches.map((s2) => s2.id).join(", ")}`
27274
- );
27275
- }
27276
- return null;
27277
- }
27278
- function getVerificationStageIds(repoPath) {
27279
- const registry2 = readStageRegistry(repoPath);
27280
- if (Array.isArray(registry2.verificationStages) && registry2.verificationStages.length > 0) {
27281
- return registry2.verificationStages;
27282
- }
27283
- return registry2.stages.filter((stage) => stage.group === "verification").map((stage) => stage.id);
27284
- }
27285
- function getArtifactsDir(repoPath) {
27286
- return readStageRegistry(repoPath).artifactsDir ?? "artifacts";
27287
- }
27288
-
27289
27379
  // src/harness/validator.ts
27290
27380
  var REQUIRED_FILES_DEFAULT = [
27291
27381
  "README.md",
@@ -27338,22 +27428,22 @@ var SHELL_SCRIPTS = [
27338
27428
  function validateHarness(repoPath) {
27339
27429
  const harnessDir = getHarnessDir(repoPath);
27340
27430
  const issues = [];
27341
- if (!fs14.existsSync(harnessDir)) {
27431
+ if (!fs15.existsSync(harnessDir)) {
27342
27432
  return {
27343
27433
  pass: false,
27344
27434
  issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
27345
27435
  };
27346
27436
  }
27347
27437
  for (const file of getRequiredFiles(repoPath)) {
27348
- const filePath = path14.join(harnessDir, file);
27349
- if (!fs14.existsSync(filePath)) {
27438
+ const filePath = path15.join(harnessDir, file);
27439
+ if (!fs15.existsSync(filePath)) {
27350
27440
  issues.push({ file, message: "Required file missing", severity: "error" });
27351
27441
  }
27352
27442
  }
27353
27443
  for (const script of SHELL_SCRIPTS) {
27354
- const scriptPath = path14.join(harnessDir, script);
27355
- if (!fs14.existsSync(scriptPath)) continue;
27356
- const stat = fs14.statSync(scriptPath);
27444
+ const scriptPath = path15.join(harnessDir, script);
27445
+ if (!fs15.existsSync(scriptPath)) continue;
27446
+ const stat = fs15.statSync(scriptPath);
27357
27447
  if (!(stat.mode & 73)) {
27358
27448
  issues.push({ file: script, message: "Script is not executable", severity: "warning" });
27359
27449
  }
@@ -27368,9 +27458,9 @@ function validateHarness(repoPath) {
27368
27458
  }
27369
27459
  const manifest = readManifest(repoPath);
27370
27460
  const profile = manifest?.profile ?? "default";
27371
- const ecosystemPath = path14.join(harnessDir, "ecosystem.agent.template.cjs");
27372
- if (profile !== "cli" && fs14.existsSync(ecosystemPath)) {
27373
- 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");
27374
27464
  if (!content.includes("module.exports")) {
27375
27465
  issues.push({
27376
27466
  file: "ecosystem.agent.template.cjs",
@@ -27379,9 +27469,9 @@ function validateHarness(repoPath) {
27379
27469
  });
27380
27470
  }
27381
27471
  }
27382
- const harnessEnvPath = path14.join(harnessDir, "harness.env");
27383
- if (fs14.existsSync(harnessEnvPath)) {
27384
- 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");
27385
27475
  if (content.includes("TODO: set migrate command")) {
27386
27476
  issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
27387
27477
  }
@@ -27389,17 +27479,17 @@ function validateHarness(repoPath) {
27389
27479
  issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
27390
27480
  }
27391
27481
  }
27392
- const verifyPath = path14.join(harnessDir, "verify.sh");
27393
- if (fs14.existsSync(verifyPath)) {
27394
- 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");
27395
27485
  if (content.includes("echo 'TODO:")) {
27396
27486
  issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
27397
27487
  }
27398
27488
  }
27399
- const stagesPath = path14.join(harnessDir, "stages.json");
27400
- if (fs14.existsSync(stagesPath)) {
27489
+ const stagesPath = path15.join(harnessDir, "stages.json");
27490
+ if (fs15.existsSync(stagesPath)) {
27401
27491
  try {
27402
- JSON.parse(fs14.readFileSync(stagesPath, "utf8"));
27492
+ JSON.parse(fs15.readFileSync(stagesPath, "utf8"));
27403
27493
  const registry2 = readStageRegistry(repoPath);
27404
27494
  if (registry2.stages.length === 0) {
27405
27495
  issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
@@ -27414,9 +27504,9 @@ function validateHarness(repoPath) {
27414
27504
  } else {
27415
27505
  issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
27416
27506
  }
27417
- const readmePath = path14.join(harnessDir, "README.md");
27418
- if (fs14.existsSync(readmePath)) {
27419
- 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");
27420
27510
  if (!content.includes(".har")) {
27421
27511
  issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
27422
27512
  }
@@ -27430,8 +27520,8 @@ function validateHarness(repoPath) {
27430
27520
  async function smokeTestHarness(repoPath) {
27431
27521
  const harnessDir = getHarnessDir(repoPath);
27432
27522
  const issues = [];
27433
- const setupScript = path14.join(harnessDir, "setup-infra.sh");
27434
- if (fs14.existsSync(setupScript)) {
27523
+ const setupScript = path15.join(harnessDir, "setup-infra.sh");
27524
+ if (fs15.existsSync(setupScript)) {
27435
27525
  const result = run(`bash "${setupScript}"`, { cwd: repoPath });
27436
27526
  if (result.code !== 0) {
27437
27527
  issues.push({
@@ -27446,8 +27536,8 @@ async function smokeTestHarness(repoPath) {
27446
27536
  }
27447
27537
 
27448
27538
  // src/harness/drift.ts
27449
- var fs15 = __toESM(require("fs"));
27450
- var path15 = __toESM(require("path"));
27539
+ var fs16 = __toESM(require("fs"));
27540
+ var path16 = __toESM(require("path"));
27451
27541
  var PROFILE_DIRS2 = {
27452
27542
  default: "har-boilerplate",
27453
27543
  cli: "har-boilerplate-cli",
@@ -27515,48 +27605,51 @@ function substituteProjectName(content, projectName) {
27515
27605
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
27516
27606
  }
27517
27607
  function listBoilerplateFiles(boilerplateDir) {
27518
- if (!fs15.existsSync(boilerplateDir)) return [];
27519
- 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();
27520
27610
  }
27521
27611
  function compareHarnessToTemplate(repoPath) {
27522
- const resolved = path15.resolve(repoPath);
27612
+ const resolved = path16.resolve(repoPath);
27523
27613
  const manifest = readManifest(resolved);
27524
27614
  const profile = manifest?.profile ?? "default";
27525
27615
  const harnessDir = getHarnessDir(resolved);
27526
- const projectName = path15.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
27527
- 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]);
27528
27618
  const templateFiles = listBoilerplateFiles(boilerplateDir);
27529
27619
  const missing = [];
27530
27620
  const checksumMismatch = [];
27531
27621
  const extra = [];
27532
27622
  const unchanged = [];
27533
27623
  for (const file of templateFiles) {
27534
- const templatePath = path15.join(boilerplateDir, file);
27535
- const harnessPath = path15.join(harnessDir, file);
27536
- 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");
27537
27628
  if (file === "harness.env") {
27538
27629
  templateContent = substituteProjectName(templateContent, projectName);
27539
27630
  }
27540
- if (!fs15.existsSync(harnessPath)) {
27541
- missing.push(file);
27631
+ if (!fs16.existsSync(harnessPath)) {
27632
+ missing.push(harnessFile);
27542
27633
  continue;
27543
27634
  }
27544
- const harnessChecksum = computeFileChecksum(fs15.readFileSync(harnessPath, "utf8"));
27635
+ const harnessChecksum = computeFileChecksum(fs16.readFileSync(harnessPath, "utf8"));
27545
27636
  const templateChecksum = computeFileChecksum(templateContent);
27546
27637
  if (harnessChecksum === templateChecksum) {
27547
- unchanged.push(file);
27638
+ unchanged.push(harnessFile);
27548
27639
  } else {
27549
- checksumMismatch.push(file);
27640
+ checksumMismatch.push(harnessFile);
27550
27641
  }
27551
27642
  }
27552
- if (fs15.existsSync(harnessDir)) {
27553
- for (const file of fs15.readdirSync(harnessDir)) {
27554
- const full = path15.join(harnessDir, file);
27555
- 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;
27556
27647
  if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
27557
27648
  if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
27558
27649
  extra.push(file);
27559
- } else if (!templateFiles.includes(file)) {
27650
+ } else if (isExpectedHarnessOnlyFile(file, templateFiles)) {
27651
+ continue;
27652
+ } else if (!templateFiles.includes(file) && !templateFiles.includes(templateFileForHarness(file))) {
27560
27653
  extra.push(file);
27561
27654
  }
27562
27655
  }
@@ -27564,6 +27657,7 @@ function compareHarnessToTemplate(repoPath) {
27564
27657
  const installed = manifest?.generatorVersion;
27565
27658
  const harnessEnv = readHarnessEnv(resolved);
27566
27659
  const missingPortVars = missingPortDocumentationVars(profile, harnessEnv);
27660
+ const agentSlotMismatch = detectAgentSlotEnvMismatch(resolved);
27567
27661
  return {
27568
27662
  generatorVersion: {
27569
27663
  installed,
@@ -27574,14 +27668,15 @@ function compareHarnessToTemplate(repoPath) {
27574
27668
  checksumMismatch,
27575
27669
  extra,
27576
27670
  unchanged,
27577
- missingPortVars
27671
+ missingPortVars,
27672
+ agentSlotMismatch
27578
27673
  };
27579
27674
  }
27580
27675
 
27581
27676
  // src/harness/maintain-bundle.ts
27582
- var fs16 = __toESM(require("fs"));
27677
+ var fs17 = __toESM(require("fs"));
27583
27678
  var os3 = __toESM(require("os"));
27584
- var path16 = __toESM(require("path"));
27679
+ var path17 = __toESM(require("path"));
27585
27680
  var MAINTAIN_DIR = "maintain";
27586
27681
  var PROFILE_DIRS3 = {
27587
27682
  default: "har-boilerplate",
@@ -27592,14 +27687,14 @@ function substituteProjectName2(content, projectName) {
27592
27687
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
27593
27688
  }
27594
27689
  function projectNameFromRepo(repoPath) {
27595
- return path16.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
27690
+ return path17.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
27596
27691
  }
27597
27692
  function boilerplateDirForProfile(profile) {
27598
- return path16.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
27693
+ return path17.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
27599
27694
  }
27600
27695
  function readBundledTemplateContent(repoPath, profile, file) {
27601
- const templatePath = path16.join(boilerplateDirForProfile(profile), file);
27602
- let content = fs16.readFileSync(templatePath, "utf8");
27696
+ const templatePath = path17.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
27697
+ let content = fs17.readFileSync(templatePath, "utf8");
27603
27698
  if (file === "harness.env") {
27604
27699
  content = substituteProjectName2(content, projectNameFromRepo(repoPath));
27605
27700
  }
@@ -27630,12 +27725,12 @@ function actionHint(file, kind2) {
27630
27725
  return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
27631
27726
  }
27632
27727
  function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
27633
- const tmpDir = fs16.mkdtempSync(path16.join(os3.tmpdir(), "har-maintain-diff-"));
27728
+ const tmpDir = fs17.mkdtempSync(path17.join(os3.tmpdir(), "har-maintain-diff-"));
27634
27729
  try {
27635
- const installedPath = path16.join(tmpDir, "installed");
27636
- const templatePath = path16.join(tmpDir, "template");
27637
- fs16.writeFileSync(installedPath, installedContent);
27638
- 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);
27639
27734
  const result = run(`diff -u "${installedPath}" "${templatePath}"`);
27640
27735
  const body = result.stdout.trim();
27641
27736
  if (!body) return "";
@@ -27644,7 +27739,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
27644
27739
  ${body.split("\n").slice(2).join("\n")}
27645
27740
  `;
27646
27741
  } finally {
27647
- fs16.rmSync(tmpDir, { recursive: true, force: true });
27742
+ fs17.rmSync(tmpDir, { recursive: true, force: true });
27648
27743
  }
27649
27744
  }
27650
27745
  function buildActions(repoPath, profile, drift) {
@@ -27659,7 +27754,7 @@ function buildActions(repoPath, profile, drift) {
27659
27754
  });
27660
27755
  }
27661
27756
  for (const file of drift.checksumMismatch) {
27662
- const installedPath = path16.join(harnessDir, file);
27757
+ const installedPath = path17.join(harnessDir, file);
27663
27758
  const installedRel = `maintain/installed/${file}`;
27664
27759
  const templateRel = `maintain/templates/${file}`;
27665
27760
  const diffRel = `maintain/diffs/${file}.diff`;
@@ -27667,7 +27762,7 @@ function buildActions(repoPath, profile, drift) {
27667
27762
  file,
27668
27763
  kind: "drift",
27669
27764
  template: templateRel,
27670
- installed: fs16.existsSync(installedPath) ? installedRel : void 0,
27765
+ installed: fs17.existsSync(installedPath) ? installedRel : void 0,
27671
27766
  diff: diffRel,
27672
27767
  hint: actionHint(file, "drift")
27673
27768
  });
@@ -27716,6 +27811,18 @@ function buildReadme(report) {
27716
27811
  lines.push(`- \`${v2}\` \u2014 copy from \`maintain/templates/harness.env\``);
27717
27812
  }
27718
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
+ }
27719
27826
  lines.push("", "## Stale files (review)", "");
27720
27827
  if (report.stale.length === 0) {
27721
27828
  lines.push("None detected.");
@@ -27738,28 +27845,28 @@ function buildReadme(report) {
27738
27845
  }
27739
27846
  function writeBundleArtifacts(repoPath, profile, drift, report) {
27740
27847
  const harnessDir = getHarnessDir(repoPath);
27741
- const bundleDir = path16.join(harnessDir, MAINTAIN_DIR);
27742
- if (fs16.existsSync(bundleDir)) {
27743
- 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 });
27744
27851
  }
27745
27852
  const dirs = [
27746
27853
  bundleDir,
27747
- path16.join(bundleDir, "templates"),
27748
- path16.join(bundleDir, "installed"),
27749
- path16.join(bundleDir, "diffs"),
27750
- path16.join(bundleDir, "stale")
27854
+ path17.join(bundleDir, "templates"),
27855
+ path17.join(bundleDir, "installed"),
27856
+ path17.join(bundleDir, "diffs"),
27857
+ path17.join(bundleDir, "stale")
27751
27858
  ];
27752
27859
  for (const dir of dirs) {
27753
- fs16.mkdirSync(dir, { recursive: true });
27860
+ fs17.mkdirSync(dir, { recursive: true });
27754
27861
  }
27755
27862
  const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
27756
27863
  for (const file of affectedFiles) {
27757
27864
  const templateContent = readBundledTemplateContent(repoPath, profile, file);
27758
- writeFileSafe(path16.join(bundleDir, "templates", file), templateContent);
27759
- const harnessPath = path16.join(harnessDir, file);
27760
- if (fs16.existsSync(harnessPath)) {
27761
- const installedContent = fs16.readFileSync(harnessPath, "utf8");
27762
- 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);
27763
27870
  const diff = createUnifiedDiff(
27764
27871
  installedContent,
27765
27872
  templateContent,
@@ -27767,14 +27874,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
27767
27874
  `templates/${file}`
27768
27875
  );
27769
27876
  if (diff) {
27770
- writeFileSafe(path16.join(bundleDir, "diffs", `${file}.diff`), diff);
27877
+ writeFileSafe(path17.join(bundleDir, "diffs", `${file}.diff`), diff);
27771
27878
  }
27772
27879
  }
27773
27880
  }
27774
- writeFileSafe(path16.join(bundleDir, "README.md"), buildReadme(report));
27775
- 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");
27776
27883
  writeFileSafe(
27777
- path16.join(bundleDir, "validation.json"),
27884
+ path17.join(bundleDir, "validation.json"),
27778
27885
  JSON.stringify(report.validation, null, 2) + "\n"
27779
27886
  );
27780
27887
  if (report.stale.length > 0) {
@@ -27786,7 +27893,7 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
27786
27893
  ...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
27787
27894
  ""
27788
27895
  ];
27789
- writeFileSafe(path16.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
27896
+ writeFileSafe(path17.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
27790
27897
  }
27791
27898
  return bundleDir;
27792
27899
  }
@@ -27802,6 +27909,7 @@ function buildMaintainBundle(repoPath, validation2, drift) {
27802
27909
  actions: buildActions(repoPath, profile, drift),
27803
27910
  stale: buildStale(drift),
27804
27911
  missingPortVars: drift.missingPortVars,
27912
+ agentSlotMismatch: drift.agentSlotMismatch,
27805
27913
  validation: {
27806
27914
  pass: validation2.pass,
27807
27915
  errors,
@@ -27812,9 +27920,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
27812
27920
  return { bundleDir, report };
27813
27921
  }
27814
27922
  function removeMaintainBundle(repoPath) {
27815
- const bundleDir = path16.join(getHarnessDir(repoPath), MAINTAIN_DIR);
27816
- if (fs16.existsSync(bundleDir)) {
27817
- 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 });
27818
27926
  }
27819
27927
  }
27820
27928
  function formatMaintainBundlePromptSection(report) {
@@ -27855,20 +27963,31 @@ function formatMaintainBundlePromptSection(report) {
27855
27963
  }
27856
27964
  lines.push("");
27857
27965
  }
27858
- 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) {
27859
27978
  lines.push("No template drift detected. Review repo stack changes manually if needed.", "");
27860
27979
  }
27861
27980
  return lines.join("\n");
27862
27981
  }
27863
27982
 
27864
27983
  // src/harness/stage-templates.ts
27865
- var fs18 = __toESM(require("fs"));
27866
- var path17 = __toESM(require("path"));
27984
+ var fs19 = __toESM(require("fs"));
27985
+ var path18 = __toESM(require("path"));
27867
27986
 
27868
27987
  // src/harness/parser.ts
27869
- var fs17 = __toESM(require("fs"));
27988
+ var fs18 = __toESM(require("fs"));
27870
27989
  function harnessExists(repoPath) {
27871
- return fs17.existsSync(`${repoPath}/.har/setup-infra.sh`);
27990
+ return fs18.existsSync(`${repoPath}/.har/setup-infra.sh`);
27872
27991
  }
27873
27992
 
27874
27993
  // src/harness/stage-templates.ts
@@ -27891,16 +28010,16 @@ var StageTemplateManifestSchema = external_exports.object({
27891
28010
  docsPath: external_exports.string().min(1)
27892
28011
  });
27893
28012
  function resolveTemplateDir(templateId) {
27894
- const dir = path17.join(resolveTemplatesDir(), "stage-templates", templateId);
27895
- if (!fs18.existsSync(dir)) {
28013
+ const dir = path18.join(resolveTemplatesDir(), "stage-templates", templateId);
28014
+ if (!fs19.existsSync(dir)) {
27896
28015
  throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
27897
28016
  }
27898
28017
  return dir;
27899
28018
  }
27900
28019
  function readTemplateManifest(templateId) {
27901
- const manifestPath = path17.join(resolveTemplateDir(templateId), "template.manifest.json");
28020
+ const manifestPath = path18.join(resolveTemplateDir(templateId), "template.manifest.json");
27902
28021
  const parsed = StageTemplateManifestSchema.safeParse(
27903
- JSON.parse(fs18.readFileSync(manifestPath, "utf8"))
28022
+ JSON.parse(fs19.readFileSync(manifestPath, "utf8"))
27904
28023
  );
27905
28024
  if (!parsed.success) {
27906
28025
  throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
@@ -27911,40 +28030,40 @@ function readTemplateManifest(templateId) {
27911
28030
  return parsed.data;
27912
28031
  }
27913
28032
  function ensureParentDir(filePath) {
27914
- const parent = path17.dirname(filePath);
27915
- if (!fs18.existsSync(parent)) {
27916
- fs18.mkdirSync(parent, { recursive: true });
28033
+ const parent = path18.dirname(filePath);
28034
+ if (!fs19.existsSync(parent)) {
28035
+ fs19.mkdirSync(parent, { recursive: true });
27917
28036
  }
27918
28037
  }
27919
28038
  function copyTemplateFile(templateDir, file, repoPath, force) {
27920
- const srcPath = path17.join(templateDir, file.src);
27921
- const destPath = path17.join(repoPath, file.dest);
27922
- 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)) {
27923
28042
  throw new Error(`Template file missing: ${file.src}`);
27924
28043
  }
27925
- if (fs18.existsSync(destPath) && !force) {
28044
+ if (fs19.existsSync(destPath) && !force) {
27926
28045
  throw new Error(
27927
28046
  `File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
27928
28047
  );
27929
28048
  }
27930
28049
  ensureParentDir(destPath);
27931
- fs18.copyFileSync(srcPath, destPath);
28050
+ fs19.copyFileSync(srcPath, destPath);
27932
28051
  if (file.executable) {
27933
- fs18.chmodSync(destPath, 493);
28052
+ fs19.chmodSync(destPath, 493);
27934
28053
  }
27935
28054
  return { written: true, path: file.dest };
27936
28055
  }
27937
28056
  function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
27938
- const packagePath = path17.join(repoPath, "package.json");
27939
- if (!fs18.existsSync(packagePath)) {
28057
+ const packagePath = path18.join(repoPath, "package.json");
28058
+ if (!fs19.existsSync(packagePath)) {
27940
28059
  throw new Error("No package.json in repo root. Add one before applying this stage template.");
27941
28060
  }
27942
- const fragmentPath = path17.join(templateDir, fragmentRelPath);
27943
- if (!fs18.existsSync(fragmentPath)) {
28061
+ const fragmentPath = path18.join(templateDir, fragmentRelPath);
28062
+ if (!fs19.existsSync(fragmentPath)) {
27944
28063
  throw new Error(`Package fragment missing: ${fragmentRelPath}`);
27945
28064
  }
27946
- const pkg = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
27947
- 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"));
27948
28067
  for (const section of ["scripts", "devDependencies"]) {
27949
28068
  const existing = pkg[section] ?? {};
27950
28069
  const incoming = fragment[section] ?? {};
@@ -27957,7 +28076,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
27957
28076
  }
27958
28077
  pkg[section] = existing;
27959
28078
  }
27960
- fs18.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
28079
+ fs19.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
27961
28080
  }
27962
28081
  function patchStageRegistry(repoPath, manifest, force) {
27963
28082
  const registry2 = readStageRegistry(repoPath);
@@ -27997,8 +28116,8 @@ function assertHarnessPresent(repoPath) {
27997
28116
  }
27998
28117
  function assertStageNotPresent(repoPath, stageId, force) {
27999
28118
  if (force) return;
28000
- const scriptPath = path17.join(repoPath, ".har", "stages", `${stageId}.sh`);
28001
- if (fs18.existsSync(scriptPath)) {
28119
+ const scriptPath = path18.join(repoPath, ".har", "stages", `${stageId}.sh`);
28120
+ if (fs19.existsSync(scriptPath)) {
28002
28121
  throw new Error(
28003
28122
  `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
28004
28123
  );
@@ -28011,7 +28130,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
28011
28130
  }
28012
28131
  }
28013
28132
  function applyStageTemplate(repoPath, templateId, options = {}) {
28014
- const resolved = path17.resolve(repoPath);
28133
+ const resolved = path18.resolve(repoPath);
28015
28134
  const force = options.force ?? false;
28016
28135
  const warnings = [];
28017
28136
  const filesWritten = [];
@@ -28030,7 +28149,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
28030
28149
  if (file.skipFlag === "skipCi" && options.skipCi) {
28031
28150
  continue;
28032
28151
  }
28033
- if (fs18.existsSync(path17.join(resolved, file.dest)) && !force) {
28152
+ if (fs19.existsSync(path18.join(resolved, file.dest)) && !force) {
28034
28153
  warnings.push(`Skipped optional file (exists): ${file.dest}`);
28035
28154
  continue;
28036
28155
  }
@@ -28065,9 +28184,9 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
28065
28184
  };
28066
28185
  }
28067
28186
  function listStageTemplateIds() {
28068
- const root = path17.join(resolveTemplatesDir(), "stage-templates");
28069
- if (!fs18.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
28070
- 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(
28071
28190
  (name) => STAGE_TEMPLATE_IDS.includes(name)
28072
28191
  );
28073
28192
  }
@@ -28094,11 +28213,11 @@ function validateAgentId(id, repoPath) {
28094
28213
  // src/core/harness.ts
28095
28214
  function listHarnessScripts(repoPath) {
28096
28215
  const harnessDir = getHarnessDir(repoPath);
28097
- if (!fs19.existsSync(harnessDir)) return [];
28098
- 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();
28099
28218
  }
28100
28219
  function describeProject(repoPath) {
28101
- const resolved = path18.resolve(repoPath);
28220
+ const resolved = path19.resolve(repoPath);
28102
28221
  const manifest = readManifest(resolved);
28103
28222
  const present = harnessExists(resolved);
28104
28223
  return {
@@ -28118,14 +28237,15 @@ function describeProject(repoPath) {
28118
28237
  };
28119
28238
  }
28120
28239
  async function initHarness(options) {
28121
- const repoPath = path18.resolve(options.repoPath);
28122
- if (!fs19.existsSync(repoPath)) {
28240
+ const repoPath = path19.resolve(options.repoPath);
28241
+ if (!fs20.existsSync(repoPath)) {
28123
28242
  throw new Error(`Path not found: ${repoPath}`);
28124
28243
  }
28125
28244
  const scaffold = scaffoldHarnessBoilerplate(repoPath, {
28126
28245
  force: options.force,
28127
28246
  profile: options.profile
28128
28247
  });
28248
+ syncAgentSlotsToHarnessEnv(repoPath);
28129
28249
  let adaptationSummary;
28130
28250
  if (options.auto) {
28131
28251
  const apiKey = requireApiKey();
@@ -28150,9 +28270,9 @@ async function initHarness(options) {
28150
28270
  };
28151
28271
  }
28152
28272
  async function maintainHarness(options) {
28153
- const repoPath = path18.resolve(options.repoPath);
28273
+ const repoPath = path19.resolve(options.repoPath);
28154
28274
  const harnessDir = getHarnessDir(repoPath);
28155
- if (!fs19.existsSync(harnessDir)) {
28275
+ if (!fs20.existsSync(harnessDir)) {
28156
28276
  throw new Error('No .har/ found. Run "har env init" first.');
28157
28277
  }
28158
28278
  if (options.auto) {
@@ -28177,6 +28297,7 @@ async function maintainHarness(options) {
28177
28297
  if (!validation2.pass) {
28178
28298
  throw new Error("Cannot finalize: harness validation has errors. Fix them first.");
28179
28299
  }
28300
+ syncAgentSlotsToHarnessEnv(repoPath);
28180
28301
  const existing = readManifest(repoPath);
28181
28302
  finalizeHarness(
28182
28303
  repoPath,
@@ -28203,11 +28324,11 @@ function addStageTemplate(repoPath, templateId, options = {}) {
28203
28324
  }
28204
28325
 
28205
28326
  // src/harness/custom-stage.ts
28206
- var fs20 = __toESM(require("fs"));
28207
- var path19 = __toESM(require("path"));
28327
+ var fs21 = __toESM(require("fs"));
28328
+ var path20 = __toESM(require("path"));
28208
28329
  var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
28209
28330
  function addCustomStage(repoPath, options) {
28210
- const resolved = path19.resolve(repoPath);
28331
+ const resolved = path20.resolve(repoPath);
28211
28332
  if (!harnessExists(resolved)) {
28212
28333
  throw new Error('No .har/ harness found. Run "har env init" first.');
28213
28334
  }
@@ -28244,20 +28365,20 @@ function addCustomStage(repoPath, options) {
28244
28365
  if (options.command) {
28245
28366
  stage.command = options.command;
28246
28367
  } else {
28247
- const scriptRel = path19.join("stages", `${id}.sh`);
28248
- const scriptAbs = path19.join(resolved, ".har", scriptRel);
28249
- 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) {
28250
28371
  throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
28251
28372
  }
28252
- const skeletonPath = path19.join(
28373
+ const skeletonPath = path20.join(
28253
28374
  resolveTemplatesDir(),
28254
28375
  "stage-templates",
28255
28376
  "custom-stage-skeleton.sh"
28256
28377
  );
28257
- const skeleton = fs20.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
28258
- fs20.mkdirSync(path19.dirname(scriptAbs), { recursive: true });
28259
- fs20.writeFileSync(scriptAbs, skeleton);
28260
- 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);
28261
28382
  filesWritten.push(`.har/${scriptRel}`);
28262
28383
  stage.script = scriptRel;
28263
28384
  stage.artifacts = [
@@ -28305,8 +28426,8 @@ function addCustomStage(repoPath, options) {
28305
28426
  }
28306
28427
 
28307
28428
  // src/harness/adaptation-prompt.ts
28308
- var fs21 = __toESM(require("fs"));
28309
- var path20 = __toESM(require("path"));
28429
+ var fs22 = __toESM(require("fs"));
28430
+ var path21 = __toESM(require("path"));
28310
28431
  var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
28311
28432
  var PROFILE_HINTS = {
28312
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.",
@@ -28318,7 +28439,7 @@ function loadTemplate(name) {
28318
28439
  if (!filePath) {
28319
28440
  throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
28320
28441
  }
28321
- return fs21.readFileSync(filePath, "utf8");
28442
+ return fs22.readFileSync(filePath, "utf8");
28322
28443
  }
28323
28444
  function applyProfilePlaceholders(content, profile) {
28324
28445
  return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
@@ -28340,7 +28461,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
28340
28461
  }
28341
28462
  function writeAdaptationPrompt(repoPath, content) {
28342
28463
  const harnessDir = getHarnessDir(repoPath);
28343
- const filePath = path20.join(harnessDir, ADAPTATION_PROMPT_FILE);
28464
+ const filePath = path21.join(harnessDir, ADAPTATION_PROMPT_FILE);
28344
28465
  writeFileSafe(filePath, content);
28345
28466
  return filePath;
28346
28467
  }
@@ -28359,25 +28480,25 @@ function printAdaptationPrompt(content) {
28359
28480
  }
28360
28481
 
28361
28482
  // src/harness/cursor-rule.ts
28362
- var fs22 = __toESM(require("fs"));
28363
- var path21 = __toESM(require("path"));
28483
+ var fs23 = __toESM(require("fs"));
28484
+ var path22 = __toESM(require("path"));
28364
28485
  var readline4 = __toESM(require("readline"));
28365
28486
  var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
28366
28487
  function getCursorRulePath(repoPath) {
28367
- return path21.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
28488
+ return path22.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
28368
28489
  }
28369
28490
  function isCursorWorkspace(repoPath) {
28370
- return fs22.existsSync(path21.join(repoPath, ".cursor"));
28491
+ return fs23.existsSync(path22.join(repoPath, ".cursor"));
28371
28492
  }
28372
28493
  function cursorRuleExists(repoPath) {
28373
- return fs22.existsSync(getCursorRulePath(repoPath));
28494
+ return fs23.existsSync(getCursorRulePath(repoPath));
28374
28495
  }
28375
28496
  function scaffoldCursorRule(repoPath) {
28376
28497
  const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
28377
28498
  if (!templatePath) {
28378
28499
  throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
28379
28500
  }
28380
- const content = fs22.readFileSync(templatePath, "utf8");
28501
+ const content = fs23.readFileSync(templatePath, "utf8");
28381
28502
  writeFileSafe(getCursorRulePath(repoPath), content);
28382
28503
  }
28383
28504
  async function handleCursorRule(options) {
@@ -28503,26 +28624,26 @@ function buildStageResult(input) {
28503
28624
  }
28504
28625
 
28505
28626
  // src/core/local-executor.ts
28506
- var fs24 = __toESM(require("fs"));
28507
- var path23 = __toESM(require("path"));
28627
+ var fs25 = __toESM(require("fs"));
28628
+ var path24 = __toESM(require("path"));
28508
28629
 
28509
28630
  // src/core/slot-registry.ts
28510
- var fs23 = __toESM(require("fs"));
28511
- var path22 = __toESM(require("path"));
28631
+ var fs24 = __toESM(require("fs"));
28632
+ var path23 = __toESM(require("path"));
28512
28633
  function getSlotRegistryDir(repoPath) {
28513
- return path22.join(getHarnessDir(repoPath), "slots");
28634
+ return path23.join(getHarnessDir(repoPath), "slots");
28514
28635
  }
28515
28636
  function getSlotRegistryPath(repoPath, agentId) {
28516
- return path22.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
28637
+ return path23.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
28517
28638
  }
28518
28639
  function isSlotResumable(session) {
28519
28640
  return session?.status === "failed" || session?.status === "starting";
28520
28641
  }
28521
28642
  function readSlotRegistry(repoPath, agentId) {
28522
28643
  const file = getSlotRegistryPath(repoPath, agentId);
28523
- if (!fs23.existsSync(file)) return void 0;
28644
+ if (!fs24.existsSync(file)) return void 0;
28524
28645
  try {
28525
- const raw = JSON.parse(fs23.readFileSync(file, "utf8"));
28646
+ const raw = JSON.parse(fs24.readFileSync(file, "utf8"));
28526
28647
  const result = SlotRegistryEntrySchema.safeParse(raw);
28527
28648
  return result.success ? result.data : void 0;
28528
28649
  } catch {
@@ -28532,7 +28653,7 @@ function readSlotRegistry(repoPath, agentId) {
28532
28653
 
28533
28654
  // src/core/local-executor.ts
28534
28655
  function resolveRepoPath(repoPath) {
28535
- return path23.resolve(repoPath);
28656
+ return path24.resolve(repoPath);
28536
28657
  }
28537
28658
  function buildPreviewUrlsFromPorts(ports, env3) {
28538
28659
  const urls = {};
@@ -28584,14 +28705,14 @@ function substituteAgentId(value, agentId) {
28584
28705
  function resolveStageScriptPath(repoPath, stage) {
28585
28706
  const harnessDir = getHarnessDir(repoPath);
28586
28707
  if (stage.script) {
28587
- return path23.join(harnessDir, stage.script);
28708
+ return path24.join(harnessDir, stage.script);
28588
28709
  }
28589
28710
  if (stage.command) {
28590
28711
  const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
28591
- return path23.join(harnessDir, scriptName);
28712
+ return path24.join(harnessDir, scriptName);
28592
28713
  }
28593
- const stageScript = path23.join(harnessDir, "stages", `${stage.id}.sh`);
28594
- if (fs24.existsSync(stageScript)) {
28714
+ const stageScript = path24.join(harnessDir, "stages", `${stage.id}.sh`);
28715
+ if (fs25.existsSync(stageScript)) {
28595
28716
  return stageScript;
28596
28717
  }
28597
28718
  throw new Error(
@@ -28606,7 +28727,7 @@ function buildExecutionPlan(repoPath, stage, options) {
28606
28727
  ...harnessEnv,
28607
28728
  ...stage.env ?? {}
28608
28729
  };
28609
- const cwd = stage.cwd ? path23.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28730
+ const cwd = stage.cwd ? path24.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28610
28731
  const extraArgs = options.args ?? [];
28611
28732
  const launchFlagArgs = [];
28612
28733
  if (stage.kind === "launch" && options.launchFlags) {
@@ -28683,19 +28804,19 @@ var LocalScriptExecutor = class {
28683
28804
  const repoPath = resolveRepoPath(ctx.repoPath);
28684
28805
  const harnessDir = getHarnessDir(repoPath);
28685
28806
  const artifactsDirName = getArtifactsDir(repoPath);
28686
- const artifactsDir = path23.join(harnessDir, artifactsDirName);
28687
- if (!fs24.existsSync(artifactsDir)) return [];
28807
+ const artifactsDir = path24.join(harnessDir, artifactsDirName);
28808
+ if (!fs25.existsSync(artifactsDir)) return [];
28688
28809
  const entries = [];
28689
28810
  const walk = (dir, prefix) => {
28690
- for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
28691
- const full = path23.join(dir, entry.name);
28692
- 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);
28693
28814
  if (entry.isDirectory()) {
28694
28815
  walk(full, relative2);
28695
28816
  continue;
28696
28817
  }
28697
28818
  if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
28698
- const stat = fs24.statSync(full);
28819
+ const stat = fs25.statSync(full);
28699
28820
  entries.push({
28700
28821
  path: full,
28701
28822
  relativePath: relative2,
@@ -28711,7 +28832,7 @@ var LocalScriptExecutor = class {
28711
28832
  var localScriptExecutor = new LocalScriptExecutor();
28712
28833
 
28713
28834
  // src/core/control-sync.ts
28714
- var path37 = __toESM(require("path"));
28835
+ var path38 = __toESM(require("path"));
28715
28836
 
28716
28837
  // src/core/control-config.ts
28717
28838
  var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
@@ -28723,32 +28844,32 @@ function isControlEnabled() {
28723
28844
  }
28724
28845
 
28725
28846
  // src/core/control-registry.ts
28726
- var fs25 = __toESM(require("fs"));
28847
+ var fs26 = __toESM(require("fs"));
28727
28848
  var os4 = __toESM(require("os"));
28728
- var path24 = __toESM(require("path"));
28849
+ var path25 = __toESM(require("path"));
28729
28850
  function getRegistryPath() {
28730
28851
  if (process.env.HAR_CONTROL_REGISTRY_PATH) {
28731
- return path24.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28852
+ return path25.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28732
28853
  }
28733
- return path24.join(os4.homedir(), ".har", "repos.json");
28854
+ return path25.join(os4.homedir(), ".har", "repos.json");
28734
28855
  }
28735
28856
  function readRegistry() {
28736
28857
  const registryPath = getRegistryPath();
28737
28858
  try {
28738
- if (!fs25.existsSync(registryPath)) return { repos: [] };
28739
- return JSON.parse(fs25.readFileSync(registryPath, "utf8"));
28859
+ if (!fs26.existsSync(registryPath)) return { repos: [] };
28860
+ return JSON.parse(fs26.readFileSync(registryPath, "utf8"));
28740
28861
  } catch {
28741
28862
  return { repos: [] };
28742
28863
  }
28743
28864
  }
28744
28865
  function writeRegistry(registry2) {
28745
28866
  const registryPath = getRegistryPath();
28746
- fs25.mkdirSync(path24.dirname(registryPath), { recursive: true });
28747
- 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");
28748
28869
  }
28749
28870
  function recordRepoForControlSync(repoPath) {
28750
28871
  if (process.env.HAR_CONTROL_DISABLED === "true") return;
28751
- const resolved = path24.resolve(repoPath);
28872
+ const resolved = path25.resolve(repoPath);
28752
28873
  if (!readManifest(resolved)) return;
28753
28874
  const registry2 = readRegistry();
28754
28875
  if (registry2.repos.includes(resolved)) return;
@@ -28757,7 +28878,7 @@ function recordRepoForControlSync(repoPath) {
28757
28878
  }
28758
28879
  function listRegisteredRepos() {
28759
28880
  const registry2 = readRegistry();
28760
- const kept = registry2.repos.filter((repoPath) => fs25.existsSync(repoPath) && readManifest(repoPath));
28881
+ const kept = registry2.repos.filter((repoPath) => fs26.existsSync(repoPath) && readManifest(repoPath));
28761
28882
  if (kept.length !== registry2.repos.length) {
28762
28883
  writeRegistry({ repos: kept });
28763
28884
  }
@@ -28765,17 +28886,17 @@ function listRegisteredRepos() {
28765
28886
  }
28766
28887
 
28767
28888
  // src/core/slot-status.ts
28768
- var fs30 = __toESM(require("fs"));
28889
+ var fs31 = __toESM(require("fs"));
28769
28890
  var os7 = __toESM(require("os"));
28770
- var path30 = __toESM(require("path"));
28891
+ var path31 = __toESM(require("path"));
28771
28892
  var import_child_process7 = require("child_process");
28772
28893
 
28773
28894
  // src/core/runs.ts
28774
28895
  var crypto2 = __toESM(require("crypto"));
28775
28896
  var import_child_process2 = require("child_process");
28776
- var fs26 = __toESM(require("fs"));
28897
+ var fs27 = __toESM(require("fs"));
28777
28898
  var os5 = __toESM(require("os"));
28778
- var path25 = __toESM(require("path"));
28899
+ var path26 = __toESM(require("path"));
28779
28900
  var RUNS_DIR = "runs";
28780
28901
  function gitCommonDir(cwd) {
28781
28902
  try {
@@ -28784,7 +28905,7 @@ function gitCommonDir(cwd) {
28784
28905
  encoding: "utf8",
28785
28906
  stdio: ["pipe", "pipe", "ignore"]
28786
28907
  }).trim();
28787
- return out ? path25.resolve(cwd, out) : void 0;
28908
+ return out ? path26.resolve(cwd, out) : void 0;
28788
28909
  } catch {
28789
28910
  return void 0;
28790
28911
  }
@@ -28806,7 +28927,7 @@ function gitPrefix(cwd) {
28806
28927
  }
28807
28928
  }
28808
28929
  function getRunsDir(harnessRoot) {
28809
- return path25.join(getHarnessDir(harnessRoot), RUNS_DIR);
28930
+ return path26.join(getHarnessDir(harnessRoot), RUNS_DIR);
28810
28931
  }
28811
28932
  function formatLocalDate(d2) {
28812
28933
  const y2 = d2.getFullYear();
@@ -28826,24 +28947,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
28826
28947
  const timePart = formatLocalTime(started);
28827
28948
  const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
28828
28949
  let filename = `${timePart}_${stageId}${agentPart}.json`;
28829
- const fullPath = path25.join(runsDir, dateFolder, filename);
28830
- if (fs26.existsSync(fullPath)) {
28950
+ const fullPath = path26.join(runsDir, dateFolder, filename);
28951
+ if (fs27.existsSync(fullPath)) {
28831
28952
  filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
28832
28953
  }
28833
- return path25.join(dateFolder, filename);
28954
+ return path26.join(dateFolder, filename);
28834
28955
  }
28835
28956
  function resolveRunFilePath(harnessRoot, run2) {
28836
28957
  if (run2.relativePath) {
28837
- return path25.join(getRunsDir(harnessRoot), run2.relativePath);
28958
+ return path26.join(getRunsDir(harnessRoot), run2.relativePath);
28838
28959
  }
28839
- return path25.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28960
+ return path26.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28840
28961
  }
28841
28962
  function collectRunFiles(runsDir) {
28842
- if (!fs26.existsSync(runsDir)) return [];
28963
+ if (!fs27.existsSync(runsDir)) return [];
28843
28964
  const files = [];
28844
28965
  const walk = (dir) => {
28845
- for (const entry of fs26.readdirSync(dir, { withFileTypes: true })) {
28846
- 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);
28847
28968
  if (entry.isDirectory()) {
28848
28969
  walk(full);
28849
28970
  } else if (entry.name.endsWith(".json")) {
@@ -28857,30 +28978,30 @@ function collectRunFiles(runsDir) {
28857
28978
  function resolveAgentWorkDir(harnessRoot, agentId) {
28858
28979
  if (agentId === void 0) return void 0;
28859
28980
  const entry = readSlotRegistry(harnessRoot, agentId);
28860
- if (entry?.workDir && fs26.existsSync(entry.workDir)) return entry.workDir;
28981
+ if (entry?.workDir && fs27.existsSync(entry.workDir)) return entry.workDir;
28861
28982
  const env3 = readHarnessEnv(harnessRoot);
28862
- const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
28863
- 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}`);
28864
28985
  const relPrefix = gitPrefix(harnessRoot);
28865
28986
  const sessionEnvFiles = [];
28866
- const worktreesRoot = path25.join(os5.homedir(), "worktrees");
28867
- if (fs26.existsSync(worktreesRoot)) {
28987
+ const worktreesRoot = path26.join(os5.homedir(), "worktrees");
28988
+ if (fs27.existsSync(worktreesRoot)) {
28868
28989
  const suffix = `-har-agent-${agentId}-`;
28869
- for (const entry2 of fs26.readdirSync(worktreesRoot, { withFileTypes: true })) {
28990
+ for (const entry2 of fs27.readdirSync(worktreesRoot, { withFileTypes: true })) {
28870
28991
  if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
28871
- const sessionDir = path25.join(worktreesRoot, entry2.name);
28992
+ const sessionDir = path26.join(worktreesRoot, entry2.name);
28872
28993
  if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
28873
- sessionEnvFiles.push(path25.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
28994
+ sessionEnvFiles.push(path26.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
28874
28995
  }
28875
28996
  }
28876
28997
  const candidates = [
28877
- path25.join(worktreeDir, `.env.agent.${agentId}`),
28878
- path25.join(harnessRoot, `.env.agent.${agentId}`),
28998
+ path26.join(worktreeDir, `.env.agent.${agentId}`),
28999
+ path26.join(harnessRoot, `.env.agent.${agentId}`),
28879
29000
  ...sessionEnvFiles.sort()
28880
29001
  ];
28881
29002
  for (const envFile of candidates) {
28882
- if (!fs26.existsSync(envFile)) continue;
28883
- const content = fs26.readFileSync(envFile, "utf8");
29003
+ if (!fs27.existsSync(envFile)) continue;
29004
+ const content = fs27.readFileSync(envFile, "utf8");
28884
29005
  const match = content.match(/^REPO_ROOT=(.+)$/m);
28885
29006
  if (match) return match[1].trim();
28886
29007
  }
@@ -28892,11 +29013,11 @@ function createRun(ctx, meta) {
28892
29013
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
28893
29014
  const runId = crypto2.randomUUID();
28894
29015
  const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
28895
- const runFilePath = path25.join(runsDir, relativePath);
28896
- fs26.mkdirSync(path25.dirname(runFilePath), { recursive: true });
29016
+ const runFilePath = path26.join(runsDir, relativePath);
29017
+ fs27.mkdirSync(path26.dirname(runFilePath), { recursive: true });
28897
29018
  const run2 = RunRecordSchema.parse({
28898
29019
  runId,
28899
- repoPath: path25.resolve(ctx.repoPath),
29020
+ repoPath: path26.resolve(ctx.repoPath),
28900
29021
  harnessRoot,
28901
29022
  stageId: meta.stageId,
28902
29023
  kind: meta.kind,
@@ -28907,7 +29028,7 @@ function createRun(ctx, meta) {
28907
29028
  relativePath,
28908
29029
  trigger: ctx.trigger ?? "cli"
28909
29030
  });
28910
- fs26.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
29031
+ fs27.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
28911
29032
  return run2;
28912
29033
  }
28913
29034
  function finishRun(repoPath, runId, update) {
@@ -28926,13 +29047,13 @@ function finishRun(repoPath, runId, update) {
28926
29047
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
28927
29048
  });
28928
29049
  const runPath = resolveRunFilePath(harnessRoot, finished);
28929
- fs26.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
29050
+ fs27.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
28930
29051
  return finished;
28931
29052
  }
28932
29053
  function findRunRecord(harnessRoot, runId) {
28933
29054
  const runsDir = getRunsDir(harnessRoot);
28934
29055
  for (const filePath of collectRunFiles(runsDir)) {
28935
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
29056
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
28936
29057
  if (parsed.success && parsed.data.runId === runId) {
28937
29058
  return parsed.data;
28938
29059
  }
@@ -28944,10 +29065,10 @@ function getRun(repoPath, runId) {
28944
29065
  }
28945
29066
  function listRuns(repoPath, filter = {}) {
28946
29067
  const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
28947
- if (!fs26.existsSync(runsDir)) return [];
29068
+ if (!fs27.existsSync(runsDir)) return [];
28948
29069
  const runs = [];
28949
29070
  for (const filePath of collectRunFiles(runsDir)) {
28950
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
29071
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
28951
29072
  if (!parsed.success) continue;
28952
29073
  if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
28953
29074
  runs.push(parsed.data);
@@ -28960,17 +29081,17 @@ function listRuns(repoPath, filter = {}) {
28960
29081
  }
28961
29082
 
28962
29083
  // src/core/slot-preflight.ts
28963
- var path29 = __toESM(require("path"));
29084
+ var path30 = __toESM(require("path"));
28964
29085
 
28965
29086
  // src/core/control-port.ts
28966
29087
  var import_child_process4 = require("child_process");
28967
- var fs28 = __toESM(require("fs"));
28968
- var path27 = __toESM(require("path"));
29088
+ var fs29 = __toESM(require("fs"));
29089
+ var path28 = __toESM(require("path"));
28969
29090
 
28970
29091
  // src/core/slot-ports.ts
28971
29092
  var import_child_process3 = require("child_process");
28972
- var fs27 = __toESM(require("fs"));
28973
- var path26 = __toESM(require("path"));
29093
+ var fs28 = __toESM(require("fs"));
29094
+ var path27 = __toESM(require("path"));
28974
29095
  function portStep(env3) {
28975
29096
  return Number(env3.HARNESS_PORT_STEP ?? 10);
28976
29097
  }
@@ -29026,7 +29147,7 @@ function allocateAppPorts(repoPath, agentId) {
29026
29147
  }
29027
29148
  function harnessUsesPm2(repoPath) {
29028
29149
  const harnessRoot = resolveHarnessRoot(repoPath);
29029
- return fs27.existsSync(path26.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29150
+ return fs28.existsSync(path27.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29030
29151
  }
29031
29152
 
29032
29153
  // src/core/control-port.ts
@@ -29081,13 +29202,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
29081
29202
  return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
29082
29203
  }
29083
29204
  function inspectControlUpReadiness(repoPath) {
29084
- const resolved = path27.resolve(repoPath);
29205
+ const resolved = path28.resolve(repoPath);
29085
29206
  const port = parseControlHostPort();
29086
29207
  const containers = listDockerContainers();
29087
29208
  const control = controlContainerOnPort(containers, port);
29088
29209
  const warnings = [];
29089
- const controlHarnessDir = path27.join(resolved, "control", ".har");
29090
- 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";
29091
29212
  if (control) {
29092
29213
  return {
29093
29214
  warnings,
@@ -29118,9 +29239,9 @@ function inspectControlUpReadiness(repoPath) {
29118
29239
  }
29119
29240
 
29120
29241
  // src/core/slot-launch-guard-occupied.ts
29121
- var fs29 = __toESM(require("fs"));
29242
+ var fs30 = __toESM(require("fs"));
29122
29243
  var os6 = __toESM(require("os"));
29123
- var path28 = __toESM(require("path"));
29244
+ var path29 = __toESM(require("path"));
29124
29245
  var import_child_process5 = require("child_process");
29125
29246
  function runGit(cwd, args) {
29126
29247
  try {
@@ -29139,7 +29260,7 @@ function worktreeDirty(worktreePath) {
29139
29260
  }
29140
29261
  function gitCommonDir2(cwd) {
29141
29262
  const out = runGit(cwd, "rev-parse --git-common-dir");
29142
- return out ? path28.resolve(cwd, out) : void 0;
29263
+ return out ? path29.resolve(cwd, out) : void 0;
29143
29264
  }
29144
29265
  function sameGitCheckout2(a2, b2) {
29145
29266
  const left2 = gitCommonDir2(a2);
@@ -29148,20 +29269,20 @@ function sameGitCheckout2(a2, b2) {
29148
29269
  }
29149
29270
  function discoverWorktree(harnessRoot, agentId, projectName) {
29150
29271
  const session = readSlotRegistry(harnessRoot, agentId);
29151
- if (session?.worktreePath && fs29.existsSync(session.worktreePath)) {
29272
+ if (session?.worktreePath && fs30.existsSync(session.worktreePath)) {
29152
29273
  return session.worktreePath;
29153
29274
  }
29154
- const legacy = path28.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29155
- if (fs29.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
29156
- const worktreesRoot = path28.join(os6.homedir(), "worktrees");
29157
- 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;
29158
29279
  const suffix = `-har-agent-${agentId}-`;
29159
29280
  const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
29160
- for (const name of fs29.readdirSync(worktreesRoot)) {
29281
+ for (const name of fs30.readdirSync(worktreesRoot)) {
29161
29282
  if (!name.includes(suffix)) continue;
29162
- const candidate = path28.join(worktreesRoot, name);
29283
+ const candidate = path29.join(worktreesRoot, name);
29163
29284
  if (!sameGitCheckout2(harnessRoot, candidate)) continue;
29164
- if (!fs29.existsSync(path28.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29285
+ if (!fs30.existsSync(path29.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29165
29286
  return candidate;
29166
29287
  }
29167
29288
  return void 0;
@@ -29169,13 +29290,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
29169
29290
  function collectOccupiedSlot(repoPath, agentId) {
29170
29291
  const harnessRoot = resolveHarnessRoot(repoPath);
29171
29292
  const env3 = readHarnessEnv(harnessRoot);
29172
- const projectName = env3.HARNESS_PROJECT_NAME ?? path28.basename(harnessRoot);
29293
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
29173
29294
  const session = readSlotRegistry(harnessRoot, agentId);
29174
29295
  const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
29175
29296
  const workDir = session?.workDir;
29176
- const envInWorkDir = workDir ? fs29.existsSync(path28.join(workDir, `.env.agent.${agentId}`)) : false;
29177
- const envInRoot = fs29.existsSync(path28.join(harnessRoot, `.env.agent.${agentId}`));
29178
- 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;
29179
29300
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29180
29301
  if (!active) return void 0;
29181
29302
  const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
@@ -29328,7 +29449,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
29328
29449
  function inspectSlotReadiness(repoPath, agentId, options = {}) {
29329
29450
  const harnessRoot = resolveHarnessRoot(repoPath);
29330
29451
  const env3 = readHarnessEnv(harnessRoot);
29331
- const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
29452
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29332
29453
  const usesPm2 = harnessUsesPm2(repoPath);
29333
29454
  const blockers = [];
29334
29455
  const remediations = [];
@@ -29530,7 +29651,7 @@ function readWorktreeBranch(worktreePath) {
29530
29651
  }
29531
29652
  function gitCommonDir3(cwd) {
29532
29653
  const out = runGit2(cwd, "rev-parse --git-common-dir");
29533
- return out ? path30.resolve(cwd, out) : void 0;
29654
+ return out ? path31.resolve(cwd, out) : void 0;
29534
29655
  }
29535
29656
  function sameGitCheckout3(a2, b2) {
29536
29657
  const left2 = gitCommonDir3(a2);
@@ -29542,12 +29663,12 @@ function gitPrefix2(cwd) {
29542
29663
  return out ?? "";
29543
29664
  }
29544
29665
  function discoverSessionWorktreePath(harnessRoot, agentId) {
29545
- const worktreesRoot = path30.join(os7.homedir(), "worktrees");
29546
- if (!fs30.existsSync(worktreesRoot)) return void 0;
29666
+ const worktreesRoot = path31.join(os7.homedir(), "worktrees");
29667
+ if (!fs31.existsSync(worktreesRoot)) return void 0;
29547
29668
  const suffix = `-har-agent-${agentId}-`;
29548
29669
  const relPrefix = gitPrefix2(harnessRoot);
29549
- const matches = fs30.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path30.join(worktreesRoot, entry.name)).filter(
29550
- (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}`))
29551
29672
  );
29552
29673
  return matches.sort()[0];
29553
29674
  }
@@ -29622,18 +29743,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
29622
29743
  }
29623
29744
  function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
29624
29745
  const env3 = readHarnessEnv(harnessRoot);
29625
- const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29746
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path31.basename(harnessRoot);
29626
29747
  const session = readSlotRegistry(harnessRoot, agentId);
29627
- const legacyWorktreePath = path30.join(
29748
+ const legacyWorktreePath = path31.join(
29628
29749
  os7.homedir(),
29629
29750
  "worktrees",
29630
29751
  `${projectName}-agent-${agentId}`
29631
29752
  );
29632
- 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);
29633
29754
  const workDir = resolveAgentWorkDir(harnessRoot, agentId);
29634
- const envInWorkDir = workDir ? fs30.existsSync(path30.join(workDir, `.env.agent.${agentId}`)) : false;
29635
- const envInRoot = fs30.existsSync(path30.join(harnessRoot, `.env.agent.${agentId}`));
29636
- 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;
29637
29758
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29638
29759
  const latest = latestRunForAgent(runs, agentId);
29639
29760
  const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
@@ -29686,7 +29807,7 @@ function collectEnvironmentStatus(repoPath) {
29686
29807
  const slotIds = getAgentSlotIds(harnessRoot);
29687
29808
  const pm2Procs = listPm2Processes2();
29688
29809
  return {
29689
- repoPath: path30.resolve(repoPath),
29810
+ repoPath: path31.resolve(repoPath),
29690
29811
  harnessRoot,
29691
29812
  gitRemote: readGitRemote(harnessRoot),
29692
29813
  profile: manifest?.profile,
@@ -29697,13 +29818,13 @@ function collectEnvironmentStatus(repoPath) {
29697
29818
 
29698
29819
  // src/core/validations.ts
29699
29820
  var crypto3 = __toESM(require("crypto"));
29700
- var fs32 = __toESM(require("fs"));
29701
- var path32 = __toESM(require("path"));
29821
+ var fs33 = __toESM(require("fs"));
29822
+ var path33 = __toESM(require("path"));
29702
29823
 
29703
29824
  // src/core/change-batch.ts
29704
- var fs31 = __toESM(require("fs"));
29825
+ var fs32 = __toESM(require("fs"));
29705
29826
  var os8 = __toESM(require("os"));
29706
- var path31 = __toESM(require("path"));
29827
+ var path32 = __toESM(require("path"));
29707
29828
  function git(checkoutDir, args, env3) {
29708
29829
  const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
29709
29830
  if (result.code !== 0) {
@@ -29719,7 +29840,7 @@ function isCheckoutRoot(checkoutDir) {
29719
29840
  const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
29720
29841
  if (!toplevel) return false;
29721
29842
  try {
29722
- return fs31.realpathSync(toplevel) === fs31.realpathSync(checkoutDir);
29843
+ return fs32.realpathSync(toplevel) === fs32.realpathSync(checkoutDir);
29723
29844
  } catch {
29724
29845
  return false;
29725
29846
  }
@@ -29741,7 +29862,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
29741
29862
  if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
29742
29863
  for (const dir of ["rebase-merge", "rebase-apply"]) {
29743
29864
  const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
29744
- if (gitPath && fs31.existsSync(path31.resolve(checkoutDir, gitPath))) return true;
29865
+ if (gitPath && fs32.existsSync(path32.resolve(checkoutDir, gitPath))) return true;
29745
29866
  }
29746
29867
  return false;
29747
29868
  }
@@ -29764,8 +29885,8 @@ function parseNameStatus(output) {
29764
29885
  return files;
29765
29886
  }
29766
29887
  function computeWorktreeSnapshot(checkoutDir) {
29767
- const tmpDir = fs31.mkdtempSync(path31.join(os8.tmpdir(), "har-idx-"));
29768
- const tmpIndex = path31.join(tmpDir, "index");
29888
+ const tmpDir = fs32.mkdtempSync(path32.join(os8.tmpdir(), "har-idx-"));
29889
+ const tmpIndex = path32.join(tmpDir, "index");
29769
29890
  const env3 = { GIT_INDEX_FILE: tmpIndex };
29770
29891
  try {
29771
29892
  const headTree = getHeadTree(checkoutDir);
@@ -29788,35 +29909,35 @@ function computeWorktreeSnapshot(checkoutDir) {
29788
29909
  changedFiles: parseNameStatus(diffOutput)
29789
29910
  };
29790
29911
  } finally {
29791
- fs31.rmSync(tmpDir, { recursive: true, force: true });
29912
+ fs32.rmSync(tmpDir, { recursive: true, force: true });
29792
29913
  }
29793
29914
  }
29794
29915
 
29795
29916
  // src/core/validations.ts
29796
29917
  var VALIDATIONS_DIR = "validations";
29797
29918
  function getValidationsDir(checkoutDir) {
29798
- return path32.join(checkoutDir, ".har", VALIDATIONS_DIR);
29919
+ return path33.join(checkoutDir, ".har", VALIDATIONS_DIR);
29799
29920
  }
29800
29921
  function validationPath(checkoutDir, treeHash) {
29801
- return path32.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29922
+ return path33.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29802
29923
  }
29803
29924
  function writeRecord(checkoutDir, record2) {
29804
29925
  const dir = getValidationsDir(checkoutDir);
29805
- fs32.mkdirSync(dir, { recursive: true });
29806
- fs32.writeFileSync(
29926
+ fs33.mkdirSync(dir, { recursive: true });
29927
+ fs33.writeFileSync(
29807
29928
  validationPath(checkoutDir, record2.treeHash),
29808
29929
  `${JSON.stringify(record2, null, 2)}
29809
29930
  `
29810
29931
  );
29811
29932
  }
29812
29933
  function ensureValidationsIgnored(checkoutDir) {
29813
- const harDir = path32.join(checkoutDir, ".har");
29814
- if (!fs32.existsSync(harDir)) return;
29815
- const gitignorePath = path32.join(harDir, ".gitignore");
29816
- 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") : "";
29817
29938
  if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
29818
29939
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
29819
- fs32.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29940
+ fs33.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29820
29941
  `);
29821
29942
  }
29822
29943
  function recordValidation(input) {
@@ -29833,8 +29954,8 @@ function recordValidation(input) {
29833
29954
  treeHash: snapshot.treeHash,
29834
29955
  headSha: snapshot.headSha,
29835
29956
  branch: snapshot.branch,
29836
- workDir: path32.resolve(input.checkoutDir),
29837
- harnessRoot: path32.resolve(input.harnessRoot),
29957
+ workDir: path33.resolve(input.checkoutDir),
29958
+ harnessRoot: path33.resolve(input.harnessRoot),
29838
29959
  agentId: input.agentId,
29839
29960
  status: input.status,
29840
29961
  full: input.full,
@@ -29846,16 +29967,16 @@ function recordValidation(input) {
29846
29967
  committedAt: existing?.committedAt
29847
29968
  });
29848
29969
  writeRecord(input.checkoutDir, record2);
29849
- if (path32.resolve(input.harnessRoot) !== path32.resolve(input.checkoutDir)) {
29970
+ if (path33.resolve(input.harnessRoot) !== path33.resolve(input.checkoutDir)) {
29850
29971
  writeRecord(input.harnessRoot, record2);
29851
29972
  }
29852
29973
  return record2;
29853
29974
  }
29854
29975
  function findValidation(checkoutDir, treeHash) {
29855
29976
  const file = validationPath(checkoutDir, treeHash);
29856
- if (!fs32.existsSync(file)) return void 0;
29977
+ if (!fs33.existsSync(file)) return void 0;
29857
29978
  try {
29858
- return ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(file, "utf8")));
29979
+ return ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(file, "utf8")));
29859
29980
  } catch {
29860
29981
  return void 0;
29861
29982
  }
@@ -29870,19 +29991,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
29870
29991
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29871
29992
  };
29872
29993
  writeRecord(checkoutDir, updated);
29873
- 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"))) {
29874
29995
  writeRecord(record2.harnessRoot, updated);
29875
29996
  }
29876
29997
  return updated;
29877
29998
  }
29878
29999
  function listValidations(harnessRoot) {
29879
30000
  const dir = getValidationsDir(harnessRoot);
29880
- if (!fs32.existsSync(dir)) return [];
30001
+ if (!fs33.existsSync(dir)) return [];
29881
30002
  const records = [];
29882
- for (const entry of fs32.readdirSync(dir)) {
30003
+ for (const entry of fs33.readdirSync(dir)) {
29883
30004
  if (!entry.endsWith(".json")) continue;
29884
30005
  try {
29885
- 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"))));
29886
30007
  } catch {
29887
30008
  }
29888
30009
  }
@@ -29912,14 +30033,26 @@ function createRemoteExecutor(apiUrl, apiKey) {
29912
30033
  }
29913
30034
 
29914
30035
  // src/core/telemetry-config.ts
29915
- var fs33 = __toESM(require("fs"));
30036
+ var fs34 = __toESM(require("fs"));
29916
30037
  var os9 = __toESM(require("os"));
29917
- var path33 = __toESM(require("path"));
30038
+ var path34 = __toESM(require("path"));
30039
+ var DEFAULT_SIGNALS_ON = {
30040
+ metrics: true,
30041
+ logs: true,
30042
+ prompts: false,
30043
+ traces: false
30044
+ };
30045
+ var DEFAULT_SIGNALS_OFF = {
30046
+ metrics: false,
30047
+ logs: false,
30048
+ prompts: false,
30049
+ traces: false
30050
+ };
29918
30051
  function getPreferencePath() {
29919
30052
  if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
29920
- return path33.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
30053
+ return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
29921
30054
  }
29922
- return path33.join(os9.homedir(), ".har", "telemetry.json");
30055
+ return path34.join(os9.homedir(), ".har", "telemetry.json");
29923
30056
  }
29924
30057
  function parseEnvOverride(raw) {
29925
30058
  if (raw === void 0 || raw === "") return void 0;
@@ -29928,29 +30061,46 @@ function parseEnvOverride(raw) {
29928
30061
  if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
29929
30062
  return void 0;
29930
30063
  }
30064
+ function normalizeSignals(enabled, raw) {
30065
+ if (!enabled) return { ...DEFAULT_SIGNALS_OFF };
30066
+ return {
30067
+ metrics: raw?.metrics !== false,
30068
+ logs: raw?.logs !== false,
30069
+ prompts: raw?.prompts === true,
30070
+ traces: raw?.traces === true
30071
+ };
30072
+ }
29931
30073
  function readTelemetryPreference() {
29932
30074
  const preferencePath = getPreferencePath();
29933
30075
  try {
29934
- if (!fs33.existsSync(preferencePath)) {
29935
- return { enabled: true };
30076
+ if (!fs34.existsSync(preferencePath)) {
30077
+ return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
29936
30078
  }
29937
- const parsed = JSON.parse(fs33.readFileSync(preferencePath, "utf8"));
30079
+ const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
30080
+ const enabled = parsed.enabled !== false;
29938
30081
  return {
29939
- enabled: parsed.enabled !== false,
30082
+ enabled,
30083
+ signals: normalizeSignals(enabled, parsed.signals),
29940
30084
  updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
29941
30085
  };
29942
30086
  } catch {
29943
- return { enabled: true };
30087
+ return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
29944
30088
  }
29945
30089
  }
29946
- function writeTelemetryPreference(enabled) {
30090
+ function writeTelemetryPreference(enabled, signals) {
30091
+ const current = readTelemetryPreference();
30092
+ const nextSignals = normalizeSignals(enabled, {
30093
+ ...current.signals,
30094
+ ...signals
30095
+ });
29947
30096
  const preference = {
29948
30097
  enabled,
30098
+ signals: enabled ? nextSignals : { ...DEFAULT_SIGNALS_OFF },
29949
30099
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29950
30100
  };
29951
30101
  const preferencePath = getPreferencePath();
29952
- fs33.mkdirSync(path33.dirname(preferencePath), { recursive: true });
29953
- 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");
29954
30104
  return preference;
29955
30105
  }
29956
30106
  function isTelemetryEnabled() {
@@ -29958,23 +30108,29 @@ function isTelemetryEnabled() {
29958
30108
  if (envOverride !== void 0) return envOverride;
29959
30109
  return readTelemetryPreference().enabled;
29960
30110
  }
30111
+ function getTelemetrySignals() {
30112
+ if (!isTelemetryEnabled()) return { ...DEFAULT_SIGNALS_OFF };
30113
+ return readTelemetryPreference().signals;
30114
+ }
29961
30115
  function getTelemetryPreferencePath() {
29962
30116
  return getPreferencePath();
29963
30117
  }
29964
30118
  var TELEMETRY_SIGNALS = [
29965
- "Claude Code: tokens (input/output/cache) and estimated USD cost via OTEL metrics",
29966
- "Codex CLI: token usage via OTEL metrics (no native USD; harvest fills gaps)",
30119
+ "Metrics (default): tokens and estimated USD cost via OTEL metrics",
30120
+ "Logs/events (default): session events (tool calls, api_request) without prompt bodies",
30121
+ "Prompts (opt-in): user/assistant text via OTEL_LOG_USER_PROMPTS \u2014 har telemetry on --prompts",
30122
+ "Traces (opt-in): thin span ingest when CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1",
29967
30123
  "Fallback: har control sync harvests local Claude/Codex session files when OTEL is missing"
29968
30124
  ];
29969
30125
 
29970
30126
  // src/core/usage-harvest/claude.ts
29971
- var fs35 = __toESM(require("fs"));
30127
+ var fs36 = __toESM(require("fs"));
29972
30128
  var os10 = __toESM(require("os"));
29973
- var path35 = __toESM(require("path"));
30129
+ var path36 = __toESM(require("path"));
29974
30130
 
29975
30131
  // src/core/telemetry-env.ts
29976
- var fs34 = __toESM(require("fs"));
29977
- var path34 = __toESM(require("path"));
30132
+ var fs35 = __toESM(require("fs"));
30133
+ var path35 = __toESM(require("path"));
29978
30134
  function escapeAttrValue(value) {
29979
30135
  return value.replace(/[,=]/g, "_");
29980
30136
  }
@@ -30006,13 +30162,31 @@ function buildTelemetryEnvBlock(attrs, options) {
30006
30162
  ];
30007
30163
  const injectOtel = isTelemetryEnabled() && options?.otelReady !== false;
30008
30164
  if (injectOtel) {
30165
+ const signals = getTelemetrySignals();
30009
30166
  lines.push(
30010
30167
  "# HAR telemetry \u2192 Mission Control OTLP (disable: har telemetry off)",
30011
30168
  "CLAUDE_CODE_ENABLE_TELEMETRY=1",
30012
- "OTEL_METRICS_EXPORTER=otlp",
30013
30169
  "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
30014
30170
  `OTEL_EXPORTER_OTLP_ENDPOINT=${apiUrl}/api/otel`
30015
30171
  );
30172
+ if (signals.metrics) {
30173
+ lines.push("OTEL_METRICS_EXPORTER=otlp");
30174
+ }
30175
+ if (signals.logs) {
30176
+ lines.push("OTEL_LOGS_EXPORTER=otlp");
30177
+ }
30178
+ if (signals.prompts) {
30179
+ lines.push(
30180
+ "OTEL_LOG_USER_PROMPTS=1",
30181
+ "OTEL_LOG_ASSISTANT_RESPONSES=1"
30182
+ );
30183
+ }
30184
+ if (signals.traces) {
30185
+ lines.push(
30186
+ "OTEL_TRACES_EXPORTER=otlp",
30187
+ "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1"
30188
+ );
30189
+ }
30016
30190
  }
30017
30191
  return lines.join("\n") + "\n";
30018
30192
  }
@@ -30023,8 +30197,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs, options) {
30023
30197
  ${TELEMETRY_MARKER_END}
30024
30198
  `;
30025
30199
  let existing = "";
30026
- if (fs34.existsSync(envFilePath)) {
30027
- existing = fs34.readFileSync(envFilePath, "utf8");
30200
+ if (fs35.existsSync(envFilePath)) {
30201
+ existing = fs35.readFileSync(envFilePath, "utf8");
30028
30202
  const start = existing.indexOf(TELEMETRY_MARKER_START);
30029
30203
  if (start >= 0) {
30030
30204
  const end = existing.indexOf(TELEMETRY_MARKER_END, start);
@@ -30035,9 +30209,9 @@ ${TELEMETRY_MARKER_END}
30035
30209
  }
30036
30210
  }
30037
30211
  }
30038
- fs34.mkdirSync(path34.dirname(envFilePath), { recursive: true });
30212
+ fs35.mkdirSync(path35.dirname(envFilePath), { recursive: true });
30039
30213
  const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
30040
- fs34.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30214
+ fs35.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30041
30215
  }
30042
30216
  function buildCodexOtelSnippet(attrs) {
30043
30217
  const apiUrl = getControlApiUrl().replace(/\/$/, "");
@@ -30058,21 +30232,21 @@ metrics_exporter = "otlp-http"
30058
30232
 
30059
30233
  // src/core/usage-harvest/claude.ts
30060
30234
  function pathsMatch(candidate, targets) {
30061
- const norm = path35.resolve(candidate);
30235
+ const norm = path36.resolve(candidate);
30062
30236
  return targets.some((t2) => {
30063
- const target = path35.resolve(t2);
30064
- 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);
30065
30239
  });
30066
30240
  }
30067
30241
  function claudeProjectsRoot() {
30068
- 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");
30069
30243
  }
30070
30244
  function encodeClaudeProjectDir(cwd) {
30071
30245
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
30072
30246
  }
30073
30247
  function readJsonlRecords(filePath) {
30074
- if (!fs35.existsSync(filePath)) return [];
30075
- 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/);
30076
30250
  const out = [];
30077
30251
  for (const line of lines) {
30078
30252
  const trimmed = line.trim();
@@ -30111,27 +30285,20 @@ function extractClaudeUsageFromRecords(records) {
30111
30285
  }
30112
30286
  return { tokensInput, tokensOutput, tokensCacheRead, tokensCacheCreation, costUsd };
30113
30287
  }
30114
- function harvestClaudeUsage(slot) {
30288
+ function findMatchingClaudeTranscripts(slot) {
30115
30289
  const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
30116
- if (targets.length === 0) return null;
30290
+ if (targets.length === 0) return [];
30117
30291
  const root = claudeProjectsRoot();
30118
- if (!fs35.existsSync(root)) return null;
30119
- const sessionKey = buildSessionKey({
30120
- branch: slot.branch,
30121
- agentId: slot.agentId,
30122
- suffix: slot.suffix,
30123
- createdAt: slot.sessionCreatedAt
30124
- });
30125
- let best = null;
30126
- let bestMtime = 0;
30127
- for (const entry of fs35.readdirSync(root, { withFileTypes: true })) {
30292
+ if (!fs36.existsSync(root)) return [];
30293
+ const matches = [];
30294
+ for (const entry of fs36.readdirSync(root, { withFileTypes: true })) {
30128
30295
  if (!entry.isDirectory()) continue;
30129
- const projectDir = path35.join(root, entry.name);
30296
+ const projectDir = path36.join(root, entry.name);
30130
30297
  const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
30131
- for (const file of fs35.readdirSync(projectDir)) {
30298
+ for (const file of fs36.readdirSync(projectDir)) {
30132
30299
  if (!file.endsWith(".jsonl")) continue;
30133
- const filePath = path35.join(projectDir, file);
30134
- const stat = fs35.statSync(filePath);
30300
+ const filePath = path36.join(projectDir, file);
30301
+ const stat = fs36.statSync(filePath);
30135
30302
  const records = readJsonlRecords(filePath);
30136
30303
  let cwdHit = encodedHit;
30137
30304
  for (const record2 of records) {
@@ -30143,16 +30310,70 @@ function harvestClaudeUsage(slot) {
30143
30310
  }
30144
30311
  }
30145
30312
  if (!cwdHit) continue;
30146
- const usage2 = extractClaudeUsageFromRecords(records);
30147
- if (usage2.tokensInput + usage2.tokensOutput + usage2.tokensCacheRead === 0 && (usage2.costUsd == null || usage2.costUsd === 0)) {
30148
- continue;
30149
- }
30150
- if (stat.mtimeMs >= bestMtime) {
30151
- bestMtime = stat.mtimeMs;
30152
- best = usage2;
30153
- }
30313
+ matches.push({ filePath, records, mtimeMs: stat.mtimeMs });
30154
30314
  }
30155
30315
  }
30316
+ return matches.sort((a2, b2) => b2.mtimeMs - a2.mtimeMs);
30317
+ }
30318
+ function extractPromptEvents(records, sessionKey, slot) {
30319
+ const events = [];
30320
+ let sequence = 0;
30321
+ for (const record2 of records) {
30322
+ if (!record2 || typeof record2 !== "object") continue;
30323
+ const payload = record2;
30324
+ const typ = String(payload.type ?? "");
30325
+ const message = payload.message;
30326
+ const role = String(message?.role ?? payload.role ?? "");
30327
+ let text = null;
30328
+ const content = message?.content ?? payload.content;
30329
+ if (typeof content === "string") text = content;
30330
+ else if (Array.isArray(content)) {
30331
+ text = content.map((part) => {
30332
+ if (typeof part === "string") return part;
30333
+ if (part && typeof part === "object" && typeof part.text === "string") {
30334
+ return part.text;
30335
+ }
30336
+ return "";
30337
+ }).filter(Boolean).join("\n");
30338
+ }
30339
+ if (!text || text.trim().length === 0) continue;
30340
+ const isUser = typ === "user" || role === "user" || typ === "human" || Boolean(payload.userType);
30341
+ const isAssistant = typ === "assistant" || role === "assistant";
30342
+ if (!isUser && !isAssistant) continue;
30343
+ sequence += 1;
30344
+ const timestamp = typeof payload.timestamp === "string" ? payload.timestamp : typeof payload.ts === "string" ? payload.ts : (/* @__PURE__ */ new Date()).toISOString();
30345
+ events.push({
30346
+ sessionKey,
30347
+ agentId: slot.agentId,
30348
+ agentTool: "claude_code",
30349
+ eventName: isUser ? "claude_code.user_prompt" : "claude_code.assistant_response",
30350
+ sequence,
30351
+ timestamp,
30352
+ promptText: isUser ? text.slice(0, 8e3) : null,
30353
+ responseText: isAssistant ? text.slice(0, 8e3) : null,
30354
+ rawTruncated: text.slice(0, 4e3),
30355
+ source: "harvest"
30356
+ });
30357
+ }
30358
+ return events;
30359
+ }
30360
+ function harvestClaudeUsage(slot) {
30361
+ const matches = findMatchingClaudeTranscripts(slot);
30362
+ const sessionKey = buildSessionKey({
30363
+ branch: slot.branch,
30364
+ agentId: slot.agentId,
30365
+ suffix: slot.suffix,
30366
+ createdAt: slot.sessionCreatedAt
30367
+ });
30368
+ let best = null;
30369
+ for (const match of matches) {
30370
+ const usage2 = extractClaudeUsageFromRecords(match.records);
30371
+ if (usage2.tokensInput + usage2.tokensOutput + usage2.tokensCacheRead === 0 && (usage2.costUsd == null || usage2.costUsd === 0)) {
30372
+ continue;
30373
+ }
30374
+ best = usage2;
30375
+ break;
30376
+ }
30156
30377
  if (!best) return null;
30157
30378
  const now = (/* @__PURE__ */ new Date()).toISOString();
30158
30379
  const tokensTotal = best.tokensInput + best.tokensOutput + best.tokensCacheRead + best.tokensCacheCreation;
@@ -30174,32 +30395,44 @@ function harvestClaudeUsage(slot) {
30174
30395
  lastSeenAt: now
30175
30396
  };
30176
30397
  }
30398
+ function harvestClaudeEvents(slot) {
30399
+ if (!getTelemetrySignals().prompts) return [];
30400
+ const matches = findMatchingClaudeTranscripts(slot);
30401
+ if (matches.length === 0) return [];
30402
+ const sessionKey = buildSessionKey({
30403
+ branch: slot.branch,
30404
+ agentId: slot.agentId,
30405
+ suffix: slot.suffix,
30406
+ createdAt: slot.sessionCreatedAt
30407
+ });
30408
+ return extractPromptEvents(matches[0].records, sessionKey, slot).slice(-40);
30409
+ }
30177
30410
 
30178
30411
  // src/core/usage-harvest/codex.ts
30179
- var fs36 = __toESM(require("fs"));
30412
+ var fs37 = __toESM(require("fs"));
30180
30413
  var os11 = __toESM(require("os"));
30181
- var path36 = __toESM(require("path"));
30414
+ var path37 = __toESM(require("path"));
30182
30415
  function pathsMatch2(candidate, targets) {
30183
- const norm = path36.resolve(candidate);
30416
+ const norm = path37.resolve(candidate);
30184
30417
  return targets.some((t2) => {
30185
- const target = path36.resolve(t2);
30186
- 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);
30187
30420
  });
30188
30421
  }
30189
30422
  function codexSessionsRoot() {
30190
- 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");
30191
30424
  }
30192
30425
  function walkJsonlFiles(dir, out = []) {
30193
- if (!fs36.existsSync(dir)) return out;
30194
- for (const entry of fs36.readdirSync(dir, { withFileTypes: true })) {
30195
- 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);
30196
30429
  if (entry.isDirectory()) walkJsonlFiles(full, out);
30197
30430
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
30198
30431
  }
30199
30432
  return out;
30200
30433
  }
30201
30434
  function readJsonl(filePath) {
30202
- const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
30435
+ const lines = fs37.readFileSync(filePath, "utf8").split(/\r?\n/);
30203
30436
  const out = [];
30204
30437
  for (const line of lines) {
30205
30438
  const trimmed = line.trim();
@@ -30263,7 +30496,7 @@ function harvestCodexUsage(slot) {
30263
30496
  const extracted = extractCodexTokens(records);
30264
30497
  if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
30265
30498
  if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
30266
- const mtime = fs36.statSync(file).mtimeMs;
30499
+ const mtime = fs37.statSync(file).mtimeMs;
30267
30500
  if (mtime >= bestMtime) {
30268
30501
  bestMtime = mtime;
30269
30502
  best = extracted;
@@ -30300,6 +30533,9 @@ function harvestUsageForSlot(slot) {
30300
30533
  if (codex) out.push(codex);
30301
30534
  return out;
30302
30535
  }
30536
+ function harvestEventsForSlot(slot) {
30537
+ return harvestClaudeEvents(slot);
30538
+ }
30303
30539
 
30304
30540
  // src/core/control-sync.ts
30305
30541
  async function postJson(url, body, dryRun) {
@@ -30342,7 +30578,7 @@ async function syncAllKnownReposWithControl(options) {
30342
30578
  }
30343
30579
  const repoPaths = new Set(listRegisteredRepos());
30344
30580
  if (options?.cwd) {
30345
- const cwd = path37.resolve(options.cwd);
30581
+ const cwd = path38.resolve(options.cwd);
30346
30582
  if (readManifest(cwd)) repoPaths.add(cwd);
30347
30583
  }
30348
30584
  try {
@@ -30350,7 +30586,7 @@ async function syncAllKnownReposWithControl(options) {
30350
30586
  if (listResponse.ok) {
30351
30587
  const repos = await listResponse.json();
30352
30588
  for (const repo of repos) {
30353
- const resolved = path37.resolve(repo.path);
30589
+ const resolved = path38.resolve(repo.path);
30354
30590
  if (readManifest(resolved)) repoPaths.add(resolved);
30355
30591
  }
30356
30592
  }
@@ -30369,7 +30605,7 @@ async function syncAllKnownReposWithControl(options) {
30369
30605
  return { synced, failed };
30370
30606
  }
30371
30607
  async function registerRepoWithControl(options) {
30372
- const repoPath = path37.resolve(options.repoPath);
30608
+ const repoPath = path38.resolve(options.repoPath);
30373
30609
  const apiUrl = options.apiUrl ?? getControlApiUrl();
30374
30610
  const manifest = readManifest(repoPath);
30375
30611
  const stagesRegistry = readStageRegistry(repoPath);
@@ -30382,7 +30618,7 @@ async function registerRepoWithControl(options) {
30382
30618
  return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
30383
30619
  }
30384
30620
  async function syncRepoWithControl(options) {
30385
- const repoPath = path37.resolve(options.repoPath);
30621
+ const repoPath = path38.resolve(options.repoPath);
30386
30622
  const apiUrl = options.apiUrl ?? getControlApiUrl();
30387
30623
  if (options.cloud) {
30388
30624
  const remote = createRemoteExecutor();
@@ -30410,7 +30646,7 @@ async function syncRepoWithControl(options) {
30410
30646
  const listResponse = await fetch(`${apiUrl}/api/repos`);
30411
30647
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
30412
30648
  const repos = await listResponse.json();
30413
- const existing = repos.find((r2) => path37.resolve(r2.path) === repoPath);
30649
+ const existing = repos.find((r2) => path38.resolve(r2.path) === repoPath);
30414
30650
  if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
30415
30651
  await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
30416
30652
  return;
@@ -30454,6 +30690,21 @@ async function syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun) {
30454
30690
  const usageBody = SyncUsageInputSchema.parse({ usage: usage2 });
30455
30691
  await postJson(`${apiUrl}/api/repos/${repoId}/usage`, usageBody, dryRun);
30456
30692
  }
30693
+ const events = status.slots.flatMap(
30694
+ (slot) => harvestEventsForSlot({
30695
+ agentId: slot.agentId,
30696
+ workDir: slot.workDir,
30697
+ worktreePath: slot.worktreePath,
30698
+ branch: slot.branch,
30699
+ suffix: slot.suffix,
30700
+ sessionCreatedAt: slot.sessionCreatedAt,
30701
+ repoPath
30702
+ })
30703
+ );
30704
+ if (events.length > 0) {
30705
+ const eventsBody = SyncSessionEventsInputSchema.parse({ events });
30706
+ await postJson(`${apiUrl}/api/repos/${repoId}/events`, eventsBody, dryRun);
30707
+ }
30457
30708
  } catch (err) {
30458
30709
  const message = err instanceof Error ? err.message : String(err);
30459
30710
  process.stderr.write(`[har control] usage harvest skipped: ${message}
@@ -30514,7 +30765,7 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
30514
30765
 
30515
30766
  // src/core/control-lifecycle.ts
30516
30767
  var import_child_process8 = require("child_process");
30517
- var path38 = __toESM(require("path"));
30768
+ var path39 = __toESM(require("path"));
30518
30769
 
30519
30770
  // src/core/control-image.ts
30520
30771
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -30537,10 +30788,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
30537
30788
  var CONTROL_CONTAINER_PORT = 3847;
30538
30789
  var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
30539
30790
  function resolveControlDir() {
30540
- return path38.resolve(__dirname, "..", "control");
30791
+ return path39.resolve(__dirname, "..", "control");
30541
30792
  }
30542
30793
  function resolveControlBuildContext() {
30543
- return path38.resolve(resolveControlDir(), "..");
30794
+ return path39.resolve(resolveControlDir(), "..");
30544
30795
  }
30545
30796
  function buildDockerRunArgs(options) {
30546
30797
  const name = options.containerName ?? CONTROL_CONTAINER_NAME;
@@ -30596,7 +30847,7 @@ async function startMissionControl(options) {
30596
30847
  "-t",
30597
30848
  imageRef,
30598
30849
  "-f",
30599
- path38.join(resolveControlDir(), "Dockerfile"),
30850
+ path39.join(resolveControlDir(), "Dockerfile"),
30600
30851
  resolveControlBuildContext()
30601
30852
  ]);
30602
30853
  if (buildCode !== 0) {
@@ -30738,7 +30989,7 @@ async function ensureTelemetryInfrastructure(options) {
30738
30989
  }
30739
30990
 
30740
30991
  // src/core/run-service.ts
30741
- var path39 = __toESM(require("path"));
30992
+ var path40 = __toESM(require("path"));
30742
30993
  function extractShellOutput(result) {
30743
30994
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
30744
30995
  const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
@@ -30902,14 +31153,14 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
30902
31153
  suffix: session.suffix,
30903
31154
  createdAt: session.createdAt
30904
31155
  });
30905
- const envFile = path39.join(session.workDir, `.env.agent.${options.agentId}`);
31156
+ const envFile = path40.join(session.workDir, `.env.agent.${options.agentId}`);
30906
31157
  try {
30907
31158
  appendTelemetryEnvToFile(
30908
31159
  envFile,
30909
31160
  {
30910
31161
  sessionKey,
30911
31162
  agentId: options.agentId,
30912
- repoPath: path39.resolve(options.repoPath),
31163
+ repoPath: path40.resolve(options.repoPath),
30913
31164
  workDir: session.workDir,
30914
31165
  branch: session.branch,
30915
31166
  suffix: session.suffix,
@@ -31275,7 +31526,7 @@ var envCommand = {
31275
31526
  }
31276
31527
  };
31277
31528
  async function handleInit(argv) {
31278
- const repoPath = path40.resolve(argv.repo);
31529
+ const repoPath = path41.resolve(argv.repo);
31279
31530
  header("har env init");
31280
31531
  info(`Repository: ${repoPath}`);
31281
31532
  try {
@@ -31333,7 +31584,7 @@ async function handleInit(argv) {
31333
31584
  }
31334
31585
  }
31335
31586
  async function handleMaintain(argv) {
31336
- const repoPath = path40.resolve(argv.repo);
31587
+ const repoPath = path41.resolve(argv.repo);
31337
31588
  header("har env maintain");
31338
31589
  info(`Repository: ${repoPath}`);
31339
31590
  try {
@@ -31431,7 +31682,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31431
31682
  if (autoYes) {
31432
31683
  const proposal = readAgentMdProposal(repoPath);
31433
31684
  if (proposal) {
31434
- writeFileSafe(path40.join(repoPath, "AGENT.md"), proposal.content);
31685
+ writeFileSafe(path41.join(repoPath, "AGENT.md"), proposal.content);
31435
31686
  clearAgentMdProposal(repoPath);
31436
31687
  info("Applied AGENT.md proposal (--yes)");
31437
31688
  }
@@ -31440,7 +31691,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31440
31691
  await promptApplyAgentMdProposal(repoPath);
31441
31692
  }
31442
31693
  async function handleAddStage(argv) {
31443
- const repoPath = path40.resolve(argv.repo);
31694
+ const repoPath = path41.resolve(argv.repo);
31444
31695
  const available = listStageTemplateIds();
31445
31696
  if (argv.list) {
31446
31697
  for (const id of available) {
@@ -31517,7 +31768,7 @@ async function handleAddStage(argv) {
31517
31768
  }
31518
31769
  }
31519
31770
  async function handlePreflight(argv) {
31520
- const repo = path40.resolve(argv.repo);
31771
+ const repo = path41.resolve(argv.repo);
31521
31772
  const agentId = validateAgentId(argv.id, repo);
31522
31773
  const result = await preflightEnvironment({
31523
31774
  repoPath: repo,
@@ -31535,7 +31786,7 @@ async function handlePreflight(argv) {
31535
31786
  process.exit(result.code);
31536
31787
  }
31537
31788
  async function handleLaunch(argv) {
31538
- const repo = path40.resolve(argv.repo);
31789
+ const repo = path41.resolve(argv.repo);
31539
31790
  const agentId = validateAgentId(argv.id, repo);
31540
31791
  let confirmReplace = argv.replace;
31541
31792
  let force = argv.force;
@@ -31619,7 +31870,7 @@ async function handleRecover(argv) {
31619
31870
  });
31620
31871
  }
31621
31872
  async function handleVerify(argv) {
31622
- const repo = path40.resolve(argv.repo);
31873
+ const repo = path41.resolve(argv.repo);
31623
31874
  const agentId = validateAgentId(argv.id, repo);
31624
31875
  const result = await runVerification({
31625
31876
  repoPath: repo,
@@ -31631,7 +31882,7 @@ async function handleVerify(argv) {
31631
31882
  process.exit(result.code);
31632
31883
  }
31633
31884
  async function handleTeardown(argv) {
31634
- const repo = path40.resolve(argv.repo);
31885
+ const repo = path41.resolve(argv.repo);
31635
31886
  const agentId = validateAgentId(argv.id, repo);
31636
31887
  const result = await teardownEnvironment({
31637
31888
  repoPath: repo,
@@ -31642,7 +31893,7 @@ async function handleTeardown(argv) {
31642
31893
  process.exit(result.code);
31643
31894
  }
31644
31895
  async function handleComplete(argv) {
31645
- const repo = path40.resolve(argv.repo);
31896
+ const repo = path41.resolve(argv.repo);
31646
31897
  const agentId = validateAgentId(argv.id, repo);
31647
31898
  const result = await completeEnvironment({
31648
31899
  repoPath: repo,
@@ -31663,7 +31914,7 @@ async function handleComplete(argv) {
31663
31914
  process.exit(result.code);
31664
31915
  }
31665
31916
  async function handleStatus(argv) {
31666
- const repoPath = path40.resolve(argv.repo);
31917
+ const repoPath = path41.resolve(argv.repo);
31667
31918
  if (argv.json) {
31668
31919
  const status = collectEnvironmentStatus(repoPath);
31669
31920
  const output = EnvironmentStatusSchema.parse(status);
@@ -31676,7 +31927,7 @@ async function handleStatus(argv) {
31676
31927
  });
31677
31928
  }
31678
31929
  async function handleRunsList(argv) {
31679
- const repoPath = path40.resolve(argv.repo);
31930
+ const repoPath = path41.resolve(argv.repo);
31680
31931
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
31681
31932
  if (argv.json) {
31682
31933
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -31693,7 +31944,7 @@ async function handleRunsList(argv) {
31693
31944
  }
31694
31945
  }
31695
31946
  async function handleRunsGet(argv) {
31696
- const run2 = getRun(path40.resolve(argv.repo), argv.runId);
31947
+ const run2 = getRun(path41.resolve(argv.repo), argv.runId);
31697
31948
  if (!run2) {
31698
31949
  error(`Run not found: ${argv.runId}`);
31699
31950
  process.exit(1);
@@ -31734,7 +31985,13 @@ function printDrift(drift) {
31734
31985
  );
31735
31986
  warn(" See maintain/templates/harness.env in the maintenance bundle.");
31736
31987
  }
31737
- 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) {
31738
31995
  success(" Harness matches bundled templates");
31739
31996
  }
31740
31997
  }
@@ -31766,7 +32023,7 @@ function printNextSteps(auto2) {
31766
32023
  }
31767
32024
 
31768
32025
  // src/cli/commands/control.ts
31769
- var path41 = __toESM(require("path"));
32026
+ var path42 = __toESM(require("path"));
31770
32027
  var controlCommand = {
31771
32028
  command: "control <subcommand>",
31772
32029
  describe: "Mission Control dashboard (local)",
@@ -31869,7 +32126,7 @@ async function handleDown() {
31869
32126
  process.exit(code);
31870
32127
  }
31871
32128
  async function handleRegister(argv) {
31872
- const repoPath = path41.resolve(argv.repo);
32129
+ const repoPath = path42.resolve(argv.repo);
31873
32130
  header("har control register");
31874
32131
  info(`Repository: ${repoPath}`);
31875
32132
  try {
@@ -31889,7 +32146,7 @@ async function handleRegister(argv) {
31889
32146
  }
31890
32147
  }
31891
32148
  async function handleSync(argv) {
31892
- const repoPath = path41.resolve(argv.repo);
32149
+ const repoPath = path42.resolve(argv.repo);
31893
32150
  try {
31894
32151
  await syncRepoWithControl({
31895
32152
  repoPath,
@@ -31928,7 +32185,7 @@ async function handleWatch(argv) {
31928
32185
  };
31929
32186
  const tick = async () => {
31930
32187
  if (argv.repo) {
31931
- await syncOne(path41.resolve(argv.repo));
32188
+ await syncOne(path42.resolve(argv.repo));
31932
32189
  return;
31933
32190
  }
31934
32191
  try {
@@ -31959,12 +32216,12 @@ async function handleLogin(argv) {
31959
32216
  }
31960
32217
 
31961
32218
  // src/cli/commands/hooks.ts
31962
- var path44 = __toESM(require("path"));
32219
+ var path45 = __toESM(require("path"));
31963
32220
 
31964
32221
  // src/core/hooks.ts
31965
- var fs37 = __toESM(require("fs"));
32222
+ var fs38 = __toESM(require("fs"));
31966
32223
  var os12 = __toESM(require("os"));
31967
- var path42 = __toESM(require("path"));
32224
+ var path43 = __toESM(require("path"));
31968
32225
  var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
31969
32226
  var MARKER_END = "# <<< har commit gate <<<";
31970
32227
  var PRE_COMMIT_BLOCK = [
@@ -31989,7 +32246,7 @@ function resolveCheckoutRoot(cwd) {
31989
32246
  function resolveHooksDir(checkoutDir) {
31990
32247
  const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
31991
32248
  if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
31992
- return path42.resolve(checkoutDir, hooksPath);
32249
+ return path43.resolve(checkoutDir, hooksPath);
31993
32250
  }
31994
32251
  function getConfiguredHooksPath(checkoutDir) {
31995
32252
  return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
@@ -31997,9 +32254,9 @@ function getConfiguredHooksPath(checkoutDir) {
31997
32254
  function defaultHarInvocation() {
31998
32255
  const entry = process.argv[1];
31999
32256
  if (entry && entry.endsWith(".js")) {
32000
- return `"${process.execPath}" "${path42.resolve(entry)}"`;
32257
+ return `"${process.execPath}" "${path43.resolve(entry)}"`;
32001
32258
  }
32002
- if (entry) return `"${path42.resolve(entry)}"`;
32259
+ if (entry) return `"${path43.resolve(entry)}"`;
32003
32260
  return "har";
32004
32261
  }
32005
32262
  function buildHookScript(harInvocation, subcommand, failOpenNotice) {
@@ -32020,40 +32277,40 @@ exit 0
32020
32277
  `;
32021
32278
  }
32022
32279
  function upsertMarkedBlock(filePath, block) {
32023
- if (!fs37.existsSync(filePath)) {
32024
- fs37.writeFileSync(filePath, `#!/bin/sh
32280
+ if (!fs38.existsSync(filePath)) {
32281
+ fs38.writeFileSync(filePath, `#!/bin/sh
32025
32282
  ${block}
32026
32283
  `, { mode: 493 });
32027
32284
  return "created";
32028
32285
  }
32029
- const content = fs37.readFileSync(filePath, "utf8");
32286
+ const content = fs38.readFileSync(filePath, "utf8");
32030
32287
  if (content.includes(MARKER_START)) {
32031
32288
  const pattern = new RegExp(
32032
32289
  `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
32033
32290
  );
32034
- fs37.writeFileSync(filePath, content.replace(pattern, block));
32035
- fs37.chmodSync(filePath, 493);
32291
+ fs38.writeFileSync(filePath, content.replace(pattern, block));
32292
+ fs38.chmodSync(filePath, 493);
32036
32293
  return "updated";
32037
32294
  }
32038
32295
  const suffix = content.endsWith("\n") ? "" : "\n";
32039
- fs37.writeFileSync(filePath, `${content}${suffix}
32296
+ fs38.writeFileSync(filePath, `${content}${suffix}
32040
32297
  ${block}
32041
32298
  `);
32042
- fs37.chmodSync(filePath, 493);
32299
+ fs38.chmodSync(filePath, 493);
32043
32300
  return "appended";
32044
32301
  }
32045
32302
  function removeMarkedBlock(filePath) {
32046
- if (!fs37.existsSync(filePath)) return false;
32047
- const content = fs37.readFileSync(filePath, "utf8");
32303
+ if (!fs38.existsSync(filePath)) return false;
32304
+ const content = fs38.readFileSync(filePath, "utf8");
32048
32305
  if (!content.includes(MARKER_START)) return false;
32049
32306
  const pattern = new RegExp(
32050
32307
  `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
32051
32308
  );
32052
32309
  const stripped = content.replace(pattern, "\n");
32053
32310
  if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
32054
- fs37.rmSync(filePath);
32311
+ fs38.rmSync(filePath);
32055
32312
  } else {
32056
- fs37.writeFileSync(filePath, stripped);
32313
+ fs38.writeFileSync(filePath, stripped);
32057
32314
  }
32058
32315
  return true;
32059
32316
  }
@@ -32075,20 +32332,20 @@ Or re-run with --force to write into that directory anyway.`
32075
32332
  );
32076
32333
  }
32077
32334
  const hooksDir = resolveHooksDir(checkout);
32078
- fs37.mkdirSync(hooksDir, { recursive: true });
32335
+ fs38.mkdirSync(hooksDir, { recursive: true });
32079
32336
  const invocation = options.harInvocation ?? defaultHarInvocation();
32080
- fs37.writeFileSync(
32081
- path42.join(hooksDir, "har-pre-commit"),
32337
+ fs38.writeFileSync(
32338
+ path43.join(hooksDir, "har-pre-commit"),
32082
32339
  buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
32083
32340
  { mode: 493 }
32084
32341
  );
32085
- fs37.writeFileSync(
32086
- path42.join(hooksDir, "har-post-commit"),
32342
+ fs38.writeFileSync(
32343
+ path43.join(hooksDir, "har-post-commit"),
32087
32344
  buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
32088
32345
  { mode: 493 }
32089
32346
  );
32090
- const preCommit = upsertMarkedBlock(path42.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32091
- 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);
32092
32349
  ensureValidationsIgnored(checkout);
32093
32350
  return { hooksDir, preCommit, postCommit };
32094
32351
  }
@@ -32097,12 +32354,12 @@ function uninstallHooks(repoPath) {
32097
32354
  if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
32098
32355
  const hooksDir = resolveHooksDir(checkout);
32099
32356
  let removed = false;
32100
- removed = removeMarkedBlock(path42.join(hooksDir, "pre-commit")) || removed;
32101
- 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;
32102
32359
  for (const file of ["har-pre-commit", "har-post-commit"]) {
32103
- const full = path42.join(hooksDir, file);
32104
- if (fs37.existsSync(full)) {
32105
- fs37.rmSync(full);
32360
+ const full = path43.join(hooksDir, file);
32361
+ if (fs38.existsSync(full)) {
32362
+ fs38.rmSync(full);
32106
32363
  removed = true;
32107
32364
  }
32108
32365
  }
@@ -32119,9 +32376,9 @@ function getCommitGateConfig(checkoutDir) {
32119
32376
  function isAgentWorktree(checkoutDir) {
32120
32377
  const branch = getCurrentBranch(checkoutDir);
32121
32378
  if (branch && /^har-agent-\d+$/.test(branch)) return true;
32122
- const worktreesRoot = path42.join(os12.homedir(), "worktrees");
32123
- const resolved = path42.resolve(checkoutDir);
32124
- 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));
32125
32382
  }
32126
32383
  function resolveEffectiveMode(checkoutDir, gate) {
32127
32384
  if (!gate.enabled) return "off";
@@ -32134,15 +32391,15 @@ function getHooksStatus(repoPath) {
32134
32391
  const hooksDir = resolveHooksDir(checkout);
32135
32392
  const gate = getCommitGateConfig(checkout);
32136
32393
  const hasBlock = (name) => {
32137
- const file = path42.join(hooksDir, name);
32138
- 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);
32139
32396
  };
32140
32397
  return {
32141
32398
  checkout,
32142
32399
  hooksDir,
32143
32400
  configuredHooksPath: getConfiguredHooksPath(checkout),
32144
- preCommitInstalled: hasBlock("pre-commit") && fs37.existsSync(path42.join(hooksDir, "har-pre-commit")),
32145
- 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")),
32146
32403
  gate,
32147
32404
  effectiveMode: resolveEffectiveMode(checkout, gate)
32148
32405
  };
@@ -32153,7 +32410,7 @@ function checkCommitGate(cwd) {
32153
32410
  }
32154
32411
  const checkout = resolveCheckoutRoot(cwd);
32155
32412
  if (!checkout) return { exitCode: 0, messages: [] };
32156
- if (!fs37.existsSync(path42.join(checkout, ".har", "stages.json"))) {
32413
+ if (!fs38.existsSync(path43.join(checkout, ".har", "stages.json"))) {
32157
32414
  return { exitCode: 0, messages: [] };
32158
32415
  }
32159
32416
  const gate = getCommitGateConfig(checkout);
@@ -32224,8 +32481,8 @@ async function recordCommitAssociation(cwd) {
32224
32481
  }
32225
32482
 
32226
32483
  // src/core/claude-hooks.ts
32227
- var fs38 = __toESM(require("fs"));
32228
- var path43 = __toESM(require("path"));
32484
+ var fs39 = __toESM(require("fs"));
32485
+ var path44 = __toESM(require("path"));
32229
32486
  var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
32230
32487
  var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
32231
32488
  var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
@@ -32236,8 +32493,8 @@ function isHarGuardEntry(entry) {
32236
32493
  );
32237
32494
  }
32238
32495
  function readSettings(settingsPath) {
32239
- if (!fs38.existsSync(settingsPath)) return {};
32240
- const raw = fs38.readFileSync(settingsPath, "utf8").trim();
32496
+ if (!fs39.existsSync(settingsPath)) return {};
32497
+ const raw = fs39.readFileSync(settingsPath, "utf8").trim();
32241
32498
  if (raw === "") return {};
32242
32499
  const parsed = JSON.parse(raw);
32243
32500
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -32246,19 +32503,19 @@ function readSettings(settingsPath) {
32246
32503
  return parsed;
32247
32504
  }
32248
32505
  function writeSettings(settingsPath, settings) {
32249
- fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
32250
- 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");
32251
32508
  }
32252
32509
  function installClaudeGuard(repoPath) {
32253
32510
  const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
32254
32511
  if (!templatePath) {
32255
32512
  throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
32256
32513
  }
32257
- const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32258
- fs38.mkdirSync(path43.dirname(guardScript), { recursive: true });
32259
- fs38.copyFileSync(templatePath, guardScript);
32260
- fs38.chmodSync(guardScript, 493);
32261
- 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);
32262
32519
  const settings = readSettings(settingsPath);
32263
32520
  const hooks = settings.hooks ?? {};
32264
32521
  const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
@@ -32272,15 +32529,15 @@ function installClaudeGuard(repoPath) {
32272
32529
  }
32273
32530
  function uninstallClaudeGuard(repoPath) {
32274
32531
  let removed = false;
32275
- const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32276
- if (fs38.existsSync(guardScript)) {
32277
- fs38.rmSync(guardScript);
32532
+ const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32533
+ if (fs39.existsSync(guardScript)) {
32534
+ fs39.rmSync(guardScript);
32278
32535
  removed = true;
32279
- const parent = path43.dirname(guardScript);
32280
- 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);
32281
32538
  }
32282
- const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32283
- if (fs38.existsSync(settingsPath)) {
32539
+ const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32540
+ if (fs39.existsSync(settingsPath)) {
32284
32541
  const settings = readSettings(settingsPath);
32285
32542
  const preToolUse = settings.hooks?.PreToolUse;
32286
32543
  if (preToolUse) {
@@ -32305,8 +32562,8 @@ function uninstallClaudeGuard(repoPath) {
32305
32562
  return { removed };
32306
32563
  }
32307
32564
  function claudeGuardInstalled(repoPath) {
32308
- const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32309
- if (!fs38.existsSync(settingsPath)) return false;
32565
+ const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32566
+ if (!fs39.existsSync(settingsPath)) return false;
32310
32567
  try {
32311
32568
  const settings = readSettings(settingsPath);
32312
32569
  return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
@@ -32322,7 +32579,7 @@ function repoOption(y2) {
32322
32579
  function handleInstall2(argv) {
32323
32580
  try {
32324
32581
  if (argv.claude) {
32325
- const result2 = installClaudeGuard(path44.resolve(argv.repo));
32582
+ const result2 = installClaudeGuard(path45.resolve(argv.repo));
32326
32583
  success("Claude Code worktree guard installed.");
32327
32584
  info(`Guard script: ${result2.guardScript}`);
32328
32585
  info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
@@ -32330,7 +32587,7 @@ function handleInstall2(argv) {
32330
32587
  info("Commit both files so the guard travels with the repo.");
32331
32588
  return;
32332
32589
  }
32333
- const result = installHooks({ repoPath: path44.resolve(argv.repo), force: argv.force });
32590
+ const result = installHooks({ repoPath: path45.resolve(argv.repo), force: argv.force });
32334
32591
  success(`Commit gate installed in ${result.hooksDir}`);
32335
32592
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
32336
32593
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -32343,7 +32600,7 @@ function handleInstall2(argv) {
32343
32600
  function handleUninstall(argv) {
32344
32601
  try {
32345
32602
  if (argv.claude) {
32346
- const result2 = uninstallClaudeGuard(path44.resolve(argv.repo));
32603
+ const result2 = uninstallClaudeGuard(path45.resolve(argv.repo));
32347
32604
  if (result2.removed) {
32348
32605
  success("Claude Code worktree guard removed.");
32349
32606
  } else {
@@ -32351,7 +32608,7 @@ function handleUninstall(argv) {
32351
32608
  }
32352
32609
  return;
32353
32610
  }
32354
- const result = uninstallHooks(path44.resolve(argv.repo));
32611
+ const result = uninstallHooks(path45.resolve(argv.repo));
32355
32612
  if (result.removed) {
32356
32613
  success(`Commit gate removed from ${result.hooksDir}`);
32357
32614
  } else {
@@ -32364,7 +32621,7 @@ function handleUninstall(argv) {
32364
32621
  }
32365
32622
  function handleStatus2(argv) {
32366
32623
  try {
32367
- const status = getHooksStatus(path44.resolve(argv.repo));
32624
+ const status = getHooksStatus(path45.resolve(argv.repo));
32368
32625
  if (argv.json) {
32369
32626
  process.stdout.write(`${JSON.stringify(status, null, 2)}
32370
32627
  `);
@@ -32382,7 +32639,7 @@ function handleStatus2(argv) {
32382
32639
  );
32383
32640
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
32384
32641
  info(
32385
- `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"}`
32386
32643
  );
32387
32644
  } catch (err) {
32388
32645
  error(err instanceof Error ? err.message : String(err));
@@ -32643,10 +32900,10 @@ function assignProp(target, prop, value) {
32643
32900
  configurable: true
32644
32901
  });
32645
32902
  }
32646
- function getElementAtPath(obj, path47) {
32647
- if (!path47)
32903
+ function getElementAtPath(obj, path48) {
32904
+ if (!path48)
32648
32905
  return obj;
32649
- return path47.reduce((acc, key) => acc?.[key], obj);
32906
+ return path48.reduce((acc, key) => acc?.[key], obj);
32650
32907
  }
32651
32908
  function promiseAllObject(promisesObj) {
32652
32909
  const keys = Object.keys(promisesObj);
@@ -32966,11 +33223,11 @@ function aborted(x2, startIndex = 0) {
32966
33223
  }
32967
33224
  return false;
32968
33225
  }
32969
- function prefixIssues(path47, issues) {
33226
+ function prefixIssues(path48, issues) {
32970
33227
  return issues.map((iss) => {
32971
33228
  var _a3;
32972
33229
  (_a3 = iss).path ?? (_a3.path = []);
32973
- iss.path.unshift(path47);
33230
+ iss.path.unshift(path48);
32974
33231
  return iss;
32975
33232
  });
32976
33233
  }
@@ -39444,7 +39701,7 @@ var Server = class extends Protocol {
39444
39701
  };
39445
39702
 
39446
39703
  // src/mcp/server.ts
39447
- var path45 = __toESM(require("path"));
39704
+ var path46 = __toESM(require("path"));
39448
39705
 
39449
39706
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
39450
39707
  var import_node_process2 = __toESM(require("node:process"), 1);
@@ -40076,7 +40333,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
40076
40333
  const input = ControlUpInputSchema.parse({ ...args, repo });
40077
40334
  const result = await startControlAndSync({
40078
40335
  detach: input.detach,
40079
- cwd: path45.resolve(input.repo)
40336
+ cwd: path46.resolve(input.repo)
40080
40337
  });
40081
40338
  if (result.code !== 0) {
40082
40339
  return {
@@ -40139,8 +40396,8 @@ var mcpCommand = {
40139
40396
  };
40140
40397
 
40141
40398
  // src/cli/commands/telemetry.ts
40142
- var fs39 = __toESM(require("fs"));
40143
- var path46 = __toESM(require("path"));
40399
+ var fs40 = __toESM(require("fs"));
40400
+ var path47 = __toESM(require("path"));
40144
40401
  function printSignals() {
40145
40402
  info("Collected when enabled (local Mission Control only):");
40146
40403
  for (const signal of TELEMETRY_SIGNALS) {
@@ -40151,18 +40408,20 @@ function printSignals() {
40151
40408
  async function handleStatus3(argv) {
40152
40409
  const preference = readTelemetryPreference();
40153
40410
  const enabled = isTelemetryEnabled();
40411
+ const signals = getTelemetrySignals();
40154
40412
  const apiUrl = getControlApiUrl();
40155
40413
  const reachable = enabled ? await isControlApiReachable(apiUrl) : false;
40156
40414
  const payload = {
40157
40415
  enabled,
40158
40416
  preferenceEnabled: preference.enabled,
40417
+ signals,
40159
40418
  preferencePath: getTelemetryPreferencePath(),
40160
40419
  updatedAt: preference.updatedAt ?? null,
40161
40420
  envOverride: process.env.HAR_TELEMETRY ?? null,
40162
40421
  controlApiUrl: apiUrl,
40163
40422
  controlReachable: reachable,
40164
40423
  otelEndpoint: `${apiUrl.replace(/\/$/, "")}/api/otel`,
40165
- signals: [...TELEMETRY_SIGNALS]
40424
+ signalDescriptions: [...TELEMETRY_SIGNALS]
40166
40425
  };
40167
40426
  if (argv.json) {
40168
40427
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
@@ -40172,21 +40431,33 @@ async function handleStatus3(argv) {
40172
40431
  header("har telemetry status");
40173
40432
  info(`Enabled: ${enabled ? "yes" : "no"}${process.env.HAR_TELEMETRY ? " (HAR_TELEMETRY override)" : ""}`);
40174
40433
  info(`Preference: ${getTelemetryPreferencePath()}${preference.updatedAt ? ` (updated ${preference.updatedAt})` : " (default on)"}`);
40434
+ info(
40435
+ `Signals: metrics=${signals.metrics} logs=${signals.logs} prompts=${signals.prompts} traces=${signals.traces}`
40436
+ );
40175
40437
  info(`Mission Control: ${apiUrl} (${reachable ? "reachable" : "not reachable"})`);
40176
40438
  info(`OTLP endpoint: ${payload.otelEndpoint}`);
40177
40439
  printSignals();
40178
40440
  }
40179
- async function handleOn() {
40441
+ async function handleOn(argv) {
40180
40442
  header("har telemetry on");
40181
- writeTelemetryPreference(true);
40443
+ const preference = writeTelemetryPreference(true, {
40444
+ prompts: argv.prompts === true ? true : void 0,
40445
+ traces: argv.traces === true ? true : void 0
40446
+ });
40182
40447
  success("Telemetry enabled (opt-out). Agent usage will be stored in local Mission Control.");
40448
+ info(
40449
+ `Signals: metrics=${preference.signals.metrics} logs=${preference.signals.logs} prompts=${preference.signals.prompts} traces=${preference.signals.traces}`
40450
+ );
40451
+ if (preference.signals.prompts) {
40452
+ warn("Prompt/response text will leave the agent machine for local Mission Control storage.");
40453
+ }
40183
40454
  printSignals();
40184
40455
  const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
40185
40456
  if (result.message) success(result.message);
40186
40457
  if (result.warning) warn(result.warning);
40187
40458
  if (result.otelReady) {
40188
40459
  info(`OTLP ingest: ${result.apiUrl.replace(/\/$/, "")}/api/otel`);
40189
- info("Usage appears under Mission Control \u2192 Worktrees. Disable: har telemetry off");
40460
+ info("Usage appears under Mission Control \u2192 Worktrees / Usage. Disable: har telemetry off");
40190
40461
  }
40191
40462
  }
40192
40463
  async function handleOff() {
@@ -40197,7 +40468,7 @@ async function handleOff() {
40197
40468
  info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
40198
40469
  }
40199
40470
  async function handleWriteEnv(argv) {
40200
- const repoPath = path46.resolve(argv.repo);
40471
+ const repoPath = path47.resolve(argv.repo);
40201
40472
  const harnessRoot = resolveHarnessRoot(repoPath);
40202
40473
  const session = readSlotRegistry(harnessRoot, argv.agentId);
40203
40474
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
@@ -40210,7 +40481,7 @@ async function handleWriteEnv(argv) {
40210
40481
  suffix,
40211
40482
  createdAt: session?.createdAt
40212
40483
  });
40213
- const envFile = argv.envFile ?? path46.join(workDir, `.env.agent.${argv.agentId}`);
40484
+ const envFile = argv.envFile ?? path47.join(workDir, `.env.agent.${argv.agentId}`);
40214
40485
  let otelReady = argv.otelReady;
40215
40486
  if (otelReady === void 0 && isTelemetryEnabled()) {
40216
40487
  const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
@@ -40229,7 +40500,7 @@ async function handleWriteEnv(argv) {
40229
40500
  success(`Wrote telemetry env to ${envFile}`);
40230
40501
  }
40231
40502
  function handlePrintEnv(argv) {
40232
- const repoPath = path46.resolve(argv.repo);
40503
+ const repoPath = path47.resolve(argv.repo);
40233
40504
  const workDir = argv.workDir ?? repoPath;
40234
40505
  const sessionKey = buildSessionKey({
40235
40506
  branch: argv.branch,
@@ -40251,7 +40522,7 @@ function handlePrintEnv(argv) {
40251
40522
  );
40252
40523
  }
40253
40524
  function handleCodexSnippet(argv) {
40254
- const repoPath = path46.resolve(argv.repo);
40525
+ const repoPath = path47.resolve(argv.repo);
40255
40526
  const harnessRoot = resolveHarnessRoot(repoPath);
40256
40527
  const session = readSlotRegistry(harnessRoot, argv.agentId);
40257
40528
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
@@ -40273,10 +40544,10 @@ function handleCodexSnippet(argv) {
40273
40544
  purpose: session?.purpose
40274
40545
  });
40275
40546
  if (argv.write) {
40276
- const outDir = path46.join(harnessRoot, ".har", "telemetry");
40277
- fs39.mkdirSync(outDir, { recursive: true });
40278
- const outPath = path46.join(outDir, "codex.otel.toml.snippet");
40279
- 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);
40280
40551
  success(`Wrote ${outPath}`);
40281
40552
  info("Merge the [otel] table into ~/.codex/config.toml manually.");
40282
40553
  return;
@@ -40296,10 +40567,20 @@ var telemetryCommand = {
40296
40567
  ).command(
40297
40568
  "on",
40298
40569
  "Enable telemetry (default) and ensure Mission Control is running",
40299
- () => {
40300
- },
40301
- () => {
40302
- void handleOn();
40570
+ (y2) => y2.option("prompts", {
40571
+ type: "boolean",
40572
+ default: false,
40573
+ describe: "Opt in to shipping user/assistant prompt text to Mission Control"
40574
+ }).option("traces", {
40575
+ type: "boolean",
40576
+ default: false,
40577
+ describe: "Opt in to thin OTEL traces ingest (Claude beta)"
40578
+ }),
40579
+ (argv) => {
40580
+ void handleOn({
40581
+ prompts: argv.prompts,
40582
+ traces: argv.traces
40583
+ });
40303
40584
  }
40304
40585
  ).command(
40305
40586
  "off",