@osfactory/har 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +43 -2
  2. package/dist/index.js +1240 -586
  3. package/dist/templates/agent-skills/har-maintain.md +36 -0
  4. package/dist/templates/agent-skills/har-wt.md +47 -0
  5. package/dist/templates/agent-skills/setup-har.md +70 -0
  6. package/dist/templates/agent-skills/skills.manifest.json +48 -0
  7. package/dist/templates/claude-worktree-guard.sh.template +38 -0
  8. package/dist/templates/har-boilerplate/CLAUDE.agent.md +4 -2
  9. package/dist/templates/har-boilerplate/STAGES.md +91 -0
  10. package/dist/templates/har-boilerplate/agent-slot.sh +35 -6
  11. package/dist/templates/har-boilerplate/stages/README.sh +7 -5
  12. package/dist/templates/har-boilerplate/verify.sh +9 -2
  13. package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +3 -1
  14. package/dist/templates/har-boilerplate-cli/STAGES.md +91 -0
  15. package/dist/templates/har-boilerplate-cli/agent-slot.sh +35 -6
  16. package/dist/templates/har-boilerplate-cli/stages/README.sh +7 -5
  17. package/dist/templates/har-boilerplate-cli/verify.sh +9 -2
  18. package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +2 -0
  19. package/dist/templates/har-boilerplate-ios/STAGES.md +91 -0
  20. package/dist/templates/har-boilerplate-ios/agent-slot.sh +35 -6
  21. package/dist/templates/har-boilerplate-ios/stages/README.sh +6 -7
  22. package/dist/templates/har-boilerplate-ios/verify.sh +9 -3
  23. package/dist/templates/stage-templates/custom-stage-skeleton.sh +65 -0
  24. package/dist/templates/stage-templates/playwright/template.manifest.json +9 -1
  25. package/dist/templates/stage-templates/rocketsim/template.manifest.json +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 path37 = url.path;
769
- if (path37.length === 0) {
768
+ const path41 = url.path;
769
+ if (path41.length === 0) {
770
770
  return;
771
771
  }
772
- if (url.scheme === "file" && path37.length === 1 && isNormalizedWindowsDriveLetter(path37[0])) {
772
+ if (url.scheme === "file" && path41.length === 1 && isNormalizedWindowsDriveLetter(path41[0])) {
773
773
  return;
774
774
  }
775
- path37.pop();
775
+ path41.pop();
776
776
  }
777
777
  function includesCredentials(url) {
778
778
  return url.username !== "" || url.password !== "";
@@ -2092,7 +2092,7 @@ var require_lib2 = __commonJS({
2092
2092
  let accum = [];
2093
2093
  let accumBytes = 0;
2094
2094
  let abort = false;
2095
- return new Body.Promise(function(resolve26, reject) {
2095
+ return new Body.Promise(function(resolve28, reject) {
2096
2096
  let resTimeout;
2097
2097
  if (_this4.timeout) {
2098
2098
  resTimeout = setTimeout(function() {
@@ -2126,7 +2126,7 @@ var require_lib2 = __commonJS({
2126
2126
  }
2127
2127
  clearTimeout(resTimeout);
2128
2128
  try {
2129
- resolve26(Buffer.concat(accum, accumBytes));
2129
+ resolve28(Buffer.concat(accum, accumBytes));
2130
2130
  } catch (err) {
2131
2131
  reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
2132
2132
  }
@@ -2801,7 +2801,7 @@ var require_lib2 = __commonJS({
2801
2801
  throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
2802
2802
  }
2803
2803
  Body.Promise = fetch3.Promise;
2804
- return new fetch3.Promise(function(resolve26, reject) {
2804
+ return new fetch3.Promise(function(resolve28, reject) {
2805
2805
  const request = new Request3(url, opts);
2806
2806
  const options = getNodeRequestOptions(request);
2807
2807
  const send = (options.protocol === "https:" ? https : http).request;
@@ -2934,7 +2934,7 @@ var require_lib2 = __commonJS({
2934
2934
  requestOpts.body = void 0;
2935
2935
  requestOpts.headers.delete("content-length");
2936
2936
  }
2937
- resolve26(fetch3(new Request3(locationURL, requestOpts)));
2937
+ resolve28(fetch3(new Request3(locationURL, requestOpts)));
2938
2938
  finalize();
2939
2939
  return;
2940
2940
  }
@@ -2955,7 +2955,7 @@ var require_lib2 = __commonJS({
2955
2955
  const codings = headers.get("Content-Encoding");
2956
2956
  if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
2957
2957
  response = new Response3(body, response_options);
2958
- resolve26(response);
2958
+ resolve28(response);
2959
2959
  return;
2960
2960
  }
2961
2961
  const zlibOptions = {
@@ -2965,7 +2965,7 @@ var require_lib2 = __commonJS({
2965
2965
  if (codings == "gzip" || codings == "x-gzip") {
2966
2966
  body = body.pipe(zlib.createGunzip(zlibOptions));
2967
2967
  response = new Response3(body, response_options);
2968
- resolve26(response);
2968
+ resolve28(response);
2969
2969
  return;
2970
2970
  }
2971
2971
  if (codings == "deflate" || codings == "x-deflate") {
@@ -2977,12 +2977,12 @@ var require_lib2 = __commonJS({
2977
2977
  body = body.pipe(zlib.createInflateRaw());
2978
2978
  }
2979
2979
  response = new Response3(body, response_options);
2980
- resolve26(response);
2980
+ resolve28(response);
2981
2981
  });
2982
2982
  raw.on("end", function() {
2983
2983
  if (!response) {
2984
2984
  response = new Response3(body, response_options);
2985
- resolve26(response);
2985
+ resolve28(response);
2986
2986
  }
2987
2987
  });
2988
2988
  return;
@@ -2990,11 +2990,11 @@ var require_lib2 = __commonJS({
2990
2990
  if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
2991
2991
  body = body.pipe(zlib.createBrotliDecompress());
2992
2992
  response = new Response3(body, response_options);
2993
- resolve26(response);
2993
+ resolve28(response);
2994
2994
  return;
2995
2995
  }
2996
2996
  response = new Response3(body, response_options);
2997
- resolve26(response);
2997
+ resolve28(response);
2998
2998
  });
2999
2999
  writeToStream(req, request);
3000
3000
  });
@@ -6351,14 +6351,14 @@ __export(fileFromPath_exports, {
6351
6351
  fileFromPathSync: () => fileFromPathSync,
6352
6352
  isFile: () => isFile
6353
6353
  });
6354
- function createFileFromPath(path37, { mtimeMs, size }, filenameOrOptions, options = {}) {
6354
+ function createFileFromPath(path41, { 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: path37, size, lastModified: mtimeMs });
6361
+ const file = new FileFromPath({ path: path41, size, lastModified: mtimeMs });
6362
6362
  if (!filename) {
6363
6363
  filename = file.name;
6364
6364
  }
@@ -6367,13 +6367,13 @@ function createFileFromPath(path37, { mtimeMs, size }, filenameOrOptions, option
6367
6367
  lastModified: file.lastModified
6368
6368
  });
6369
6369
  }
6370
- function fileFromPathSync(path37, filenameOrOptions, options = {}) {
6371
- const stats = (0, import_fs5.statSync)(path37);
6372
- return createFileFromPath(path37, stats, filenameOrOptions, options);
6370
+ function fileFromPathSync(path41, filenameOrOptions, options = {}) {
6371
+ const stats = (0, import_fs5.statSync)(path41);
6372
+ return createFileFromPath(path41, stats, filenameOrOptions, options);
6373
6373
  }
6374
- async function fileFromPath2(path37, filenameOrOptions, options) {
6375
- const stats = await import_fs5.promises.stat(path37);
6376
- return createFileFromPath(path37, stats, filenameOrOptions, options);
6374
+ async function fileFromPath2(path41, filenameOrOptions, options) {
6375
+ const stats = await import_fs5.promises.stat(path41);
6376
+ return createFileFromPath(path41, 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({
@@ -9384,7 +9384,7 @@ var require_compile = __commonJS({
9384
9384
  const schOrFunc = root.refs[ref];
9385
9385
  if (schOrFunc)
9386
9386
  return schOrFunc;
9387
- let _sch = resolve26.call(this, root, ref);
9387
+ let _sch = resolve28.call(this, root, ref);
9388
9388
  if (_sch === void 0) {
9389
9389
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
9390
9390
  const { schemaId } = this.opts;
@@ -9411,7 +9411,7 @@ var require_compile = __commonJS({
9411
9411
  function sameSchemaEnv(s1, s2) {
9412
9412
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
9413
9413
  }
9414
- function resolve26(root, ref) {
9414
+ function resolve28(root, ref) {
9415
9415
  let sch;
9416
9416
  while (typeof (sch = this.refs[ref]) == "string")
9417
9417
  ref = sch;
@@ -9629,8 +9629,8 @@ var require_utils2 = __commonJS({
9629
9629
  }
9630
9630
  return ind;
9631
9631
  }
9632
- function removeDotSegments(path37) {
9633
- let input = path37;
9632
+ function removeDotSegments(path41) {
9633
+ let input = path41;
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 [path37, query] = wsComponent.resourceName.split("?");
9886
- wsComponent.path = path37 && path37 !== "/" ? path37 : void 0;
9885
+ const [path41, query] = wsComponent.resourceName.split("?");
9886
+ wsComponent.path = path41 && path41 !== "/" ? path41 : void 0;
9887
9887
  wsComponent.query = query;
9888
9888
  wsComponent.resourceName = void 0;
9889
9889
  }
@@ -10042,7 +10042,7 @@ var require_fast_uri = __commonJS({
10042
10042
  }
10043
10043
  return uri;
10044
10044
  }
10045
- function resolve26(baseURI, relativeURI, options) {
10045
+ function resolve28(baseURI, relativeURI, options) {
10046
10046
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
10047
10047
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
10048
10048
  schemelessOptions.skipEscape = true;
@@ -10300,7 +10300,7 @@ var require_fast_uri = __commonJS({
10300
10300
  var fastUri = {
10301
10301
  SCHEMES,
10302
10302
  normalize: normalize3,
10303
- resolve: resolve26,
10303
+ resolve: resolve28,
10304
10304
  resolveComponent,
10305
10305
  equal,
10306
10306
  serialize,
@@ -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, fs32, exportName) {
13279
+ function addFormats(ajv, list, fs35, 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, fs32[f2]);
13284
+ ajv.addFormat(f2, fs35[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: (path37) => {
14568
+ require: (path41) => {
14569
14569
  if (typeof require !== "undefined") {
14570
- return require(path37);
14571
- } else if (path37.match(/\.json$/)) {
14572
- return JSON.parse((0, import_fs2.readFileSync)(path37, "utf8"));
14570
+ return require(path41);
14571
+ } else if (path41.match(/\.json$/)) {
14572
+ return JSON.parse((0, import_fs2.readFileSync)(path41, "utf8"));
14573
14573
  } else {
14574
14574
  throw Error("only .json config files are supported in ESM");
14575
14575
  }
@@ -17095,12 +17095,12 @@ var YargsInstance = class {
17095
17095
  async getCompletion(args, done) {
17096
17096
  argsert("<array> [function]", [args, done], arguments.length);
17097
17097
  if (!done) {
17098
- return new Promise((resolve26, reject) => {
17098
+ return new Promise((resolve28, reject) => {
17099
17099
  __classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
17100
17100
  if (err)
17101
17101
  reject(err);
17102
17102
  else
17103
- resolve26(completions);
17103
+ resolve28(completions);
17104
17104
  });
17105
17105
  });
17106
17106
  } else {
@@ -18158,17 +18158,15 @@ function getHarPackageVersion() {
18158
18158
  throw new Error("Could not resolve @osfactory/har package.json for version lookup");
18159
18159
  }
18160
18160
 
18161
- // src/cli/commands/env.ts
18162
- var path31 = __toESM(require("path"));
18163
-
18164
- // src/core/harness.ts
18165
- var fs18 = __toESM(require("fs"));
18166
- var path16 = __toESM(require("path"));
18167
-
18168
- // src/harness/generator.ts
18169
- var fs6 = __toESM(require("fs"));
18161
+ // src/cli/commands/agents.ts
18170
18162
  var path6 = __toESM(require("path"));
18171
18163
 
18164
+ // src/harness/agent-skills.ts
18165
+ var fs5 = __toESM(require("fs"));
18166
+ var os2 = __toESM(require("os"));
18167
+ var path5 = __toESM(require("path"));
18168
+ var readline = __toESM(require("readline"));
18169
+
18172
18170
  // src/utils/file-ops.ts
18173
18171
  var fs2 = __toESM(require("fs"));
18174
18172
  var path2 = __toESM(require("path"));
@@ -18769,31 +18767,10 @@ function resolveTemplateFile(name) {
18769
18767
  return fs3.existsSync(filePath) ? filePath : null;
18770
18768
  }
18771
18769
 
18772
- // src/core/gitignore.ts
18773
- var fs4 = __toESM(require("fs"));
18774
- var path4 = __toESM(require("path"));
18775
- var HARNESS_ROOT_GITIGNORE_PATTERNS = [
18776
- ".env.agent.*",
18777
- "ecosystem.agent.*.config.cjs"
18778
- ];
18779
- function ensureRootGitignorePatterns(checkoutDir) {
18780
- const gitignorePath = path4.join(checkoutDir, ".gitignore");
18781
- const content = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
18782
- const lines = content.split("\n").map((line) => line.trim());
18783
- const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
18784
- if (missing.length === 0) return;
18785
- const header2 = lines.length === 0 || content.trim().length === 0 ? "# HAR agent slot artifacts (generated by .har/launch.sh)\n" : "";
18786
- const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
18787
- const block = lines.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
18788
- ` : `${header2}${missing.join("\n")}
18789
- `;
18790
- fs4.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
18791
- }
18792
-
18793
18770
  // src/harness/manifest.ts
18794
18771
  var crypto = __toESM(require("crypto"));
18795
- var fs5 = __toESM(require("fs"));
18796
- var path5 = __toESM(require("path"));
18772
+ var fs4 = __toESM(require("fs"));
18773
+ var path4 = __toESM(require("path"));
18797
18774
 
18798
18775
  // node_modules/zod/v3/external.js
18799
18776
  var external_exports = {};
@@ -19273,8 +19250,8 @@ function getErrorMap() {
19273
19250
 
19274
19251
  // node_modules/zod/v3/helpers/parseUtil.js
19275
19252
  var makeIssue = (params) => {
19276
- const { data, path: path37, errorMaps, issueData } = params;
19277
- const fullPath = [...path37, ...issueData.path || []];
19253
+ const { data, path: path41, errorMaps, issueData } = params;
19254
+ const fullPath = [...path41, ...issueData.path || []];
19278
19255
  const fullIssue = {
19279
19256
  ...issueData,
19280
19257
  path: fullPath
@@ -19390,11 +19367,11 @@ var errorUtil;
19390
19367
 
19391
19368
  // node_modules/zod/v3/types.js
19392
19369
  var ParseInputLazyPath = class {
19393
- constructor(parent, value, path37, key) {
19370
+ constructor(parent, value, path41, key) {
19394
19371
  this._cachedPath = [];
19395
19372
  this.parent = parent;
19396
19373
  this.data = value;
19397
- this._path = path37;
19374
+ this._path = path41;
19398
19375
  this._key = key;
19399
19376
  }
19400
19377
  get path() {
@@ -22848,6 +22825,15 @@ var HAR_STAGE_KINDS = [
22848
22825
  "custom"
22849
22826
  ];
22850
22827
  var HAR_AGENT_SLOT_MIN = 1;
22828
+ var AgentSkillTargetSchema = external_exports.enum(["claude", "cursor", "codex"]);
22829
+ var ScaffoldedAgentFileSchema = external_exports.object({
22830
+ /** Repo-relative path, or '~/…' when scope is 'global'. */
22831
+ path: external_exports.string(),
22832
+ agent: AgentSkillTargetSchema,
22833
+ skill: external_exports.string(),
22834
+ checksum: external_exports.string(),
22835
+ scope: external_exports.enum(["repo", "global"]).default("repo")
22836
+ });
22851
22837
  var HarnessManifestSchema = external_exports.object({
22852
22838
  version: external_exports.string(),
22853
22839
  generatorVersion: external_exports.string(),
@@ -22861,7 +22847,8 @@ var HarnessManifestSchema = external_exports.object({
22861
22847
  }).optional(),
22862
22848
  adaptationSummary: external_exports.string().optional(),
22863
22849
  profile: external_exports.enum(["default", "cli", "ios"]).optional(),
22864
- fileChecksums: external_exports.record(external_exports.string()).optional()
22850
+ fileChecksums: external_exports.record(external_exports.string()).optional(),
22851
+ scaffoldedAgentFiles: external_exports.array(ScaffoldedAgentFileSchema).optional()
22865
22852
  });
22866
22853
  var HarnessStageKindSchema = external_exports.enum(HAR_STAGE_KINDS);
22867
22854
  var HarnessArtifactKindSchema = external_exports.enum([
@@ -23131,15 +23118,15 @@ var CHECKSUM_SKIP = /* @__PURE__ */ new Set([
23131
23118
  "ADAPT-PROMPT.md"
23132
23119
  ]);
23133
23120
  function getHarnessDir(repoPath) {
23134
- return path5.join(repoPath, DEFAULT_HAR_DIR);
23121
+ return path4.join(repoPath, DEFAULT_HAR_DIR);
23135
23122
  }
23136
23123
  function getManifestPath(repoPath) {
23137
- return path5.join(getHarnessDir(repoPath), "manifest.json");
23124
+ return path4.join(getHarnessDir(repoPath), "manifest.json");
23138
23125
  }
23139
23126
  function readManifest(repoPath) {
23140
23127
  const manifestPath = getManifestPath(repoPath);
23141
- if (!fs5.existsSync(manifestPath)) return null;
23142
- const raw = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
23128
+ if (!fs4.existsSync(manifestPath)) return null;
23129
+ const raw = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
23143
23130
  const result = HarnessManifestSchema.safeParse(raw);
23144
23131
  return result.success ? result.data : null;
23145
23132
  }
@@ -23152,11 +23139,11 @@ function computeFileChecksum(content) {
23152
23139
  }
23153
23140
  function computeHarnessChecksums(harnessDir) {
23154
23141
  const checksums = {};
23155
- if (!fs5.existsSync(harnessDir)) return checksums;
23156
- for (const entry of fs5.readdirSync(harnessDir, { withFileTypes: true })) {
23142
+ if (!fs4.existsSync(harnessDir)) return checksums;
23143
+ for (const entry of fs4.readdirSync(harnessDir, { withFileTypes: true })) {
23157
23144
  if (!entry.isFile() || CHECKSUM_SKIP.has(entry.name)) continue;
23158
- const full = path5.join(harnessDir, entry.name);
23159
- checksums[entry.name] = computeFileChecksum(fs5.readFileSync(full, "utf8"));
23145
+ const full = path4.join(harnessDir, entry.name);
23146
+ checksums[entry.name] = computeFileChecksum(fs4.readFileSync(full, "utf8"));
23160
23147
  }
23161
23148
  return checksums;
23162
23149
  }
@@ -23176,17 +23163,331 @@ function createManifest(repoPath, adaptationSummary, stack, profile) {
23176
23163
  };
23177
23164
  }
23178
23165
  function resolveHarnessRoot(inputPath) {
23179
- let current = path5.resolve(inputPath);
23180
- const { root } = path5.parse(current);
23166
+ let current = path4.resolve(inputPath);
23167
+ const { root } = path4.parse(current);
23181
23168
  for (; ; ) {
23182
- const manifestPath = path5.join(current, DEFAULT_HAR_DIR, "manifest.json");
23183
- if (fs5.existsSync(manifestPath)) {
23169
+ const manifestPath = path4.join(current, DEFAULT_HAR_DIR, "manifest.json");
23170
+ if (fs4.existsSync(manifestPath)) {
23184
23171
  return current;
23185
23172
  }
23186
23173
  if (current === root) break;
23187
- current = path5.dirname(current);
23174
+ current = path4.dirname(current);
23175
+ }
23176
+ return path4.resolve(inputPath);
23177
+ }
23178
+
23179
+ // src/harness/agent-skills.ts
23180
+ var AGENT_SKILL_TARGETS = ["claude", "cursor", "codex"];
23181
+ var MANAGED_HEADER = "<!-- managed by har \u2014 regenerated by `har agents install` / `har env maintain`; edit at your own risk -->";
23182
+ function agentSkillsTemplateDir() {
23183
+ const dir = path5.join(resolveTemplatesDir(), "agent-skills");
23184
+ if (!fs5.existsSync(dir)) {
23185
+ throw new Error("Agent skills templates not found (templates/agent-skills). Run npm run build.");
23188
23186
  }
23189
- return path5.resolve(inputPath);
23187
+ return dir;
23188
+ }
23189
+ function readSkillsManifest() {
23190
+ const manifestPath = path5.join(agentSkillsTemplateDir(), "skills.manifest.json");
23191
+ return JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
23192
+ }
23193
+ function readSkillBody(entry) {
23194
+ return fs5.readFileSync(path5.join(agentSkillsTemplateDir(), entry.body), "utf8");
23195
+ }
23196
+ function renderYamlValue(value) {
23197
+ if (typeof value === "string") return JSON.stringify(value);
23198
+ return String(value);
23199
+ }
23200
+ function renderClaudeSkill(entry, body) {
23201
+ const frontmatter = entry.claude?.frontmatter ?? { name: entry.id, description: entry.title };
23202
+ const lines = Object.entries(frontmatter).map(([key, value]) => `${key}: ${renderYamlValue(value)}`);
23203
+ return `---
23204
+ ${lines.join("\n")}
23205
+ ---
23206
+
23207
+ ${MANAGED_HEADER}
23208
+
23209
+ ${body}`;
23210
+ }
23211
+ function renderPlainSkill(body) {
23212
+ return `${MANAGED_HEADER}
23213
+
23214
+ ${body}`;
23215
+ }
23216
+ function renderSkillFiles(agents) {
23217
+ const manifest = readSkillsManifest();
23218
+ const files = [];
23219
+ for (const entry of manifest.skills) {
23220
+ const body = readSkillBody(entry);
23221
+ if (agents.includes("claude")) {
23222
+ files.push({
23223
+ agent: "claude",
23224
+ skill: entry.id,
23225
+ scope: "repo",
23226
+ relPath: path5.join(".claude", "skills", entry.id, "SKILL.md"),
23227
+ content: renderClaudeSkill(entry, body)
23228
+ });
23229
+ }
23230
+ if (agents.includes("cursor") && entry.cursor?.command) {
23231
+ files.push({
23232
+ agent: "cursor",
23233
+ skill: entry.id,
23234
+ scope: "repo",
23235
+ relPath: path5.join(".cursor", "commands", `${entry.id}.md`),
23236
+ content: renderPlainSkill(body)
23237
+ });
23238
+ }
23239
+ if (agents.includes("codex") && entry.codex?.promptName) {
23240
+ files.push({
23241
+ agent: "codex",
23242
+ skill: entry.id,
23243
+ scope: "global",
23244
+ relPath: path5.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
23245
+ content: renderPlainSkill(body)
23246
+ });
23247
+ }
23248
+ }
23249
+ return files;
23250
+ }
23251
+ function resolveScaffoldPath(repoPath, file) {
23252
+ if (file.scope === "global") {
23253
+ return path5.join(os2.homedir(), file.relPath.replace(/^~\//, ""));
23254
+ }
23255
+ return path5.join(repoPath, file.relPath);
23256
+ }
23257
+ function isHarManaged(filePath) {
23258
+ return fs5.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
23259
+ }
23260
+ function scaffoldAgentSkills(repoPath, agents, options = {}) {
23261
+ const written = [];
23262
+ const skipped = [];
23263
+ const records = [];
23264
+ for (const file of renderSkillFiles(agents)) {
23265
+ const destPath = resolveScaffoldPath(repoPath, file);
23266
+ if (fs5.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
23267
+ warn(`Skipped ${file.relPath} \u2014 exists with user modifications (use --force to overwrite)`);
23268
+ skipped.push(file.relPath);
23269
+ continue;
23270
+ }
23271
+ writeFileSafe(destPath, file.content);
23272
+ written.push(file.relPath);
23273
+ records.push({
23274
+ path: file.relPath,
23275
+ agent: file.agent,
23276
+ skill: file.skill,
23277
+ checksum: computeFileChecksum(file.content),
23278
+ scope: file.scope
23279
+ });
23280
+ }
23281
+ recordScaffoldedFiles(repoPath, agents, records);
23282
+ return { written, skipped };
23283
+ }
23284
+ function recordScaffoldedFiles(repoPath, agents, records) {
23285
+ const manifest = readManifest(repoPath);
23286
+ if (!manifest) return;
23287
+ const kept = (manifest.scaffoldedAgentFiles ?? []).filter(
23288
+ (entry) => !agents.includes(entry.agent)
23289
+ );
23290
+ writeManifest(repoPath, {
23291
+ ...manifest,
23292
+ scaffoldedAgentFiles: [...kept, ...records],
23293
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
23294
+ });
23295
+ }
23296
+ function removeAgentSkills(repoPath, agents) {
23297
+ const removed = [];
23298
+ for (const file of renderSkillFiles(agents)) {
23299
+ const destPath = resolveScaffoldPath(repoPath, file);
23300
+ if (!fs5.existsSync(destPath)) continue;
23301
+ if (!isHarManaged(destPath)) {
23302
+ warn(`Kept ${file.relPath} \u2014 has user modifications`);
23303
+ continue;
23304
+ }
23305
+ fs5.rmSync(destPath);
23306
+ removed.push(file.relPath);
23307
+ const parent = path5.dirname(destPath);
23308
+ if (fs5.existsSync(parent) && fs5.readdirSync(parent).length === 0) {
23309
+ fs5.rmdirSync(parent);
23310
+ }
23311
+ }
23312
+ const manifest = readManifest(repoPath);
23313
+ if (manifest?.scaffoldedAgentFiles) {
23314
+ writeManifest(repoPath, {
23315
+ ...manifest,
23316
+ scaffoldedAgentFiles: manifest.scaffoldedAgentFiles.filter(
23317
+ (entry) => !agents.includes(entry.agent)
23318
+ ),
23319
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
23320
+ });
23321
+ }
23322
+ return removed;
23323
+ }
23324
+ function detectAgentTargets(repoPath) {
23325
+ const manifest = readManifest(repoPath);
23326
+ const scaffolded = new Set((manifest?.scaffoldedAgentFiles ?? []).map((entry) => entry.agent));
23327
+ const detected = new Set(scaffolded);
23328
+ if (fs5.existsSync(path5.join(repoPath, ".claude"))) detected.add("claude");
23329
+ if (fs5.existsSync(path5.join(repoPath, ".cursor"))) detected.add("cursor");
23330
+ if (fs5.existsSync(path5.join(os2.homedir(), ".codex"))) detected.add("codex");
23331
+ return AGENT_SKILL_TARGETS.filter((agent) => detected.has(agent));
23332
+ }
23333
+ function parseAgentTargets(raw) {
23334
+ const parts = raw.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
23335
+ const invalid = parts.filter((part) => !AGENT_SKILL_TARGETS.includes(part));
23336
+ if (invalid.length > 0) {
23337
+ throw new Error(
23338
+ `Unknown agent target(s): ${invalid.join(", ")}. Valid: ${AGENT_SKILL_TARGETS.join(", ")}`
23339
+ );
23340
+ }
23341
+ return AGENT_SKILL_TARGETS.filter((agent) => parts.includes(agent));
23342
+ }
23343
+ async function handleAgentSkills(options) {
23344
+ const { repoPath, mode } = options;
23345
+ if (options.enabled === false) return false;
23346
+ let targets;
23347
+ if (options.agents !== void 0) {
23348
+ targets = parseAgentTargets(options.agents);
23349
+ } else {
23350
+ targets = detectAgentTargets(repoPath);
23351
+ if (targets.length === 0) return false;
23352
+ const manifest = readManifest(repoPath);
23353
+ const alreadyScaffolded = (manifest?.scaffoldedAgentFiles ?? []).length > 0;
23354
+ const shouldPrompt = !options.autoYes && !(mode === "maintain" && alreadyScaffolded);
23355
+ if (shouldPrompt) {
23356
+ const accepted = await askYesNo(
23357
+ `Scaffold agent skills (/setup-har, /har-wt, /har-maintain) for: ${targets.join(", ")}? [Y/n]`
23358
+ );
23359
+ if (!accepted) {
23360
+ info("Skipped agent skills");
23361
+ return false;
23362
+ }
23363
+ }
23364
+ }
23365
+ if (targets.length === 0) return false;
23366
+ const result = scaffoldAgentSkills(repoPath, targets, { force: options.force });
23367
+ for (const file of result.written) {
23368
+ info(`${mode === "maintain" ? "Refreshed" : "Wrote"} ${file}`);
23369
+ }
23370
+ return result.written.length > 0;
23371
+ }
23372
+ async function askYesNo(question) {
23373
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
23374
+ return new Promise((resolve28) => {
23375
+ process.stderr.write(`${question} `);
23376
+ rl.once("line", (answer) => {
23377
+ rl.close();
23378
+ const trimmed = answer.trim();
23379
+ if (trimmed === "") {
23380
+ resolve28(true);
23381
+ return;
23382
+ }
23383
+ resolve28(/^y(es)?$/i.test(trimmed));
23384
+ });
23385
+ });
23386
+ }
23387
+
23388
+ // src/cli/commands/agents.ts
23389
+ var targetsOption = (y2) => y2.option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("claude", { type: "boolean", default: false, describe: "Target Claude Code (.claude/skills/)" }).option("cursor", { type: "boolean", default: false, describe: "Target Cursor (.cursor/commands/)" }).option("codex", { type: "boolean", default: false, describe: "Target Codex CLI (~/.codex/prompts/, global)" }).option("agents", {
23390
+ type: "string",
23391
+ describe: `Comma-separated targets (${AGENT_SKILL_TARGETS.join(",")}); alternative to the per-agent flags`
23392
+ });
23393
+ function resolveTargets(argv, repoPath) {
23394
+ if (argv.agents) return parseAgentTargets(argv.agents);
23395
+ const flagged = AGENT_SKILL_TARGETS.filter((agent) => argv[agent]);
23396
+ if (flagged.length > 0) return flagged;
23397
+ return detectAgentTargets(repoPath);
23398
+ }
23399
+ var agentsCommand = {
23400
+ command: "agents <subcommand>",
23401
+ describe: "Manage scaffolded agent skills (/setup-har, /har-wt, /har-maintain)",
23402
+ builder: (yargs) => yargs.command(
23403
+ "install",
23404
+ "Scaffold agent skills into this repo (Claude/Cursor) and globally (Codex)",
23405
+ (y2) => targetsOption(y2).option("force", {
23406
+ type: "boolean",
23407
+ default: false,
23408
+ describe: "Overwrite files that have user modifications"
23409
+ }),
23410
+ handleInstall
23411
+ ).command("remove", "Remove har-managed agent skill files", targetsOption, handleRemove).demandCommand(1, "Specify a subcommand: install | remove"),
23412
+ handler: () => {
23413
+ }
23414
+ };
23415
+ function handleInstall(argv) {
23416
+ const repoPath = path6.resolve(argv.repo);
23417
+ header("har agents install");
23418
+ try {
23419
+ const targets = resolveTargets(argv, repoPath);
23420
+ if (targets.length === 0) {
23421
+ warn("No agent targets detected. Pass --claude, --cursor, --codex, or --agents=\u2026");
23422
+ process.exit(1);
23423
+ }
23424
+ const result = scaffoldAgentSkills(repoPath, targets, { force: argv.force });
23425
+ for (const file of result.written) {
23426
+ info(` + ${file}`);
23427
+ }
23428
+ if (result.written.length === 0 && result.skipped.length > 0) {
23429
+ warn("Nothing written \u2014 all target files have user modifications (use --force).");
23430
+ process.exit(1);
23431
+ }
23432
+ success(`Agent skills installed for: ${targets.join(", ")}`);
23433
+ if (targets.includes("codex")) {
23434
+ info("Codex prompts are global (~/.codex/prompts/) \u2014 Codex has no per-repo prompt support.");
23435
+ }
23436
+ } catch (err) {
23437
+ error(err.message);
23438
+ process.exit(1);
23439
+ }
23440
+ }
23441
+ function handleRemove(argv) {
23442
+ const repoPath = path6.resolve(argv.repo);
23443
+ header("har agents remove");
23444
+ try {
23445
+ const targets = resolveTargets(argv, repoPath);
23446
+ if (targets.length === 0) {
23447
+ warn("No agent targets detected or specified.");
23448
+ return;
23449
+ }
23450
+ const removed = removeAgentSkills(repoPath, targets);
23451
+ for (const file of removed) {
23452
+ info(` - ${file}`);
23453
+ }
23454
+ success(removed.length > 0 ? "Agent skills removed." : "Nothing to remove.");
23455
+ } catch (err) {
23456
+ error(err.message);
23457
+ process.exit(1);
23458
+ }
23459
+ }
23460
+
23461
+ // src/cli/commands/env.ts
23462
+ var path34 = __toESM(require("path"));
23463
+
23464
+ // src/core/harness.ts
23465
+ var fs19 = __toESM(require("fs"));
23466
+ var path18 = __toESM(require("path"));
23467
+
23468
+ // src/harness/generator.ts
23469
+ var fs7 = __toESM(require("fs"));
23470
+ var path8 = __toESM(require("path"));
23471
+
23472
+ // src/core/gitignore.ts
23473
+ var fs6 = __toESM(require("fs"));
23474
+ var path7 = __toESM(require("path"));
23475
+ var HARNESS_ROOT_GITIGNORE_PATTERNS = [
23476
+ ".env.agent.*",
23477
+ "ecosystem.agent.*.config.cjs"
23478
+ ];
23479
+ function ensureRootGitignorePatterns(checkoutDir) {
23480
+ const gitignorePath = path7.join(checkoutDir, ".gitignore");
23481
+ const content = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
23482
+ const lines = content.split("\n").map((line) => line.trim());
23483
+ const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
23484
+ if (missing.length === 0) return;
23485
+ const header2 = lines.length === 0 || content.trim().length === 0 ? "# HAR agent slot artifacts (generated by .har/launch.sh)\n" : "";
23486
+ const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
23487
+ const block = lines.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
23488
+ ` : `${header2}${missing.join("\n")}
23489
+ `;
23490
+ fs6.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
23190
23491
  }
23191
23492
 
23192
23493
  // src/harness/generator.ts
@@ -23202,46 +23503,46 @@ var CLI_PRUNE_FILES = [
23202
23503
  ];
23203
23504
  function pruneCliProfile(harnessDir) {
23204
23505
  for (const file of CLI_PRUNE_FILES) {
23205
- const filePath = path6.join(harnessDir, file);
23206
- if (fs6.existsSync(filePath)) {
23207
- fs6.unlinkSync(filePath);
23506
+ const filePath = path8.join(harnessDir, file);
23507
+ if (fs7.existsSync(filePath)) {
23508
+ fs7.unlinkSync(filePath);
23208
23509
  }
23209
23510
  }
23210
23511
  }
23211
23512
  function scaffoldClaudeMd(repoPath, projectName, force) {
23212
23513
  const templatePath = resolveTemplateFile("CLAUDE.md.template");
23213
23514
  if (!templatePath) return;
23214
- const dest = path6.join(repoPath, "CLAUDE.md");
23215
- if (fs6.existsSync(dest) && !force) return;
23515
+ const dest = path8.join(repoPath, "CLAUDE.md");
23516
+ if (fs7.existsSync(dest) && !force) return;
23216
23517
  const displayName = projectName.replace(/_/g, " ");
23217
- const content = fs6.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
23218
- fs6.writeFileSync(dest, content);
23518
+ const content = fs7.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
23519
+ fs7.writeFileSync(dest, content);
23219
23520
  }
23220
23521
  function scaffoldHarnessBoilerplate(repoPath, options = {}) {
23221
- const harnessDir = path6.join(repoPath, DEFAULT_HAR_DIR);
23222
- const projectName = path6.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
23522
+ const harnessDir = path8.join(repoPath, DEFAULT_HAR_DIR);
23523
+ const projectName = path8.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
23223
23524
  const profile = options.profile ?? "default";
23224
- const boilerplateDir = path6.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
23225
- if (fs6.existsSync(harnessDir) && !options.force) {
23525
+ const boilerplateDir = path8.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
23526
+ if (fs7.existsSync(harnessDir) && !options.force) {
23226
23527
  throw new Error(
23227
23528
  '.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
23228
23529
  );
23229
23530
  }
23230
- if (!fs6.existsSync(boilerplateDir)) {
23531
+ if (!fs7.existsSync(boilerplateDir)) {
23231
23532
  throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
23232
23533
  }
23233
- if (options.force && fs6.existsSync(harnessDir)) {
23234
- fs6.rmSync(harnessDir, { recursive: true, force: true });
23534
+ if (options.force && fs7.existsSync(harnessDir)) {
23535
+ fs7.rmSync(harnessDir, { recursive: true, force: true });
23235
23536
  }
23236
23537
  copyDirRecursive(boilerplateDir, harnessDir);
23237
23538
  if (profile === "cli") {
23238
23539
  pruneCliProfile(harnessDir);
23239
23540
  }
23240
- const harnessEnvPath = path6.join(harnessDir, "harness.env");
23241
- if (fs6.existsSync(harnessEnvPath)) {
23242
- let content = fs6.readFileSync(harnessEnvPath, "utf8");
23541
+ const harnessEnvPath = path8.join(harnessDir, "harness.env");
23542
+ if (fs7.existsSync(harnessEnvPath)) {
23543
+ let content = fs7.readFileSync(harnessEnvPath, "utf8");
23243
23544
  content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
23244
- fs6.writeFileSync(harnessEnvPath, content);
23545
+ fs7.writeFileSync(harnessEnvPath, content);
23245
23546
  }
23246
23547
  const manifest = createManifest(
23247
23548
  repoPath,
@@ -23264,8 +23565,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
23264
23565
  }
23265
23566
 
23266
23567
  // src/llm/authoring-agent.ts
23267
- var fs10 = __toESM(require("fs"));
23268
- var path9 = __toESM(require("path"));
23568
+ var fs11 = __toESM(require("fs"));
23569
+ var path11 = __toESM(require("path"));
23269
23570
 
23270
23571
  // node_modules/@anthropic-ai/sdk/error.mjs
23271
23572
  var error_exports = {};
@@ -23664,13 +23965,13 @@ var MultipartBody = class {
23664
23965
  // node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
23665
23966
  var import_web = require("node:stream/web");
23666
23967
  var fileFromPathWarned = false;
23667
- async function fileFromPath3(path37, ...args) {
23968
+ async function fileFromPath3(path41, ...args) {
23668
23969
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
23669
23970
  if (!fileFromPathWarned) {
23670
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path37)}) instead`);
23971
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path41)}) instead`);
23671
23972
  fileFromPathWarned = true;
23672
23973
  }
23673
- return await _fileFromPath(path37, ...args);
23974
+ return await _fileFromPath(path41, ...args);
23674
23975
  }
23675
23976
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
23676
23977
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -24166,8 +24467,8 @@ async function defaultParseResponse(props) {
24166
24467
  }
24167
24468
  var APIPromise = class _APIPromise extends Promise {
24168
24469
  constructor(responsePromise, parseResponse = defaultParseResponse) {
24169
- super((resolve26) => {
24170
- resolve26(null);
24470
+ super((resolve28) => {
24471
+ resolve28(null);
24171
24472
  });
24172
24473
  this.responsePromise = responsePromise;
24173
24474
  this.parseResponse = parseResponse;
@@ -24267,29 +24568,29 @@ var APIClient = class {
24267
24568
  defaultIdempotencyKey() {
24268
24569
  return `stainless-node-retry-${uuid4()}`;
24269
24570
  }
24270
- get(path37, opts) {
24271
- return this.methodRequest("get", path37, opts);
24571
+ get(path41, opts) {
24572
+ return this.methodRequest("get", path41, opts);
24272
24573
  }
24273
- post(path37, opts) {
24274
- return this.methodRequest("post", path37, opts);
24574
+ post(path41, opts) {
24575
+ return this.methodRequest("post", path41, opts);
24275
24576
  }
24276
- patch(path37, opts) {
24277
- return this.methodRequest("patch", path37, opts);
24577
+ patch(path41, opts) {
24578
+ return this.methodRequest("patch", path41, opts);
24278
24579
  }
24279
- put(path37, opts) {
24280
- return this.methodRequest("put", path37, opts);
24580
+ put(path41, opts) {
24581
+ return this.methodRequest("put", path41, opts);
24281
24582
  }
24282
- delete(path37, opts) {
24283
- return this.methodRequest("delete", path37, opts);
24583
+ delete(path41, opts) {
24584
+ return this.methodRequest("delete", path41, opts);
24284
24585
  }
24285
- methodRequest(method, path37, opts) {
24586
+ methodRequest(method, path41, opts) {
24286
24587
  return this.request(Promise.resolve(opts).then(async (opts2) => {
24287
24588
  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;
24288
- return { method, path: path37, ...opts2, body };
24589
+ return { method, path: path41, ...opts2, body };
24289
24590
  }));
24290
24591
  }
24291
- getAPIList(path37, Page, opts) {
24292
- return this.requestAPIList(Page, { method: "get", path: path37, ...opts });
24592
+ getAPIList(path41, Page, opts) {
24593
+ return this.requestAPIList(Page, { method: "get", path: path41, ...opts });
24293
24594
  }
24294
24595
  calculateContentLength(body) {
24295
24596
  if (typeof body === "string") {
@@ -24307,10 +24608,10 @@ var APIClient = class {
24307
24608
  return null;
24308
24609
  }
24309
24610
  buildRequest(options) {
24310
- const { method, path: path37, query, headers = {} } = options;
24611
+ const { method, path: path41, query, headers = {} } = options;
24311
24612
  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;
24312
24613
  const contentLength = this.calculateContentLength(body);
24313
- const url = this.buildURL(path37, query);
24614
+ const url = this.buildURL(path41, query);
24314
24615
  if ("timeout" in options)
24315
24616
  validatePositiveInteger("timeout", options.timeout);
24316
24617
  const timeout = options.timeout ?? this.timeout;
@@ -24419,8 +24720,8 @@ var APIClient = class {
24419
24720
  const request = this.makeRequest(options, null);
24420
24721
  return new PagePromise(this, request, Page);
24421
24722
  }
24422
- buildURL(path37, query) {
24423
- const url = isAbsoluteURL(path37) ? new URL(path37) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path37.startsWith("/") ? path37.slice(1) : path37));
24723
+ buildURL(path41, query) {
24724
+ const url = isAbsoluteURL(path41) ? new URL(path41) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path41.startsWith("/") ? path41.slice(1) : path41));
24424
24725
  const defaultQuery = this.defaultQuery();
24425
24726
  if (!isEmptyObj(defaultQuery)) {
24426
24727
  query = { ...defaultQuery, ...query };
@@ -24705,7 +25006,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
24705
25006
  var isAbsoluteURL = (url) => {
24706
25007
  return startsWithSchemeRegexp.test(url);
24707
25008
  };
24708
- var sleep = (ms) => new Promise((resolve26) => setTimeout(resolve26, ms));
25009
+ var sleep = (ms) => new Promise((resolve28) => setTimeout(resolve28, ms));
24709
25010
  var validatePositiveInteger = (name, n2) => {
24710
25011
  if (typeof n2 !== "number" || !Number.isInteger(n2)) {
24711
25012
  throw new AnthropicError(`${name} must be an integer`);
@@ -25196,12 +25497,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25196
25497
  }
25197
25498
  return this._emit("error", new AnthropicError(String(error3)));
25198
25499
  });
25199
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve26, reject) => {
25200
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve26, "f");
25500
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve28, reject) => {
25501
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve28, "f");
25201
25502
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
25202
25503
  }), "f");
25203
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve26, reject) => {
25204
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve26, "f");
25504
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve28, reject) => {
25505
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve28, "f");
25205
25506
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
25206
25507
  }), "f");
25207
25508
  __classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
@@ -25330,11 +25631,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25330
25631
  * const message = await stream.emitted('message') // rejects if the stream errors
25331
25632
  */
25332
25633
  emitted(event) {
25333
- return new Promise((resolve26, reject) => {
25634
+ return new Promise((resolve28, reject) => {
25334
25635
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
25335
25636
  if (event !== "error")
25336
25637
  this.once("error", reject);
25337
- this.once(event, resolve26);
25638
+ this.once(event, resolve28);
25338
25639
  });
25339
25640
  }
25340
25641
  async done() {
@@ -25561,7 +25862,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25561
25862
  if (done) {
25562
25863
  return { value: void 0, done: true };
25563
25864
  }
25564
- return new Promise((resolve26, reject) => readQueue.push({ resolve: resolve26, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
25865
+ return new Promise((resolve28, reject) => readQueue.push({ resolve: resolve28, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
25565
25866
  }
25566
25867
  const chunk = pushQueue.shift();
25567
25868
  return { value: chunk, done: false };
@@ -25709,12 +26010,12 @@ var MessageStream = class _MessageStream {
25709
26010
  }
25710
26011
  return this._emit("error", new AnthropicError(String(error3)));
25711
26012
  });
25712
- __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve26, reject) => {
25713
- __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve26, "f");
26013
+ __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve28, reject) => {
26014
+ __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve28, "f");
25714
26015
  __classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
25715
26016
  }), "f");
25716
- __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve26, reject) => {
25717
- __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve26, "f");
26017
+ __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve28, reject) => {
26018
+ __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve28, "f");
25718
26019
  __classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
25719
26020
  }), "f");
25720
26021
  __classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
@@ -25843,11 +26144,11 @@ var MessageStream = class _MessageStream {
25843
26144
  * const message = await stream.emitted('message') // rejects if the stream errors
25844
26145
  */
25845
26146
  emitted(event) {
25846
- return new Promise((resolve26, reject) => {
26147
+ return new Promise((resolve28, reject) => {
25847
26148
  __classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
25848
26149
  if (event !== "error")
25849
26150
  this.once("error", reject);
25850
- this.once(event, resolve26);
26151
+ this.once(event, resolve28);
25851
26152
  });
25852
26153
  }
25853
26154
  async done() {
@@ -26074,7 +26375,7 @@ var MessageStream = class _MessageStream {
26074
26375
  if (done) {
26075
26376
  return { value: void 0, done: true };
26076
26377
  }
26077
- return new Promise((resolve26, reject) => readQueue.push({ resolve: resolve26, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26378
+ return new Promise((resolve28, reject) => readQueue.push({ resolve: resolve28, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26078
26379
  }
26079
26380
  const chunk = pushQueue.shift();
26080
26381
  return { value: chunk, done: false };
@@ -26243,34 +26544,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
26243
26544
  var sdk_default = Anthropic;
26244
26545
 
26245
26546
  // src/llm/tools.ts
26246
- var fs9 = __toESM(require("fs"));
26247
- var path8 = __toESM(require("path"));
26248
- var readline2 = __toESM(require("readline"));
26547
+ var fs10 = __toESM(require("fs"));
26548
+ var path10 = __toESM(require("path"));
26549
+ var readline3 = __toESM(require("readline"));
26249
26550
 
26250
26551
  // src/harness/agent-md.ts
26251
- var fs8 = __toESM(require("fs"));
26252
- var path7 = __toESM(require("path"));
26253
- var readline = __toESM(require("readline"));
26552
+ var fs9 = __toESM(require("fs"));
26553
+ var path9 = __toESM(require("path"));
26554
+ var readline2 = __toESM(require("readline"));
26254
26555
  var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
26255
26556
  var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
26256
26557
  function writeAgentMdProposal(repoPath, content, rationale) {
26257
26558
  const harnessDir = getHarnessDir(repoPath);
26258
- writeFileSafe(path7.join(harnessDir, AGENT_MD_PROPOSAL), content);
26559
+ writeFileSafe(path9.join(harnessDir, AGENT_MD_PROPOSAL), content);
26259
26560
  writeFileSafe(
26260
- path7.join(harnessDir, AGENT_MD_PROPOSAL_META),
26561
+ path9.join(harnessDir, AGENT_MD_PROPOSAL_META),
26261
26562
  JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
26262
26563
  );
26263
26564
  }
26264
26565
  function readAgentMdProposal(repoPath) {
26265
26566
  const harnessDir = getHarnessDir(repoPath);
26266
- const proposalPath = path7.join(harnessDir, AGENT_MD_PROPOSAL);
26267
- const metaPath = path7.join(harnessDir, AGENT_MD_PROPOSAL_META);
26268
- if (!fs8.existsSync(proposalPath)) return null;
26269
- const content = fs8.readFileSync(proposalPath, "utf8");
26567
+ const proposalPath = path9.join(harnessDir, AGENT_MD_PROPOSAL);
26568
+ const metaPath = path9.join(harnessDir, AGENT_MD_PROPOSAL_META);
26569
+ if (!fs9.existsSync(proposalPath)) return null;
26570
+ const content = fs9.readFileSync(proposalPath, "utf8");
26270
26571
  let rationale = "";
26271
- if (fs8.existsSync(metaPath)) {
26572
+ if (fs9.existsSync(metaPath)) {
26272
26573
  try {
26273
- rationale = JSON.parse(fs8.readFileSync(metaPath, "utf8")).rationale ?? "";
26574
+ rationale = JSON.parse(fs9.readFileSync(metaPath, "utf8")).rationale ?? "";
26274
26575
  } catch {
26275
26576
  rationale = "";
26276
26577
  }
@@ -26278,21 +26579,21 @@ function readAgentMdProposal(repoPath) {
26278
26579
  return {
26279
26580
  content,
26280
26581
  rationale,
26281
- createdAt: fs8.existsSync(metaPath) ? JSON.parse(fs8.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
26582
+ createdAt: fs9.existsSync(metaPath) ? JSON.parse(fs9.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
26282
26583
  };
26283
26584
  }
26284
26585
  function clearAgentMdProposal(repoPath) {
26285
26586
  const harnessDir = getHarnessDir(repoPath);
26286
26587
  for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
26287
- const p2 = path7.join(harnessDir, file);
26288
- if (fs8.existsSync(p2)) fs8.unlinkSync(p2);
26588
+ const p2 = path9.join(harnessDir, file);
26589
+ if (fs9.existsSync(p2)) fs9.unlinkSync(p2);
26289
26590
  }
26290
26591
  }
26291
26592
  async function promptApplyAgentMdProposal(repoPath) {
26292
26593
  const proposal = readAgentMdProposal(repoPath);
26293
26594
  if (!proposal) return false;
26294
- const agentMdPath = path7.join(repoPath, "AGENT.md");
26295
- const exists = fs8.existsSync(agentMdPath);
26595
+ const agentMdPath = path9.join(repoPath, "AGENT.md");
26596
+ const exists = fs9.existsSync(agentMdPath);
26296
26597
  process.stderr.write("\n");
26297
26598
  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");
26298
26599
  process.stderr.write("Proposed AGENT.md (repo root)\n");
@@ -26315,13 +26616,13 @@ async function promptApplyAgentMdProposal(repoPath) {
26315
26616
  `);
26316
26617
  if (exists) {
26317
26618
  warn("AGENT.md already exists at repo root.");
26318
- const answer = await askYesNo("Replace AGENT.md with this proposal? (y/n)");
26619
+ const answer = await askYesNo2("Replace AGENT.md with this proposal? (y/n)");
26319
26620
  if (!answer) {
26320
26621
  info("Skipped \u2014 proposal kept at .har/AGENT.md.proposed");
26321
26622
  return false;
26322
26623
  }
26323
26624
  } else {
26324
- const answer = await askYesNo("Create AGENT.md at repo root? (y/n)");
26625
+ const answer = await askYesNo2("Create AGENT.md at repo root? (y/n)");
26325
26626
  if (!answer) {
26326
26627
  info("Skipped \u2014 proposal kept at .har/AGENT.md.proposed");
26327
26628
  return false;
@@ -26332,13 +26633,13 @@ async function promptApplyAgentMdProposal(repoPath) {
26332
26633
  info("Wrote AGENT.md");
26333
26634
  return true;
26334
26635
  }
26335
- async function askYesNo(question) {
26336
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
26337
- return new Promise((resolve26) => {
26636
+ async function askYesNo2(question) {
26637
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
26638
+ return new Promise((resolve28) => {
26338
26639
  process.stderr.write(`${question} `);
26339
26640
  rl.once("line", (answer) => {
26340
26641
  rl.close();
26341
- resolve26(/^y(es)?$/i.test(answer.trim()));
26642
+ resolve28(/^y(es)?$/i.test(answer.trim()));
26342
26643
  });
26343
26644
  });
26344
26645
  }
@@ -26480,13 +26781,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
26480
26781
  async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
26481
26782
  switch (toolName) {
26482
26783
  case "readRepoFile": {
26483
- const filePath = path8.join(repoPath, input.path);
26784
+ const filePath = path10.join(repoPath, input.path);
26484
26785
  const content = readFile(filePath, input.maxChars ?? 8e3);
26485
26786
  info(`Read repo: ${input.path}`);
26486
26787
  return content;
26487
26788
  }
26488
26789
  case "listRepoDir": {
26489
- const dirPath = path8.join(repoPath, input.path ?? ".");
26790
+ const dirPath = path10.join(repoPath, input.path ?? ".");
26490
26791
  const entries = listDir(dirPath, input.maxFiles ?? 60);
26491
26792
  info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
26492
26793
  return entries.length > 0 ? entries.join("\n") : "(empty directory)";
@@ -26511,17 +26812,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26511
26812
  const filePath = resolveSafePath(harnessDir, relPath);
26512
26813
  writeFileSafe(filePath, input.content);
26513
26814
  if (relPath.endsWith(".sh")) {
26514
- fs9.chmodSync(filePath, 493);
26815
+ fs10.chmodSync(filePath, 493);
26515
26816
  }
26516
26817
  info(`Wrote harness: ${relPath}`);
26517
26818
  return `Written ${relPath} (${input.content.length} bytes)`;
26518
26819
  }
26519
26820
  case "editHarnessFile": {
26520
26821
  const filePath = resolveSafePath(harnessDir, input.path);
26521
- if (!fs9.existsSync(filePath)) {
26822
+ if (!fs10.existsSync(filePath)) {
26522
26823
  return `Error: file not found: ${input.path}`;
26523
26824
  }
26524
- const content = fs9.readFileSync(filePath, "utf8");
26825
+ const content = fs10.readFileSync(filePath, "utf8");
26525
26826
  const oldStr = input.old_string;
26526
26827
  const newStr = input.new_string;
26527
26828
  const count = content.split(oldStr).length - 1;
@@ -26538,10 +26839,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26538
26839
  }
26539
26840
  case "deleteHarnessFile": {
26540
26841
  const filePath = resolveSafePath(harnessDir, input.path);
26541
- if (!fs9.existsSync(filePath)) {
26842
+ if (!fs10.existsSync(filePath)) {
26542
26843
  return `File not found (already deleted): ${input.path}`;
26543
26844
  }
26544
- fs9.unlinkSync(filePath);
26845
+ fs10.unlinkSync(filePath);
26545
26846
  info(`Deleted harness: ${input.path}`);
26546
26847
  return `Deleted ${input.path}`;
26547
26848
  }
@@ -26567,8 +26868,8 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26567
26868
  }
26568
26869
  }
26569
26870
  async function askUser(question, options) {
26570
- const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
26571
- return new Promise((resolve26) => {
26871
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
26872
+ return new Promise((resolve28) => {
26572
26873
  process.stderr.write("\n");
26573
26874
  process.stderr.write(`\u2753 ${question}
26574
26875
  `);
@@ -26579,7 +26880,7 @@ async function askUser(question, options) {
26579
26880
  process.stderr.write("> ");
26580
26881
  rl.once("line", (answer) => {
26581
26882
  rl.close();
26582
- resolve26(answer.trim());
26883
+ resolve28(answer.trim());
26583
26884
  });
26584
26885
  });
26585
26886
  }
@@ -26592,29 +26893,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
26592
26893
  const mode = options.mode ?? "init";
26593
26894
  const systemPromptPath = resolvePromptPath("system-authoring.md");
26594
26895
  const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
26595
- let systemPrompt = fs10.existsSync(systemPromptPath) ? fs10.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
26896
+ let systemPrompt = fs11.existsSync(systemPromptPath) ? fs11.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
26596
26897
  if (agentMdTemplatePath) {
26597
26898
  systemPrompt += `
26598
26899
 
26599
26900
  ## AGENT.md template (starting point for proposeAgentMd)
26600
26901
 
26601
- ${fs10.readFileSync(agentMdTemplatePath, "utf8")}`;
26902
+ ${fs11.readFileSync(agentMdTemplatePath, "utf8")}`;
26602
26903
  }
26603
26904
  const tools = buildAuthoringTools();
26604
26905
  const messages = [];
26605
26906
  let rootListing;
26606
26907
  try {
26607
- rootListing = fs10.readdirSync(repoPath).slice(0, 50).join("\n");
26908
+ rootListing = fs11.readdirSync(repoPath).slice(0, 50).join("\n");
26608
26909
  } catch {
26609
26910
  throw new Error(`Cannot read repo at path: ${repoPath}`);
26610
26911
  }
26611
26912
  let harnessListing;
26612
26913
  try {
26613
- harnessListing = fs10.readdirSync(harnessDir).join("\n");
26914
+ harnessListing = fs11.readdirSync(harnessDir).join("\n");
26614
26915
  } catch {
26615
26916
  throw new Error(`Cannot read harness dir at: ${harnessDir}`);
26616
26917
  }
26617
- const existingAgentMd = fs10.existsSync(path9.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.";
26918
+ 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.";
26618
26919
  const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
26619
26920
  Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
26620
26921
  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/.
@@ -26694,8 +26995,8 @@ Try running with --verbose to see what the agent is doing.`
26694
26995
  }
26695
26996
 
26696
26997
  // src/harness/validator.ts
26697
- var fs13 = __toESM(require("fs"));
26698
- var path12 = __toESM(require("path"));
26998
+ var fs14 = __toESM(require("fs"));
26999
+ var path14 = __toESM(require("path"));
26699
27000
 
26700
27001
  // src/utils/shell.ts
26701
27002
  var import_child_process = require("child_process");
@@ -26723,7 +27024,7 @@ function runScript(scriptPath, args = [], options = {}) {
26723
27024
  function runScriptCapture(scriptPath, args = [], options = {}) {
26724
27025
  const stream = options.stream ?? false;
26725
27026
  const { stream: _stream, ...spawnOptions } = options;
26726
- return new Promise((resolve26) => {
27027
+ return new Promise((resolve28) => {
26727
27028
  const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
26728
27029
  ...spawnOptions,
26729
27030
  stdio: ["inherit", "pipe", "pipe"]
@@ -26738,13 +27039,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
26738
27039
  stderr += d2;
26739
27040
  if (stream) process.stderr.write(d2);
26740
27041
  });
26741
- proc.on("close", (code) => resolve26({ stdout, stderr, code: code ?? 0 }));
27042
+ proc.on("close", (code) => resolve28({ stdout, stderr, code: code ?? 0 }));
26742
27043
  });
26743
27044
  }
26744
27045
  function runShellCommand(command2, options = {}) {
26745
27046
  const stream = options.stream ?? false;
26746
27047
  const { stream: _stream, ...spawnOptions } = options;
26747
- return new Promise((resolve26) => {
27048
+ return new Promise((resolve28) => {
26748
27049
  const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
26749
27050
  ...spawnOptions,
26750
27051
  stdio: ["inherit", "pipe", "pipe"]
@@ -26759,22 +27060,22 @@ function runShellCommand(command2, options = {}) {
26759
27060
  stderr += d2;
26760
27061
  if (stream) process.stderr.write(d2);
26761
27062
  });
26762
- proc.on("close", (code) => resolve26({ stdout, stderr, code: code ?? 0 }));
27063
+ proc.on("close", (code) => resolve28({ stdout, stderr, code: code ?? 0 }));
26763
27064
  });
26764
27065
  }
26765
27066
 
26766
27067
  // src/harness/stages.ts
26767
- var fs12 = __toESM(require("fs"));
26768
- var path11 = __toESM(require("path"));
27068
+ var fs13 = __toESM(require("fs"));
27069
+ var path13 = __toESM(require("path"));
26769
27070
 
26770
27071
  // src/harness/env.ts
26771
- var fs11 = __toESM(require("fs"));
26772
- var path10 = __toESM(require("path"));
27072
+ var fs12 = __toESM(require("fs"));
27073
+ var path12 = __toESM(require("path"));
26773
27074
  function readHarnessEnv(repoPath) {
26774
- const envPath = path10.join(getHarnessDir(repoPath), "harness.env");
26775
- if (!fs11.existsSync(envPath)) return {};
27075
+ const envPath = path12.join(getHarnessDir(repoPath), "harness.env");
27076
+ if (!fs12.existsSync(envPath)) return {};
26776
27077
  const values = {};
26777
- for (const line of fs11.readFileSync(envPath, "utf8").split("\n")) {
27078
+ for (const line of fs12.readFileSync(envPath, "utf8").split("\n")) {
26778
27079
  const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
26779
27080
  if (!match) continue;
26780
27081
  values[match[1]] = match[2].replace(/^"|"$/g, "");
@@ -26801,7 +27102,7 @@ var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
26801
27102
  "custom"
26802
27103
  ]);
26803
27104
  function getStageRegistryPath(repoPath) {
26804
- return path11.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
27105
+ return path13.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
26805
27106
  }
26806
27107
  function synthesizeStageRegistry(repoPath) {
26807
27108
  const harnessDir = getHarnessDir(repoPath);
@@ -26876,13 +27177,13 @@ function readAgentSlotsFromHarnessEnv(harnessEnv) {
26876
27177
  }
26877
27178
  function stageScriptExists(harnessDir, stage) {
26878
27179
  if (stage.script) {
26879
- return fs12.existsSync(path11.join(harnessDir, stage.script));
27180
+ return fs13.existsSync(path13.join(harnessDir, stage.script));
26880
27181
  }
26881
27182
  if (stage.command) {
26882
27183
  const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
26883
- return fs12.existsSync(path11.join(harnessDir, scriptName));
27184
+ return fs13.existsSync(path13.join(harnessDir, scriptName));
26884
27185
  }
26885
- return fs12.existsSync(path11.join(harnessDir, "stages", `${stage.id}.sh`));
27186
+ return fs13.existsSync(path13.join(harnessDir, "stages", `${stage.id}.sh`));
26886
27187
  }
26887
27188
  function addStageIfRunnable(stages, harnessDir, stage) {
26888
27189
  const parsed = HarnessStageSchema.parse(stage);
@@ -26892,10 +27193,10 @@ function addStageIfRunnable(stages, harnessDir, stage) {
26892
27193
  }
26893
27194
  function readStageRegistry(repoPath) {
26894
27195
  const registryPath = getStageRegistryPath(repoPath);
26895
- if (!fs12.existsSync(registryPath)) {
27196
+ if (!fs13.existsSync(registryPath)) {
26896
27197
  return synthesizeStageRegistry(repoPath);
26897
27198
  }
26898
- const raw = JSON.parse(fs12.readFileSync(registryPath, "utf8"));
27199
+ const raw = JSON.parse(fs13.readFileSync(registryPath, "utf8"));
26899
27200
  const parsed = HarnessStageRegistrySchema.safeParse(raw);
26900
27201
  if (!parsed.success) {
26901
27202
  throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
@@ -26908,7 +27209,7 @@ function writeStageRegistry(repoPath, registry2) {
26908
27209
  if (!parsed.success) {
26909
27210
  throw new Error(`Invalid stage registry: ${parsed.error.message}`);
26910
27211
  }
26911
- fs12.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
27212
+ fs13.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
26912
27213
  }
26913
27214
  function listStages(repoPath) {
26914
27215
  return readStageRegistry(repoPath).stages;
@@ -27014,22 +27315,22 @@ var SHELL_SCRIPTS = [
27014
27315
  function validateHarness(repoPath) {
27015
27316
  const harnessDir = getHarnessDir(repoPath);
27016
27317
  const issues = [];
27017
- if (!fs13.existsSync(harnessDir)) {
27318
+ if (!fs14.existsSync(harnessDir)) {
27018
27319
  return {
27019
27320
  pass: false,
27020
27321
  issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
27021
27322
  };
27022
27323
  }
27023
27324
  for (const file of getRequiredFiles(repoPath)) {
27024
- const filePath = path12.join(harnessDir, file);
27025
- if (!fs13.existsSync(filePath)) {
27325
+ const filePath = path14.join(harnessDir, file);
27326
+ if (!fs14.existsSync(filePath)) {
27026
27327
  issues.push({ file, message: "Required file missing", severity: "error" });
27027
27328
  }
27028
27329
  }
27029
27330
  for (const script of SHELL_SCRIPTS) {
27030
- const scriptPath = path12.join(harnessDir, script);
27031
- if (!fs13.existsSync(scriptPath)) continue;
27032
- const stat = fs13.statSync(scriptPath);
27331
+ const scriptPath = path14.join(harnessDir, script);
27332
+ if (!fs14.existsSync(scriptPath)) continue;
27333
+ const stat = fs14.statSync(scriptPath);
27033
27334
  if (!(stat.mode & 73)) {
27034
27335
  issues.push({ file: script, message: "Script is not executable", severity: "warning" });
27035
27336
  }
@@ -27044,9 +27345,9 @@ function validateHarness(repoPath) {
27044
27345
  }
27045
27346
  const manifest = readManifest(repoPath);
27046
27347
  const profile = manifest?.profile ?? "default";
27047
- const ecosystemPath = path12.join(harnessDir, "ecosystem.agent.template.cjs");
27048
- if (profile !== "cli" && fs13.existsSync(ecosystemPath)) {
27049
- const content = fs13.readFileSync(ecosystemPath, "utf8");
27348
+ const ecosystemPath = path14.join(harnessDir, "ecosystem.agent.template.cjs");
27349
+ if (profile !== "cli" && fs14.existsSync(ecosystemPath)) {
27350
+ const content = fs14.readFileSync(ecosystemPath, "utf8");
27050
27351
  if (!content.includes("module.exports")) {
27051
27352
  issues.push({
27052
27353
  file: "ecosystem.agent.template.cjs",
@@ -27055,9 +27356,9 @@ function validateHarness(repoPath) {
27055
27356
  });
27056
27357
  }
27057
27358
  }
27058
- const harnessEnvPath = path12.join(harnessDir, "harness.env");
27059
- if (fs13.existsSync(harnessEnvPath)) {
27060
- const content = fs13.readFileSync(harnessEnvPath, "utf8");
27359
+ const harnessEnvPath = path14.join(harnessDir, "harness.env");
27360
+ if (fs14.existsSync(harnessEnvPath)) {
27361
+ const content = fs14.readFileSync(harnessEnvPath, "utf8");
27061
27362
  if (content.includes("TODO: set migrate command")) {
27062
27363
  issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
27063
27364
  }
@@ -27065,17 +27366,17 @@ function validateHarness(repoPath) {
27065
27366
  issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
27066
27367
  }
27067
27368
  }
27068
- const verifyPath = path12.join(harnessDir, "verify.sh");
27069
- if (fs13.existsSync(verifyPath)) {
27070
- const content = fs13.readFileSync(verifyPath, "utf8");
27369
+ const verifyPath = path14.join(harnessDir, "verify.sh");
27370
+ if (fs14.existsSync(verifyPath)) {
27371
+ const content = fs14.readFileSync(verifyPath, "utf8");
27071
27372
  if (content.includes("echo 'TODO:")) {
27072
27373
  issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
27073
27374
  }
27074
27375
  }
27075
- const stagesPath = path12.join(harnessDir, "stages.json");
27076
- if (fs13.existsSync(stagesPath)) {
27376
+ const stagesPath = path14.join(harnessDir, "stages.json");
27377
+ if (fs14.existsSync(stagesPath)) {
27077
27378
  try {
27078
- JSON.parse(fs13.readFileSync(stagesPath, "utf8"));
27379
+ JSON.parse(fs14.readFileSync(stagesPath, "utf8"));
27079
27380
  const registry2 = readStageRegistry(repoPath);
27080
27381
  if (registry2.stages.length === 0) {
27081
27382
  issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
@@ -27090,9 +27391,9 @@ function validateHarness(repoPath) {
27090
27391
  } else {
27091
27392
  issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
27092
27393
  }
27093
- const readmePath = path12.join(harnessDir, "README.md");
27094
- if (fs13.existsSync(readmePath)) {
27095
- const content = fs13.readFileSync(readmePath, "utf8");
27394
+ const readmePath = path14.join(harnessDir, "README.md");
27395
+ if (fs14.existsSync(readmePath)) {
27396
+ const content = fs14.readFileSync(readmePath, "utf8");
27096
27397
  if (!content.includes(".har")) {
27097
27398
  issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
27098
27399
  }
@@ -27106,8 +27407,8 @@ function validateHarness(repoPath) {
27106
27407
  async function smokeTestHarness(repoPath) {
27107
27408
  const harnessDir = getHarnessDir(repoPath);
27108
27409
  const issues = [];
27109
- const setupScript = path12.join(harnessDir, "setup-infra.sh");
27110
- if (fs13.existsSync(setupScript)) {
27410
+ const setupScript = path14.join(harnessDir, "setup-infra.sh");
27411
+ if (fs14.existsSync(setupScript)) {
27111
27412
  const result = run(`bash "${setupScript}"`, { cwd: repoPath });
27112
27413
  if (result.code !== 0) {
27113
27414
  issues.push({
@@ -27122,8 +27423,8 @@ async function smokeTestHarness(repoPath) {
27122
27423
  }
27123
27424
 
27124
27425
  // src/harness/drift.ts
27125
- var fs14 = __toESM(require("fs"));
27126
- var path13 = __toESM(require("path"));
27426
+ var fs15 = __toESM(require("fs"));
27427
+ var path15 = __toESM(require("path"));
27127
27428
  var PROFILE_DIRS2 = {
27128
27429
  default: "har-boilerplate",
27129
27430
  cli: "har-boilerplate-cli",
@@ -27191,33 +27492,33 @@ function substituteProjectName(content, projectName) {
27191
27492
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
27192
27493
  }
27193
27494
  function listBoilerplateFiles(boilerplateDir) {
27194
- if (!fs14.existsSync(boilerplateDir)) return [];
27195
- return fs14.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
27495
+ if (!fs15.existsSync(boilerplateDir)) return [];
27496
+ return fs15.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
27196
27497
  }
27197
27498
  function compareHarnessToTemplate(repoPath) {
27198
- const resolved = path13.resolve(repoPath);
27499
+ const resolved = path15.resolve(repoPath);
27199
27500
  const manifest = readManifest(resolved);
27200
27501
  const profile = manifest?.profile ?? "default";
27201
27502
  const harnessDir = getHarnessDir(resolved);
27202
- const projectName = path13.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
27203
- const boilerplateDir = path13.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
27503
+ const projectName = path15.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
27504
+ const boilerplateDir = path15.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
27204
27505
  const templateFiles = listBoilerplateFiles(boilerplateDir);
27205
27506
  const missing = [];
27206
27507
  const checksumMismatch = [];
27207
27508
  const extra = [];
27208
27509
  const unchanged = [];
27209
27510
  for (const file of templateFiles) {
27210
- const templatePath = path13.join(boilerplateDir, file);
27211
- const harnessPath = path13.join(harnessDir, file);
27212
- let templateContent = fs14.readFileSync(templatePath, "utf8");
27511
+ const templatePath = path15.join(boilerplateDir, file);
27512
+ const harnessPath = path15.join(harnessDir, file);
27513
+ let templateContent = fs15.readFileSync(templatePath, "utf8");
27213
27514
  if (file === "harness.env") {
27214
27515
  templateContent = substituteProjectName(templateContent, projectName);
27215
27516
  }
27216
- if (!fs14.existsSync(harnessPath)) {
27517
+ if (!fs15.existsSync(harnessPath)) {
27217
27518
  missing.push(file);
27218
27519
  continue;
27219
27520
  }
27220
- const harnessChecksum = computeFileChecksum(fs14.readFileSync(harnessPath, "utf8"));
27521
+ const harnessChecksum = computeFileChecksum(fs15.readFileSync(harnessPath, "utf8"));
27221
27522
  const templateChecksum = computeFileChecksum(templateContent);
27222
27523
  if (harnessChecksum === templateChecksum) {
27223
27524
  unchanged.push(file);
@@ -27225,10 +27526,10 @@ function compareHarnessToTemplate(repoPath) {
27225
27526
  checksumMismatch.push(file);
27226
27527
  }
27227
27528
  }
27228
- if (fs14.existsSync(harnessDir)) {
27229
- for (const file of fs14.readdirSync(harnessDir)) {
27230
- const full = path13.join(harnessDir, file);
27231
- if (!fs14.statSync(full).isFile()) continue;
27529
+ if (fs15.existsSync(harnessDir)) {
27530
+ for (const file of fs15.readdirSync(harnessDir)) {
27531
+ const full = path15.join(harnessDir, file);
27532
+ if (!fs15.statSync(full).isFile()) continue;
27232
27533
  if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
27233
27534
  if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
27234
27535
  extra.push(file);
@@ -27255,9 +27556,9 @@ function compareHarnessToTemplate(repoPath) {
27255
27556
  }
27256
27557
 
27257
27558
  // src/harness/maintain-bundle.ts
27258
- var fs15 = __toESM(require("fs"));
27259
- var os2 = __toESM(require("os"));
27260
- var path14 = __toESM(require("path"));
27559
+ var fs16 = __toESM(require("fs"));
27560
+ var os3 = __toESM(require("os"));
27561
+ var path16 = __toESM(require("path"));
27261
27562
  var MAINTAIN_DIR = "maintain";
27262
27563
  var PROFILE_DIRS3 = {
27263
27564
  default: "har-boilerplate",
@@ -27268,14 +27569,14 @@ function substituteProjectName2(content, projectName) {
27268
27569
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
27269
27570
  }
27270
27571
  function projectNameFromRepo(repoPath) {
27271
- return path14.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
27572
+ return path16.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
27272
27573
  }
27273
27574
  function boilerplateDirForProfile(profile) {
27274
- return path14.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
27575
+ return path16.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
27275
27576
  }
27276
27577
  function readBundledTemplateContent(repoPath, profile, file) {
27277
- const templatePath = path14.join(boilerplateDirForProfile(profile), file);
27278
- let content = fs15.readFileSync(templatePath, "utf8");
27578
+ const templatePath = path16.join(boilerplateDirForProfile(profile), file);
27579
+ let content = fs16.readFileSync(templatePath, "utf8");
27279
27580
  if (file === "harness.env") {
27280
27581
  content = substituteProjectName2(content, projectNameFromRepo(repoPath));
27281
27582
  }
@@ -27306,12 +27607,12 @@ function actionHint(file, kind2) {
27306
27607
  return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
27307
27608
  }
27308
27609
  function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
27309
- const tmpDir = fs15.mkdtempSync(path14.join(os2.tmpdir(), "har-maintain-diff-"));
27610
+ const tmpDir = fs16.mkdtempSync(path16.join(os3.tmpdir(), "har-maintain-diff-"));
27310
27611
  try {
27311
- const installedPath = path14.join(tmpDir, "installed");
27312
- const templatePath = path14.join(tmpDir, "template");
27313
- fs15.writeFileSync(installedPath, installedContent);
27314
- fs15.writeFileSync(templatePath, templateContent);
27612
+ const installedPath = path16.join(tmpDir, "installed");
27613
+ const templatePath = path16.join(tmpDir, "template");
27614
+ fs16.writeFileSync(installedPath, installedContent);
27615
+ fs16.writeFileSync(templatePath, templateContent);
27315
27616
  const result = run(`diff -u "${installedPath}" "${templatePath}"`);
27316
27617
  const body = result.stdout.trim();
27317
27618
  if (!body) return "";
@@ -27320,7 +27621,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
27320
27621
  ${body.split("\n").slice(2).join("\n")}
27321
27622
  `;
27322
27623
  } finally {
27323
- fs15.rmSync(tmpDir, { recursive: true, force: true });
27624
+ fs16.rmSync(tmpDir, { recursive: true, force: true });
27324
27625
  }
27325
27626
  }
27326
27627
  function buildActions(repoPath, profile, drift) {
@@ -27335,7 +27636,7 @@ function buildActions(repoPath, profile, drift) {
27335
27636
  });
27336
27637
  }
27337
27638
  for (const file of drift.checksumMismatch) {
27338
- const installedPath = path14.join(harnessDir, file);
27639
+ const installedPath = path16.join(harnessDir, file);
27339
27640
  const installedRel = `maintain/installed/${file}`;
27340
27641
  const templateRel = `maintain/templates/${file}`;
27341
27642
  const diffRel = `maintain/diffs/${file}.diff`;
@@ -27343,7 +27644,7 @@ function buildActions(repoPath, profile, drift) {
27343
27644
  file,
27344
27645
  kind: "drift",
27345
27646
  template: templateRel,
27346
- installed: fs15.existsSync(installedPath) ? installedRel : void 0,
27647
+ installed: fs16.existsSync(installedPath) ? installedRel : void 0,
27347
27648
  diff: diffRel,
27348
27649
  hint: actionHint(file, "drift")
27349
27650
  });
@@ -27414,28 +27715,28 @@ function buildReadme(report) {
27414
27715
  }
27415
27716
  function writeBundleArtifacts(repoPath, profile, drift, report) {
27416
27717
  const harnessDir = getHarnessDir(repoPath);
27417
- const bundleDir = path14.join(harnessDir, MAINTAIN_DIR);
27418
- if (fs15.existsSync(bundleDir)) {
27419
- fs15.rmSync(bundleDir, { recursive: true, force: true });
27718
+ const bundleDir = path16.join(harnessDir, MAINTAIN_DIR);
27719
+ if (fs16.existsSync(bundleDir)) {
27720
+ fs16.rmSync(bundleDir, { recursive: true, force: true });
27420
27721
  }
27421
27722
  const dirs = [
27422
27723
  bundleDir,
27423
- path14.join(bundleDir, "templates"),
27424
- path14.join(bundleDir, "installed"),
27425
- path14.join(bundleDir, "diffs"),
27426
- path14.join(bundleDir, "stale")
27724
+ path16.join(bundleDir, "templates"),
27725
+ path16.join(bundleDir, "installed"),
27726
+ path16.join(bundleDir, "diffs"),
27727
+ path16.join(bundleDir, "stale")
27427
27728
  ];
27428
27729
  for (const dir of dirs) {
27429
- fs15.mkdirSync(dir, { recursive: true });
27730
+ fs16.mkdirSync(dir, { recursive: true });
27430
27731
  }
27431
27732
  const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
27432
27733
  for (const file of affectedFiles) {
27433
27734
  const templateContent = readBundledTemplateContent(repoPath, profile, file);
27434
- writeFileSafe(path14.join(bundleDir, "templates", file), templateContent);
27435
- const harnessPath = path14.join(harnessDir, file);
27436
- if (fs15.existsSync(harnessPath)) {
27437
- const installedContent = fs15.readFileSync(harnessPath, "utf8");
27438
- writeFileSafe(path14.join(bundleDir, "installed", file), installedContent);
27735
+ writeFileSafe(path16.join(bundleDir, "templates", file), templateContent);
27736
+ const harnessPath = path16.join(harnessDir, file);
27737
+ if (fs16.existsSync(harnessPath)) {
27738
+ const installedContent = fs16.readFileSync(harnessPath, "utf8");
27739
+ writeFileSafe(path16.join(bundleDir, "installed", file), installedContent);
27439
27740
  const diff = createUnifiedDiff(
27440
27741
  installedContent,
27441
27742
  templateContent,
@@ -27443,14 +27744,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
27443
27744
  `templates/${file}`
27444
27745
  );
27445
27746
  if (diff) {
27446
- writeFileSafe(path14.join(bundleDir, "diffs", `${file}.diff`), diff);
27747
+ writeFileSafe(path16.join(bundleDir, "diffs", `${file}.diff`), diff);
27447
27748
  }
27448
27749
  }
27449
27750
  }
27450
- writeFileSafe(path14.join(bundleDir, "README.md"), buildReadme(report));
27451
- writeFileSafe(path14.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
27751
+ writeFileSafe(path16.join(bundleDir, "README.md"), buildReadme(report));
27752
+ writeFileSafe(path16.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
27452
27753
  writeFileSafe(
27453
- path14.join(bundleDir, "validation.json"),
27754
+ path16.join(bundleDir, "validation.json"),
27454
27755
  JSON.stringify(report.validation, null, 2) + "\n"
27455
27756
  );
27456
27757
  if (report.stale.length > 0) {
@@ -27462,7 +27763,7 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
27462
27763
  ...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
27463
27764
  ""
27464
27765
  ];
27465
- writeFileSafe(path14.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
27766
+ writeFileSafe(path16.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
27466
27767
  }
27467
27768
  return bundleDir;
27468
27769
  }
@@ -27488,9 +27789,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
27488
27789
  return { bundleDir, report };
27489
27790
  }
27490
27791
  function removeMaintainBundle(repoPath) {
27491
- const bundleDir = path14.join(getHarnessDir(repoPath), MAINTAIN_DIR);
27492
- if (fs15.existsSync(bundleDir)) {
27493
- fs15.rmSync(bundleDir, { recursive: true, force: true });
27792
+ const bundleDir = path16.join(getHarnessDir(repoPath), MAINTAIN_DIR);
27793
+ if (fs16.existsSync(bundleDir)) {
27794
+ fs16.rmSync(bundleDir, { recursive: true, force: true });
27494
27795
  }
27495
27796
  }
27496
27797
  function formatMaintainBundlePromptSection(report) {
@@ -27538,66 +27839,89 @@ function formatMaintainBundlePromptSection(report) {
27538
27839
  }
27539
27840
 
27540
27841
  // src/harness/stage-templates.ts
27541
- var fs17 = __toESM(require("fs"));
27542
- var path15 = __toESM(require("path"));
27842
+ var fs18 = __toESM(require("fs"));
27843
+ var path17 = __toESM(require("path"));
27543
27844
 
27544
27845
  // src/harness/parser.ts
27545
- var fs16 = __toESM(require("fs"));
27846
+ var fs17 = __toESM(require("fs"));
27546
27847
  function harnessExists(repoPath) {
27547
- return fs16.existsSync(`${repoPath}/.har/setup-infra.sh`);
27848
+ return fs17.existsSync(`${repoPath}/.har/setup-infra.sh`);
27548
27849
  }
27549
27850
 
27550
27851
  // src/harness/stage-templates.ts
27852
+ var STAGE_TEMPLATE_IDS = ["playwright", "rocketsim"];
27853
+ var TemplateManifestFileSchema = external_exports.object({
27854
+ src: external_exports.string().min(1),
27855
+ dest: external_exports.string().min(1),
27856
+ executable: external_exports.boolean().optional(),
27857
+ skipFlag: external_exports.string().optional()
27858
+ });
27859
+ var StageTemplateManifestSchema = external_exports.object({
27860
+ id: external_exports.enum(STAGE_TEMPLATE_IDS),
27861
+ stageId: external_exports.string().min(1),
27862
+ verificationStages: external_exports.array(external_exports.string().min(1)).min(1),
27863
+ stage: external_exports.record(external_exports.unknown()),
27864
+ files: external_exports.array(TemplateManifestFileSchema).min(1),
27865
+ optionalFiles: external_exports.array(TemplateManifestFileSchema).optional(),
27866
+ merge: external_exports.record(external_exports.string()).optional(),
27867
+ nextSteps: external_exports.array(external_exports.string().min(1)).min(1),
27868
+ docsPath: external_exports.string().min(1)
27869
+ });
27551
27870
  function resolveTemplateDir(templateId) {
27552
- const dir = path15.join(resolveTemplatesDir(), "stage-templates", templateId);
27553
- if (!fs17.existsSync(dir)) {
27871
+ const dir = path17.join(resolveTemplatesDir(), "stage-templates", templateId);
27872
+ if (!fs18.existsSync(dir)) {
27554
27873
  throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
27555
27874
  }
27556
27875
  return dir;
27557
27876
  }
27558
27877
  function readTemplateManifest(templateId) {
27559
- const manifestPath = path15.join(resolveTemplateDir(templateId), "template.manifest.json");
27560
- const raw = JSON.parse(fs17.readFileSync(manifestPath, "utf8"));
27561
- if (raw.id !== templateId) {
27562
- throw new Error(`Template manifest id mismatch: expected ${templateId}, got ${raw.id}`);
27878
+ const manifestPath = path17.join(resolveTemplateDir(templateId), "template.manifest.json");
27879
+ const parsed = StageTemplateManifestSchema.safeParse(
27880
+ JSON.parse(fs18.readFileSync(manifestPath, "utf8"))
27881
+ );
27882
+ if (!parsed.success) {
27883
+ throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
27884
+ }
27885
+ if (parsed.data.id !== templateId) {
27886
+ throw new Error(`Template manifest id mismatch: expected ${templateId}, got ${parsed.data.id}`);
27563
27887
  }
27564
- return raw;
27888
+ return parsed.data;
27565
27889
  }
27566
27890
  function ensureParentDir(filePath) {
27567
- const parent = path15.dirname(filePath);
27568
- if (!fs17.existsSync(parent)) {
27569
- fs17.mkdirSync(parent, { recursive: true });
27891
+ const parent = path17.dirname(filePath);
27892
+ if (!fs18.existsSync(parent)) {
27893
+ fs18.mkdirSync(parent, { recursive: true });
27570
27894
  }
27571
27895
  }
27572
27896
  function copyTemplateFile(templateDir, file, repoPath, force) {
27573
- const srcPath = path15.join(templateDir, file.src);
27574
- const destPath = path15.join(repoPath, file.dest);
27575
- if (!fs17.existsSync(srcPath)) {
27897
+ const srcPath = path17.join(templateDir, file.src);
27898
+ const destPath = path17.join(repoPath, file.dest);
27899
+ if (!fs18.existsSync(srcPath)) {
27576
27900
  throw new Error(`Template file missing: ${file.src}`);
27577
27901
  }
27578
- if (fs17.existsSync(destPath) && !force) {
27902
+ if (fs18.existsSync(destPath) && !force) {
27579
27903
  throw new Error(
27580
27904
  `File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
27581
27905
  );
27582
27906
  }
27583
27907
  ensureParentDir(destPath);
27584
- fs17.copyFileSync(srcPath, destPath);
27908
+ fs18.copyFileSync(srcPath, destPath);
27585
27909
  if (file.executable) {
27586
- fs17.chmodSync(destPath, 493);
27910
+ fs18.chmodSync(destPath, 493);
27587
27911
  }
27588
27912
  return { written: true, path: file.dest };
27589
27913
  }
27590
27914
  function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
27591
- const packagePath = path15.join(repoPath, "package.json");
27592
- if (!fs17.existsSync(packagePath)) {
27593
- throw new Error("No package.json in repo root. Add one before applying the Playwright template.");
27915
+ const packagePath = path17.join(repoPath, "package.json");
27916
+ if (!fs18.existsSync(packagePath)) {
27917
+ throw new Error("No package.json in repo root. Add one before applying this stage template.");
27594
27918
  }
27595
- const fragmentPath = path15.join(templateDir, fragmentRelPath);
27596
- if (!fs17.existsSync(fragmentPath)) {
27919
+ const fragmentPath = path17.join(templateDir, fragmentRelPath);
27920
+ if (!fs18.existsSync(fragmentPath)) {
27597
27921
  throw new Error(`Package fragment missing: ${fragmentRelPath}`);
27598
27922
  }
27599
- const pkg = JSON.parse(fs17.readFileSync(packagePath, "utf8"));
27600
- const fragment = JSON.parse(fs17.readFileSync(fragmentPath, "utf8"));
27923
+ const pkg = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
27924
+ const fragment = JSON.parse(fs18.readFileSync(fragmentPath, "utf8"));
27601
27925
  for (const section of ["scripts", "devDependencies"]) {
27602
27926
  const existing = pkg[section] ?? {};
27603
27927
  const incoming = fragment[section] ?? {};
@@ -27610,7 +27934,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
27610
27934
  }
27611
27935
  pkg[section] = existing;
27612
27936
  }
27613
- fs17.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
27937
+ fs18.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
27614
27938
  }
27615
27939
  function patchStageRegistry(repoPath, manifest, force) {
27616
27940
  const registry2 = readStageRegistry(repoPath);
@@ -27623,8 +27947,7 @@ function patchStageRegistry(repoPath, manifest, force) {
27623
27947
  }
27624
27948
  const stages = existing ? registry2.stages.map((s2) => s2.id === stage.id ? stage : s2) : [...registry2.stages, stage];
27625
27949
  const verificationStages = [...registry2.verificationStages ?? []];
27626
- const toAdd = manifest.verificationStages ?? (manifest.verificationStageId ? [manifest.verificationStageId] : [manifest.stageId]);
27627
- for (const id of toAdd) {
27950
+ for (const id of manifest.verificationStages) {
27628
27951
  if (!verificationStages.includes(id)) {
27629
27952
  verificationStages.push(id);
27630
27953
  }
@@ -27633,7 +27956,7 @@ function patchStageRegistry(repoPath, manifest, force) {
27633
27956
  if (verifyIdx >= 0) {
27634
27957
  stages[verifyIdx] = {
27635
27958
  ...stages[verifyIdx],
27636
- description: "Verification pipeline (quick smoke by default; --full adds tests, lint, and browser-e2e when installed)",
27959
+ description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`,
27637
27960
  acceptsArgs: ["--full"]
27638
27961
  };
27639
27962
  }
@@ -27651,8 +27974,8 @@ function assertHarnessPresent(repoPath) {
27651
27974
  }
27652
27975
  function assertStageNotPresent(repoPath, stageId, force) {
27653
27976
  if (force) return;
27654
- const scriptPath = path15.join(repoPath, ".har", "stages", `${stageId}.sh`);
27655
- if (fs17.existsSync(scriptPath)) {
27977
+ const scriptPath = path17.join(repoPath, ".har", "stages", `${stageId}.sh`);
27978
+ if (fs18.existsSync(scriptPath)) {
27656
27979
  throw new Error(
27657
27980
  `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
27658
27981
  );
@@ -27665,7 +27988,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
27665
27988
  }
27666
27989
  }
27667
27990
  function applyStageTemplate(repoPath, templateId, options = {}) {
27668
- const resolved = path15.resolve(repoPath);
27991
+ const resolved = path17.resolve(repoPath);
27669
27992
  const force = options.force ?? false;
27670
27993
  const warnings = [];
27671
27994
  const filesWritten = [];
@@ -27684,7 +28007,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
27684
28007
  if (file.skipFlag === "skipCi" && options.skipCi) {
27685
28008
  continue;
27686
28009
  }
27687
- if (fs17.existsSync(path15.join(resolved, file.dest)) && !force) {
28010
+ if (fs18.existsSync(path17.join(resolved, file.dest)) && !force) {
27688
28011
  warnings.push(`Skipped optional file (exists): ${file.dest}`);
27689
28012
  continue;
27690
28013
  }
@@ -27709,21 +28032,22 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
27709
28032
  for (const warning of warnings) {
27710
28033
  warn(` \u26A0 ${warning}`);
27711
28034
  }
27712
- const nextSteps = manifest.nextSteps ?? [
27713
- "npm install",
27714
- "npx playwright install",
27715
- "./.har/launch.sh 1",
27716
- `./.har/stages/${manifest.stageId}.sh 1`,
27717
- "npx playwright show-report .har/artifacts/browser-e2e/playwright-report"
27718
- ];
27719
28035
  return {
27720
28036
  templateId,
27721
28037
  stageId: manifest.stageId,
27722
28038
  filesWritten,
27723
28039
  warnings,
27724
- nextSteps
28040
+ nextSteps: manifest.nextSteps,
28041
+ docsPath: manifest.docsPath
27725
28042
  };
27726
28043
  }
28044
+ function listStageTemplateIds() {
28045
+ const root = path17.join(resolveTemplatesDir(), "stage-templates");
28046
+ if (!fs18.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
28047
+ return fs18.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
28048
+ (name) => STAGE_TEMPLATE_IDS.includes(name)
28049
+ );
28050
+ }
27727
28051
 
27728
28052
  // src/utils/validation.ts
27729
28053
  function requireApiKey() {
@@ -27747,11 +28071,11 @@ function validateAgentId(id, repoPath) {
27747
28071
  // src/core/harness.ts
27748
28072
  function listHarnessScripts(repoPath) {
27749
28073
  const harnessDir = getHarnessDir(repoPath);
27750
- if (!fs18.existsSync(harnessDir)) return [];
27751
- return fs18.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
28074
+ if (!fs19.existsSync(harnessDir)) return [];
28075
+ return fs19.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
27752
28076
  }
27753
28077
  function describeProject(repoPath) {
27754
- const resolved = path16.resolve(repoPath);
28078
+ const resolved = path18.resolve(repoPath);
27755
28079
  const manifest = readManifest(resolved);
27756
28080
  const present = harnessExists(resolved);
27757
28081
  return {
@@ -27771,8 +28095,8 @@ function describeProject(repoPath) {
27771
28095
  };
27772
28096
  }
27773
28097
  async function initHarness(options) {
27774
- const repoPath = path16.resolve(options.repoPath);
27775
- if (!fs18.existsSync(repoPath)) {
28098
+ const repoPath = path18.resolve(options.repoPath);
28099
+ if (!fs19.existsSync(repoPath)) {
27776
28100
  throw new Error(`Path not found: ${repoPath}`);
27777
28101
  }
27778
28102
  const scaffold = scaffoldHarnessBoilerplate(repoPath, {
@@ -27803,9 +28127,9 @@ async function initHarness(options) {
27803
28127
  };
27804
28128
  }
27805
28129
  async function maintainHarness(options) {
27806
- const repoPath = path16.resolve(options.repoPath);
28130
+ const repoPath = path18.resolve(options.repoPath);
27807
28131
  const harnessDir = getHarnessDir(repoPath);
27808
- if (!fs18.existsSync(harnessDir)) {
28132
+ if (!fs19.existsSync(harnessDir)) {
27809
28133
  throw new Error('No .har/ found. Run "har env init" first.');
27810
28134
  }
27811
28135
  if (options.auto) {
@@ -27855,9 +28179,111 @@ function addStageTemplate(repoPath, templateId, options = {}) {
27855
28179
  return applyStageTemplate(repoPath, templateId, options);
27856
28180
  }
27857
28181
 
28182
+ // src/harness/custom-stage.ts
28183
+ var fs20 = __toESM(require("fs"));
28184
+ var path19 = __toESM(require("path"));
28185
+ var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
28186
+ function addCustomStage(repoPath, options) {
28187
+ const resolved = path19.resolve(repoPath);
28188
+ if (!harnessExists(resolved)) {
28189
+ throw new Error('No .har/ harness found. Run "har env init" first.');
28190
+ }
28191
+ const id = options.id.trim();
28192
+ if (!STAGE_ID_PATTERN.test(id)) {
28193
+ throw new Error(
28194
+ `Invalid stage id "${id}". Use lowercase letters, digits, dots, dashes (e.g. "unit-tests").`
28195
+ );
28196
+ }
28197
+ const kind2 = options.kind ?? "custom";
28198
+ if (!HarnessStageKindSchema.options.includes(kind2)) {
28199
+ throw new Error(
28200
+ `Invalid stage kind "${kind2}". Available: ${HarnessStageKindSchema.options.join(", ")}`
28201
+ );
28202
+ }
28203
+ if (Boolean(options.command) === Boolean(options.script)) {
28204
+ throw new Error(
28205
+ 'Choose exactly one execution mode: --command "<shell command>" for a one-liner, or --script to scaffold .har/stages/<id>.sh.'
28206
+ );
28207
+ }
28208
+ const registry2 = readStageRegistry(resolved);
28209
+ const existing = registry2.stages.find((s2) => s2.id === id);
28210
+ if (existing && !options.force) {
28211
+ throw new Error(`Stage "${id}" already registered in .har/stages.json. Use --force to replace.`);
28212
+ }
28213
+ const filesWritten = [];
28214
+ const stage = {
28215
+ id,
28216
+ kind: kind2,
28217
+ description: options.description ?? `Custom ${kind2} stage`,
28218
+ requiresAgentId: true,
28219
+ artifacts: []
28220
+ };
28221
+ if (options.command) {
28222
+ stage.command = options.command;
28223
+ } else {
28224
+ const scriptRel = path19.join("stages", `${id}.sh`);
28225
+ const scriptAbs = path19.join(resolved, ".har", scriptRel);
28226
+ if (fs20.existsSync(scriptAbs) && !options.force) {
28227
+ throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
28228
+ }
28229
+ const skeletonPath = path19.join(
28230
+ resolveTemplatesDir(),
28231
+ "stage-templates",
28232
+ "custom-stage-skeleton.sh"
28233
+ );
28234
+ const skeleton = fs20.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
28235
+ fs20.mkdirSync(path19.dirname(scriptAbs), { recursive: true });
28236
+ fs20.writeFileSync(scriptAbs, skeleton);
28237
+ fs20.chmodSync(scriptAbs, 493);
28238
+ filesWritten.push(`.har/${scriptRel}`);
28239
+ stage.script = scriptRel;
28240
+ stage.artifacts = [
28241
+ {
28242
+ path: `.har/artifacts/${id}`,
28243
+ kind: "directory",
28244
+ description: `Artifacts for the ${id} stage`
28245
+ }
28246
+ ];
28247
+ }
28248
+ const parsedStage = HarnessStageSchema.parse(stage);
28249
+ const stages = existing ? registry2.stages.map((s2) => s2.id === id ? parsedStage : s2) : [...registry2.stages, parsedStage];
28250
+ const verification = options.verification ?? false;
28251
+ const verificationStages = [...registry2.verificationStages ?? []];
28252
+ if (verification && !verificationStages.includes(id)) {
28253
+ verificationStages.push(id);
28254
+ const verifyIdx = stages.findIndex((s2) => s2.id === "verify");
28255
+ if (verifyIdx >= 0) {
28256
+ stages[verifyIdx] = {
28257
+ ...stages[verifyIdx],
28258
+ description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`
28259
+ };
28260
+ }
28261
+ }
28262
+ writeStageRegistry(resolved, { ...registry2, stages, verificationStages });
28263
+ filesWritten.push(".har/stages.json");
28264
+ const nextSteps = options.command ? [
28265
+ `Try it: ${options.command.replace(/\{agentId\}/g, "1")}`,
28266
+ `Agents run it via the MCP tool har_run_stage (stageId: "${id}")`,
28267
+ ...verification ? ["./.har/verify.sh 1 --full # runs it as part of full verification"] : []
28268
+ ] : [
28269
+ `Edit .har/stages/${id}.sh \u2014 replace the TODO block with the real check`,
28270
+ "./.har/launch.sh 1",
28271
+ `./.har/stages/${id}.sh 1`,
28272
+ ...verification ? ["./.har/verify.sh 1 --full # runs it as part of full verification"] : []
28273
+ ];
28274
+ return {
28275
+ stageId: id,
28276
+ kind: kind2,
28277
+ mode: options.command ? "command" : "script",
28278
+ filesWritten,
28279
+ verification,
28280
+ nextSteps
28281
+ };
28282
+ }
28283
+
27858
28284
  // src/harness/adaptation-prompt.ts
27859
- var fs19 = __toESM(require("fs"));
27860
- var path17 = __toESM(require("path"));
28285
+ var fs21 = __toESM(require("fs"));
28286
+ var path20 = __toESM(require("path"));
27861
28287
  var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
27862
28288
  var PROFILE_HINTS = {
27863
28289
  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.",
@@ -27869,7 +28295,7 @@ function loadTemplate(name) {
27869
28295
  if (!filePath) {
27870
28296
  throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
27871
28297
  }
27872
- return fs19.readFileSync(filePath, "utf8");
28298
+ return fs21.readFileSync(filePath, "utf8");
27873
28299
  }
27874
28300
  function applyProfilePlaceholders(content, profile) {
27875
28301
  return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
@@ -27891,7 +28317,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
27891
28317
  }
27892
28318
  function writeAdaptationPrompt(repoPath, content) {
27893
28319
  const harnessDir = getHarnessDir(repoPath);
27894
- const filePath = path17.join(harnessDir, ADAPTATION_PROMPT_FILE);
28320
+ const filePath = path20.join(harnessDir, ADAPTATION_PROMPT_FILE);
27895
28321
  writeFileSafe(filePath, content);
27896
28322
  return filePath;
27897
28323
  }
@@ -27910,25 +28336,25 @@ function printAdaptationPrompt(content) {
27910
28336
  }
27911
28337
 
27912
28338
  // src/harness/cursor-rule.ts
27913
- var fs20 = __toESM(require("fs"));
27914
- var path18 = __toESM(require("path"));
27915
- var readline3 = __toESM(require("readline"));
28339
+ var fs22 = __toESM(require("fs"));
28340
+ var path21 = __toESM(require("path"));
28341
+ var readline4 = __toESM(require("readline"));
27916
28342
  var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
27917
28343
  function getCursorRulePath(repoPath) {
27918
- return path18.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
28344
+ return path21.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
27919
28345
  }
27920
28346
  function isCursorWorkspace(repoPath) {
27921
- return fs20.existsSync(path18.join(repoPath, ".cursor"));
28347
+ return fs22.existsSync(path21.join(repoPath, ".cursor"));
27922
28348
  }
27923
28349
  function cursorRuleExists(repoPath) {
27924
- return fs20.existsSync(getCursorRulePath(repoPath));
28350
+ return fs22.existsSync(getCursorRulePath(repoPath));
27925
28351
  }
27926
28352
  function scaffoldCursorRule(repoPath) {
27927
28353
  const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
27928
28354
  if (!templatePath) {
27929
28355
  throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
27930
28356
  }
27931
- const content = fs20.readFileSync(templatePath, "utf8");
28357
+ const content = fs22.readFileSync(templatePath, "utf8");
27932
28358
  writeFileSafe(getCursorRulePath(repoPath), content);
27933
28359
  }
27934
28360
  async function handleCursorRule(options) {
@@ -27962,20 +28388,20 @@ async function handleCursorRule(options) {
27962
28388
  }
27963
28389
  async function promptScaffoldCursorRule(exists) {
27964
28390
  const action = exists ? "Update" : "Create";
27965
- return askYesNo2(`${action} ${CURSOR_RULE_RELATIVE_PATH}? [Y/n]`);
28391
+ return askYesNo3(`${action} ${CURSOR_RULE_RELATIVE_PATH}? [Y/n]`);
27966
28392
  }
27967
- async function askYesNo2(question) {
27968
- const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
27969
- return new Promise((resolve26) => {
28393
+ async function askYesNo3(question) {
28394
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
28395
+ return new Promise((resolve28) => {
27970
28396
  process.stderr.write(`${question} `);
27971
28397
  rl.once("line", (answer) => {
27972
28398
  rl.close();
27973
28399
  const trimmed = answer.trim();
27974
28400
  if (trimmed === "") {
27975
- resolve26(true);
28401
+ resolve28(true);
27976
28402
  return;
27977
28403
  }
27978
- resolve26(/^y(es)?$/i.test(trimmed));
28404
+ resolve28(/^y(es)?$/i.test(trimmed));
27979
28405
  });
27980
28406
  });
27981
28407
  }
@@ -28054,26 +28480,26 @@ function buildStageResult(input) {
28054
28480
  }
28055
28481
 
28056
28482
  // src/core/local-executor.ts
28057
- var fs22 = __toESM(require("fs"));
28058
- var path20 = __toESM(require("path"));
28483
+ var fs24 = __toESM(require("fs"));
28484
+ var path23 = __toESM(require("path"));
28059
28485
 
28060
28486
  // src/core/slot-registry.ts
28061
- var fs21 = __toESM(require("fs"));
28062
- var path19 = __toESM(require("path"));
28487
+ var fs23 = __toESM(require("fs"));
28488
+ var path22 = __toESM(require("path"));
28063
28489
  function getSlotRegistryDir(repoPath) {
28064
- return path19.join(getHarnessDir(repoPath), "slots");
28490
+ return path22.join(getHarnessDir(repoPath), "slots");
28065
28491
  }
28066
28492
  function getSlotRegistryPath(repoPath, agentId) {
28067
- return path19.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
28493
+ return path22.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
28068
28494
  }
28069
28495
  function isSlotResumable(session) {
28070
28496
  return session?.status === "failed" || session?.status === "starting";
28071
28497
  }
28072
28498
  function readSlotRegistry(repoPath, agentId) {
28073
28499
  const file = getSlotRegistryPath(repoPath, agentId);
28074
- if (!fs21.existsSync(file)) return void 0;
28500
+ if (!fs23.existsSync(file)) return void 0;
28075
28501
  try {
28076
- const raw = JSON.parse(fs21.readFileSync(file, "utf8"));
28502
+ const raw = JSON.parse(fs23.readFileSync(file, "utf8"));
28077
28503
  const result = SlotRegistryEntrySchema.safeParse(raw);
28078
28504
  return result.success ? result.data : void 0;
28079
28505
  } catch {
@@ -28083,7 +28509,7 @@ function readSlotRegistry(repoPath, agentId) {
28083
28509
 
28084
28510
  // src/core/local-executor.ts
28085
28511
  function resolveRepoPath(repoPath) {
28086
- return path20.resolve(repoPath);
28512
+ return path23.resolve(repoPath);
28087
28513
  }
28088
28514
  function buildPreviewUrlsFromPorts(ports, env3) {
28089
28515
  const urls = {};
@@ -28135,14 +28561,14 @@ function substituteAgentId(value, agentId) {
28135
28561
  function resolveStageScriptPath(repoPath, stage) {
28136
28562
  const harnessDir = getHarnessDir(repoPath);
28137
28563
  if (stage.script) {
28138
- return path20.join(harnessDir, stage.script);
28564
+ return path23.join(harnessDir, stage.script);
28139
28565
  }
28140
28566
  if (stage.command) {
28141
28567
  const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
28142
- return path20.join(harnessDir, scriptName);
28568
+ return path23.join(harnessDir, scriptName);
28143
28569
  }
28144
- const stageScript = path20.join(harnessDir, "stages", `${stage.id}.sh`);
28145
- if (fs22.existsSync(stageScript)) {
28570
+ const stageScript = path23.join(harnessDir, "stages", `${stage.id}.sh`);
28571
+ if (fs24.existsSync(stageScript)) {
28146
28572
  return stageScript;
28147
28573
  }
28148
28574
  throw new Error(
@@ -28157,7 +28583,7 @@ function buildExecutionPlan(repoPath, stage, options) {
28157
28583
  ...harnessEnv,
28158
28584
  ...stage.env ?? {}
28159
28585
  };
28160
- const cwd = stage.cwd ? path20.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28586
+ const cwd = stage.cwd ? path23.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28161
28587
  const extraArgs = options.args ?? [];
28162
28588
  const launchFlagArgs = [];
28163
28589
  if (stage.kind === "launch" && options.launchFlags) {
@@ -28234,19 +28660,19 @@ var LocalScriptExecutor = class {
28234
28660
  const repoPath = resolveRepoPath(ctx.repoPath);
28235
28661
  const harnessDir = getHarnessDir(repoPath);
28236
28662
  const artifactsDirName = getArtifactsDir(repoPath);
28237
- const artifactsDir = path20.join(harnessDir, artifactsDirName);
28238
- if (!fs22.existsSync(artifactsDir)) return [];
28663
+ const artifactsDir = path23.join(harnessDir, artifactsDirName);
28664
+ if (!fs24.existsSync(artifactsDir)) return [];
28239
28665
  const entries = [];
28240
28666
  const walk = (dir, prefix) => {
28241
- for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
28242
- const full = path20.join(dir, entry.name);
28243
- const relative2 = path20.join(prefix, entry.name);
28667
+ for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
28668
+ const full = path23.join(dir, entry.name);
28669
+ const relative2 = path23.join(prefix, entry.name);
28244
28670
  if (entry.isDirectory()) {
28245
28671
  walk(full, relative2);
28246
28672
  continue;
28247
28673
  }
28248
28674
  if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
28249
- const stat = fs22.statSync(full);
28675
+ const stat = fs24.statSync(full);
28250
28676
  entries.push({
28251
28677
  path: full,
28252
28678
  relativePath: relative2,
@@ -28262,7 +28688,7 @@ var LocalScriptExecutor = class {
28262
28688
  var localScriptExecutor = new LocalScriptExecutor();
28263
28689
 
28264
28690
  // src/core/control-sync.ts
28265
- var path30 = __toESM(require("path"));
28691
+ var path33 = __toESM(require("path"));
28266
28692
 
28267
28693
  // src/core/control-config.ts
28268
28694
  var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
@@ -28274,32 +28700,32 @@ function isControlEnabled() {
28274
28700
  }
28275
28701
 
28276
28702
  // src/core/control-registry.ts
28277
- var fs23 = __toESM(require("fs"));
28278
- var os3 = __toESM(require("os"));
28279
- var path21 = __toESM(require("path"));
28703
+ var fs25 = __toESM(require("fs"));
28704
+ var os4 = __toESM(require("os"));
28705
+ var path24 = __toESM(require("path"));
28280
28706
  function getRegistryPath() {
28281
28707
  if (process.env.HAR_CONTROL_REGISTRY_PATH) {
28282
- return path21.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28708
+ return path24.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28283
28709
  }
28284
- return path21.join(os3.homedir(), ".har", "repos.json");
28710
+ return path24.join(os4.homedir(), ".har", "repos.json");
28285
28711
  }
28286
28712
  function readRegistry() {
28287
28713
  const registryPath = getRegistryPath();
28288
28714
  try {
28289
- if (!fs23.existsSync(registryPath)) return { repos: [] };
28290
- return JSON.parse(fs23.readFileSync(registryPath, "utf8"));
28715
+ if (!fs25.existsSync(registryPath)) return { repos: [] };
28716
+ return JSON.parse(fs25.readFileSync(registryPath, "utf8"));
28291
28717
  } catch {
28292
28718
  return { repos: [] };
28293
28719
  }
28294
28720
  }
28295
28721
  function writeRegistry(registry2) {
28296
28722
  const registryPath = getRegistryPath();
28297
- fs23.mkdirSync(path21.dirname(registryPath), { recursive: true });
28298
- fs23.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
28723
+ fs25.mkdirSync(path24.dirname(registryPath), { recursive: true });
28724
+ fs25.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
28299
28725
  }
28300
28726
  function recordRepoForControlSync(repoPath) {
28301
28727
  if (process.env.HAR_CONTROL_DISABLED === "true") return;
28302
- const resolved = path21.resolve(repoPath);
28728
+ const resolved = path24.resolve(repoPath);
28303
28729
  if (!readManifest(resolved)) return;
28304
28730
  const registry2 = readRegistry();
28305
28731
  if (registry2.repos.includes(resolved)) return;
@@ -28308,7 +28734,7 @@ function recordRepoForControlSync(repoPath) {
28308
28734
  }
28309
28735
  function listRegisteredRepos() {
28310
28736
  const registry2 = readRegistry();
28311
- const kept = registry2.repos.filter((repoPath) => fs23.existsSync(repoPath) && readManifest(repoPath));
28737
+ const kept = registry2.repos.filter((repoPath) => fs25.existsSync(repoPath) && readManifest(repoPath));
28312
28738
  if (kept.length !== registry2.repos.length) {
28313
28739
  writeRegistry({ repos: kept });
28314
28740
  }
@@ -28316,17 +28742,17 @@ function listRegisteredRepos() {
28316
28742
  }
28317
28743
 
28318
28744
  // src/core/slot-status.ts
28319
- var fs28 = __toESM(require("fs"));
28320
- var os6 = __toESM(require("os"));
28321
- var path27 = __toESM(require("path"));
28745
+ var fs30 = __toESM(require("fs"));
28746
+ var os7 = __toESM(require("os"));
28747
+ var path30 = __toESM(require("path"));
28322
28748
  var import_child_process7 = require("child_process");
28323
28749
 
28324
28750
  // src/core/runs.ts
28325
28751
  var crypto2 = __toESM(require("crypto"));
28326
28752
  var import_child_process2 = require("child_process");
28327
- var fs24 = __toESM(require("fs"));
28328
- var os4 = __toESM(require("os"));
28329
- var path22 = __toESM(require("path"));
28753
+ var fs26 = __toESM(require("fs"));
28754
+ var os5 = __toESM(require("os"));
28755
+ var path25 = __toESM(require("path"));
28330
28756
  var RUNS_DIR = "runs";
28331
28757
  function gitCommonDir(cwd) {
28332
28758
  try {
@@ -28335,7 +28761,7 @@ function gitCommonDir(cwd) {
28335
28761
  encoding: "utf8",
28336
28762
  stdio: ["pipe", "pipe", "ignore"]
28337
28763
  }).trim();
28338
- return out ? path22.resolve(cwd, out) : void 0;
28764
+ return out ? path25.resolve(cwd, out) : void 0;
28339
28765
  } catch {
28340
28766
  return void 0;
28341
28767
  }
@@ -28357,7 +28783,7 @@ function gitPrefix(cwd) {
28357
28783
  }
28358
28784
  }
28359
28785
  function getRunsDir(harnessRoot) {
28360
- return path22.join(getHarnessDir(harnessRoot), RUNS_DIR);
28786
+ return path25.join(getHarnessDir(harnessRoot), RUNS_DIR);
28361
28787
  }
28362
28788
  function formatLocalDate(d2) {
28363
28789
  const y2 = d2.getFullYear();
@@ -28377,24 +28803,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
28377
28803
  const timePart = formatLocalTime(started);
28378
28804
  const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
28379
28805
  let filename = `${timePart}_${stageId}${agentPart}.json`;
28380
- const fullPath = path22.join(runsDir, dateFolder, filename);
28381
- if (fs24.existsSync(fullPath)) {
28806
+ const fullPath = path25.join(runsDir, dateFolder, filename);
28807
+ if (fs26.existsSync(fullPath)) {
28382
28808
  filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
28383
28809
  }
28384
- return path22.join(dateFolder, filename);
28810
+ return path25.join(dateFolder, filename);
28385
28811
  }
28386
28812
  function resolveRunFilePath(harnessRoot, run2) {
28387
28813
  if (run2.relativePath) {
28388
- return path22.join(getRunsDir(harnessRoot), run2.relativePath);
28814
+ return path25.join(getRunsDir(harnessRoot), run2.relativePath);
28389
28815
  }
28390
- return path22.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28816
+ return path25.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28391
28817
  }
28392
28818
  function collectRunFiles(runsDir) {
28393
- if (!fs24.existsSync(runsDir)) return [];
28819
+ if (!fs26.existsSync(runsDir)) return [];
28394
28820
  const files = [];
28395
28821
  const walk = (dir) => {
28396
- for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
28397
- const full = path22.join(dir, entry.name);
28822
+ for (const entry of fs26.readdirSync(dir, { withFileTypes: true })) {
28823
+ const full = path25.join(dir, entry.name);
28398
28824
  if (entry.isDirectory()) {
28399
28825
  walk(full);
28400
28826
  } else if (entry.name.endsWith(".json")) {
@@ -28408,30 +28834,30 @@ function collectRunFiles(runsDir) {
28408
28834
  function resolveAgentWorkDir(harnessRoot, agentId) {
28409
28835
  if (agentId === void 0) return void 0;
28410
28836
  const entry = readSlotRegistry(harnessRoot, agentId);
28411
- if (entry?.workDir && fs24.existsSync(entry.workDir)) return entry.workDir;
28837
+ if (entry?.workDir && fs26.existsSync(entry.workDir)) return entry.workDir;
28412
28838
  const env3 = readHarnessEnv(harnessRoot);
28413
- const projectName = env3.HARNESS_PROJECT_NAME ?? path22.basename(harnessRoot);
28414
- const worktreeDir = path22.join(os4.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
28839
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
28840
+ const worktreeDir = path25.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
28415
28841
  const relPrefix = gitPrefix(harnessRoot);
28416
28842
  const sessionEnvFiles = [];
28417
- const worktreesRoot = path22.join(os4.homedir(), "worktrees");
28418
- if (fs24.existsSync(worktreesRoot)) {
28843
+ const worktreesRoot = path25.join(os5.homedir(), "worktrees");
28844
+ if (fs26.existsSync(worktreesRoot)) {
28419
28845
  const suffix = `-har-agent-${agentId}-`;
28420
- for (const entry2 of fs24.readdirSync(worktreesRoot, { withFileTypes: true })) {
28846
+ for (const entry2 of fs26.readdirSync(worktreesRoot, { withFileTypes: true })) {
28421
28847
  if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
28422
- const sessionDir = path22.join(worktreesRoot, entry2.name);
28848
+ const sessionDir = path25.join(worktreesRoot, entry2.name);
28423
28849
  if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
28424
- sessionEnvFiles.push(path22.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
28850
+ sessionEnvFiles.push(path25.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
28425
28851
  }
28426
28852
  }
28427
28853
  const candidates = [
28428
- path22.join(worktreeDir, `.env.agent.${agentId}`),
28429
- path22.join(harnessRoot, `.env.agent.${agentId}`),
28854
+ path25.join(worktreeDir, `.env.agent.${agentId}`),
28855
+ path25.join(harnessRoot, `.env.agent.${agentId}`),
28430
28856
  ...sessionEnvFiles.sort()
28431
28857
  ];
28432
28858
  for (const envFile of candidates) {
28433
- if (!fs24.existsSync(envFile)) continue;
28434
- const content = fs24.readFileSync(envFile, "utf8");
28859
+ if (!fs26.existsSync(envFile)) continue;
28860
+ const content = fs26.readFileSync(envFile, "utf8");
28435
28861
  const match = content.match(/^REPO_ROOT=(.+)$/m);
28436
28862
  if (match) return match[1].trim();
28437
28863
  }
@@ -28443,11 +28869,11 @@ function createRun(ctx, meta) {
28443
28869
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
28444
28870
  const runId = crypto2.randomUUID();
28445
28871
  const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
28446
- const runFilePath = path22.join(runsDir, relativePath);
28447
- fs24.mkdirSync(path22.dirname(runFilePath), { recursive: true });
28872
+ const runFilePath = path25.join(runsDir, relativePath);
28873
+ fs26.mkdirSync(path25.dirname(runFilePath), { recursive: true });
28448
28874
  const run2 = RunRecordSchema.parse({
28449
28875
  runId,
28450
- repoPath: path22.resolve(ctx.repoPath),
28876
+ repoPath: path25.resolve(ctx.repoPath),
28451
28877
  harnessRoot,
28452
28878
  stageId: meta.stageId,
28453
28879
  kind: meta.kind,
@@ -28458,7 +28884,7 @@ function createRun(ctx, meta) {
28458
28884
  relativePath,
28459
28885
  trigger: ctx.trigger ?? "cli"
28460
28886
  });
28461
- fs24.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
28887
+ fs26.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
28462
28888
  return run2;
28463
28889
  }
28464
28890
  function finishRun(repoPath, runId, update) {
@@ -28477,13 +28903,13 @@ function finishRun(repoPath, runId, update) {
28477
28903
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
28478
28904
  });
28479
28905
  const runPath = resolveRunFilePath(harnessRoot, finished);
28480
- fs24.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
28906
+ fs26.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
28481
28907
  return finished;
28482
28908
  }
28483
28909
  function findRunRecord(harnessRoot, runId) {
28484
28910
  const runsDir = getRunsDir(harnessRoot);
28485
28911
  for (const filePath of collectRunFiles(runsDir)) {
28486
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs24.readFileSync(filePath, "utf8")));
28912
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
28487
28913
  if (parsed.success && parsed.data.runId === runId) {
28488
28914
  return parsed.data;
28489
28915
  }
@@ -28495,10 +28921,10 @@ function getRun(repoPath, runId) {
28495
28921
  }
28496
28922
  function listRuns(repoPath, filter = {}) {
28497
28923
  const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
28498
- if (!fs24.existsSync(runsDir)) return [];
28924
+ if (!fs26.existsSync(runsDir)) return [];
28499
28925
  const runs = [];
28500
28926
  for (const filePath of collectRunFiles(runsDir)) {
28501
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs24.readFileSync(filePath, "utf8")));
28927
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
28502
28928
  if (!parsed.success) continue;
28503
28929
  if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
28504
28930
  runs.push(parsed.data);
@@ -28511,17 +28937,17 @@ function listRuns(repoPath, filter = {}) {
28511
28937
  }
28512
28938
 
28513
28939
  // src/core/slot-preflight.ts
28514
- var path26 = __toESM(require("path"));
28940
+ var path29 = __toESM(require("path"));
28515
28941
 
28516
28942
  // src/core/control-port.ts
28517
28943
  var import_child_process4 = require("child_process");
28518
- var fs26 = __toESM(require("fs"));
28519
- var path24 = __toESM(require("path"));
28944
+ var fs28 = __toESM(require("fs"));
28945
+ var path27 = __toESM(require("path"));
28520
28946
 
28521
28947
  // src/core/slot-ports.ts
28522
28948
  var import_child_process3 = require("child_process");
28523
- var fs25 = __toESM(require("fs"));
28524
- var path23 = __toESM(require("path"));
28949
+ var fs27 = __toESM(require("fs"));
28950
+ var path26 = __toESM(require("path"));
28525
28951
  function portStep(env3) {
28526
28952
  return Number(env3.HARNESS_PORT_STEP ?? 10);
28527
28953
  }
@@ -28577,7 +29003,7 @@ function allocateAppPorts(repoPath, agentId) {
28577
29003
  }
28578
29004
  function harnessUsesPm2(repoPath) {
28579
29005
  const harnessRoot = resolveHarnessRoot(repoPath);
28580
- return fs25.existsSync(path23.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29006
+ return fs27.existsSync(path26.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
28581
29007
  }
28582
29008
 
28583
29009
  // src/core/control-port.ts
@@ -28632,13 +29058,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
28632
29058
  return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
28633
29059
  }
28634
29060
  function inspectControlUpReadiness(repoPath) {
28635
- const resolved = path24.resolve(repoPath);
29061
+ const resolved = path27.resolve(repoPath);
28636
29062
  const port = parseControlHostPort();
28637
29063
  const containers = listDockerContainers();
28638
29064
  const control = controlContainerOnPort(containers, port);
28639
29065
  const warnings = [];
28640
- const controlHarnessDir = path24.join(resolved, "control", ".har");
28641
- const harnessSlot1Active = fs26.existsSync(controlHarnessDir) && readSlotRegistry(path24.join(resolved, "control"), 1)?.status === "active";
29066
+ const controlHarnessDir = path27.join(resolved, "control", ".har");
29067
+ const harnessSlot1Active = fs28.existsSync(controlHarnessDir) && readSlotRegistry(path27.join(resolved, "control"), 1)?.status === "active";
28642
29068
  if (control) {
28643
29069
  return {
28644
29070
  warnings,
@@ -28669,9 +29095,9 @@ function inspectControlUpReadiness(repoPath) {
28669
29095
  }
28670
29096
 
28671
29097
  // src/core/slot-launch-guard-occupied.ts
28672
- var fs27 = __toESM(require("fs"));
28673
- var os5 = __toESM(require("os"));
28674
- var path25 = __toESM(require("path"));
29098
+ var fs29 = __toESM(require("fs"));
29099
+ var os6 = __toESM(require("os"));
29100
+ var path28 = __toESM(require("path"));
28675
29101
  var import_child_process5 = require("child_process");
28676
29102
  function runGit(cwd, args) {
28677
29103
  try {
@@ -28690,7 +29116,7 @@ function worktreeDirty(worktreePath) {
28690
29116
  }
28691
29117
  function gitCommonDir2(cwd) {
28692
29118
  const out = runGit(cwd, "rev-parse --git-common-dir");
28693
- return out ? path25.resolve(cwd, out) : void 0;
29119
+ return out ? path28.resolve(cwd, out) : void 0;
28694
29120
  }
28695
29121
  function sameGitCheckout2(a2, b2) {
28696
29122
  const left2 = gitCommonDir2(a2);
@@ -28699,20 +29125,20 @@ function sameGitCheckout2(a2, b2) {
28699
29125
  }
28700
29126
  function discoverWorktree(harnessRoot, agentId, projectName) {
28701
29127
  const session = readSlotRegistry(harnessRoot, agentId);
28702
- if (session?.worktreePath && fs27.existsSync(session.worktreePath)) {
29128
+ if (session?.worktreePath && fs29.existsSync(session.worktreePath)) {
28703
29129
  return session.worktreePath;
28704
29130
  }
28705
- const legacy = path25.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
28706
- if (fs27.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
28707
- const worktreesRoot = path25.join(os5.homedir(), "worktrees");
28708
- if (!fs27.existsSync(worktreesRoot)) return void 0;
29131
+ const legacy = path28.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29132
+ if (fs29.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
29133
+ const worktreesRoot = path28.join(os6.homedir(), "worktrees");
29134
+ if (!fs29.existsSync(worktreesRoot)) return void 0;
28709
29135
  const suffix = `-har-agent-${agentId}-`;
28710
29136
  const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
28711
- for (const name of fs27.readdirSync(worktreesRoot)) {
29137
+ for (const name of fs29.readdirSync(worktreesRoot)) {
28712
29138
  if (!name.includes(suffix)) continue;
28713
- const candidate = path25.join(worktreesRoot, name);
29139
+ const candidate = path28.join(worktreesRoot, name);
28714
29140
  if (!sameGitCheckout2(harnessRoot, candidate)) continue;
28715
- if (!fs27.existsSync(path25.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29141
+ if (!fs29.existsSync(path28.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
28716
29142
  return candidate;
28717
29143
  }
28718
29144
  return void 0;
@@ -28720,13 +29146,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
28720
29146
  function collectOccupiedSlot(repoPath, agentId) {
28721
29147
  const harnessRoot = resolveHarnessRoot(repoPath);
28722
29148
  const env3 = readHarnessEnv(harnessRoot);
28723
- const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
29149
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path28.basename(harnessRoot);
28724
29150
  const session = readSlotRegistry(harnessRoot, agentId);
28725
29151
  const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
28726
29152
  const workDir = session?.workDir;
28727
- const envInWorkDir = workDir ? fs27.existsSync(path25.join(workDir, `.env.agent.${agentId}`)) : false;
28728
- const envInRoot = fs27.existsSync(path25.join(harnessRoot, `.env.agent.${agentId}`));
28729
- const envInWorktree = worktreePath ? fs27.existsSync(path25.join(worktreePath, `.env.agent.${agentId}`)) : false;
29153
+ const envInWorkDir = workDir ? fs29.existsSync(path28.join(workDir, `.env.agent.${agentId}`)) : false;
29154
+ const envInRoot = fs29.existsSync(path28.join(harnessRoot, `.env.agent.${agentId}`));
29155
+ const envInWorktree = worktreePath ? fs29.existsSync(path28.join(worktreePath, `.env.agent.${agentId}`)) : false;
28730
29156
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
28731
29157
  if (!active) return void 0;
28732
29158
  const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
@@ -28879,7 +29305,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
28879
29305
  function inspectSlotReadiness(repoPath, agentId, options = {}) {
28880
29306
  const harnessRoot = resolveHarnessRoot(repoPath);
28881
29307
  const env3 = readHarnessEnv(harnessRoot);
28882
- const projectName = env3.HARNESS_PROJECT_NAME ?? path26.basename(harnessRoot);
29308
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
28883
29309
  const usesPm2 = harnessUsesPm2(repoPath);
28884
29310
  const blockers = [];
28885
29311
  const remediations = [];
@@ -29081,7 +29507,7 @@ function readWorktreeBranch(worktreePath) {
29081
29507
  }
29082
29508
  function gitCommonDir3(cwd) {
29083
29509
  const out = runGit2(cwd, "rev-parse --git-common-dir");
29084
- return out ? path27.resolve(cwd, out) : void 0;
29510
+ return out ? path30.resolve(cwd, out) : void 0;
29085
29511
  }
29086
29512
  function sameGitCheckout3(a2, b2) {
29087
29513
  const left2 = gitCommonDir3(a2);
@@ -29093,12 +29519,12 @@ function gitPrefix2(cwd) {
29093
29519
  return out ?? "";
29094
29520
  }
29095
29521
  function discoverSessionWorktreePath(harnessRoot, agentId) {
29096
- const worktreesRoot = path27.join(os6.homedir(), "worktrees");
29097
- if (!fs28.existsSync(worktreesRoot)) return void 0;
29522
+ const worktreesRoot = path30.join(os7.homedir(), "worktrees");
29523
+ if (!fs30.existsSync(worktreesRoot)) return void 0;
29098
29524
  const suffix = `-har-agent-${agentId}-`;
29099
29525
  const relPrefix = gitPrefix2(harnessRoot);
29100
- const matches = fs28.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path27.join(worktreesRoot, entry.name)).filter(
29101
- (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs28.existsSync(path27.join(candidate, relPrefix, `.env.agent.${agentId}`))
29526
+ const matches = fs30.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path30.join(worktreesRoot, entry.name)).filter(
29527
+ (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs30.existsSync(path30.join(candidate, relPrefix, `.env.agent.${agentId}`))
29102
29528
  );
29103
29529
  return matches.sort()[0];
29104
29530
  }
@@ -29173,18 +29599,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
29173
29599
  }
29174
29600
  function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
29175
29601
  const env3 = readHarnessEnv(harnessRoot);
29176
- const projectName = env3.HARNESS_PROJECT_NAME ?? path27.basename(harnessRoot);
29602
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29177
29603
  const session = readSlotRegistry(harnessRoot, agentId);
29178
- const legacyWorktreePath = path27.join(
29179
- os6.homedir(),
29604
+ const legacyWorktreePath = path30.join(
29605
+ os7.homedir(),
29180
29606
  "worktrees",
29181
29607
  `${projectName}-agent-${agentId}`
29182
29608
  );
29183
- const worktreePath = session?.worktreePath && fs28.existsSync(session.worktreePath) ? session.worktreePath : fs28.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
29609
+ const worktreePath = session?.worktreePath && fs30.existsSync(session.worktreePath) ? session.worktreePath : fs30.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
29184
29610
  const workDir = resolveAgentWorkDir(harnessRoot, agentId);
29185
- const envInWorkDir = workDir ? fs28.existsSync(path27.join(workDir, `.env.agent.${agentId}`)) : false;
29186
- const envInRoot = fs28.existsSync(path27.join(harnessRoot, `.env.agent.${agentId}`));
29187
- const envInWorktree = worktreePath ? fs28.existsSync(path27.join(worktreePath, `.env.agent.${agentId}`)) : false;
29611
+ const envInWorkDir = workDir ? fs30.existsSync(path30.join(workDir, `.env.agent.${agentId}`)) : false;
29612
+ const envInRoot = fs30.existsSync(path30.join(harnessRoot, `.env.agent.${agentId}`));
29613
+ const envInWorktree = worktreePath ? fs30.existsSync(path30.join(worktreePath, `.env.agent.${agentId}`)) : false;
29188
29614
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29189
29615
  const latest = latestRunForAgent(runs, agentId);
29190
29616
  const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
@@ -29237,7 +29663,7 @@ function collectEnvironmentStatus(repoPath) {
29237
29663
  const slotIds = getAgentSlotIds(harnessRoot);
29238
29664
  const pm2Procs = listPm2Processes2();
29239
29665
  return {
29240
- repoPath: path27.resolve(repoPath),
29666
+ repoPath: path30.resolve(repoPath),
29241
29667
  harnessRoot,
29242
29668
  gitRemote: readGitRemote(harnessRoot),
29243
29669
  profile: manifest?.profile,
@@ -29248,13 +29674,13 @@ function collectEnvironmentStatus(repoPath) {
29248
29674
 
29249
29675
  // src/core/validations.ts
29250
29676
  var crypto3 = __toESM(require("crypto"));
29251
- var fs30 = __toESM(require("fs"));
29252
- var path29 = __toESM(require("path"));
29677
+ var fs32 = __toESM(require("fs"));
29678
+ var path32 = __toESM(require("path"));
29253
29679
 
29254
29680
  // src/core/change-batch.ts
29255
- var fs29 = __toESM(require("fs"));
29256
- var os7 = __toESM(require("os"));
29257
- var path28 = __toESM(require("path"));
29681
+ var fs31 = __toESM(require("fs"));
29682
+ var os8 = __toESM(require("os"));
29683
+ var path31 = __toESM(require("path"));
29258
29684
  function git(checkoutDir, args, env3) {
29259
29685
  const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
29260
29686
  if (result.code !== 0) {
@@ -29270,7 +29696,7 @@ function isCheckoutRoot(checkoutDir) {
29270
29696
  const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
29271
29697
  if (!toplevel) return false;
29272
29698
  try {
29273
- return fs29.realpathSync(toplevel) === fs29.realpathSync(checkoutDir);
29699
+ return fs31.realpathSync(toplevel) === fs31.realpathSync(checkoutDir);
29274
29700
  } catch {
29275
29701
  return false;
29276
29702
  }
@@ -29292,7 +29718,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
29292
29718
  if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
29293
29719
  for (const dir of ["rebase-merge", "rebase-apply"]) {
29294
29720
  const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
29295
- if (gitPath && fs29.existsSync(path28.resolve(checkoutDir, gitPath))) return true;
29721
+ if (gitPath && fs31.existsSync(path31.resolve(checkoutDir, gitPath))) return true;
29296
29722
  }
29297
29723
  return false;
29298
29724
  }
@@ -29315,8 +29741,8 @@ function parseNameStatus(output) {
29315
29741
  return files;
29316
29742
  }
29317
29743
  function computeWorktreeSnapshot(checkoutDir) {
29318
- const tmpDir = fs29.mkdtempSync(path28.join(os7.tmpdir(), "har-idx-"));
29319
- const tmpIndex = path28.join(tmpDir, "index");
29744
+ const tmpDir = fs31.mkdtempSync(path31.join(os8.tmpdir(), "har-idx-"));
29745
+ const tmpIndex = path31.join(tmpDir, "index");
29320
29746
  const env3 = { GIT_INDEX_FILE: tmpIndex };
29321
29747
  try {
29322
29748
  const headTree = getHeadTree(checkoutDir);
@@ -29339,35 +29765,35 @@ function computeWorktreeSnapshot(checkoutDir) {
29339
29765
  changedFiles: parseNameStatus(diffOutput)
29340
29766
  };
29341
29767
  } finally {
29342
- fs29.rmSync(tmpDir, { recursive: true, force: true });
29768
+ fs31.rmSync(tmpDir, { recursive: true, force: true });
29343
29769
  }
29344
29770
  }
29345
29771
 
29346
29772
  // src/core/validations.ts
29347
29773
  var VALIDATIONS_DIR = "validations";
29348
29774
  function getValidationsDir(checkoutDir) {
29349
- return path29.join(checkoutDir, ".har", VALIDATIONS_DIR);
29775
+ return path32.join(checkoutDir, ".har", VALIDATIONS_DIR);
29350
29776
  }
29351
29777
  function validationPath(checkoutDir, treeHash) {
29352
- return path29.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29778
+ return path32.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29353
29779
  }
29354
29780
  function writeRecord(checkoutDir, record2) {
29355
29781
  const dir = getValidationsDir(checkoutDir);
29356
- fs30.mkdirSync(dir, { recursive: true });
29357
- fs30.writeFileSync(
29782
+ fs32.mkdirSync(dir, { recursive: true });
29783
+ fs32.writeFileSync(
29358
29784
  validationPath(checkoutDir, record2.treeHash),
29359
29785
  `${JSON.stringify(record2, null, 2)}
29360
29786
  `
29361
29787
  );
29362
29788
  }
29363
29789
  function ensureValidationsIgnored(checkoutDir) {
29364
- const harDir = path29.join(checkoutDir, ".har");
29365
- if (!fs30.existsSync(harDir)) return;
29366
- const gitignorePath = path29.join(harDir, ".gitignore");
29367
- const content = fs30.existsSync(gitignorePath) ? fs30.readFileSync(gitignorePath, "utf8") : "";
29790
+ const harDir = path32.join(checkoutDir, ".har");
29791
+ if (!fs32.existsSync(harDir)) return;
29792
+ const gitignorePath = path32.join(harDir, ".gitignore");
29793
+ const content = fs32.existsSync(gitignorePath) ? fs32.readFileSync(gitignorePath, "utf8") : "";
29368
29794
  if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
29369
29795
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
29370
- fs30.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29796
+ fs32.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29371
29797
  `);
29372
29798
  }
29373
29799
  function recordValidation(input) {
@@ -29384,8 +29810,8 @@ function recordValidation(input) {
29384
29810
  treeHash: snapshot.treeHash,
29385
29811
  headSha: snapshot.headSha,
29386
29812
  branch: snapshot.branch,
29387
- workDir: path29.resolve(input.checkoutDir),
29388
- harnessRoot: path29.resolve(input.harnessRoot),
29813
+ workDir: path32.resolve(input.checkoutDir),
29814
+ harnessRoot: path32.resolve(input.harnessRoot),
29389
29815
  agentId: input.agentId,
29390
29816
  status: input.status,
29391
29817
  full: input.full,
@@ -29397,16 +29823,16 @@ function recordValidation(input) {
29397
29823
  committedAt: existing?.committedAt
29398
29824
  });
29399
29825
  writeRecord(input.checkoutDir, record2);
29400
- if (path29.resolve(input.harnessRoot) !== path29.resolve(input.checkoutDir)) {
29826
+ if (path32.resolve(input.harnessRoot) !== path32.resolve(input.checkoutDir)) {
29401
29827
  writeRecord(input.harnessRoot, record2);
29402
29828
  }
29403
29829
  return record2;
29404
29830
  }
29405
29831
  function findValidation(checkoutDir, treeHash) {
29406
29832
  const file = validationPath(checkoutDir, treeHash);
29407
- if (!fs30.existsSync(file)) return void 0;
29833
+ if (!fs32.existsSync(file)) return void 0;
29408
29834
  try {
29409
- return ValidationRecordSchema.parse(JSON.parse(fs30.readFileSync(file, "utf8")));
29835
+ return ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(file, "utf8")));
29410
29836
  } catch {
29411
29837
  return void 0;
29412
29838
  }
@@ -29421,19 +29847,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
29421
29847
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29422
29848
  };
29423
29849
  writeRecord(checkoutDir, updated);
29424
- if (record2.harnessRoot && path29.resolve(record2.harnessRoot) !== path29.resolve(checkoutDir) && fs30.existsSync(path29.join(record2.harnessRoot, ".har"))) {
29850
+ if (record2.harnessRoot && path32.resolve(record2.harnessRoot) !== path32.resolve(checkoutDir) && fs32.existsSync(path32.join(record2.harnessRoot, ".har"))) {
29425
29851
  writeRecord(record2.harnessRoot, updated);
29426
29852
  }
29427
29853
  return updated;
29428
29854
  }
29429
29855
  function listValidations(harnessRoot) {
29430
29856
  const dir = getValidationsDir(harnessRoot);
29431
- if (!fs30.existsSync(dir)) return [];
29857
+ if (!fs32.existsSync(dir)) return [];
29432
29858
  const records = [];
29433
- for (const entry of fs30.readdirSync(dir)) {
29859
+ for (const entry of fs32.readdirSync(dir)) {
29434
29860
  if (!entry.endsWith(".json")) continue;
29435
29861
  try {
29436
- records.push(ValidationRecordSchema.parse(JSON.parse(fs30.readFileSync(path29.join(dir, entry), "utf8"))));
29862
+ records.push(ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(path32.join(dir, entry), "utf8"))));
29437
29863
  } catch {
29438
29864
  }
29439
29865
  }
@@ -29491,7 +29917,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
29491
29917
  const deadline = Date.now() + timeoutMs;
29492
29918
  while (Date.now() < deadline) {
29493
29919
  if (await isControlApiReachable(apiUrl)) return true;
29494
- await new Promise((resolve26) => setTimeout(resolve26, intervalMs));
29920
+ await new Promise((resolve28) => setTimeout(resolve28, intervalMs));
29495
29921
  }
29496
29922
  return false;
29497
29923
  }
@@ -29503,7 +29929,7 @@ async function syncAllKnownReposWithControl(options) {
29503
29929
  }
29504
29930
  const repoPaths = new Set(listRegisteredRepos());
29505
29931
  if (options?.cwd) {
29506
- const cwd = path30.resolve(options.cwd);
29932
+ const cwd = path33.resolve(options.cwd);
29507
29933
  if (readManifest(cwd)) repoPaths.add(cwd);
29508
29934
  }
29509
29935
  try {
@@ -29511,7 +29937,7 @@ async function syncAllKnownReposWithControl(options) {
29511
29937
  if (listResponse.ok) {
29512
29938
  const repos = await listResponse.json();
29513
29939
  for (const repo of repos) {
29514
- const resolved = path30.resolve(repo.path);
29940
+ const resolved = path33.resolve(repo.path);
29515
29941
  if (readManifest(resolved)) repoPaths.add(resolved);
29516
29942
  }
29517
29943
  }
@@ -29530,7 +29956,7 @@ async function syncAllKnownReposWithControl(options) {
29530
29956
  return { synced, failed };
29531
29957
  }
29532
29958
  async function registerRepoWithControl(options) {
29533
- const repoPath = path30.resolve(options.repoPath);
29959
+ const repoPath = path33.resolve(options.repoPath);
29534
29960
  const apiUrl = options.apiUrl ?? getControlApiUrl();
29535
29961
  const manifest = readManifest(repoPath);
29536
29962
  const stagesRegistry = readStageRegistry(repoPath);
@@ -29543,7 +29969,7 @@ async function registerRepoWithControl(options) {
29543
29969
  return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
29544
29970
  }
29545
29971
  async function syncRepoWithControl(options) {
29546
- const repoPath = path30.resolve(options.repoPath);
29972
+ const repoPath = path33.resolve(options.repoPath);
29547
29973
  const apiUrl = options.apiUrl ?? getControlApiUrl();
29548
29974
  if (options.cloud) {
29549
29975
  const remote = createRemoteExecutor();
@@ -29571,7 +29997,7 @@ async function syncRepoWithControl(options) {
29571
29997
  const listResponse = await fetch(`${apiUrl}/api/repos`);
29572
29998
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
29573
29999
  const repos = await listResponse.json();
29574
- const existing = repos.find((r2) => path30.resolve(r2.path) === repoPath);
30000
+ const existing = repos.find((r2) => path33.resolve(r2.path) === repoPath);
29575
30001
  if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
29576
30002
  await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
29577
30003
  return;
@@ -29978,6 +30404,13 @@ var envCommand = {
29978
30404
  type: "boolean",
29979
30405
  default: false,
29980
30406
  describe: "Skip Cursor rule scaffolding"
30407
+ }).option("agents", {
30408
+ type: "string",
30409
+ describe: "Scaffold agent skills for these targets (comma-separated: claude,cursor,codex); auto-detected when omitted"
30410
+ }).option("no-agents", {
30411
+ type: "boolean",
30412
+ default: false,
30413
+ describe: "Skip agent skills scaffolding"
29981
30414
  }),
29982
30415
  handleInit
29983
30416
  ).command(
@@ -30002,14 +30435,46 @@ var envCommand = {
30002
30435
  type: "boolean",
30003
30436
  default: false,
30004
30437
  describe: "Skip Cursor rule scaffolding"
30438
+ }).option("agents", {
30439
+ type: "string",
30440
+ describe: "Scaffold agent skills for these targets (comma-separated: claude,cursor,codex); auto-detected when omitted"
30441
+ }).option("no-agents", {
30442
+ type: "boolean",
30443
+ default: false,
30444
+ describe: "Skip agent skills scaffolding"
30005
30445
  }),
30006
30446
  handleMaintain
30007
30447
  ).command(
30008
- "add-stage <template>",
30009
- "Add an optional stage template (e.g. playwright)",
30448
+ "add-stage [template]",
30449
+ `Add a stage template (${STAGE_TEMPLATE_IDS.join(", ")}) or a custom stage (--custom)`,
30010
30450
  (y2) => y2.positional("template", {
30011
30451
  type: "string",
30012
- describe: "Stage template id (playwright)"
30452
+ describe: `Stage template id (${STAGE_TEMPLATE_IDS.join(", ")}), or the new stage id with --custom`
30453
+ }).option("list", {
30454
+ type: "boolean",
30455
+ default: false,
30456
+ describe: "List available stage templates and exit"
30457
+ }).option("custom", {
30458
+ type: "boolean",
30459
+ default: false,
30460
+ describe: "Register a custom stage instead of a shipped template"
30461
+ }).option("kind", {
30462
+ type: "string",
30463
+ describe: "Custom stage kind (setup, launch, verify, test, inspect, reset, teardown, custom)"
30464
+ }).option("command", {
30465
+ type: "string",
30466
+ describe: 'Custom stage shell command ({agentId} is substituted), e.g. "npm test"'
30467
+ }).option("script", {
30468
+ type: "boolean",
30469
+ default: false,
30470
+ describe: "Scaffold .har/stages/<id>.sh from the contract skeleton (see .har/STAGES.md)"
30471
+ }).option("description", {
30472
+ type: "string",
30473
+ describe: "Custom stage description shown in the registry and Mission Control"
30474
+ }).option("verification", {
30475
+ type: "boolean",
30476
+ default: false,
30477
+ describe: "Include the custom stage in verify --full (stages.json verificationStages)"
30013
30478
  }).option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("force", {
30014
30479
  type: "boolean",
30015
30480
  default: false,
@@ -30017,7 +30482,7 @@ var envCommand = {
30017
30482
  }).option("skip-ci", {
30018
30483
  type: "boolean",
30019
30484
  default: false,
30020
- describe: "Do not copy .github/workflows/playwright.yml"
30485
+ describe: "Do not copy optional CI workflow files (e.g. .github/workflows/playwright.yml)"
30021
30486
  }),
30022
30487
  handleAddStage
30023
30488
  ).command(
@@ -30112,7 +30577,7 @@ var envCommand = {
30112
30577
  }
30113
30578
  };
30114
30579
  async function handleInit(argv) {
30115
- const repoPath = path31.resolve(argv.repo);
30580
+ const repoPath = path34.resolve(argv.repo);
30116
30581
  header("har env init");
30117
30582
  info(`Repository: ${repoPath}`);
30118
30583
  try {
@@ -30156,6 +30621,14 @@ async function handleInit(argv) {
30156
30621
  autoYes: argv.yes,
30157
30622
  mode: "init"
30158
30623
  });
30624
+ await handleAgentSkills({
30625
+ repoPath,
30626
+ agents: argv.agents,
30627
+ enabled: argv.noAgents ? false : void 0,
30628
+ autoYes: argv.yes,
30629
+ force: argv.force,
30630
+ mode: "init"
30631
+ });
30159
30632
  printNextSteps(argv.auto);
30160
30633
  } catch (err) {
30161
30634
  error(err.message);
@@ -30163,7 +30636,7 @@ async function handleInit(argv) {
30163
30636
  }
30164
30637
  }
30165
30638
  async function handleMaintain(argv) {
30166
- const repoPath = path31.resolve(argv.repo);
30639
+ const repoPath = path34.resolve(argv.repo);
30167
30640
  header("har env maintain");
30168
30641
  info(`Repository: ${repoPath}`);
30169
30642
  try {
@@ -30221,6 +30694,13 @@ async function handleMaintain(argv) {
30221
30694
  autoYes: argv.yes,
30222
30695
  mode: "maintain"
30223
30696
  });
30697
+ await handleAgentSkills({
30698
+ repoPath,
30699
+ agents: argv.agents,
30700
+ enabled: argv.noAgents ? false : void 0,
30701
+ autoYes: argv.yes,
30702
+ mode: "maintain"
30703
+ });
30224
30704
  } catch (err) {
30225
30705
  error(err.message);
30226
30706
  process.exit(1);
@@ -30252,7 +30732,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
30252
30732
  if (autoYes) {
30253
30733
  const proposal = readAgentMdProposal(repoPath);
30254
30734
  if (proposal) {
30255
- writeFileSafe(path31.join(repoPath, "AGENT.md"), proposal.content);
30735
+ writeFileSafe(path34.join(repoPath, "AGENT.md"), proposal.content);
30256
30736
  clearAgentMdProposal(repoPath);
30257
30737
  info("Applied AGENT.md proposal (--yes)");
30258
30738
  }
@@ -30261,16 +30741,64 @@ async function handleAgentMdProposal(repoPath, autoYes) {
30261
30741
  await promptApplyAgentMdProposal(repoPath);
30262
30742
  }
30263
30743
  async function handleAddStage(argv) {
30264
- const repoPath = path31.resolve(argv.repo);
30265
- if (argv.template !== "playwright") {
30266
- error(`Unknown stage template: ${argv.template ?? "(missing)"}. Available: playwright`);
30744
+ const repoPath = path34.resolve(argv.repo);
30745
+ const available = listStageTemplateIds();
30746
+ if (argv.list) {
30747
+ for (const id of available) {
30748
+ console.log(id);
30749
+ }
30750
+ return;
30751
+ }
30752
+ if (argv.custom) {
30753
+ if (!argv.template) {
30754
+ error(
30755
+ 'Missing stage id. Usage: har env add-stage <id> --custom (--command "npm test" | --script) [--kind test] [--verification]'
30756
+ );
30757
+ process.exit(1);
30758
+ }
30759
+ header("har env add-stage --custom");
30760
+ info(`Repository: ${repoPath}`);
30761
+ info(`Stage: ${argv.template}`);
30762
+ try {
30763
+ const result = addCustomStage(repoPath, {
30764
+ id: argv.template,
30765
+ kind: argv.kind,
30766
+ command: argv.command,
30767
+ script: argv.script,
30768
+ description: argv.description,
30769
+ verification: argv.verification,
30770
+ force: argv.force
30771
+ });
30772
+ divider();
30773
+ success(`Custom stage registered: ${result.stageId} (kind: ${result.kind}, ${result.mode})`);
30774
+ for (const file of result.filesWritten) {
30775
+ info(` + ${file}`);
30776
+ }
30777
+ console.error("");
30778
+ console.error(" Next steps:");
30779
+ for (const step of result.nextSteps) {
30780
+ console.error(` ${step}`);
30781
+ }
30782
+ console.error("");
30783
+ console.error(" Docs: .har/STAGES.md");
30784
+ console.error("");
30785
+ } catch (err) {
30786
+ error(err.message);
30787
+ process.exit(1);
30788
+ }
30789
+ return;
30790
+ }
30791
+ if (!argv.template || !available.includes(argv.template)) {
30792
+ error(
30793
+ `Unknown stage template: ${argv.template ?? "(missing)"}. Available: ${available.join(", ")}. For a project-specific stage, use: har env add-stage <id> --custom`
30794
+ );
30267
30795
  process.exit(1);
30268
30796
  }
30269
30797
  header("har env add-stage");
30270
30798
  info(`Repository: ${repoPath}`);
30271
30799
  info(`Template: ${argv.template}`);
30272
30800
  try {
30273
- const result = addStageTemplate(repoPath, "playwright", {
30801
+ const result = addStageTemplate(repoPath, argv.template, {
30274
30802
  force: argv.force,
30275
30803
  skipCi: argv.skipCi
30276
30804
  });
@@ -30282,7 +30810,7 @@ async function handleAddStage(argv) {
30282
30810
  console.error(` ${step}`);
30283
30811
  }
30284
30812
  console.error("");
30285
- console.error(" Docs: .har/stages/PLAYWRIGHT.md");
30813
+ console.error(` Docs: ${result.docsPath}`);
30286
30814
  console.error("");
30287
30815
  } catch (err) {
30288
30816
  error(err.message);
@@ -30290,7 +30818,7 @@ async function handleAddStage(argv) {
30290
30818
  }
30291
30819
  }
30292
30820
  async function handlePreflight(argv) {
30293
- const repo = path31.resolve(argv.repo);
30821
+ const repo = path34.resolve(argv.repo);
30294
30822
  const agentId = validateAgentId(argv.id, repo);
30295
30823
  const result = await preflightEnvironment({
30296
30824
  repoPath: repo,
@@ -30308,7 +30836,7 @@ async function handlePreflight(argv) {
30308
30836
  process.exit(result.code);
30309
30837
  }
30310
30838
  async function handleLaunch(argv) {
30311
- const repo = path31.resolve(argv.repo);
30839
+ const repo = path34.resolve(argv.repo);
30312
30840
  const agentId = validateAgentId(argv.id, repo);
30313
30841
  let confirmReplace = argv.replace;
30314
30842
  let force = argv.force;
@@ -30318,10 +30846,10 @@ async function handleLaunch(argv) {
30318
30846
  if (!confirmReplace && process.stdin.isTTY && process.stdout.isTTY) {
30319
30847
  warn("Occupied slot \u2014 review before replacing:");
30320
30848
  console.error(guard.reason ?? "");
30321
- const readline4 = await import("readline");
30322
- const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
30323
- const answer = await new Promise((resolve26) => {
30324
- rl.question("Replace this slot? [y/N] ", resolve26);
30849
+ const readline5 = await import("readline");
30850
+ const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
30851
+ const answer = await new Promise((resolve28) => {
30852
+ rl.question("Replace this slot? [y/N] ", resolve28);
30325
30853
  });
30326
30854
  rl.close();
30327
30855
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -30335,10 +30863,10 @@ async function handleLaunch(argv) {
30335
30863
  if (!guardAfterConfirm.allowed && guardAfterConfirm.blocked && guardAfterConfirm.slot?.dirty) {
30336
30864
  if (!force && process.stdin.isTTY && process.stdout.isTTY) {
30337
30865
  warn("Worktree has uncommitted changes.");
30338
- const readline4 = await import("readline");
30339
- const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
30340
- const answer = await new Promise((resolve26) => {
30341
- rl.question("Discard uncommitted changes? [y/N] ", resolve26);
30866
+ const readline5 = await import("readline");
30867
+ const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
30868
+ const answer = await new Promise((resolve28) => {
30869
+ rl.question("Discard uncommitted changes? [y/N] ", resolve28);
30342
30870
  });
30343
30871
  rl.close();
30344
30872
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -30385,7 +30913,7 @@ async function handleRecover(argv) {
30385
30913
  });
30386
30914
  }
30387
30915
  async function handleVerify(argv) {
30388
- const repo = path31.resolve(argv.repo);
30916
+ const repo = path34.resolve(argv.repo);
30389
30917
  const agentId = validateAgentId(argv.id, repo);
30390
30918
  const result = await runVerification({
30391
30919
  repoPath: repo,
@@ -30397,7 +30925,7 @@ async function handleVerify(argv) {
30397
30925
  process.exit(result.code);
30398
30926
  }
30399
30927
  async function handleTeardown(argv) {
30400
- const repo = path31.resolve(argv.repo);
30928
+ const repo = path34.resolve(argv.repo);
30401
30929
  const agentId = validateAgentId(argv.id, repo);
30402
30930
  const result = await teardownEnvironment({
30403
30931
  repoPath: repo,
@@ -30408,7 +30936,7 @@ async function handleTeardown(argv) {
30408
30936
  process.exit(result.code);
30409
30937
  }
30410
30938
  async function handleComplete(argv) {
30411
- const repo = path31.resolve(argv.repo);
30939
+ const repo = path34.resolve(argv.repo);
30412
30940
  const agentId = validateAgentId(argv.id, repo);
30413
30941
  const result = await completeEnvironment({
30414
30942
  repoPath: repo,
@@ -30429,7 +30957,7 @@ async function handleComplete(argv) {
30429
30957
  process.exit(result.code);
30430
30958
  }
30431
30959
  async function handleStatus(argv) {
30432
- const repoPath = path31.resolve(argv.repo);
30960
+ const repoPath = path34.resolve(argv.repo);
30433
30961
  if (argv.json) {
30434
30962
  const status = collectEnvironmentStatus(repoPath);
30435
30963
  const output = EnvironmentStatusSchema.parse(status);
@@ -30442,7 +30970,7 @@ async function handleStatus(argv) {
30442
30970
  });
30443
30971
  }
30444
30972
  async function handleRunsList(argv) {
30445
- const repoPath = path31.resolve(argv.repo);
30973
+ const repoPath = path34.resolve(argv.repo);
30446
30974
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
30447
30975
  if (argv.json) {
30448
30976
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -30459,7 +30987,7 @@ async function handleRunsList(argv) {
30459
30987
  }
30460
30988
  }
30461
30989
  async function handleRunsGet(argv) {
30462
- const run2 = getRun(path31.resolve(argv.repo), argv.runId);
30990
+ const run2 = getRun(path34.resolve(argv.repo), argv.runId);
30463
30991
  if (!run2) {
30464
30992
  error(`Run not found: ${argv.runId}`);
30465
30993
  process.exit(1);
@@ -30532,11 +31060,11 @@ function printNextSteps(auto2) {
30532
31060
  }
30533
31061
 
30534
31062
  // src/cli/commands/control.ts
30535
- var path33 = __toESM(require("path"));
31063
+ var path36 = __toESM(require("path"));
30536
31064
 
30537
31065
  // src/core/control-lifecycle.ts
30538
31066
  var import_child_process8 = require("child_process");
30539
- var path32 = __toESM(require("path"));
31067
+ var path35 = __toESM(require("path"));
30540
31068
 
30541
31069
  // src/core/control-image.ts
30542
31070
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -30555,13 +31083,13 @@ function shouldBuildControlLocally() {
30555
31083
 
30556
31084
  // src/core/control-lifecycle.ts
30557
31085
  function resolveControlDir() {
30558
- return path32.resolve(__dirname, "..", "control");
31086
+ return path35.resolve(__dirname, "..", "control");
30559
31087
  }
30560
31088
  function resolveControlComposeFiles(options) {
30561
31089
  const controlDir = resolveControlDir();
30562
- const files = [path32.join(controlDir, "docker-compose.yml")];
31090
+ const files = [path35.join(controlDir, "docker-compose.yml")];
30563
31091
  if (options?.build ?? shouldBuildControlLocally()) {
30564
- files.push(path32.join(controlDir, "docker-compose.build.yml"));
31092
+ files.push(path35.join(controlDir, "docker-compose.build.yml"));
30565
31093
  }
30566
31094
  return files;
30567
31095
  }
@@ -30730,7 +31258,7 @@ async function handleDown() {
30730
31258
  process.exit(code);
30731
31259
  }
30732
31260
  async function handleRegister(argv) {
30733
- const repoPath = path33.resolve(argv.repo);
31261
+ const repoPath = path36.resolve(argv.repo);
30734
31262
  header("har control register");
30735
31263
  info(`Repository: ${repoPath}`);
30736
31264
  try {
@@ -30750,7 +31278,7 @@ async function handleRegister(argv) {
30750
31278
  }
30751
31279
  }
30752
31280
  async function handleSync(argv) {
30753
- const repoPath = path33.resolve(argv.repo);
31281
+ const repoPath = path36.resolve(argv.repo);
30754
31282
  try {
30755
31283
  await syncRepoWithControl({
30756
31284
  repoPath,
@@ -30789,7 +31317,7 @@ async function handleWatch(argv) {
30789
31317
  };
30790
31318
  const tick = async () => {
30791
31319
  if (argv.repo) {
30792
- await syncOne(path33.resolve(argv.repo));
31320
+ await syncOne(path36.resolve(argv.repo));
30793
31321
  return;
30794
31322
  }
30795
31323
  try {
@@ -30820,12 +31348,12 @@ async function handleLogin(argv) {
30820
31348
  }
30821
31349
 
30822
31350
  // src/cli/commands/hooks.ts
30823
- var path35 = __toESM(require("path"));
31351
+ var path39 = __toESM(require("path"));
30824
31352
 
30825
31353
  // src/core/hooks.ts
30826
- var fs31 = __toESM(require("fs"));
30827
- var os8 = __toESM(require("os"));
30828
- var path34 = __toESM(require("path"));
31354
+ var fs33 = __toESM(require("fs"));
31355
+ var os9 = __toESM(require("os"));
31356
+ var path37 = __toESM(require("path"));
30829
31357
  var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
30830
31358
  var MARKER_END = "# <<< har commit gate <<<";
30831
31359
  var PRE_COMMIT_BLOCK = [
@@ -30850,7 +31378,7 @@ function resolveCheckoutRoot(cwd) {
30850
31378
  function resolveHooksDir(checkoutDir) {
30851
31379
  const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
30852
31380
  if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
30853
- return path34.resolve(checkoutDir, hooksPath);
31381
+ return path37.resolve(checkoutDir, hooksPath);
30854
31382
  }
30855
31383
  function getConfiguredHooksPath(checkoutDir) {
30856
31384
  return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
@@ -30858,9 +31386,9 @@ function getConfiguredHooksPath(checkoutDir) {
30858
31386
  function defaultHarInvocation() {
30859
31387
  const entry = process.argv[1];
30860
31388
  if (entry && entry.endsWith(".js")) {
30861
- return `"${process.execPath}" "${path34.resolve(entry)}"`;
31389
+ return `"${process.execPath}" "${path37.resolve(entry)}"`;
30862
31390
  }
30863
- if (entry) return `"${path34.resolve(entry)}"`;
31391
+ if (entry) return `"${path37.resolve(entry)}"`;
30864
31392
  return "har";
30865
31393
  }
30866
31394
  function buildHookScript(harInvocation, subcommand, failOpenNotice) {
@@ -30881,40 +31409,40 @@ exit 0
30881
31409
  `;
30882
31410
  }
30883
31411
  function upsertMarkedBlock(filePath, block) {
30884
- if (!fs31.existsSync(filePath)) {
30885
- fs31.writeFileSync(filePath, `#!/bin/sh
31412
+ if (!fs33.existsSync(filePath)) {
31413
+ fs33.writeFileSync(filePath, `#!/bin/sh
30886
31414
  ${block}
30887
31415
  `, { mode: 493 });
30888
31416
  return "created";
30889
31417
  }
30890
- const content = fs31.readFileSync(filePath, "utf8");
31418
+ const content = fs33.readFileSync(filePath, "utf8");
30891
31419
  if (content.includes(MARKER_START)) {
30892
31420
  const pattern = new RegExp(
30893
31421
  `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
30894
31422
  );
30895
- fs31.writeFileSync(filePath, content.replace(pattern, block));
30896
- fs31.chmodSync(filePath, 493);
31423
+ fs33.writeFileSync(filePath, content.replace(pattern, block));
31424
+ fs33.chmodSync(filePath, 493);
30897
31425
  return "updated";
30898
31426
  }
30899
31427
  const suffix = content.endsWith("\n") ? "" : "\n";
30900
- fs31.writeFileSync(filePath, `${content}${suffix}
31428
+ fs33.writeFileSync(filePath, `${content}${suffix}
30901
31429
  ${block}
30902
31430
  `);
30903
- fs31.chmodSync(filePath, 493);
31431
+ fs33.chmodSync(filePath, 493);
30904
31432
  return "appended";
30905
31433
  }
30906
31434
  function removeMarkedBlock(filePath) {
30907
- if (!fs31.existsSync(filePath)) return false;
30908
- const content = fs31.readFileSync(filePath, "utf8");
31435
+ if (!fs33.existsSync(filePath)) return false;
31436
+ const content = fs33.readFileSync(filePath, "utf8");
30909
31437
  if (!content.includes(MARKER_START)) return false;
30910
31438
  const pattern = new RegExp(
30911
31439
  `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
30912
31440
  );
30913
31441
  const stripped = content.replace(pattern, "\n");
30914
31442
  if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
30915
- fs31.rmSync(filePath);
31443
+ fs33.rmSync(filePath);
30916
31444
  } else {
30917
- fs31.writeFileSync(filePath, stripped);
31445
+ fs33.writeFileSync(filePath, stripped);
30918
31446
  }
30919
31447
  return true;
30920
31448
  }
@@ -30936,20 +31464,20 @@ Or re-run with --force to write into that directory anyway.`
30936
31464
  );
30937
31465
  }
30938
31466
  const hooksDir = resolveHooksDir(checkout);
30939
- fs31.mkdirSync(hooksDir, { recursive: true });
31467
+ fs33.mkdirSync(hooksDir, { recursive: true });
30940
31468
  const invocation = options.harInvocation ?? defaultHarInvocation();
30941
- fs31.writeFileSync(
30942
- path34.join(hooksDir, "har-pre-commit"),
31469
+ fs33.writeFileSync(
31470
+ path37.join(hooksDir, "har-pre-commit"),
30943
31471
  buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
30944
31472
  { mode: 493 }
30945
31473
  );
30946
- fs31.writeFileSync(
30947
- path34.join(hooksDir, "har-post-commit"),
31474
+ fs33.writeFileSync(
31475
+ path37.join(hooksDir, "har-post-commit"),
30948
31476
  buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
30949
31477
  { mode: 493 }
30950
31478
  );
30951
- const preCommit = upsertMarkedBlock(path34.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
30952
- const postCommit = upsertMarkedBlock(path34.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
31479
+ const preCommit = upsertMarkedBlock(path37.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
31480
+ const postCommit = upsertMarkedBlock(path37.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
30953
31481
  ensureValidationsIgnored(checkout);
30954
31482
  return { hooksDir, preCommit, postCommit };
30955
31483
  }
@@ -30958,12 +31486,12 @@ function uninstallHooks(repoPath) {
30958
31486
  if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
30959
31487
  const hooksDir = resolveHooksDir(checkout);
30960
31488
  let removed = false;
30961
- removed = removeMarkedBlock(path34.join(hooksDir, "pre-commit")) || removed;
30962
- removed = removeMarkedBlock(path34.join(hooksDir, "post-commit")) || removed;
31489
+ removed = removeMarkedBlock(path37.join(hooksDir, "pre-commit")) || removed;
31490
+ removed = removeMarkedBlock(path37.join(hooksDir, "post-commit")) || removed;
30963
31491
  for (const file of ["har-pre-commit", "har-post-commit"]) {
30964
- const full = path34.join(hooksDir, file);
30965
- if (fs31.existsSync(full)) {
30966
- fs31.rmSync(full);
31492
+ const full = path37.join(hooksDir, file);
31493
+ if (fs33.existsSync(full)) {
31494
+ fs33.rmSync(full);
30967
31495
  removed = true;
30968
31496
  }
30969
31497
  }
@@ -30980,9 +31508,9 @@ function getCommitGateConfig(checkoutDir) {
30980
31508
  function isAgentWorktree(checkoutDir) {
30981
31509
  const branch = getCurrentBranch(checkoutDir);
30982
31510
  if (branch && /^har-agent-\d+$/.test(branch)) return true;
30983
- const worktreesRoot = path34.join(os8.homedir(), "worktrees");
30984
- const resolved = path34.resolve(checkoutDir);
30985
- return resolved.startsWith(`${worktreesRoot}${path34.sep}`) && /-agent-\d+$/.test(path34.basename(resolved));
31511
+ const worktreesRoot = path37.join(os9.homedir(), "worktrees");
31512
+ const resolved = path37.resolve(checkoutDir);
31513
+ return resolved.startsWith(`${worktreesRoot}${path37.sep}`) && /-agent-\d+$/.test(path37.basename(resolved));
30986
31514
  }
30987
31515
  function resolveEffectiveMode(checkoutDir, gate) {
30988
31516
  if (!gate.enabled) return "off";
@@ -30995,15 +31523,15 @@ function getHooksStatus(repoPath) {
30995
31523
  const hooksDir = resolveHooksDir(checkout);
30996
31524
  const gate = getCommitGateConfig(checkout);
30997
31525
  const hasBlock = (name) => {
30998
- const file = path34.join(hooksDir, name);
30999
- return fs31.existsSync(file) && fs31.readFileSync(file, "utf8").includes(MARKER_START);
31526
+ const file = path37.join(hooksDir, name);
31527
+ return fs33.existsSync(file) && fs33.readFileSync(file, "utf8").includes(MARKER_START);
31000
31528
  };
31001
31529
  return {
31002
31530
  checkout,
31003
31531
  hooksDir,
31004
31532
  configuredHooksPath: getConfiguredHooksPath(checkout),
31005
- preCommitInstalled: hasBlock("pre-commit") && fs31.existsSync(path34.join(hooksDir, "har-pre-commit")),
31006
- postCommitInstalled: hasBlock("post-commit") && fs31.existsSync(path34.join(hooksDir, "har-post-commit")),
31533
+ preCommitInstalled: hasBlock("pre-commit") && fs33.existsSync(path37.join(hooksDir, "har-pre-commit")),
31534
+ postCommitInstalled: hasBlock("post-commit") && fs33.existsSync(path37.join(hooksDir, "har-post-commit")),
31007
31535
  gate,
31008
31536
  effectiveMode: resolveEffectiveMode(checkout, gate)
31009
31537
  };
@@ -31014,7 +31542,7 @@ function checkCommitGate(cwd) {
31014
31542
  }
31015
31543
  const checkout = resolveCheckoutRoot(cwd);
31016
31544
  if (!checkout) return { exitCode: 0, messages: [] };
31017
- if (!fs31.existsSync(path34.join(checkout, ".har", "stages.json"))) {
31545
+ if (!fs33.existsSync(path37.join(checkout, ".har", "stages.json"))) {
31018
31546
  return { exitCode: 0, messages: [] };
31019
31547
  }
31020
31548
  const gate = getCommitGateConfig(checkout);
@@ -31084,13 +31612,114 @@ async function recordCommitAssociation(cwd) {
31084
31612
  }
31085
31613
  }
31086
31614
 
31615
+ // src/core/claude-hooks.ts
31616
+ var fs34 = __toESM(require("fs"));
31617
+ var path38 = __toESM(require("path"));
31618
+ var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
31619
+ var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
31620
+ var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
31621
+ var GUARD_COMMAND = `"$CLAUDE_PROJECT_DIR"/${CLAUDE_GUARD_RELATIVE_PATH}`;
31622
+ function isHarGuardEntry(entry) {
31623
+ return (entry.hooks ?? []).some(
31624
+ (hook) => typeof hook.command === "string" && hook.command.includes("claude-worktree-guard.sh")
31625
+ );
31626
+ }
31627
+ function readSettings(settingsPath) {
31628
+ if (!fs34.existsSync(settingsPath)) return {};
31629
+ const raw = fs34.readFileSync(settingsPath, "utf8").trim();
31630
+ if (raw === "") return {};
31631
+ const parsed = JSON.parse(raw);
31632
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
31633
+ throw new Error(`${settingsPath} is not a JSON object \u2014 fix it before installing the guard.`);
31634
+ }
31635
+ return parsed;
31636
+ }
31637
+ function writeSettings(settingsPath, settings) {
31638
+ fs34.mkdirSync(path38.dirname(settingsPath), { recursive: true });
31639
+ fs34.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31640
+ }
31641
+ function installClaudeGuard(repoPath) {
31642
+ const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
31643
+ if (!templatePath) {
31644
+ throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
31645
+ }
31646
+ const guardScript = path38.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
31647
+ fs34.mkdirSync(path38.dirname(guardScript), { recursive: true });
31648
+ fs34.copyFileSync(templatePath, guardScript);
31649
+ fs34.chmodSync(guardScript, 493);
31650
+ const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
31651
+ const settings = readSettings(settingsPath);
31652
+ const hooks = settings.hooks ?? {};
31653
+ const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
31654
+ preToolUse.push({
31655
+ matcher: GUARD_MATCHER,
31656
+ hooks: [{ type: "command", command: GUARD_COMMAND }]
31657
+ });
31658
+ settings.hooks = { ...hooks, PreToolUse: preToolUse };
31659
+ writeSettings(settingsPath, settings);
31660
+ return { guardScript, settingsPath };
31661
+ }
31662
+ function uninstallClaudeGuard(repoPath) {
31663
+ let removed = false;
31664
+ const guardScript = path38.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
31665
+ if (fs34.existsSync(guardScript)) {
31666
+ fs34.rmSync(guardScript);
31667
+ removed = true;
31668
+ const parent = path38.dirname(guardScript);
31669
+ if (fs34.existsSync(parent) && fs34.readdirSync(parent).length === 0) fs34.rmdirSync(parent);
31670
+ }
31671
+ const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
31672
+ if (fs34.existsSync(settingsPath)) {
31673
+ const settings = readSettings(settingsPath);
31674
+ const preToolUse = settings.hooks?.PreToolUse;
31675
+ if (preToolUse) {
31676
+ const filtered = preToolUse.filter((entry) => !isHarGuardEntry(entry));
31677
+ if (filtered.length !== preToolUse.length) {
31678
+ removed = true;
31679
+ if (filtered.length > 0) {
31680
+ settings.hooks = { ...settings.hooks, PreToolUse: filtered };
31681
+ } else {
31682
+ const rest = { ...settings.hooks };
31683
+ delete rest.PreToolUse;
31684
+ if (Object.keys(rest).length > 0) {
31685
+ settings.hooks = rest;
31686
+ } else {
31687
+ delete settings.hooks;
31688
+ }
31689
+ }
31690
+ writeSettings(settingsPath, settings);
31691
+ }
31692
+ }
31693
+ }
31694
+ return { removed };
31695
+ }
31696
+ function claudeGuardInstalled(repoPath) {
31697
+ const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
31698
+ if (!fs34.existsSync(settingsPath)) return false;
31699
+ try {
31700
+ const settings = readSettings(settingsPath);
31701
+ return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
31702
+ } catch {
31703
+ return false;
31704
+ }
31705
+ }
31706
+
31087
31707
  // src/cli/commands/hooks.ts
31088
31708
  function repoOption(y2) {
31089
31709
  return y2.option("repo", { type: "string", default: ".", describe: "Path to the repository" });
31090
31710
  }
31091
- function handleInstall(argv) {
31711
+ function handleInstall2(argv) {
31092
31712
  try {
31093
- const result = installHooks({ repoPath: path35.resolve(argv.repo), force: argv.force });
31713
+ if (argv.claude) {
31714
+ const result2 = installClaudeGuard(path39.resolve(argv.repo));
31715
+ success("Claude Code worktree guard installed.");
31716
+ info(`Guard script: ${result2.guardScript}`);
31717
+ info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
31718
+ info("File edits in the MAIN checkout are now blocked with a pointer to /har-wt.");
31719
+ info("Commit both files so the guard travels with the repo.");
31720
+ return;
31721
+ }
31722
+ const result = installHooks({ repoPath: path39.resolve(argv.repo), force: argv.force });
31094
31723
  success(`Commit gate installed in ${result.hooksDir}`);
31095
31724
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
31096
31725
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -31102,7 +31731,16 @@ function handleInstall(argv) {
31102
31731
  }
31103
31732
  function handleUninstall(argv) {
31104
31733
  try {
31105
- const result = uninstallHooks(path35.resolve(argv.repo));
31734
+ if (argv.claude) {
31735
+ const result2 = uninstallClaudeGuard(path39.resolve(argv.repo));
31736
+ if (result2.removed) {
31737
+ success("Claude Code worktree guard removed.");
31738
+ } else {
31739
+ info("No Claude Code worktree guard was installed.");
31740
+ }
31741
+ return;
31742
+ }
31743
+ const result = uninstallHooks(path39.resolve(argv.repo));
31106
31744
  if (result.removed) {
31107
31745
  success(`Commit gate removed from ${result.hooksDir}`);
31108
31746
  } else {
@@ -31115,7 +31753,7 @@ function handleUninstall(argv) {
31115
31753
  }
31116
31754
  function handleStatus2(argv) {
31117
31755
  try {
31118
- const status = getHooksStatus(path35.resolve(argv.repo));
31756
+ const status = getHooksStatus(path39.resolve(argv.repo));
31119
31757
  if (argv.json) {
31120
31758
  process.stdout.write(`${JSON.stringify(status, null, 2)}
31121
31759
  `);
@@ -31132,6 +31770,9 @@ function handleStatus2(argv) {
31132
31770
  `Gate: enabled=${status.gate.enabled} mode=${status.gate.mode} scope=${status.gate.scope}`
31133
31771
  );
31134
31772
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
31773
+ info(
31774
+ `Claude guard: ${claudeGuardInstalled(path39.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
31775
+ );
31135
31776
  } catch (err) {
31136
31777
  error(err instanceof Error ? err.message : String(err));
31137
31778
  process.exitCode = 1;
@@ -31158,9 +31799,22 @@ var hooksCommand = {
31158
31799
  type: "boolean",
31159
31800
  default: false,
31160
31801
  describe: "Write hooks even when core.hooksPath points at a managed directory"
31802
+ }).option("claude", {
31803
+ type: "boolean",
31804
+ default: false,
31805
+ describe: "Install the Claude Code worktree guard instead (blocks Edit/Write in the main checkout, points to /har-wt)"
31161
31806
  }),
31162
- handleInstall
31163
- ).command("uninstall", "Remove the har commit gate hooks", repoOption, handleUninstall).command(
31807
+ handleInstall2
31808
+ ).command(
31809
+ "uninstall",
31810
+ "Remove the har commit gate hooks",
31811
+ (y2) => repoOption(y2).option("claude", {
31812
+ type: "boolean",
31813
+ default: false,
31814
+ describe: "Remove the Claude Code worktree guard instead"
31815
+ }),
31816
+ handleUninstall
31817
+ ).command(
31164
31818
  "status",
31165
31819
  "Show hook installation and gate configuration",
31166
31820
  (y2) => repoOption(y2).option("json", { type: "boolean", default: false }),
@@ -31378,10 +32032,10 @@ function assignProp(target, prop, value) {
31378
32032
  configurable: true
31379
32033
  });
31380
32034
  }
31381
- function getElementAtPath(obj, path37) {
31382
- if (!path37)
32035
+ function getElementAtPath(obj, path41) {
32036
+ if (!path41)
31383
32037
  return obj;
31384
- return path37.reduce((acc, key) => acc?.[key], obj);
32038
+ return path41.reduce((acc, key) => acc?.[key], obj);
31385
32039
  }
31386
32040
  function promiseAllObject(promisesObj) {
31387
32041
  const keys = Object.keys(promisesObj);
@@ -31701,11 +32355,11 @@ function aborted(x2, startIndex = 0) {
31701
32355
  }
31702
32356
  return false;
31703
32357
  }
31704
- function prefixIssues(path37, issues) {
32358
+ function prefixIssues(path41, issues) {
31705
32359
  return issues.map((iss) => {
31706
32360
  var _a3;
31707
32361
  (_a3 = iss).path ?? (_a3.path = []);
31708
- iss.path.unshift(path37);
32362
+ iss.path.unshift(path41);
31709
32363
  return iss;
31710
32364
  });
31711
32365
  }
@@ -37028,7 +37682,7 @@ var Protocol = class {
37028
37682
  return;
37029
37683
  }
37030
37684
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
37031
- await new Promise((resolve26) => setTimeout(resolve26, pollInterval));
37685
+ await new Promise((resolve28) => setTimeout(resolve28, pollInterval));
37032
37686
  options?.signal?.throwIfAborted();
37033
37687
  }
37034
37688
  } catch (error3) {
@@ -37045,7 +37699,7 @@ var Protocol = class {
37045
37699
  */
37046
37700
  request(request, resultSchema, options) {
37047
37701
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
37048
- return new Promise((resolve26, reject) => {
37702
+ return new Promise((resolve28, reject) => {
37049
37703
  const earlyReject = (error3) => {
37050
37704
  reject(error3);
37051
37705
  };
@@ -37123,7 +37777,7 @@ var Protocol = class {
37123
37777
  if (!parseResult.success) {
37124
37778
  reject(parseResult.error);
37125
37779
  } else {
37126
- resolve26(parseResult.data);
37780
+ resolve28(parseResult.data);
37127
37781
  }
37128
37782
  } catch (error3) {
37129
37783
  reject(error3);
@@ -37384,12 +38038,12 @@ var Protocol = class {
37384
38038
  }
37385
38039
  } catch {
37386
38040
  }
37387
- return new Promise((resolve26, reject) => {
38041
+ return new Promise((resolve28, reject) => {
37388
38042
  if (signal.aborted) {
37389
38043
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
37390
38044
  return;
37391
38045
  }
37392
- const timeoutId = setTimeout(resolve26, interval);
38046
+ const timeoutId = setTimeout(resolve28, interval);
37393
38047
  signal.addEventListener("abort", () => {
37394
38048
  clearTimeout(timeoutId);
37395
38049
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -38179,7 +38833,7 @@ var Server = class extends Protocol {
38179
38833
  };
38180
38834
 
38181
38835
  // src/mcp/server.ts
38182
- var path36 = __toESM(require("path"));
38836
+ var path40 = __toESM(require("path"));
38183
38837
 
38184
38838
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
38185
38839
  var import_node_process2 = __toESM(require("node:process"), 1);
@@ -38262,12 +38916,12 @@ var StdioServerTransport = class {
38262
38916
  this.onclose?.();
38263
38917
  }
38264
38918
  send(message) {
38265
- return new Promise((resolve26) => {
38919
+ return new Promise((resolve28) => {
38266
38920
  const json = serializeMessage(message);
38267
38921
  if (this._stdout.write(json)) {
38268
- resolve26();
38922
+ resolve28();
38269
38923
  } else {
38270
- this._stdout.once("drain", resolve26);
38924
+ this._stdout.once("drain", resolve28);
38271
38925
  }
38272
38926
  });
38273
38927
  }
@@ -38811,7 +39465,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
38811
39465
  const input = ControlUpInputSchema.parse({ ...args, repo });
38812
39466
  const result = await startControlAndSync({
38813
39467
  detach: input.detach,
38814
- cwd: path36.resolve(input.repo)
39468
+ cwd: path40.resolve(input.repo)
38815
39469
  });
38816
39470
  if (result.code !== 0) {
38817
39471
  return {
@@ -38875,7 +39529,7 @@ var mcpCommand = {
38875
39529
 
38876
39530
  // src/cli/index.ts
38877
39531
  async function runCli() {
38878
- await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
39532
+ await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(agentsCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
38879
39533
  }
38880
39534
 
38881
39535
  // src/index.ts