@osfactory/har 0.56.1 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -553,8 +553,8 @@ var init_parseUtil = __esm({
553
553
  init_errors();
554
554
  init_en();
555
555
  makeIssue = (params) => {
556
- const { data, path: path62, errorMaps, issueData } = params;
557
- const fullPath = [...path62, ...issueData.path || []];
556
+ const { data, path: path66, errorMaps, issueData } = params;
557
+ const fullPath = [...path66, ...issueData.path || []];
558
558
  const fullIssue = {
559
559
  ...issueData,
560
560
  path: fullPath
@@ -862,11 +862,11 @@ var init_types = __esm({
862
862
  init_parseUtil();
863
863
  init_util();
864
864
  ParseInputLazyPath = class {
865
- constructor(parent, value, path62, key) {
865
+ constructor(parent, value, path66, key) {
866
866
  this._cachedPath = [];
867
867
  this.parent = parent;
868
868
  this.data = value;
869
- this._path = path62;
869
+ this._path = path66;
870
870
  this._key = key;
871
871
  }
872
872
  get path() {
@@ -4248,7 +4248,7 @@ var init_zod = __esm({
4248
4248
  });
4249
4249
 
4250
4250
  // packages/schemas/src/schema.ts
4251
- var HAR_STAGE_KINDS, HAR_AGENT_SLOT_MIN, AgentSkillTargetSchema, ScaffoldedAgentFileSchema, HarnessManifestSchema, HarnessStageKindSchema, HarnessArtifactKindSchema, HarnessArtifactSchema, HarnessStageIdSchema, HarnessStageSchema, HarnessAgentSlotsSchema, CommitGateConfigSchema, OnboardingPreferencesSchema, HarnessStageRegistrySchema, HarnessVerificationStepSchema, ShellRunOutputSchema, HarnessVerificationResultSchema, HarnessStageRunStatusSchema, HarnessStageLogSchema, HarnessStageUrlSchema, HarnessStageRunResultSchema, StageKindSchema, VerificationResultSchema, StageResultSchema, RunRecordTriggerSchema, WorkUnitIdSchema, WorkAttemptIdSchema, WorkUnitMetadataSchema, WorkUnitOutcomeSchema, WorkUnitRecordSchema, WorkAttemptRecordSchema, RunRecordSchema, SlotRegistryEntrySchema, AgentSlotHarnessUsageSchema, PreflightBlockerSchema, SlotReadinessSchema, AgentSlotStatusSchema, EnvironmentStatusSchema, RegisterRepoInputSchema, UnregisterRepoInputSchema, UnregisterWorktreeResultSchema, UnregisterRepoResultSchema, ResetMissionControlInputSchema, HarnessScrubResultSchema, ResetMissionControlResultSchema, DeleteWorktreesInputSchema, DeleteWorktreeResultSchema, DeleteWorktreesResultSchema, SyncRunsInputSchema, SyncSlotsInputSchema, SyncWorkUnitsInputSchema, ChangedFileStatusSchema, ChangedFileSchema, ValidationRecordSchema, SyncValidationsInputSchema, ValidationBindingRecordSchema, SyncValidationBindingsInputSchema, AgentToolSchema, UsageSourceSchema, AgentTrajectorySourceSchema, AgentTrajectoryContentDisclosureSchema, AgentTrajectoryRecordV1Schema, AgentSessionUsageSchema, SyncUsageInputSchema, AgentSessionEventSchema, SyncSessionEventsInputSchema;
4251
+ var HAR_STAGE_KINDS, HAR_AGENT_SLOT_MIN, AgentSkillTargetSchema, ScaffoldedAgentFileSchema, HarnessManifestSchema, HarnessStageKindSchema, HarnessArtifactKindSchema, HarnessArtifactSchema, HarnessStageIdSchema, HarnessStageSchema, HarnessAgentSlotsSchema, CommitGateConfigSchema, OnboardingPreferencesSchema, HarnessStageRegistrySchema, HarnessVerificationStepSchema, ShellRunOutputSchema, HarnessVerificationResultSchema, HarnessStageRunStatusSchema, HarnessStageLogSchema, HarnessStageUrlSchema, HarnessStageRunResultSchema, StageKindSchema, VerificationResultSchema, StageResultSchema, RunRecordTriggerSchema, WorkUnitIdSchema, WorkAttemptIdSchema, WorkUnitRelatedLinkSchema, WorkUnitMetadataSchema, WorkUnitOutcomeSchema, WorkUnitRecordSchema, WorkAttemptRecordSchema, RunRecordSchema, SlotRegistryEntrySchema, AgentSlotHarnessUsageSchema, PreflightBlockerSchema, SlotReadinessSchema, AgentSlotStatusSchema, EnvironmentStatusSchema, RegisterRepoInputSchema, UnregisterRepoInputSchema, UnregisterWorktreeResultSchema, UnregisterRepoResultSchema, ResetMissionControlInputSchema, HarnessScrubResultSchema, ResetMissionControlResultSchema, DeleteWorktreesInputSchema, DeleteWorktreeResultSchema, DeleteWorktreesResultSchema, SyncRunsInputSchema, SyncSlotsInputSchema, SyncWorkUnitsInputSchema, ChangedFileStatusSchema, ChangedFileSchema, ValidationRecordSchema, SyncValidationsInputSchema, ValidationBindingRecordSchema, SyncValidationBindingsInputSchema, AgentToolSchema, UsageSourceSchema, AgentTrajectorySourceSchema, AgentTrajectoryContentDisclosureSchema, AgentTrajectoryRecordV1Schema, AgentSessionUsageSchema, SyncUsageInputSchema, AgentSessionEventSchema, SyncSessionEventsInputSchema;
4252
4252
  var init_schema = __esm({
4253
4253
  "packages/schemas/src/schema.ts"() {
4254
4254
  "use strict";
@@ -4413,12 +4413,18 @@ var init_schema = __esm({
4413
4413
  "work unit id must start with an alphanumeric character and contain only stable identifier characters"
4414
4414
  );
4415
4415
  WorkAttemptIdSchema = external_exports.string().uuid();
4416
+ WorkUnitRelatedLinkSchema = external_exports.object({
4417
+ source: external_exports.string().min(1).max(64),
4418
+ url: external_exports.string().url(),
4419
+ label: external_exports.string().min(1).max(128).optional()
4420
+ });
4416
4421
  WorkUnitMetadataSchema = external_exports.object({
4417
4422
  workUnitId: WorkUnitIdSchema,
4418
4423
  source: external_exports.string().min(1).max(64).optional(),
4419
4424
  sourceUrl: external_exports.string().url().optional(),
4420
4425
  title: external_exports.string().min(1).max(256).optional(),
4421
- parentWorkUnitId: WorkUnitIdSchema.optional()
4426
+ parentWorkUnitId: WorkUnitIdSchema.optional(),
4427
+ relatedLinks: external_exports.array(WorkUnitRelatedLinkSchema).optional()
4422
4428
  });
4423
4429
  WorkUnitOutcomeSchema = external_exports.object({
4424
4430
  decision: external_exports.enum(["completed", "abandoned"]),
@@ -28353,6 +28359,7 @@ var init_manifest = __esm({
28353
28359
  DEFAULT_HAR_DIR = ".har";
28354
28360
  CHECKSUM_SKIP = /* @__PURE__ */ new Set([
28355
28361
  "manifest.json",
28362
+ "plugins.json",
28356
28363
  "AGENTS.md.proposed",
28357
28364
  "AGENTS.md.proposed.meta.json",
28358
28365
  "AGENT.md.proposed",
@@ -28757,7 +28764,7 @@ function runScript(scriptPath, args = [], options = {}) {
28757
28764
  function runScriptCapture(scriptPath, args = [], options = {}) {
28758
28765
  const stream = options.stream ?? false;
28759
28766
  const { stream: _stream, ...spawnOptions } = options;
28760
- return new Promise((resolve47) => {
28767
+ return new Promise((resolve50) => {
28761
28768
  const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
28762
28769
  ...spawnOptions,
28763
28770
  stdio: ["inherit", "pipe", "pipe"]
@@ -28772,13 +28779,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
28772
28779
  stderr += d;
28773
28780
  if (stream) process.stderr.write(d);
28774
28781
  });
28775
- proc.on("close", (code) => resolve47({ stdout, stderr, code: code ?? 0 }));
28782
+ proc.on("close", (code) => resolve50({ stdout, stderr, code: code ?? 0 }));
28776
28783
  });
28777
28784
  }
28778
28785
  function runShellCommand(command2, options = {}) {
28779
28786
  const stream = options.stream ?? false;
28780
28787
  const { stream: _stream, ...spawnOptions } = options;
28781
- return new Promise((resolve47) => {
28788
+ return new Promise((resolve50) => {
28782
28789
  const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
28783
28790
  ...spawnOptions,
28784
28791
  stdio: ["inherit", "pipe", "pipe"]
@@ -28793,7 +28800,7 @@ function runShellCommand(command2, options = {}) {
28793
28800
  stderr += d;
28794
28801
  if (stream) process.stderr.write(d);
28795
28802
  });
28796
- proc.on("close", (code) => resolve47({ stdout, stderr, code: code ?? 0 }));
28803
+ proc.on("close", (code) => resolve50({ stdout, stderr, code: code ?? 0 }));
28797
28804
  });
28798
28805
  }
28799
28806
  var import_child_process;
@@ -29495,6 +29502,22 @@ var init_runs = __esm({
29495
29502
  }
29496
29503
  });
29497
29504
 
29505
+ // src/harness/capabilities.ts
29506
+ function harnessUsesPm2(repoPath) {
29507
+ const harnessRoot = resolveHarnessRoot(repoPath);
29508
+ return fs13.existsSync(path13.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29509
+ }
29510
+ var fs13, path13;
29511
+ var init_capabilities = __esm({
29512
+ "src/harness/capabilities.ts"() {
29513
+ "use strict";
29514
+ fs13 = __toESM(require("fs"));
29515
+ path13 = __toESM(require("path"));
29516
+ init_manifest();
29517
+ init_env();
29518
+ }
29519
+ });
29520
+
29498
29521
  // src/core/slot-ports.ts
29499
29522
  function portStep(env3) {
29500
29523
  return Number(env3.HARNESS_PORT_STEP ?? 10);
@@ -29549,19 +29572,14 @@ function allocateAppPorts(repoPath, agentId) {
29549
29572
  allocated: frontend !== feDefault || api !== apiDefault || debug !== debugDefault
29550
29573
  };
29551
29574
  }
29552
- function harnessUsesPm2(repoPath) {
29553
- const harnessRoot = resolveHarnessRoot(repoPath);
29554
- return fs13.existsSync(path13.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29555
- }
29556
- var import_child_process3, fs13, path13;
29575
+ var import_child_process3;
29557
29576
  var init_slot_ports = __esm({
29558
29577
  "src/core/slot-ports.ts"() {
29559
29578
  "use strict";
29560
29579
  import_child_process3 = require("child_process");
29561
- fs13 = __toESM(require("fs"));
29562
- path13 = __toESM(require("path"));
29563
29580
  init_env();
29564
29581
  init_manifest();
29582
+ init_capabilities();
29565
29583
  }
29566
29584
  });
29567
29585
 
@@ -30628,6 +30646,7 @@ var init_slot_preflight = __esm({
30628
30646
  init_control_port();
30629
30647
  init_slot_launch_guard_occupied();
30630
30648
  init_slot_ports();
30649
+ init_capabilities();
30631
30650
  init_slot_registry();
30632
30651
  init_worktree_untracked();
30633
30652
  import_child_process8 = require("child_process");
@@ -30891,6 +30910,29 @@ function listRecords(harnessRoot, dir, parse4) {
30891
30910
  }
30892
30911
  return records;
30893
30912
  }
30913
+ function parseWorkLinkSpec(spec) {
30914
+ const parts = spec.split("|");
30915
+ if (parts.length < 2) {
30916
+ throw new Error(
30917
+ `Invalid work link "${spec}" \u2014 use source|url or source|url|label (example: github|https://github.com/org/repo/pull/42|PR #42)`
30918
+ );
30919
+ }
30920
+ const [source, url, ...labelParts] = parts;
30921
+ const label = labelParts.length > 0 ? labelParts.join("|") : void 0;
30922
+ return WorkUnitRelatedLinkSchema.parse({ source, url, label });
30923
+ }
30924
+ function mergeRelatedLinks(existing, incoming, primaryUrl) {
30925
+ if (!incoming?.length) return existing?.length ? existing : void 0;
30926
+ const seen = /* @__PURE__ */ new Set();
30927
+ const merged = [];
30928
+ for (const link of [...existing ?? [], ...incoming]) {
30929
+ if (primaryUrl && link.url === primaryUrl) continue;
30930
+ if (seen.has(link.url)) continue;
30931
+ seen.add(link.url);
30932
+ merged.push(link);
30933
+ }
30934
+ return merged.length > 0 ? merged : void 0;
30935
+ }
30894
30936
  function findWorkUnit(harnessRoot, workUnitId) {
30895
30937
  const metadata = WorkUnitMetadataSchema.parse({ workUnitId });
30896
30938
  const file = hashedRecordPath(harnessRoot, WORK_UNITS_DIR, metadata.workUnitId);
@@ -30920,13 +30962,19 @@ function upsertWorkUnit(harnessRoot, input) {
30920
30962
  }
30921
30963
  }
30922
30964
  const now = (/* @__PURE__ */ new Date()).toISOString();
30965
+ const sourceUrl = existing?.sourceUrl ?? metadata.sourceUrl;
30923
30966
  const record2 = WorkUnitRecordSchema.parse({
30924
30967
  ...existing,
30925
30968
  workUnitId: metadata.workUnitId,
30926
30969
  source: existing?.source ?? metadata.source,
30927
- sourceUrl: existing?.sourceUrl ?? metadata.sourceUrl,
30970
+ sourceUrl,
30928
30971
  title: existing?.title ?? metadata.title,
30929
30972
  parentWorkUnitId: existing?.parentWorkUnitId ?? metadata.parentWorkUnitId,
30973
+ relatedLinks: mergeRelatedLinks(
30974
+ existing?.relatedLinks,
30975
+ metadata.relatedLinks,
30976
+ sourceUrl
30977
+ ),
30930
30978
  version: 1,
30931
30979
  createdAt: existing?.createdAt ?? now,
30932
30980
  updatedAt: now,
@@ -30939,6 +30987,23 @@ function upsertWorkUnit(harnessRoot, input) {
30939
30987
  markDirty(harnessRoot);
30940
30988
  return record2;
30941
30989
  }
30990
+ function addWorkUnitLinks(harnessRoot, workUnitId, links) {
30991
+ const existing = findWorkUnit(harnessRoot, workUnitId);
30992
+ if (!existing) throw new Error(`Unknown work unit: ${workUnitId}`);
30993
+ const parsed = links.map((link) => WorkUnitRelatedLinkSchema.parse(link));
30994
+ const relatedLinks = mergeRelatedLinks(existing.relatedLinks, parsed, existing.sourceUrl);
30995
+ const record2 = WorkUnitRecordSchema.parse({
30996
+ ...existing,
30997
+ relatedLinks,
30998
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30999
+ });
31000
+ writeRecord2(
31001
+ hashedRecordPath(harnessRoot, WORK_UNITS_DIR, workUnitId),
31002
+ record2
31003
+ );
31004
+ markDirty(harnessRoot);
31005
+ return record2;
31006
+ }
30942
31007
  function listWorkUnits(harnessRoot) {
30943
31008
  return listRecords(
30944
31009
  harnessRoot,
@@ -32418,7 +32483,7 @@ async function postForResponse(url, headers, payload) {
32418
32483
  } catch (err) {
32419
32484
  lastErr = err;
32420
32485
  if (attempt < MAX_FETCH_ATTEMPTS) {
32421
- await new Promise((resolve47) => setTimeout(resolve47, attempt * 100));
32486
+ await new Promise((resolve50) => setTimeout(resolve50, attempt * 100));
32422
32487
  }
32423
32488
  }
32424
32489
  }
@@ -32618,7 +32683,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
32618
32683
  const deadline = Date.now() + timeoutMs;
32619
32684
  while (Date.now() < deadline) {
32620
32685
  if (await isControlApiReachable(apiUrl)) return true;
32621
- await new Promise((resolve47) => setTimeout(resolve47, intervalMs));
32686
+ await new Promise((resolve50) => setTimeout(resolve50, intervalMs));
32622
32687
  }
32623
32688
  return false;
32624
32689
  }
@@ -34115,15 +34180,15 @@ var require_route = __commonJS({
34115
34180
  };
34116
34181
  }
34117
34182
  function wrapConversion(toModel, graph) {
34118
- const path62 = [graph[toModel].parent, toModel];
34183
+ const path66 = [graph[toModel].parent, toModel];
34119
34184
  let fn = conversions[graph[toModel].parent][toModel];
34120
34185
  let cur = graph[toModel].parent;
34121
34186
  while (graph[cur].parent) {
34122
- path62.unshift(graph[cur].parent);
34187
+ path66.unshift(graph[cur].parent);
34123
34188
  fn = link(conversions[graph[cur].parent][cur], fn);
34124
34189
  cur = graph[cur].parent;
34125
34190
  }
34126
- fn.conversion = path62;
34191
+ fn.conversion = path66;
34127
34192
  return fn;
34128
34193
  }
34129
34194
  module2.exports = function(fromModel) {
@@ -40053,7 +40118,7 @@ var require_iso2022 = __commonJS({
40053
40118
  // node_modules/chardet/index.js
40054
40119
  var require_chardet = __commonJS({
40055
40120
  "node_modules/chardet/index.js"(exports2, module2) {
40056
- var fs48 = require("fs");
40121
+ var fs52 = require("fs");
40057
40122
  var utf8 = require_utf8();
40058
40123
  var unicode = require_unicode();
40059
40124
  var mbcs = require_mbcs();
@@ -40127,28 +40192,28 @@ var require_chardet = __commonJS({
40127
40192
  var fd;
40128
40193
  var handler = function(err, buffer) {
40129
40194
  if (fd) {
40130
- fs48.closeSync(fd);
40195
+ fs52.closeSync(fd);
40131
40196
  }
40132
40197
  if (err) return cb(err, null);
40133
40198
  cb(null, self.detect(buffer, opts));
40134
40199
  };
40135
40200
  if (opts && opts.sampleSize) {
40136
- fd = fs48.openSync(filepath, "r"), sample = Buffer.allocUnsafe(opts.sampleSize);
40137
- fs48.read(fd, sample, 0, opts.sampleSize, null, function(err) {
40201
+ fd = fs52.openSync(filepath, "r"), sample = Buffer.allocUnsafe(opts.sampleSize);
40202
+ fs52.read(fd, sample, 0, opts.sampleSize, null, function(err) {
40138
40203
  handler(err, sample);
40139
40204
  });
40140
40205
  return;
40141
40206
  }
40142
- fs48.readFile(filepath, handler);
40207
+ fs52.readFile(filepath, handler);
40143
40208
  };
40144
40209
  module2.exports.detectFileSync = function(filepath, opts) {
40145
40210
  if (opts && opts.sampleSize) {
40146
- var fd = fs48.openSync(filepath, "r"), sample2 = Buffer.allocUnsafe(opts.sampleSize);
40147
- fs48.readSync(fd, sample2, 0, opts.sampleSize);
40148
- fs48.closeSync(fd);
40211
+ var fd = fs52.openSync(filepath, "r"), sample2 = Buffer.allocUnsafe(opts.sampleSize);
40212
+ fs52.readSync(fd, sample2, 0, opts.sampleSize);
40213
+ fs52.closeSync(fd);
40149
40214
  return self.detect(sample2, opts);
40150
40215
  }
40151
- return self.detect(fs48.readFileSync(filepath), opts);
40216
+ return self.detect(fs52.readFileSync(filepath), opts);
40152
40217
  };
40153
40218
  module2.exports.detectAll = function(buffer, opts) {
40154
40219
  if (typeof opts !== "object") {
@@ -43613,16 +43678,16 @@ var require_os_tmpdir = __commonJS({
43613
43678
  var isWindows = process.platform === "win32";
43614
43679
  var trailingSlashRe = isWindows ? /[^:]\\$/ : /.\/$/;
43615
43680
  module2.exports = function() {
43616
- var path62;
43681
+ var path66;
43617
43682
  if (isWindows) {
43618
- path62 = process.env.TEMP || process.env.TMP || (process.env.SystemRoot || process.env.windir) + "\\temp";
43683
+ path66 = process.env.TEMP || process.env.TMP || (process.env.SystemRoot || process.env.windir) + "\\temp";
43619
43684
  } else {
43620
- path62 = process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp";
43685
+ path66 = process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp";
43621
43686
  }
43622
- if (trailingSlashRe.test(path62)) {
43623
- path62 = path62.slice(0, -1);
43687
+ if (trailingSlashRe.test(path66)) {
43688
+ path66 = path66.slice(0, -1);
43624
43689
  }
43625
- return path62;
43690
+ return path66;
43626
43691
  };
43627
43692
  }
43628
43693
  });
@@ -43630,8 +43695,8 @@ var require_os_tmpdir = __commonJS({
43630
43695
  // node_modules/tmp/lib/tmp.js
43631
43696
  var require_tmp = __commonJS({
43632
43697
  "node_modules/tmp/lib/tmp.js"(exports2, module2) {
43633
- var fs48 = require("fs");
43634
- var path62 = require("path");
43698
+ var fs52 = require("fs");
43699
+ var path66 = require("path");
43635
43700
  var crypto6 = require("crypto");
43636
43701
  var osTmpDir = require_os_tmpdir();
43637
43702
  var _c2 = process.binding("constants");
@@ -43673,7 +43738,7 @@ var require_tmp = __commonJS({
43673
43738
  }
43674
43739
  function _generateTmpName(opts) {
43675
43740
  if (opts.name) {
43676
- return path62.join(opts.dir || tmpDir, opts.name);
43741
+ return path66.join(opts.dir || tmpDir, opts.name);
43677
43742
  }
43678
43743
  if (opts.template) {
43679
43744
  return opts.template.replace(TEMPLATE_PATTERN, _randomChars(6));
@@ -43684,7 +43749,7 @@ var require_tmp = __commonJS({
43684
43749
  _randomChars(12),
43685
43750
  opts.postfix || ""
43686
43751
  ].join("");
43687
- return path62.join(opts.dir || tmpDir, name);
43752
+ return path66.join(opts.dir || tmpDir, name);
43688
43753
  }
43689
43754
  function tmpName(options, callback) {
43690
43755
  var args = _parseArguments(options, callback), opts = args[0], cb = args[1], tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;
@@ -43694,7 +43759,7 @@ var require_tmp = __commonJS({
43694
43759
  return cb(new Error("Invalid template provided"));
43695
43760
  (function _getUniqueName() {
43696
43761
  const name = _generateTmpName(opts);
43697
- fs48.stat(name, function(err) {
43762
+ fs52.stat(name, function(err) {
43698
43763
  if (!err) {
43699
43764
  if (tries-- > 0) return _getUniqueName();
43700
43765
  return cb(new Error("Could not get a unique tmp filename, max tries reached " + name));
@@ -43712,7 +43777,7 @@ var require_tmp = __commonJS({
43712
43777
  do {
43713
43778
  const name = _generateTmpName(opts);
43714
43779
  try {
43715
- fs48.statSync(name);
43780
+ fs52.statSync(name);
43716
43781
  } catch (e) {
43717
43782
  return name;
43718
43783
  }
@@ -43724,13 +43789,13 @@ var require_tmp = __commonJS({
43724
43789
  opts.postfix = _isUndefined(opts.postfix) ? ".tmp" : opts.postfix;
43725
43790
  tmpName(opts, function _tmpNameCreated(err, name) {
43726
43791
  if (err) return cb(err);
43727
- fs48.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err2, fd) {
43792
+ fs52.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err2, fd) {
43728
43793
  if (err2) return cb(err2);
43729
43794
  if (opts.discardDescriptor) {
43730
- return fs48.close(fd, function _discardCallback(err3) {
43795
+ return fs52.close(fd, function _discardCallback(err3) {
43731
43796
  if (err3) {
43732
43797
  try {
43733
- fs48.unlinkSync(name);
43798
+ fs52.unlinkSync(name);
43734
43799
  } catch (e) {
43735
43800
  if (!isENOENT(e)) {
43736
43801
  err3 = e;
@@ -43753,9 +43818,9 @@ var require_tmp = __commonJS({
43753
43818
  opts.postfix = opts.postfix || ".tmp";
43754
43819
  const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;
43755
43820
  const name = tmpNameSync(opts);
43756
- var fd = fs48.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
43821
+ var fd = fs52.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);
43757
43822
  if (opts.discardDescriptor) {
43758
- fs48.closeSync(fd);
43823
+ fs52.closeSync(fd);
43759
43824
  fd = void 0;
43760
43825
  }
43761
43826
  return {
@@ -43767,9 +43832,9 @@ var require_tmp = __commonJS({
43767
43832
  function _rmdirRecursiveSync(root) {
43768
43833
  const dirs = [root];
43769
43834
  do {
43770
- var dir2 = dirs.pop(), deferred = false, files = fs48.readdirSync(dir2);
43835
+ var dir2 = dirs.pop(), deferred = false, files = fs52.readdirSync(dir2);
43771
43836
  for (var i = 0, length = files.length; i < length; i++) {
43772
- var file2 = path62.join(dir2, files[i]), stat = fs48.lstatSync(file2);
43837
+ var file2 = path66.join(dir2, files[i]), stat = fs52.lstatSync(file2);
43773
43838
  if (stat.isDirectory()) {
43774
43839
  if (!deferred) {
43775
43840
  deferred = true;
@@ -43777,11 +43842,11 @@ var require_tmp = __commonJS({
43777
43842
  }
43778
43843
  dirs.push(file2);
43779
43844
  } else {
43780
- fs48.unlinkSync(file2);
43845
+ fs52.unlinkSync(file2);
43781
43846
  }
43782
43847
  }
43783
43848
  if (!deferred) {
43784
- fs48.rmdirSync(dir2);
43849
+ fs52.rmdirSync(dir2);
43785
43850
  }
43786
43851
  } while (dirs.length !== 0);
43787
43852
  }
@@ -43789,7 +43854,7 @@ var require_tmp = __commonJS({
43789
43854
  var args = _parseArguments(options, callback), opts = args[0], cb = args[1];
43790
43855
  tmpName(opts, function _tmpNameCreated(err, name) {
43791
43856
  if (err) return cb(err);
43792
- fs48.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err2) {
43857
+ fs52.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err2) {
43793
43858
  if (err2) return cb(err2);
43794
43859
  cb(null, name, _prepareTmpDirRemoveCallback(name, opts));
43795
43860
  });
@@ -43798,7 +43863,7 @@ var require_tmp = __commonJS({
43798
43863
  function dirSync(options) {
43799
43864
  var args = _parseArguments(options), opts = args[0];
43800
43865
  const name = tmpNameSync(opts);
43801
- fs48.mkdirSync(name, opts.mode || DIR_MODE);
43866
+ fs52.mkdirSync(name, opts.mode || DIR_MODE);
43802
43867
  return {
43803
43868
  name,
43804
43869
  removeCallback: _prepareTmpDirRemoveCallback(name, opts)
@@ -43808,7 +43873,7 @@ var require_tmp = __commonJS({
43808
43873
  const removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {
43809
43874
  try {
43810
43875
  if (0 <= fdPath[0]) {
43811
- fs48.closeSync(fdPath[0]);
43876
+ fs52.closeSync(fdPath[0]);
43812
43877
  }
43813
43878
  } catch (e) {
43814
43879
  if (!isEBADF(e) && !isENOENT(e)) {
@@ -43816,7 +43881,7 @@ var require_tmp = __commonJS({
43816
43881
  }
43817
43882
  }
43818
43883
  try {
43819
- fs48.unlinkSync(fdPath[1]);
43884
+ fs52.unlinkSync(fdPath[1]);
43820
43885
  } catch (e) {
43821
43886
  if (!isENOENT(e)) {
43822
43887
  throw e;
@@ -43829,7 +43894,7 @@ var require_tmp = __commonJS({
43829
43894
  return removeCallback;
43830
43895
  }
43831
43896
  function _prepareTmpDirRemoveCallback(name, opts) {
43832
- const removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs48.rmdirSync.bind(fs48);
43897
+ const removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs52.rmdirSync.bind(fs52);
43833
43898
  const removeCallback = _prepareRemoveCallback(removeFunction, name);
43834
43899
  if (!opts.keep) {
43835
43900
  _removeObjects.unshift(removeCallback);
@@ -44980,7 +45045,7 @@ var require_Observable = __commonJS({
44980
45045
  Observable2.prototype.forEach = function(next, promiseCtor) {
44981
45046
  var _this = this;
44982
45047
  promiseCtor = getPromiseCtor(promiseCtor);
44983
- return new promiseCtor(function(resolve47, reject) {
45048
+ return new promiseCtor(function(resolve50, reject) {
44984
45049
  var subscriber = new Subscriber_1.SafeSubscriber({
44985
45050
  next: function(value) {
44986
45051
  try {
@@ -44991,7 +45056,7 @@ var require_Observable = __commonJS({
44991
45056
  }
44992
45057
  },
44993
45058
  error: reject,
44994
- complete: resolve47
45059
+ complete: resolve50
44995
45060
  });
44996
45061
  _this.subscribe(subscriber);
44997
45062
  });
@@ -45013,14 +45078,14 @@ var require_Observable = __commonJS({
45013
45078
  Observable2.prototype.toPromise = function(promiseCtor) {
45014
45079
  var _this = this;
45015
45080
  promiseCtor = getPromiseCtor(promiseCtor);
45016
- return new promiseCtor(function(resolve47, reject) {
45081
+ return new promiseCtor(function(resolve50, reject) {
45017
45082
  var value;
45018
45083
  _this.subscribe(function(x) {
45019
45084
  return value = x;
45020
45085
  }, function(err) {
45021
45086
  return reject(err);
45022
45087
  }, function() {
45023
- return resolve47(value);
45088
+ return resolve50(value);
45024
45089
  });
45025
45090
  });
45026
45091
  };
@@ -47116,11 +47181,11 @@ var require_innerFrom = __commonJS({
47116
47181
  "use strict";
47117
47182
  var __awaiter = exports2 && exports2.__awaiter || function(thisArg, _arguments, P2, generator) {
47118
47183
  function adopt(value) {
47119
- return value instanceof P2 ? value : new P2(function(resolve47) {
47120
- resolve47(value);
47184
+ return value instanceof P2 ? value : new P2(function(resolve50) {
47185
+ resolve50(value);
47121
47186
  });
47122
47187
  }
47123
- return new (P2 || (P2 = Promise))(function(resolve47, reject) {
47188
+ return new (P2 || (P2 = Promise))(function(resolve50, reject) {
47124
47189
  function fulfilled(value) {
47125
47190
  try {
47126
47191
  step(generator.next(value));
@@ -47136,7 +47201,7 @@ var require_innerFrom = __commonJS({
47136
47201
  }
47137
47202
  }
47138
47203
  function step(result) {
47139
- result.done ? resolve47(result.value) : adopt(result.value).then(fulfilled, rejected);
47204
+ result.done ? resolve50(result.value) : adopt(result.value).then(fulfilled, rejected);
47140
47205
  }
47141
47206
  step((generator = generator.apply(thisArg, _arguments || [])).next());
47142
47207
  });
@@ -47218,14 +47283,14 @@ var require_innerFrom = __commonJS({
47218
47283
  }, i);
47219
47284
  function verb(n) {
47220
47285
  i[n] = o[n] && function(v2) {
47221
- return new Promise(function(resolve47, reject) {
47222
- v2 = o[n](v2), settle(resolve47, reject, v2.done, v2.value);
47286
+ return new Promise(function(resolve50, reject) {
47287
+ v2 = o[n](v2), settle(resolve50, reject, v2.done, v2.value);
47223
47288
  });
47224
47289
  };
47225
47290
  }
47226
- function settle(resolve47, reject, d, v2) {
47291
+ function settle(resolve50, reject, d, v2) {
47227
47292
  Promise.resolve(v2).then(function(v3) {
47228
- resolve47({ value: v3, done: d });
47293
+ resolve50({ value: v3, done: d });
47229
47294
  }, reject);
47230
47295
  }
47231
47296
  };
@@ -47844,7 +47909,7 @@ var require_lastValueFrom = __commonJS({
47844
47909
  var EmptyError_1 = require_EmptyError();
47845
47910
  function lastValueFrom2(source, config2) {
47846
47911
  var hasConfig = typeof config2 === "object";
47847
- return new Promise(function(resolve47, reject) {
47912
+ return new Promise(function(resolve50, reject) {
47848
47913
  var _hasValue = false;
47849
47914
  var _value;
47850
47915
  source.subscribe({
@@ -47855,9 +47920,9 @@ var require_lastValueFrom = __commonJS({
47855
47920
  error: reject,
47856
47921
  complete: function() {
47857
47922
  if (_hasValue) {
47858
- resolve47(_value);
47923
+ resolve50(_value);
47859
47924
  } else if (hasConfig) {
47860
- resolve47(config2.defaultValue);
47925
+ resolve50(config2.defaultValue);
47861
47926
  } else {
47862
47927
  reject(new EmptyError_1.EmptyError());
47863
47928
  }
@@ -47879,16 +47944,16 @@ var require_firstValueFrom = __commonJS({
47879
47944
  var Subscriber_1 = require_Subscriber();
47880
47945
  function firstValueFrom(source, config2) {
47881
47946
  var hasConfig = typeof config2 === "object";
47882
- return new Promise(function(resolve47, reject) {
47947
+ return new Promise(function(resolve50, reject) {
47883
47948
  var subscriber = new Subscriber_1.SafeSubscriber({
47884
47949
  next: function(value) {
47885
- resolve47(value);
47950
+ resolve50(value);
47886
47951
  subscriber.unsubscribe();
47887
47952
  },
47888
47953
  error: reject,
47889
47954
  complete: function() {
47890
47955
  if (hasConfig) {
47891
- resolve47(config2.defaultValue);
47956
+ resolve50(config2.defaultValue);
47892
47957
  } else {
47893
47958
  reject(new EmptyError_1.EmptyError());
47894
47959
  }
@@ -53681,14 +53746,14 @@ var require_run_async = __commonJS({
53681
53746
  return function() {
53682
53747
  var args = arguments;
53683
53748
  var originalThis = this;
53684
- var promise = new Promise(function(resolve47, reject) {
53749
+ var promise = new Promise(function(resolve50, reject) {
53685
53750
  var resolved = false;
53686
53751
  const wrappedResolve = function(value) {
53687
53752
  if (resolved) {
53688
53753
  console.warn("Run-async promise already resolved.");
53689
53754
  }
53690
53755
  resolved = true;
53691
- resolve47(value);
53756
+ resolve50(value);
53692
53757
  };
53693
53758
  var rejected = false;
53694
53759
  const wrappedReject = function(value) {
@@ -53788,32 +53853,32 @@ __export(otel_hooks_exports, {
53788
53853
  });
53789
53854
  function getOtelHooksHome() {
53790
53855
  if (process.env.HAR_OTEL_HOOKS_HOME) {
53791
- return path48.resolve(process.env.HAR_OTEL_HOOKS_HOME);
53856
+ return path51.resolve(process.env.HAR_OTEL_HOOKS_HOME);
53792
53857
  }
53793
- return path48.join(os16.homedir(), ".har", "otel-hooks");
53858
+ return path51.join(os17.homedir(), ".har", "otel-hooks");
53794
53859
  }
53795
53860
  function getOtelHooksConfigPath(hooksHome = getOtelHooksHome()) {
53796
- return path48.join(hooksHome, "otel_config.json");
53861
+ return path51.join(hooksHome, "otel_config.json");
53797
53862
  }
53798
53863
  function getOtelHooksWrapperPath(hooksHome = getOtelHooksHome()) {
53799
- return path48.join(hooksHome, "run-otel-hook.sh");
53864
+ return path51.join(hooksHome, "run-otel-hook.sh");
53800
53865
  }
53801
53866
  function getOtelHooksStateDir(hooksHome = getOtelHooksHome()) {
53802
- return path48.join(hooksHome, "state");
53867
+ return path51.join(hooksHome, "state");
53803
53868
  }
53804
53869
  function localOtelHookBin(hooksHome) {
53805
- return path48.join(hooksHome, "node_modules", ".bin", "otel-hook");
53870
+ return path51.join(hooksHome, "node_modules", ".bin", "otel-hook");
53806
53871
  }
53807
53872
  function installedPackageVersion(hooksHome) {
53808
53873
  try {
53809
- const pkgPath = path48.join(
53874
+ const pkgPath = path51.join(
53810
53875
  hooksHome,
53811
53876
  "node_modules",
53812
53877
  "@osfactory",
53813
53878
  "otel-hook",
53814
53879
  "package.json"
53815
53880
  );
53816
- const pkg = JSON.parse(fs42.readFileSync(pkgPath, "utf8"));
53881
+ const pkg = JSON.parse(fs45.readFileSync(pkgPath, "utf8"));
53817
53882
  return typeof pkg.version === "string" ? pkg.version : null;
53818
53883
  } catch {
53819
53884
  return null;
@@ -53864,15 +53929,15 @@ function buildOtelHooksConfig(options) {
53864
53929
  };
53865
53930
  }
53866
53931
  function writeOtelHooksConfig(config2, hooksHome = getOtelHooksHome()) {
53867
- fs42.mkdirSync(hooksHome, { recursive: true });
53932
+ fs45.mkdirSync(hooksHome, { recursive: true });
53868
53933
  const configPath = getOtelHooksConfigPath(hooksHome);
53869
- fs42.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
53934
+ fs45.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
53870
53935
  `);
53871
53936
  return configPath;
53872
53937
  }
53873
53938
  function writeOtelHooksWrapper(otelHookCommand, hooksHome = getOtelHooksHome()) {
53874
- fs42.mkdirSync(hooksHome, { recursive: true });
53875
- fs42.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
53939
+ fs45.mkdirSync(hooksHome, { recursive: true });
53940
+ fs45.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
53876
53941
  const wrapperPath = getOtelHooksWrapperPath(hooksHome);
53877
53942
  const configPath = getOtelHooksConfigPath(hooksHome);
53878
53943
  const stateDir = getOtelHooksStateDir(hooksHome);
@@ -53884,22 +53949,22 @@ exec "${otelHookCommand}" run \\
53884
53949
  --state-dir "${stateDir}" \\
53885
53950
  "$@"
53886
53951
  `;
53887
- fs42.writeFileSync(wrapperPath, script, { mode: 493 });
53952
+ fs45.writeFileSync(wrapperPath, script, { mode: 493 });
53888
53953
  try {
53889
- fs42.chmodSync(wrapperPath, 493);
53954
+ fs45.chmodSync(wrapperPath, 493);
53890
53955
  } catch {
53891
53956
  }
53892
53957
  return wrapperPath;
53893
53958
  }
53894
53959
  function commandExists3(command2) {
53895
- const result = (0, import_child_process12.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
53960
+ const result = (0, import_child_process13.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
53896
53961
  encoding: "utf8"
53897
53962
  });
53898
53963
  return result.status === 0 && Boolean(result.stdout?.trim());
53899
53964
  }
53900
53965
  function resolveOtelHookCommand(hooksHome) {
53901
53966
  const local = localOtelHookBin(hooksHome);
53902
- if (fs42.existsSync(local)) return local;
53967
+ if (fs45.existsSync(local)) return local;
53903
53968
  return null;
53904
53969
  }
53905
53970
  function resolveHooksInstaller(hooksHome) {
@@ -53914,7 +53979,7 @@ function resolveHooksInstaller(hooksHome) {
53914
53979
  return candidates.find((candidate) => commandExists3(candidate.command)) ?? null;
53915
53980
  }
53916
53981
  function tryInstallPackage(hooksHome) {
53917
- fs42.mkdirSync(hooksHome, { recursive: true });
53982
+ fs45.mkdirSync(hooksHome, { recursive: true });
53918
53983
  const installer = resolveHooksInstaller(hooksHome);
53919
53984
  if (!installer) {
53920
53985
  return {
@@ -53923,11 +53988,11 @@ function tryInstallPackage(hooksHome) {
53923
53988
  };
53924
53989
  }
53925
53990
  const described = `${installer.command} ${installer.args.join(" ")}`;
53926
- const result = (0, import_child_process12.spawnSync)(installer.command, installer.args, {
53991
+ const result = (0, import_child_process13.spawnSync)(installer.command, installer.args, {
53927
53992
  encoding: "utf8",
53928
53993
  timeout: 18e4
53929
53994
  });
53930
- if (result.status === 0 && fs42.existsSync(localOtelHookBin(hooksHome))) {
53995
+ if (result.status === 0 && fs45.existsSync(localOtelHookBin(hooksHome))) {
53931
53996
  return { ok: true, detail: described };
53932
53997
  }
53933
53998
  return {
@@ -53945,7 +54010,7 @@ function uninstallLegacyPythonHooks(hooksHome) {
53945
54010
  for (const [cmd, args] of attempts) {
53946
54011
  if (!commandExists3(cmd)) continue;
53947
54012
  try {
53948
- (0, import_child_process12.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
54013
+ (0, import_child_process13.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
53949
54014
  } catch {
53950
54015
  }
53951
54016
  }
@@ -53953,14 +54018,14 @@ function uninstallLegacyPythonHooks(hooksHome) {
53953
54018
  }
53954
54019
  function removeLegacyPythonArtifacts(hooksHome) {
53955
54020
  const legacyPaths = [
53956
- path48.join(hooksHome, ".venv"),
53957
- path48.join(hooksHome, "otel_config.yaml"),
53958
- path48.join(hooksHome, "venv")
54021
+ path51.join(hooksHome, ".venv"),
54022
+ path51.join(hooksHome, "otel_config.yaml"),
54023
+ path51.join(hooksHome, "venv")
53959
54024
  ];
53960
54025
  for (const target of legacyPaths) {
53961
54026
  try {
53962
- if (fs42.existsSync(target)) {
53963
- fs42.rmSync(target, { recursive: true, force: true });
54027
+ if (fs45.existsSync(target)) {
54028
+ fs45.rmSync(target, { recursive: true, force: true });
53964
54029
  }
53965
54030
  } catch {
53966
54031
  }
@@ -53977,7 +54042,7 @@ function normalizeLegacyProviderFlagsInCommand(command2) {
53977
54042
  }
53978
54043
  function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
53979
54044
  const hookCommand = `${wrapperPath} --provider ${providerId}`;
53980
- const result = (0, import_child_process12.spawnSync)(
54045
+ const result = (0, import_child_process13.spawnSync)(
53981
54046
  otelHookCommand,
53982
54047
  [
53983
54048
  "setup",
@@ -54001,10 +54066,10 @@ function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
54001
54066
  };
54002
54067
  }
54003
54068
  function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
54004
- if (!fs42.existsSync(filePath)) return false;
54069
+ if (!fs45.existsSync(filePath)) return false;
54005
54070
  let raw;
54006
54071
  try {
54007
- raw = fs42.readFileSync(filePath, "utf8");
54072
+ raw = fs45.readFileSync(filePath, "utf8");
54008
54073
  } catch {
54009
54074
  return false;
54010
54075
  }
@@ -54051,15 +54116,15 @@ function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
54051
54116
  return changed;
54052
54117
  };
54053
54118
  if (!walk(parsed)) return false;
54054
- fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54119
+ fs45.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54055
54120
  `);
54056
54121
  return true;
54057
54122
  }
54058
54123
  function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
54059
- if (!fs42.existsSync(filePath)) return false;
54124
+ if (!fs45.existsSync(filePath)) return false;
54060
54125
  let raw;
54061
54126
  try {
54062
- raw = fs42.readFileSync(filePath, "utf8");
54127
+ raw = fs45.readFileSync(filePath, "utf8");
54063
54128
  } catch {
54064
54129
  return false;
54065
54130
  }
@@ -54094,21 +54159,21 @@ function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
54094
54159
  }
54095
54160
  }
54096
54161
  if (!changed) return false;
54097
- fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54162
+ fs45.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54098
54163
  `);
54099
54164
  return true;
54100
54165
  }
54101
54166
  function rewriteKnownHookRegistrations(wrapperPath) {
54102
- const home = os16.homedir();
54167
+ const home = os17.homedir();
54103
54168
  const candidates = [
54104
- path48.join(home, ".cursor", "hooks.json"),
54105
- path48.join(home, ".claude", "settings.json"),
54106
- path48.join(home, ".codex", "hooks.json")
54169
+ path51.join(home, ".cursor", "hooks.json"),
54170
+ path51.join(home, ".claude", "settings.json"),
54171
+ path51.join(home, ".codex", "hooks.json")
54107
54172
  ];
54108
54173
  for (const filePath of candidates) {
54109
54174
  try {
54110
54175
  rewriteHookCommandsToWrapper(filePath, wrapperPath);
54111
- if (filePath.endsWith(`${path48.sep}.cursor${path48.sep}hooks.json`)) {
54176
+ if (filePath.endsWith(`${path51.sep}.cursor${path51.sep}hooks.json`)) {
54112
54177
  pruneLegacyCursorHookEvents(filePath, wrapperPath);
54113
54178
  }
54114
54179
  } catch {
@@ -54124,7 +54189,7 @@ function ensureOtelHooks(options) {
54124
54189
  const hooksHome = getOtelHooksHome();
54125
54190
  const errors = [];
54126
54191
  const providersSetup = [];
54127
- fs42.mkdirSync(hooksHome, { recursive: true });
54192
+ fs45.mkdirSync(hooksHome, { recursive: true });
54128
54193
  let otelHookCommand = resolveOtelHookCommand(hooksHome);
54129
54194
  const needsInstall = !otelHookCommand || installedPackageVersion(hooksHome) !== pinnedVersion();
54130
54195
  if (needsInstall) {
@@ -54218,14 +54283,14 @@ function ensureOtelHooks(options) {
54218
54283
  function disableOtelHooksExport() {
54219
54284
  return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
54220
54285
  }
54221
- var import_child_process12, fs42, os16, path48, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
54286
+ var import_child_process13, fs45, os17, path51, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
54222
54287
  var init_otel_hooks = __esm({
54223
54288
  "src/core/otel-hooks.ts"() {
54224
54289
  "use strict";
54225
- import_child_process12 = require("child_process");
54226
- fs42 = __toESM(require("fs"));
54227
- os16 = __toESM(require("os"));
54228
- path48 = __toESM(require("path"));
54290
+ import_child_process13 = require("child_process");
54291
+ fs45 = __toESM(require("fs"));
54292
+ os17 = __toESM(require("os"));
54293
+ path51 = __toESM(require("path"));
54229
54294
  init_control_config();
54230
54295
  init_telemetry_config();
54231
54296
  OTEL_HOOKS_PACKAGE = "@osfactory/otel-hook@0.2.0";
@@ -57200,7 +57265,7 @@ var require_compile = __commonJS({
57200
57265
  const schOrFunc = root.refs[ref];
57201
57266
  if (schOrFunc)
57202
57267
  return schOrFunc;
57203
- let _sch = resolve47.call(this, root, ref);
57268
+ let _sch = resolve50.call(this, root, ref);
57204
57269
  if (_sch === void 0) {
57205
57270
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
57206
57271
  const { schemaId } = this.opts;
@@ -57227,7 +57292,7 @@ var require_compile = __commonJS({
57227
57292
  function sameSchemaEnv(s1, s2) {
57228
57293
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
57229
57294
  }
57230
- function resolve47(root, ref) {
57295
+ function resolve50(root, ref) {
57231
57296
  let sch;
57232
57297
  while (typeof (sch = this.refs[ref]) == "string")
57233
57298
  ref = sch;
@@ -57445,8 +57510,8 @@ var require_utils = __commonJS({
57445
57510
  }
57446
57511
  return ind;
57447
57512
  }
57448
- function removeDotSegments(path62) {
57449
- let input = path62;
57513
+ function removeDotSegments(path66) {
57514
+ let input = path66;
57450
57515
  const output = [];
57451
57516
  let nextSlash = -1;
57452
57517
  let len = 0;
@@ -57698,8 +57763,8 @@ var require_schemes = __commonJS({
57698
57763
  wsComponent.secure = void 0;
57699
57764
  }
57700
57765
  if (wsComponent.resourceName) {
57701
- const [path62, query] = wsComponent.resourceName.split("?");
57702
- wsComponent.path = path62 && path62 !== "/" ? path62 : void 0;
57766
+ const [path66, query] = wsComponent.resourceName.split("?");
57767
+ wsComponent.path = path66 && path66 !== "/" ? path66 : void 0;
57703
57768
  wsComponent.query = query;
57704
57769
  wsComponent.resourceName = void 0;
57705
57770
  }
@@ -57858,7 +57923,7 @@ var require_fast_uri = __commonJS({
57858
57923
  }
57859
57924
  return uri;
57860
57925
  }
57861
- function resolve47(baseURI, relativeURI, options) {
57926
+ function resolve50(baseURI, relativeURI, options) {
57862
57927
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
57863
57928
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
57864
57929
  schemelessOptions.skipEscape = true;
@@ -58116,7 +58181,7 @@ var require_fast_uri = __commonJS({
58116
58181
  var fastUri = {
58117
58182
  SCHEMES,
58118
58183
  normalize: normalize3,
58119
- resolve: resolve47,
58184
+ resolve: resolve50,
58120
58185
  resolveComponent,
58121
58186
  equal,
58122
58187
  serialize,
@@ -61092,12 +61157,12 @@ var require_dist = __commonJS({
61092
61157
  throw new Error(`Unknown format "${name}"`);
61093
61158
  return f;
61094
61159
  };
61095
- function addFormats(ajv, list, fs48, exportName) {
61160
+ function addFormats(ajv, list, fs52, exportName) {
61096
61161
  var _a2;
61097
61162
  var _b2;
61098
61163
  (_a2 = (_b2 = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
61099
61164
  for (const f of list)
61100
- ajv.addFormat(f, fs48[f]);
61165
+ ajv.addFormat(f, fs52[f]);
61101
61166
  }
61102
61167
  module2.exports = exports2 = formatsPlugin;
61103
61168
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -62381,11 +62446,11 @@ var parser = new YargsParser({
62381
62446
  resolve: import_path2.resolve,
62382
62447
  // TODO: figure out a way to combine ESM and CJS coverage, such that
62383
62448
  // we can exercise all the lines below:
62384
- require: (path62) => {
62449
+ require: (path66) => {
62385
62450
  if (typeof require !== "undefined") {
62386
- return require(path62);
62387
- } else if (path62.match(/\.json$/)) {
62388
- return JSON.parse((0, import_fs2.readFileSync)(path62, "utf8"));
62451
+ return require(path66);
62452
+ } else if (path66.match(/\.json$/)) {
62453
+ return JSON.parse((0, import_fs2.readFileSync)(path66, "utf8"));
62389
62454
  } else {
62390
62455
  throw Error("only .json config files are supported in ESM");
62391
62456
  }
@@ -64911,12 +64976,12 @@ var YargsInstance = class {
64911
64976
  async getCompletion(args, done) {
64912
64977
  argsert("<array> [function]", [args, done], arguments.length);
64913
64978
  if (!done) {
64914
- return new Promise((resolve47, reject) => {
64979
+ return new Promise((resolve50, reject) => {
64915
64980
  __classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
64916
64981
  if (err)
64917
64982
  reject(err);
64918
64983
  else
64919
- resolve47(completions);
64984
+ resolve50(completions);
64920
64985
  });
64921
64986
  });
64922
64987
  } else {
@@ -66205,16 +66270,16 @@ async function handleAgentSkills(options) {
66205
66270
  }
66206
66271
  async function askYesNo(question) {
66207
66272
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
66208
- return new Promise((resolve47) => {
66273
+ return new Promise((resolve50) => {
66209
66274
  process.stderr.write(`${question} `);
66210
66275
  rl.once("line", (answer) => {
66211
66276
  rl.close();
66212
66277
  const trimmed = answer.trim();
66213
66278
  if (trimmed === "") {
66214
- resolve47(true);
66279
+ resolve50(true);
66215
66280
  return;
66216
66281
  }
66217
- resolve47(/^y(es)?$/i.test(trimmed));
66282
+ resolve50(/^y(es)?$/i.test(trimmed));
66218
66283
  });
66219
66284
  });
66220
66285
  }
@@ -66294,7 +66359,7 @@ function handleRemove(argv) {
66294
66359
  }
66295
66360
 
66296
66361
  // src/cli/commands/env.ts
66297
- var path51 = __toESM(require("path"));
66362
+ var path55 = __toESM(require("path"));
66298
66363
 
66299
66364
  // src/cli/finish-command.ts
66300
66365
  init_sync_context();
@@ -66304,12 +66369,12 @@ async function finishCommand(code) {
66304
66369
  }
66305
66370
 
66306
66371
  // src/core/harness.ts
66307
- var fs38 = __toESM(require("fs"));
66308
- var path43 = __toESM(require("path"));
66372
+ var fs41 = __toESM(require("fs"));
66373
+ var path46 = __toESM(require("path"));
66309
66374
 
66310
66375
  // src/harness/generator.ts
66311
- var fs29 = __toESM(require("fs"));
66312
- var path35 = __toESM(require("path"));
66376
+ var fs31 = __toESM(require("fs"));
66377
+ var path37 = __toESM(require("path"));
66313
66378
  init_file_ops();
66314
66379
  init_logging();
66315
66380
 
@@ -66363,12 +66428,163 @@ function isExpectedHarnessOnlyFile(harnessFile, templateFiles) {
66363
66428
 
66364
66429
  // src/harness/generator.ts
66365
66430
  init_manifest();
66366
- init_stages();
66431
+
66432
+ // src/harness/plugin-ledger.ts
66433
+ var fs29 = __toESM(require("fs"));
66434
+ var path35 = __toESM(require("path"));
66435
+ init_zod();
66436
+ init_file_ops();
66437
+ init_manifest();
66438
+ var PluginLedgerEntrySchema = external_exports.object({
66439
+ id: external_exports.string().min(1),
66440
+ source: external_exports.enum(["bundled", "path", "npm", "git"]),
66441
+ /** Original install spec (id, path, package, or git URL). */
66442
+ spec: external_exports.string().min(1),
66443
+ /** Package/plugin version when known. */
66444
+ version: external_exports.string().optional(),
66445
+ stageIds: external_exports.array(external_exports.string().min(1)).min(1),
66446
+ installedAt: external_exports.string()
66447
+ });
66448
+ var PluginLedgerSchema = external_exports.object({
66449
+ version: external_exports.literal("1"),
66450
+ /** Profile used at init, when known. */
66451
+ profile: external_exports.enum(["default", "cli", "ios"]).optional(),
66452
+ /** Ordered runtime bundles that composed the harness scaffold. */
66453
+ bundles: external_exports.array(external_exports.string().min(1)).optional(),
66454
+ plugins: external_exports.array(PluginLedgerEntrySchema).default([])
66455
+ });
66456
+ function getPluginLedgerPath(repoPath) {
66457
+ return path35.join(path35.resolve(repoPath), DEFAULT_HAR_DIR, "plugins.json");
66458
+ }
66459
+ function readPluginLedger(repoPath) {
66460
+ const ledgerPath = getPluginLedgerPath(repoPath);
66461
+ if (!fs29.existsSync(ledgerPath)) return null;
66462
+ try {
66463
+ const raw = JSON.parse(fs29.readFileSync(ledgerPath, "utf8"));
66464
+ const parsed = PluginLedgerSchema.safeParse(raw);
66465
+ return parsed.success ? parsed.data : null;
66466
+ } catch {
66467
+ return null;
66468
+ }
66469
+ }
66470
+ function writePluginLedger(repoPath, ledger) {
66471
+ const ledgerPath = getPluginLedgerPath(repoPath);
66472
+ const harnessDir = path35.dirname(ledgerPath);
66473
+ if (!fs29.existsSync(harnessDir)) {
66474
+ fs29.mkdirSync(harnessDir, { recursive: true });
66475
+ }
66476
+ writeFileSafe(ledgerPath, `${JSON.stringify(ledger, null, 2)}
66477
+ `);
66478
+ }
66479
+ function upsertPluginLedgerEntry(repoPath, entry, extras) {
66480
+ const existing = readPluginLedger(repoPath) ?? {
66481
+ version: "1",
66482
+ plugins: []
66483
+ };
66484
+ const plugins = existing.plugins.filter((p) => p.id !== entry.id);
66485
+ plugins.push(entry);
66486
+ plugins.sort((a, b2) => a.id.localeCompare(b2.id));
66487
+ const ledger = {
66488
+ version: "1",
66489
+ profile: extras?.profile ?? existing.profile,
66490
+ bundles: extras?.bundles ?? existing.bundles,
66491
+ plugins
66492
+ };
66493
+ writePluginLedger(repoPath, ledger);
66494
+ return ledger;
66495
+ }
66496
+ function ensurePluginLedgerScaffold(repoPath, options) {
66497
+ const existing = readPluginLedger(repoPath);
66498
+ const ledger = {
66499
+ version: "1",
66500
+ profile: options.profile ?? existing?.profile,
66501
+ bundles: options.bundles ?? existing?.bundles,
66502
+ plugins: existing?.plugins ?? []
66503
+ };
66504
+ writePluginLedger(repoPath, ledger);
66505
+ return ledger;
66506
+ }
66507
+
66508
+ // src/harness/profiles.ts
66509
+ var fs30 = __toESM(require("fs"));
66510
+ var path36 = __toESM(require("path"));
66511
+ init_zod();
66512
+ var HARNESS_PROFILES = ["default", "cli", "ios"];
66513
+ var ProfileBundleRefSchema = external_exports.object({
66514
+ /** Stable bundle id recorded in .har/plugins.json */
66515
+ id: external_exports.string().min(1),
66516
+ /**
66517
+ * Directory under src/templates/ (e.g. "runtime-bundles/shared-kernel"
66518
+ * or "har-boilerplate").
66519
+ */
66520
+ path: external_exports.string().min(1)
66521
+ });
66522
+ var ProfileManifestSchema = external_exports.object({
66523
+ id: external_exports.enum(HARNESS_PROFILES),
66524
+ description: external_exports.string().min(1),
66525
+ /** Ordered runtime bundles — later bundles overwrite earlier files on conflict. */
66526
+ bundles: external_exports.array(ProfileBundleRefSchema).min(1)
66527
+ });
66528
+ var PROFILE_COMPOSITIONS = {
66529
+ default: {
66530
+ id: "default",
66531
+ description: "Web app \u2014 shared kernel + PM2 runtime + Docker infra",
66532
+ bundles: [
66533
+ { id: "shared-kernel", path: "runtime-bundles/shared-kernel" },
66534
+ { id: "pm2-runtime", path: "runtime-bundles/pm2-runtime" },
66535
+ { id: "runtime-default", path: "har-boilerplate" }
66536
+ ]
66537
+ },
66538
+ cli: {
66539
+ id: "cli",
66540
+ description: "CLI/library \u2014 shared kernel + CLI overlay (no PM2)",
66541
+ bundles: [
66542
+ { id: "shared-kernel", path: "runtime-bundles/shared-kernel" },
66543
+ { id: "runtime-cli", path: "har-boilerplate-cli" }
66544
+ ]
66545
+ },
66546
+ ios: {
66547
+ id: "ios",
66548
+ description: "iOS \u2014 shared kernel + Xcode/Simulator overlay",
66549
+ bundles: [
66550
+ { id: "shared-kernel", path: "runtime-bundles/shared-kernel" },
66551
+ { id: "xcode-sim", path: "runtime-bundles/xcode-sim" },
66552
+ { id: "runtime-ios", path: "har-boilerplate-ios" }
66553
+ ]
66554
+ }
66555
+ };
66367
66556
  var PROFILE_DIRS = {
66368
66557
  default: "har-boilerplate",
66369
66558
  cli: "har-boilerplate-cli",
66370
66559
  ios: "har-boilerplate-ios"
66371
66560
  };
66561
+ function readProfileManifest(profile) {
66562
+ const templates = resolveTemplatesDir();
66563
+ const manifestPath = path36.join(templates, "profiles", profile, "profile.manifest.json");
66564
+ if (fs30.existsSync(manifestPath)) {
66565
+ const parsed = ProfileManifestSchema.safeParse(
66566
+ JSON.parse(fs30.readFileSync(manifestPath, "utf8"))
66567
+ );
66568
+ if (!parsed.success) {
66569
+ throw new Error(`Invalid profile manifest for ${profile}: ${parsed.error.message}`);
66570
+ }
66571
+ if (parsed.data.id !== profile) {
66572
+ throw new Error(`Profile manifest id mismatch: expected ${profile}, got ${parsed.data.id}`);
66573
+ }
66574
+ return parsed.data;
66575
+ }
66576
+ return PROFILE_COMPOSITIONS[profile];
66577
+ }
66578
+ function resolveProfileBundleDir(bundle) {
66579
+ const dir = path36.join(resolveTemplatesDir(), bundle.path);
66580
+ if (!fs30.existsSync(dir)) {
66581
+ throw new Error(`Profile bundle not found: ${bundle.id} (${bundle.path}). Run npm run build.`);
66582
+ }
66583
+ return dir;
66584
+ }
66585
+
66586
+ // src/harness/generator.ts
66587
+ init_stages();
66372
66588
  var CLI_PRUNE_FILES = [
66373
66589
  "ecosystem.agent.template.cjs",
66374
66590
  "env.template",
@@ -66376,39 +66592,46 @@ var CLI_PRUNE_FILES = [
66376
66592
  ];
66377
66593
  function pruneCliProfile(harnessDir) {
66378
66594
  for (const file of CLI_PRUNE_FILES) {
66379
- const filePath = path35.join(harnessDir, file);
66380
- if (fs29.existsSync(filePath)) {
66381
- fs29.unlinkSync(filePath);
66595
+ const filePath = path37.join(harnessDir, file);
66596
+ if (fs31.existsSync(filePath)) {
66597
+ fs31.unlinkSync(filePath);
66382
66598
  }
66383
66599
  }
66384
66600
  }
66385
66601
  function scaffoldHarnessBoilerplate(repoPath, options = {}) {
66386
- const harnessDir = path35.join(repoPath, DEFAULT_HAR_DIR);
66387
- const projectName = path35.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
66602
+ const harnessDir = path37.join(repoPath, DEFAULT_HAR_DIR);
66603
+ const projectName = path37.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
66388
66604
  const profile = options.profile ?? "default";
66389
- const boilerplateDir = path35.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
66390
- if (fs29.existsSync(harnessDir) && !options.force) {
66605
+ const profileManifest = readProfileManifest(profile);
66606
+ const bundleIds = profileManifest.bundles.map((b2) => b2.id);
66607
+ const primaryOverlay = PROFILE_DIRS[profile];
66608
+ const boilerplateDir = path37.join(resolveTemplatesDir(), primaryOverlay);
66609
+ if (fs31.existsSync(harnessDir) && !options.force) {
66391
66610
  throw new Error(
66392
66611
  '.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
66393
66612
  );
66394
66613
  }
66395
- if (!fs29.existsSync(boilerplateDir)) {
66614
+ if (!fs31.existsSync(boilerplateDir)) {
66396
66615
  throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
66397
66616
  }
66398
- if (options.force && fs29.existsSync(harnessDir)) {
66399
- fs29.rmSync(harnessDir, { recursive: true, force: true });
66617
+ if (options.force && fs31.existsSync(harnessDir)) {
66618
+ fs31.rmSync(harnessDir, { recursive: true, force: true });
66619
+ }
66620
+ fs31.mkdirSync(harnessDir, { recursive: true });
66621
+ for (const bundle of profileManifest.bundles) {
66622
+ const bundleDir = resolveProfileBundleDir(bundle);
66623
+ copyDirRecursive(bundleDir, harnessDir);
66400
66624
  }
66401
- copyDirRecursive(boilerplateDir, harnessDir);
66402
66625
  writeHarnessGitignore(harnessDir, boilerplateDir);
66403
66626
  if (profile === "cli") {
66404
66627
  pruneCliProfile(harnessDir);
66405
66628
  }
66406
66629
  syncAgentSlotsToHarnessEnv(repoPath);
66407
- const harnessEnvPath = path35.join(harnessDir, "harness.env");
66408
- if (fs29.existsSync(harnessEnvPath)) {
66409
- let content = fs29.readFileSync(harnessEnvPath, "utf8");
66630
+ const harnessEnvPath = path37.join(harnessDir, "harness.env");
66631
+ if (fs31.existsSync(harnessEnvPath)) {
66632
+ let content = fs31.readFileSync(harnessEnvPath, "utf8");
66410
66633
  content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
66411
- fs29.writeFileSync(harnessEnvPath, content);
66634
+ fs31.writeFileSync(harnessEnvPath, content);
66412
66635
  }
66413
66636
  const manifest = createManifest(
66414
66637
  repoPath,
@@ -66417,10 +66640,12 @@ function scaffoldHarnessBoilerplate(repoPath, options = {}) {
66417
66640
  profile
66418
66641
  );
66419
66642
  writeManifest(repoPath, manifest);
66643
+ ensurePluginLedgerScaffold(repoPath, { profile, bundles: bundleIds });
66420
66644
  ensureRootGitignorePatterns(repoPath);
66421
66645
  success(`Copied harness boilerplate to .har/ (profile: ${profile})`);
66422
66646
  info(`Project name: ${projectName}`);
66423
- return { harnessDir, projectName };
66647
+ info(`Bundles: ${bundleIds.join(" \u2192 ")}`);
66648
+ return { harnessDir, projectName, bundles: bundleIds };
66424
66649
  }
66425
66650
  function finalizeHarness(repoPath, adaptationSummary, stack) {
66426
66651
  const existing = readManifest(repoPath);
@@ -66430,16 +66655,16 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
66430
66655
  }
66431
66656
 
66432
66657
  // src/harness/agent-md.ts
66433
- var fs31 = __toESM(require("fs"));
66434
- var path37 = __toESM(require("path"));
66658
+ var fs33 = __toESM(require("fs"));
66659
+ var path39 = __toESM(require("path"));
66435
66660
  init_file_ops();
66436
66661
  init_logging();
66437
66662
  init_manifest();
66438
66663
 
66439
66664
  // src/harness/instruction-files.ts
66440
- var fs30 = __toESM(require("fs"));
66665
+ var fs32 = __toESM(require("fs"));
66441
66666
  var os14 = __toESM(require("os"));
66442
- var path36 = __toESM(require("path"));
66667
+ var path38 = __toESM(require("path"));
66443
66668
 
66444
66669
  // node_modules/@inquirer/core/dist/esm/lib/key.mjs
66445
66670
  var isUpKey = (key) => (
@@ -67459,13 +67684,13 @@ var PromisePolyfill = class extends Promise {
67459
67684
  // Available starting from Node 22
67460
67685
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
67461
67686
  static withResolver() {
67462
- let resolve47;
67687
+ let resolve50;
67463
67688
  let reject;
67464
67689
  const promise = new Promise((res, rej) => {
67465
- resolve47 = res;
67690
+ resolve50 = res;
67466
67691
  reject = rej;
67467
67692
  });
67468
- return { promise, resolve: resolve47, reject };
67693
+ return { promise, resolve: resolve50, reject };
67469
67694
  }
67470
67695
  };
67471
67696
 
@@ -67482,7 +67707,7 @@ function createPrompt(view) {
67482
67707
  output
67483
67708
  });
67484
67709
  const screen = new ScreenManager(rl);
67485
- const { promise, resolve: resolve47, reject } = PromisePolyfill.withResolver();
67710
+ const { promise, resolve: resolve50, reject } = PromisePolyfill.withResolver();
67486
67711
  const cancel = () => reject(new CancelPromptError());
67487
67712
  if (signal) {
67488
67713
  const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
@@ -67506,7 +67731,7 @@ function createPrompt(view) {
67506
67731
  cycle(() => {
67507
67732
  try {
67508
67733
  const nextView = view(config2, (value) => {
67509
- setImmediate(() => resolve47(value));
67734
+ setImmediate(() => resolve50(value));
67510
67735
  });
67511
67736
  const [content, bottomContent] = typeof nextView === "string" ? [nextView] : nextView;
67512
67737
  screen.render(content, bottomContent);
@@ -68493,9 +68718,9 @@ var import_run_async = __toESM(require_run_async(), 1);
68493
68718
  var import_mute_stream2 = __toESM(require_lib(), 1);
68494
68719
  var import_ansi_escapes5 = __toESM(require_ansi_escapes(), 1);
68495
68720
  var _ = {
68496
- set: (obj, path62 = "", value) => {
68721
+ set: (obj, path66 = "", value) => {
68497
68722
  let pointer = obj;
68498
- path62.split(".").forEach((key, index, arr) => {
68723
+ path66.split(".").forEach((key, index, arr) => {
68499
68724
  if (key === "__proto__" || key === "constructor")
68500
68725
  return;
68501
68726
  if (index === arr.length - 1) {
@@ -68506,8 +68731,8 @@ var _ = {
68506
68731
  pointer = pointer[key];
68507
68732
  });
68508
68733
  },
68509
- get: (obj, path62 = "", defaultValue) => {
68510
- const travel = (regexp) => String.prototype.split.call(path62, regexp).filter(Boolean).reduce(
68734
+ get: (obj, path66 = "", defaultValue) => {
68735
+ const travel = (regexp) => String.prototype.split.call(path66, regexp).filter(Boolean).reduce(
68511
68736
  // @ts-expect-error implicit any on res[key]
68512
68737
  (res, key) => res !== null && res !== void 0 ? res[key] : res,
68513
68738
  obj
@@ -68621,7 +68846,7 @@ var PromptsRunner = class {
68621
68846
  throw new Error(`Prompt for type ${question.type} not found`);
68622
68847
  }
68623
68848
  let cleanupSignal;
68624
- const promptFn = isPromptConstructor(prompt2) ? (q, { signal: signal2 } = {}) => new Promise((resolve47, reject) => {
68849
+ const promptFn = isPromptConstructor(prompt2) ? (q, { signal: signal2 } = {}) => new Promise((resolve50, reject) => {
68625
68850
  const rl = import_node_readline.default.createInterface(setupReadlineOptions(this.opt));
68626
68851
  rl.resume();
68627
68852
  const onClose = () => {
@@ -68657,7 +68882,7 @@ var PromptsRunner = class {
68657
68882
  cleanupSignal = void 0;
68658
68883
  };
68659
68884
  }
68660
- activePrompt.run().then(resolve47, reject).finally(cleanup);
68885
+ activePrompt.run().then(resolve50, reject).finally(cleanup);
68661
68886
  }) : prompt2;
68662
68887
  const { signal: moduleSignal } = this.opt;
68663
68888
  if (moduleSignal?.aborted) {
@@ -68833,12 +69058,12 @@ function outsideContentShrinkMessage(beforeLines, afterLines) {
68833
69058
  }
68834
69059
  function detectInstructionFiles(repoPath) {
68835
69060
  return {
68836
- agentsMd: fs30.existsSync(path36.join(repoPath, AGENTS_MD)),
68837
- agentMd: fs30.existsSync(path36.join(repoPath, LEGACY_AGENT_MD)),
68838
- claudeMd: fs30.existsSync(path36.join(repoPath, CLAUDE_MD)),
68839
- cursorDir: fs30.existsSync(path36.join(repoPath, ".cursor")),
68840
- claudeDir: fs30.existsSync(path36.join(repoPath, ".claude")),
68841
- codexHome: fs30.existsSync(path36.join(os14.homedir(), ".codex"))
69061
+ agentsMd: fs32.existsSync(path38.join(repoPath, AGENTS_MD)),
69062
+ agentMd: fs32.existsSync(path38.join(repoPath, LEGACY_AGENT_MD)),
69063
+ claudeMd: fs32.existsSync(path38.join(repoPath, CLAUDE_MD)),
69064
+ cursorDir: fs32.existsSync(path38.join(repoPath, ".cursor")),
69065
+ claudeDir: fs32.existsSync(path38.join(repoPath, ".claude")),
69066
+ codexHome: fs32.existsSync(path38.join(os14.homedir(), ".codex"))
68842
69067
  };
68843
69068
  }
68844
69069
  function formatDetectionReport(detection) {
@@ -68893,7 +69118,7 @@ function loadHarAgentsSectionFromTemplate() {
68893
69118
  if (!templatePath) {
68894
69119
  throw new Error("AGENTS.md.template not found. Run npm run build.");
68895
69120
  }
68896
- const content = fs30.readFileSync(templatePath, "utf8");
69121
+ const content = fs32.readFileSync(templatePath, "utf8");
68897
69122
  const start = content.indexOf(HAR_SECTION_START);
68898
69123
  const end = content.indexOf(HAR_SECTION_END);
68899
69124
  if (start === -1 || end === -1 || end < start) {
@@ -68906,7 +69131,7 @@ function loadAgentsMdTemplate() {
68906
69131
  if (!templatePath) {
68907
69132
  throw new Error("AGENTS.md.template not found. Run npm run build.");
68908
69133
  }
68909
- return fs30.readFileSync(templatePath, "utf8");
69134
+ return fs32.readFileSync(templatePath, "utf8");
68910
69135
  }
68911
69136
  function upsertMarkedBlock2(content, block, startMarker, endMarker) {
68912
69137
  const existingBlock = findLastCompleteMarkedBlock(content, startMarker, endMarker);
@@ -68939,13 +69164,13 @@ function mergeAgentsMdContent(existing, incoming) {
68939
69164
  return content;
68940
69165
  }
68941
69166
  function upsertAgentsMdHarSection(repoPath, options = {}) {
68942
- const dest = path36.join(repoPath, AGENTS_MD);
69167
+ const dest = path38.join(repoPath, AGENTS_MD);
68943
69168
  const section = loadHarAgentsSectionFromTemplate();
68944
- if (!fs30.existsSync(dest)) {
69169
+ if (!fs32.existsSync(dest)) {
68945
69170
  writeFileSafe(dest, loadAgentsMdTemplate());
68946
69171
  return "created";
68947
69172
  }
68948
- const existing = fs30.readFileSync(dest, "utf8");
69173
+ const existing = fs32.readFileSync(dest, "utf8");
68949
69174
  const sanitized = stripClaudePointerFromAgentsMd(existing);
68950
69175
  const { content, action } = upsertMarkedBlock2(
68951
69176
  sanitized,
@@ -68970,15 +69195,15 @@ function upsertAgentsMdHarSection(repoPath, options = {}) {
68970
69195
  return action;
68971
69196
  }
68972
69197
  function migrateLegacyAgentMd(repoPath) {
68973
- const legacyPath = path36.join(repoPath, LEGACY_AGENT_MD);
68974
- const agentsPath = path36.join(repoPath, AGENTS_MD);
68975
- if (!fs30.existsSync(legacyPath)) return false;
68976
- const legacy = fs30.readFileSync(legacyPath, "utf8");
68977
- if (!fs30.existsSync(agentsPath)) {
69198
+ const legacyPath = path38.join(repoPath, LEGACY_AGENT_MD);
69199
+ const agentsPath = path38.join(repoPath, AGENTS_MD);
69200
+ if (!fs32.existsSync(legacyPath)) return false;
69201
+ const legacy = fs32.readFileSync(legacyPath, "utf8");
69202
+ if (!fs32.existsSync(agentsPath)) {
68978
69203
  writeFileSafe(agentsPath, legacy);
68979
69204
  upsertAgentsMdHarSection(repoPath);
68980
69205
  } else {
68981
- const agents = fs30.readFileSync(agentsPath, "utf8");
69206
+ const agents = fs32.readFileSync(agentsPath, "utf8");
68982
69207
  const legacyTrimmed = legacy.trim();
68983
69208
  if (legacyTrimmed.length > 0 && !agents.includes(legacyTrimmed)) {
68984
69209
  const suffix = agents.endsWith("\n") ? "" : "\n";
@@ -68988,7 +69213,7 @@ ${legacyTrimmed}
68988
69213
  }
68989
69214
  upsertAgentsMdHarSection(repoPath);
68990
69215
  }
68991
- fs30.unlinkSync(legacyPath);
69216
+ fs32.unlinkSync(legacyPath);
68992
69217
  info(`Migrated ${LEGACY_AGENT_MD} \u2192 ${AGENTS_MD} (removed legacy file)`);
68993
69218
  return true;
68994
69219
  }
@@ -68998,7 +69223,7 @@ function loadClaudeMdTemplate(projectName) {
68998
69223
  throw new Error("CLAUDE.md.template not found. Run npm run build.");
68999
69224
  }
69000
69225
  const displayName = projectName.replace(/_/g, " ");
69001
- return fs30.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
69226
+ return fs32.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
69002
69227
  }
69003
69228
  function isThinClaudePointer(content) {
69004
69229
  const trimmed = content.trim();
@@ -69012,14 +69237,14 @@ This repository uses a \`.har/\` harness. Read [AGENTS.md](./AGENTS.md) and
69012
69237
  [\`.har/README.md\`](./.har/README.md) before making changes.
69013
69238
  ${CLAUDE_HAR_SECTION_END}`;
69014
69239
  function ensureClaudeMdPointer(repoPath, options = {}) {
69015
- const dest = path36.join(repoPath, CLAUDE_MD);
69016
- const projectName = path36.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69240
+ const dest = path38.join(repoPath, CLAUDE_MD);
69241
+ const projectName = path38.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69017
69242
  const thin = loadClaudeMdTemplate(projectName);
69018
- if (!fs30.existsSync(dest)) {
69243
+ if (!fs32.existsSync(dest)) {
69019
69244
  writeFileSafe(dest, thin);
69020
69245
  return "created";
69021
69246
  }
69022
- const existing = fs30.readFileSync(dest, "utf8");
69247
+ const existing = fs32.readFileSync(dest, "utf8");
69023
69248
  if (isThinClaudePointer(existing) || options.force) {
69024
69249
  writeFileSafe(dest, thin);
69025
69250
  return "updated";
@@ -69168,19 +69393,19 @@ var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
69168
69393
  var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
69169
69394
  function readAgentMdProposal(repoPath) {
69170
69395
  const harnessDir = getHarnessDir(repoPath);
69171
- const proposalPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL);
69172
- const metaPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL_META);
69173
- const legacyProposalPath = path37.join(harnessDir, AGENT_MD_PROPOSAL);
69174
- const legacyMetaPath = path37.join(harnessDir, AGENT_MD_PROPOSAL_META);
69175
- const resolvedProposal = fs31.existsSync(proposalPath) ? proposalPath : fs31.existsSync(legacyProposalPath) ? legacyProposalPath : null;
69396
+ const proposalPath = path39.join(harnessDir, AGENTS_MD_PROPOSAL);
69397
+ const metaPath = path39.join(harnessDir, AGENTS_MD_PROPOSAL_META);
69398
+ const legacyProposalPath = path39.join(harnessDir, AGENT_MD_PROPOSAL);
69399
+ const legacyMetaPath = path39.join(harnessDir, AGENT_MD_PROPOSAL_META);
69400
+ const resolvedProposal = fs33.existsSync(proposalPath) ? proposalPath : fs33.existsSync(legacyProposalPath) ? legacyProposalPath : null;
69176
69401
  if (!resolvedProposal) return null;
69177
69402
  const resolvedMeta = resolvedProposal === proposalPath ? metaPath : legacyMetaPath;
69178
- const content = fs31.readFileSync(resolvedProposal, "utf8");
69403
+ const content = fs33.readFileSync(resolvedProposal, "utf8");
69179
69404
  let rationale = "";
69180
69405
  let createdAt = (/* @__PURE__ */ new Date()).toISOString();
69181
- if (fs31.existsSync(resolvedMeta)) {
69406
+ if (fs33.existsSync(resolvedMeta)) {
69182
69407
  try {
69183
- const meta = JSON.parse(fs31.readFileSync(resolvedMeta, "utf8"));
69408
+ const meta = JSON.parse(fs33.readFileSync(resolvedMeta, "utf8"));
69184
69409
  rationale = meta.rationale ?? "";
69185
69410
  createdAt = meta.createdAt ?? createdAt;
69186
69411
  } catch {
@@ -69197,8 +69422,8 @@ function clearAgentMdProposal(repoPath) {
69197
69422
  AGENT_MD_PROPOSAL,
69198
69423
  AGENT_MD_PROPOSAL_META
69199
69424
  ]) {
69200
- const p = path37.join(harnessDir, file);
69201
- if (fs31.existsSync(p)) fs31.unlinkSync(p);
69425
+ const p = path39.join(harnessDir, file);
69426
+ if (fs33.existsSync(p)) fs33.unlinkSync(p);
69202
69427
  }
69203
69428
  }
69204
69429
  function wouldProposalShrinkExisting(existing, proposal) {
@@ -69211,9 +69436,9 @@ function wouldProposalShrinkExisting(existing, proposal) {
69211
69436
  function applyAgentMdProposalMerge(repoPath, options = {}) {
69212
69437
  const proposal = readAgentMdProposal(repoPath);
69213
69438
  if (!proposal) return false;
69214
- const agentsMdPath = path37.join(repoPath, AGENTS_MD);
69215
- if (fs31.existsSync(agentsMdPath)) {
69216
- const existing = fs31.readFileSync(agentsMdPath, "utf8");
69439
+ const agentsMdPath = path39.join(repoPath, AGENTS_MD);
69440
+ if (fs33.existsSync(agentsMdPath)) {
69441
+ const existing = fs33.readFileSync(agentsMdPath, "utf8");
69217
69442
  if (wouldProposalShrinkExisting(existing, proposal.content)) {
69218
69443
  const message = `${AGENTS_MD} proposal would drop project-specific content outside har:agent-environment markers \u2014 merge custom sections from .har/${AGENTS_MD_PROPOSAL} manually, then delete the proposal.`;
69219
69444
  if (options.rejectSignificantShrink) {
@@ -69236,8 +69461,8 @@ function finalizeAgentsMdInstructionFiles(repoPath) {
69236
69461
  }
69237
69462
 
69238
69463
  // src/harness/validator.ts
69239
- var fs32 = __toESM(require("fs"));
69240
- var path38 = __toESM(require("path"));
69464
+ var fs34 = __toESM(require("fs"));
69465
+ var path40 = __toESM(require("path"));
69241
69466
  init_shell();
69242
69467
  init_manifest();
69243
69468
  init_stages();
@@ -69292,22 +69517,22 @@ var SHELL_SCRIPTS = [
69292
69517
  function validateHarness(repoPath) {
69293
69518
  const harnessDir = getHarnessDir(repoPath);
69294
69519
  const issues = [];
69295
- if (!fs32.existsSync(harnessDir)) {
69520
+ if (!fs34.existsSync(harnessDir)) {
69296
69521
  return {
69297
69522
  pass: false,
69298
69523
  issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
69299
69524
  };
69300
69525
  }
69301
69526
  for (const file of getRequiredFiles(repoPath)) {
69302
- const filePath = path38.join(harnessDir, file);
69303
- if (!fs32.existsSync(filePath)) {
69527
+ const filePath = path40.join(harnessDir, file);
69528
+ if (!fs34.existsSync(filePath)) {
69304
69529
  issues.push({ file, message: "Required file missing", severity: "error" });
69305
69530
  }
69306
69531
  }
69307
69532
  for (const script of SHELL_SCRIPTS) {
69308
- const scriptPath = path38.join(harnessDir, script);
69309
- if (!fs32.existsSync(scriptPath)) continue;
69310
- const stat = fs32.statSync(scriptPath);
69533
+ const scriptPath = path40.join(harnessDir, script);
69534
+ if (!fs34.existsSync(scriptPath)) continue;
69535
+ const stat = fs34.statSync(scriptPath);
69311
69536
  if (!(stat.mode & 73)) {
69312
69537
  issues.push({ file: script, message: "Script is not executable", severity: "warning" });
69313
69538
  }
@@ -69322,9 +69547,9 @@ function validateHarness(repoPath) {
69322
69547
  }
69323
69548
  const manifest = readManifest(repoPath);
69324
69549
  const profile = manifest?.profile ?? "default";
69325
- const ecosystemPath = path38.join(harnessDir, "ecosystem.agent.template.cjs");
69326
- if (profile !== "cli" && fs32.existsSync(ecosystemPath)) {
69327
- const content = fs32.readFileSync(ecosystemPath, "utf8");
69550
+ const ecosystemPath = path40.join(harnessDir, "ecosystem.agent.template.cjs");
69551
+ if (profile !== "cli" && fs34.existsSync(ecosystemPath)) {
69552
+ const content = fs34.readFileSync(ecosystemPath, "utf8");
69328
69553
  if (!content.includes("module.exports")) {
69329
69554
  issues.push({
69330
69555
  file: "ecosystem.agent.template.cjs",
@@ -69333,9 +69558,9 @@ function validateHarness(repoPath) {
69333
69558
  });
69334
69559
  }
69335
69560
  }
69336
- const harnessEnvPath = path38.join(harnessDir, "harness.env");
69337
- if (fs32.existsSync(harnessEnvPath)) {
69338
- const content = fs32.readFileSync(harnessEnvPath, "utf8");
69561
+ const harnessEnvPath = path40.join(harnessDir, "harness.env");
69562
+ if (fs34.existsSync(harnessEnvPath)) {
69563
+ const content = fs34.readFileSync(harnessEnvPath, "utf8");
69339
69564
  if (content.includes("TODO: set migrate command")) {
69340
69565
  issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
69341
69566
  }
@@ -69343,17 +69568,17 @@ function validateHarness(repoPath) {
69343
69568
  issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
69344
69569
  }
69345
69570
  }
69346
- const verifyPath = path38.join(harnessDir, "verify.sh");
69347
- if (fs32.existsSync(verifyPath)) {
69348
- const content = fs32.readFileSync(verifyPath, "utf8");
69571
+ const verifyPath = path40.join(harnessDir, "verify.sh");
69572
+ if (fs34.existsSync(verifyPath)) {
69573
+ const content = fs34.readFileSync(verifyPath, "utf8");
69349
69574
  if (content.includes("echo 'TODO:")) {
69350
69575
  issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
69351
69576
  }
69352
69577
  }
69353
- const stagesPath = path38.join(harnessDir, "stages.json");
69354
- if (fs32.existsSync(stagesPath)) {
69578
+ const stagesPath = path40.join(harnessDir, "stages.json");
69579
+ if (fs34.existsSync(stagesPath)) {
69355
69580
  try {
69356
- JSON.parse(fs32.readFileSync(stagesPath, "utf8"));
69581
+ JSON.parse(fs34.readFileSync(stagesPath, "utf8"));
69357
69582
  const registry2 = readStageRegistry(repoPath);
69358
69583
  if (registry2.stages.length === 0) {
69359
69584
  issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
@@ -69368,9 +69593,9 @@ function validateHarness(repoPath) {
69368
69593
  } else {
69369
69594
  issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
69370
69595
  }
69371
- const readmePath = path38.join(harnessDir, "README.md");
69372
- if (fs32.existsSync(readmePath)) {
69373
- const content = fs32.readFileSync(readmePath, "utf8");
69596
+ const readmePath = path40.join(harnessDir, "README.md");
69597
+ if (fs34.existsSync(readmePath)) {
69598
+ const content = fs34.readFileSync(readmePath, "utf8");
69374
69599
  if (!content.includes(".har")) {
69375
69600
  issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
69376
69601
  }
@@ -69384,8 +69609,8 @@ function validateHarness(repoPath) {
69384
69609
  async function smokeTestHarness(repoPath) {
69385
69610
  const harnessDir = getHarnessDir(repoPath);
69386
69611
  const issues = [];
69387
- const setupScript = path38.join(harnessDir, "setup-infra.sh");
69388
- if (fs32.existsSync(setupScript)) {
69612
+ const setupScript = path40.join(harnessDir, "setup-infra.sh");
69613
+ if (fs34.existsSync(setupScript)) {
69389
69614
  const result = run(`bash "${setupScript}"`, { cwd: repoPath });
69390
69615
  if (result.code !== 0) {
69391
69616
  issues.push({
@@ -69400,16 +69625,11 @@ async function smokeTestHarness(repoPath) {
69400
69625
  }
69401
69626
 
69402
69627
  // src/harness/drift.ts
69403
- var fs33 = __toESM(require("fs"));
69404
- var path39 = __toESM(require("path"));
69628
+ var fs35 = __toESM(require("fs"));
69629
+ var path41 = __toESM(require("path"));
69405
69630
  init_env();
69406
69631
  init_manifest();
69407
69632
  init_stages();
69408
- var PROFILE_DIRS2 = {
69409
- default: "har-boilerplate",
69410
- cli: "har-boilerplate-cli",
69411
- ios: "har-boilerplate-ios"
69412
- };
69413
69633
  var CLI_EXPECTED_ABSENT = /* @__PURE__ */ new Set([
69414
69634
  "ecosystem.agent.template.cjs",
69415
69635
  "env.template",
@@ -69472,16 +69692,16 @@ function substituteProjectName(content, projectName) {
69472
69692
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
69473
69693
  }
69474
69694
  function listBoilerplateFiles(boilerplateDir) {
69475
- if (!fs33.existsSync(boilerplateDir)) return [];
69476
- return fs33.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
69695
+ if (!fs35.existsSync(boilerplateDir)) return [];
69696
+ return fs35.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
69477
69697
  }
69478
69698
  function compareHarnessToTemplate(repoPath) {
69479
- const resolved = path39.resolve(repoPath);
69699
+ const resolved = path41.resolve(repoPath);
69480
69700
  const manifest = readManifest(resolved);
69481
69701
  const profile = manifest?.profile ?? "default";
69482
69702
  const harnessDir = getHarnessDir(resolved);
69483
- const projectName = path39.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
69484
- const boilerplateDir = path39.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
69703
+ const projectName = path41.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
69704
+ const boilerplateDir = path41.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
69485
69705
  const templateFiles = listBoilerplateFiles(boilerplateDir);
69486
69706
  const missing = [];
69487
69707
  const checksumMismatch = [];
@@ -69489,17 +69709,17 @@ function compareHarnessToTemplate(repoPath) {
69489
69709
  const unchanged = [];
69490
69710
  for (const file of templateFiles) {
69491
69711
  const harnessFile = harnessFileForTemplate(file);
69492
- const templatePath = path39.join(boilerplateDir, file);
69493
- const harnessPath = path39.join(harnessDir, harnessFile);
69494
- let templateContent = fs33.readFileSync(templatePath, "utf8");
69712
+ const templatePath = path41.join(boilerplateDir, file);
69713
+ const harnessPath = path41.join(harnessDir, harnessFile);
69714
+ let templateContent = fs35.readFileSync(templatePath, "utf8");
69495
69715
  if (file === "harness.env") {
69496
69716
  templateContent = substituteProjectName(templateContent, projectName);
69497
69717
  }
69498
- if (!fs33.existsSync(harnessPath)) {
69718
+ if (!fs35.existsSync(harnessPath)) {
69499
69719
  missing.push(harnessFile);
69500
69720
  continue;
69501
69721
  }
69502
- const harnessChecksum = computeFileChecksum(fs33.readFileSync(harnessPath, "utf8"));
69722
+ const harnessChecksum = computeFileChecksum(fs35.readFileSync(harnessPath, "utf8"));
69503
69723
  const templateChecksum = computeFileChecksum(templateContent);
69504
69724
  if (harnessChecksum === templateChecksum) {
69505
69725
  unchanged.push(harnessFile);
@@ -69507,11 +69727,13 @@ function compareHarnessToTemplate(repoPath) {
69507
69727
  checksumMismatch.push(harnessFile);
69508
69728
  }
69509
69729
  }
69510
- if (fs33.existsSync(harnessDir)) {
69511
- for (const file of fs33.readdirSync(harnessDir)) {
69512
- const full = path39.join(harnessDir, file);
69513
- if (!fs33.statSync(full).isFile()) continue;
69514
- if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
69730
+ if (fs35.existsSync(harnessDir)) {
69731
+ for (const file of fs35.readdirSync(harnessDir)) {
69732
+ const full = path41.join(harnessDir, file);
69733
+ if (!fs35.statSync(full).isFile()) continue;
69734
+ if (file === "manifest.json" || file === "plugins.json" || file.startsWith("ADAPT-PROMPT")) {
69735
+ continue;
69736
+ }
69515
69737
  if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
69516
69738
  extra.push(file);
69517
69739
  } else if (isExpectedHarnessOnlyFile(file, templateFiles)) {
@@ -69541,107 +69763,356 @@ function compareHarnessToTemplate(repoPath) {
69541
69763
  }
69542
69764
 
69543
69765
  // src/harness/maintain-bundle.ts
69544
- var fs37 = __toESM(require("fs"));
69545
- var os15 = __toESM(require("os"));
69546
- var path42 = __toESM(require("path"));
69766
+ var fs40 = __toESM(require("fs"));
69767
+ var os16 = __toESM(require("os"));
69768
+ var path45 = __toESM(require("path"));
69547
69769
  init_file_ops();
69548
69770
  init_shell();
69549
69771
  init_manifest();
69550
69772
 
69551
69773
  // src/harness/plugin-drift.ts
69552
- var fs36 = __toESM(require("fs"));
69553
- var path41 = __toESM(require("path"));
69774
+ var fs39 = __toESM(require("fs"));
69775
+ var path44 = __toESM(require("path"));
69554
69776
  init_manifest();
69555
69777
 
69556
69778
  // src/harness/plugins.ts
69557
- var fs35 = __toESM(require("fs"));
69558
- var path40 = __toESM(require("path"));
69779
+ var fs38 = __toESM(require("fs"));
69780
+ var path43 = __toESM(require("path"));
69559
69781
  init_zod();
69560
69782
  init_logging();
69561
69783
 
69562
69784
  // src/harness/parser.ts
69563
- var fs34 = __toESM(require("fs"));
69785
+ var fs36 = __toESM(require("fs"));
69564
69786
  init_manifest();
69565
69787
  function harnessExists(repoPath) {
69566
- return fs34.existsSync(`${repoPath}/.har/setup-infra.sh`);
69788
+ return fs36.existsSync(`${repoPath}/.har/setup-infra.sh`);
69789
+ }
69790
+
69791
+ // src/harness/plugin-resolve.ts
69792
+ var import_child_process10 = require("child_process");
69793
+ var fs37 = __toESM(require("fs"));
69794
+ var os15 = __toESM(require("os"));
69795
+ var path42 = __toESM(require("path"));
69796
+ function hasManifest(dir) {
69797
+ return fs37.existsSync(path42.join(dir, "template.manifest.json"));
69798
+ }
69799
+ function readPackageVersion(dir) {
69800
+ const pkgPath = path42.join(dir, "package.json");
69801
+ if (!fs37.existsSync(pkgPath)) return void 0;
69802
+ try {
69803
+ const pkg = JSON.parse(fs37.readFileSync(pkgPath, "utf8"));
69804
+ return typeof pkg.version === "string" ? pkg.version : void 0;
69805
+ } catch {
69806
+ return void 0;
69807
+ }
69808
+ }
69809
+ function readManifestId(dir) {
69810
+ const manifestPath = path42.join(dir, "template.manifest.json");
69811
+ if (!fs37.existsSync(manifestPath)) return void 0;
69812
+ try {
69813
+ const raw = JSON.parse(fs37.readFileSync(manifestPath, "utf8"));
69814
+ return typeof raw.id === "string" ? raw.id : void 0;
69815
+ } catch {
69816
+ return void 0;
69817
+ }
69818
+ }
69819
+ function looksLikePath(spec) {
69820
+ return spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~") || spec.startsWith("file:");
69821
+ }
69822
+ function looksLikeGit(spec) {
69823
+ return spec.startsWith("git+") || spec.startsWith("github:") || spec.startsWith("gitlab:") || spec.startsWith("bitbucket:") || /^https?:\/\/.+\.git$/i.test(spec) || /^git@/.test(spec);
69824
+ }
69825
+ function looksLikeNpm(spec) {
69826
+ if (looksLikePath(spec) || looksLikeGit(spec)) return false;
69827
+ if (spec.includes("/") && !spec.startsWith("@")) return false;
69828
+ return /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@.+)?$/i.test(spec);
69829
+ }
69830
+ function resolveBundledDir(pluginId) {
69831
+ const dir = path42.join(resolveTemplatesDir(), "plugins", pluginId);
69832
+ return hasManifest(dir) ? dir : null;
69833
+ }
69834
+ function expandHome(p) {
69835
+ if (p.startsWith("~/")) return path42.join(os15.homedir(), p.slice(2));
69836
+ if (p === "~") return os15.homedir();
69837
+ return p;
69838
+ }
69839
+ function resolvePathSpec(spec) {
69840
+ const raw = spec.startsWith("file:") ? spec.slice("file:".length) : spec;
69841
+ const dir = path42.resolve(expandHome(raw));
69842
+ if (!fs37.existsSync(dir) || !fs37.statSync(dir).isDirectory()) {
69843
+ throw new Error(`Plugin path not found: ${spec}`);
69844
+ }
69845
+ if (!hasManifest(dir)) {
69846
+ throw new Error(`No template.manifest.json in ${dir}`);
69847
+ }
69848
+ const id = readManifestId(dir) ?? path42.basename(dir);
69849
+ return {
69850
+ id,
69851
+ kind: "path",
69852
+ dir,
69853
+ spec,
69854
+ version: readPackageVersion(dir),
69855
+ cleanupDirs: []
69856
+ };
69857
+ }
69858
+ function makeTempDir(prefix) {
69859
+ return fs37.mkdtempSync(path42.join(os15.tmpdir(), prefix));
69860
+ }
69861
+ function resolveNpmSpec(spec) {
69862
+ const tmp = makeTempDir("har-plugin-npm-");
69863
+ const cleanupDirs = [tmp];
69864
+ try {
69865
+ (0, import_child_process10.execFileSync)("npm", ["pack", spec, "--pack-destination", tmp], {
69866
+ encoding: "utf8",
69867
+ stdio: ["ignore", "pipe", "pipe"],
69868
+ timeout: 12e4
69869
+ });
69870
+ const tarballs = fs37.readdirSync(tmp).filter((f) => f.endsWith(".tgz"));
69871
+ if (tarballs.length === 0) {
69872
+ throw new Error(`npm pack produced no tarball for ${spec}`);
69873
+ }
69874
+ const extractDir = path42.join(tmp, "extract");
69875
+ fs37.mkdirSync(extractDir);
69876
+ (0, import_child_process10.execFileSync)("tar", ["-xzf", path42.join(tmp, tarballs[0]), "-C", extractDir], {
69877
+ encoding: "utf8",
69878
+ stdio: ["ignore", "pipe", "pipe"]
69879
+ });
69880
+ const packageDir = path42.join(extractDir, "package");
69881
+ let dir = packageDir;
69882
+ if (!hasManifest(dir)) {
69883
+ const nested = fs37.readdirSync(extractDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path42.join(extractDir, e.name)).find((d) => hasManifest(d));
69884
+ if (!nested) {
69885
+ throw new Error(
69886
+ `npm package ${spec} has no template.manifest.json (not a HAR plugin bundle)`
69887
+ );
69888
+ }
69889
+ dir = nested;
69890
+ }
69891
+ const id = readManifestId(dir) ?? spec.replace(/^@/, "").replace(/[/:].*/g, "");
69892
+ return {
69893
+ id,
69894
+ kind: "npm",
69895
+ dir,
69896
+ spec,
69897
+ version: readPackageVersion(dir),
69898
+ cleanupDirs
69899
+ };
69900
+ } catch (err) {
69901
+ for (const d of cleanupDirs) {
69902
+ fs37.rmSync(d, { recursive: true, force: true });
69903
+ }
69904
+ const message = err instanceof Error ? err.message : String(err);
69905
+ throw new Error(`Failed to install plugin from npm (${spec}): ${message}`);
69906
+ }
69907
+ }
69908
+ function resolveGitSpec(spec) {
69909
+ const tmp = makeTempDir("har-plugin-git-");
69910
+ const cleanupDirs = [tmp];
69911
+ const cloneDir = path42.join(tmp, "repo");
69912
+ try {
69913
+ let url = spec;
69914
+ if (spec.startsWith("github:")) {
69915
+ url = `https://github.com/${spec.slice("github:".length)}.git`;
69916
+ } else if (spec.startsWith("gitlab:")) {
69917
+ url = `https://gitlab.com/${spec.slice("gitlab:".length)}.git`;
69918
+ } else if (spec.startsWith("bitbucket:")) {
69919
+ url = `https://bitbucket.org/${spec.slice("bitbucket:".length)}.git`;
69920
+ } else if (spec.startsWith("git+")) {
69921
+ url = spec.slice("git+".length);
69922
+ }
69923
+ (0, import_child_process10.execFileSync)("git", ["clone", "--depth", "1", url, cloneDir], {
69924
+ encoding: "utf8",
69925
+ stdio: ["ignore", "pipe", "pipe"],
69926
+ timeout: 18e4
69927
+ });
69928
+ let dir = cloneDir;
69929
+ if (!hasManifest(dir)) {
69930
+ const pluginsRoot = path42.join(cloneDir, "plugins");
69931
+ if (fs37.existsSync(pluginsRoot)) {
69932
+ const child = fs37.readdirSync(pluginsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path42.join(pluginsRoot, e.name)).find((d) => hasManifest(d));
69933
+ if (child) dir = child;
69934
+ }
69935
+ }
69936
+ if (!hasManifest(dir)) {
69937
+ throw new Error(`Git repo ${spec} has no template.manifest.json (not a HAR plugin bundle)`);
69938
+ }
69939
+ const id = readManifestId(dir) ?? path42.basename(url.replace(/\.git$/, ""));
69940
+ return {
69941
+ id,
69942
+ kind: "git",
69943
+ dir,
69944
+ spec,
69945
+ version: readPackageVersion(dir),
69946
+ cleanupDirs
69947
+ };
69948
+ } catch (err) {
69949
+ for (const d of cleanupDirs) {
69950
+ fs37.rmSync(d, { recursive: true, force: true });
69951
+ }
69952
+ const message = err instanceof Error ? err.message : String(err);
69953
+ throw new Error(`Failed to install plugin from git (${spec}): ${message}`);
69954
+ }
69955
+ }
69956
+ function resolvePluginSource(spec) {
69957
+ const trimmed = spec.trim();
69958
+ if (!trimmed) {
69959
+ throw new Error("Plugin spec is empty");
69960
+ }
69961
+ if (looksLikePath(trimmed)) {
69962
+ return resolvePathSpec(trimmed);
69963
+ }
69964
+ if (looksLikeGit(trimmed)) {
69965
+ return resolveGitSpec(trimmed);
69966
+ }
69967
+ const bundled = resolveBundledDir(trimmed);
69968
+ if (bundled) {
69969
+ return {
69970
+ id: trimmed,
69971
+ kind: "bundled",
69972
+ dir: bundled,
69973
+ spec: trimmed,
69974
+ version: readPackageVersion(bundled),
69975
+ cleanupDirs: []
69976
+ };
69977
+ }
69978
+ if (looksLikeNpm(trimmed) && (trimmed.includes("@") || trimmed.includes("/"))) {
69979
+ return resolveNpmSpec(trimmed);
69980
+ }
69981
+ if (looksLikeNpm(trimmed)) {
69982
+ try {
69983
+ return resolveNpmSpec(trimmed);
69984
+ } catch (npmErr) {
69985
+ const available2 = listBundledPluginIds();
69986
+ const npmMessage = npmErr instanceof Error ? npmErr.message : String(npmErr);
69987
+ throw new Error(
69988
+ `Unknown plugin: ${trimmed}. Bundled: ${available2.join(", ") || "(none)"}. Or pass a path, npm package, or git URL. (${npmMessage})`
69989
+ );
69990
+ }
69991
+ }
69992
+ const available = listBundledPluginIds();
69993
+ throw new Error(
69994
+ `Unknown plugin: ${trimmed}. Bundled: ${available.join(", ") || "(none)"}. Or pass a path (./plugin), npm package (@org/pkg), or git URL (github:org/repo).`
69995
+ );
69996
+ }
69997
+ function listBundledPluginIds() {
69998
+ const root = path42.join(resolveTemplatesDir(), "plugins");
69999
+ if (!fs37.existsSync(root)) return [];
70000
+ return fs37.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => hasManifest(path42.join(root, name))).sort();
70001
+ }
70002
+ function cleanupResolvedPlugin(source) {
70003
+ for (const dir of source.cleanupDirs) {
70004
+ fs37.rmSync(dir, { recursive: true, force: true });
70005
+ }
69567
70006
  }
69568
70007
 
69569
70008
  // src/harness/plugins.ts
69570
70009
  init_schema2();
69571
70010
  init_stages();
69572
- var PLUGIN_IDS = ["playwright", "rocketsim", "kerno", "gitleaks", "trivy", "semgrep"];
69573
70011
  var PluginManifestFileSchema = external_exports.object({
69574
70012
  src: external_exports.string().min(1),
69575
70013
  dest: external_exports.string().min(1),
69576
70014
  executable: external_exports.boolean().optional(),
69577
70015
  skipFlag: external_exports.string().optional()
69578
70016
  });
70017
+ var PluginIdSchema = external_exports.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i, "Plugin id must be a slug (letters, digits, ._-)");
69579
70018
  var PluginManifestSchema = external_exports.object({
69580
- id: external_exports.enum(PLUGIN_IDS),
69581
- stageId: external_exports.string().min(1),
70019
+ id: PluginIdSchema,
70020
+ /** @deprecated Prefer `stages` — primary stage id for single-stage plugins. */
70021
+ stageId: external_exports.string().min(1).optional(),
69582
70022
  verificationStages: external_exports.array(external_exports.string().min(1)).min(1),
69583
- stage: external_exports.record(external_exports.unknown()),
70023
+ /** @deprecated Prefer `stages` — single stage object. */
70024
+ stage: external_exports.record(external_exports.unknown()).optional(),
70025
+ /** One or more stages registered by this plugin. */
70026
+ stages: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
69584
70027
  files: external_exports.array(PluginManifestFileSchema).min(1),
69585
70028
  optionalFiles: external_exports.array(PluginManifestFileSchema).optional(),
69586
70029
  merge: external_exports.record(external_exports.string()).optional(),
69587
70030
  nextSteps: external_exports.array(external_exports.string().min(1)).min(1),
69588
70031
  docsPath: external_exports.string().min(1)
70032
+ }).superRefine((data, ctx) => {
70033
+ const hasStages = Array.isArray(data.stages) && data.stages.length > 0;
70034
+ const hasLegacy = data.stage !== void 0 && data.stageId !== void 0;
70035
+ if (!hasStages && !hasLegacy) {
70036
+ ctx.addIssue({
70037
+ code: external_exports.ZodIssueCode.custom,
70038
+ message: "Plugin manifest requires `stages` (preferred) or legacy `stage` + `stageId`"
70039
+ });
70040
+ }
69589
70041
  });
69590
- function resolvePluginDir(pluginId) {
69591
- const dir = path40.join(resolveTemplatesDir(), "plugins", pluginId);
69592
- if (!fs35.existsSync(dir)) {
70042
+ function normalizePluginStages(manifest) {
70043
+ if (manifest.stages && manifest.stages.length > 0) {
70044
+ return manifest.stages.map((s) => HarnessStageSchema.parse(s));
70045
+ }
70046
+ if (manifest.stage) {
70047
+ return [HarnessStageSchema.parse(manifest.stage)];
70048
+ }
70049
+ throw new Error(`Plugin ${manifest.id} has no stages`);
70050
+ }
70051
+ function primaryStageId(manifest) {
70052
+ const stages = normalizePluginStages(manifest);
70053
+ return manifest.stageId ?? stages[0].id;
70054
+ }
70055
+ function resolveBundledPluginDir(pluginId) {
70056
+ const dir = path43.join(resolveTemplatesDir(), "plugins", pluginId);
70057
+ if (!fs38.existsSync(dir)) {
69593
70058
  throw new Error(`Plugin not found: ${pluginId}. Run npm run build.`);
69594
70059
  }
69595
70060
  return dir;
69596
70061
  }
69597
- function readPluginManifest(pluginId) {
69598
- const manifestPath = path40.join(resolvePluginDir(pluginId), "template.manifest.json");
70062
+ function readPluginManifestFromDir(pluginDir, expectedId) {
70063
+ const manifestPath = path43.join(pluginDir, "template.manifest.json");
70064
+ if (!fs38.existsSync(manifestPath)) {
70065
+ throw new Error(`No template.manifest.json in ${pluginDir}`);
70066
+ }
69599
70067
  const parsed = PluginManifestSchema.safeParse(
69600
- JSON.parse(fs35.readFileSync(manifestPath, "utf8"))
70068
+ JSON.parse(fs38.readFileSync(manifestPath, "utf8"))
69601
70069
  );
69602
70070
  if (!parsed.success) {
69603
- throw new Error(`Invalid plugin manifest for ${pluginId}: ${parsed.error.message}`);
70071
+ throw new Error(`Invalid plugin manifest: ${parsed.error.message}`);
69604
70072
  }
69605
- if (parsed.data.id !== pluginId) {
69606
- throw new Error(`Plugin manifest id mismatch: expected ${pluginId}, got ${parsed.data.id}`);
70073
+ if (expectedId && parsed.data.id !== expectedId) {
70074
+ throw new Error(`Plugin manifest id mismatch: expected ${expectedId}, got ${parsed.data.id}`);
69607
70075
  }
69608
70076
  return parsed.data;
69609
70077
  }
70078
+ function readPluginManifest(pluginId) {
70079
+ return readPluginManifestFromDir(resolveBundledPluginDir(pluginId), pluginId);
70080
+ }
69610
70081
  function ensureParentDir(filePath) {
69611
- const parent = path40.dirname(filePath);
69612
- if (!fs35.existsSync(parent)) {
69613
- fs35.mkdirSync(parent, { recursive: true });
70082
+ const parent = path43.dirname(filePath);
70083
+ if (!fs38.existsSync(parent)) {
70084
+ fs38.mkdirSync(parent, { recursive: true });
69614
70085
  }
69615
70086
  }
69616
70087
  function copyPluginFile(pluginDir, file, repoPath, force) {
69617
- const srcPath = path40.join(pluginDir, file.src);
69618
- const destPath = path40.join(repoPath, file.dest);
69619
- if (!fs35.existsSync(srcPath)) {
70088
+ const srcPath = path43.join(pluginDir, file.src);
70089
+ const destPath = path43.join(repoPath, file.dest);
70090
+ if (!fs38.existsSync(srcPath)) {
69620
70091
  throw new Error(`Plugin file missing: ${file.src}`);
69621
70092
  }
69622
- if (fs35.existsSync(destPath) && !force) {
70093
+ if (fs38.existsSync(destPath) && !force) {
69623
70094
  throw new Error(
69624
70095
  `File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
69625
70096
  );
69626
70097
  }
69627
70098
  ensureParentDir(destPath);
69628
- fs35.copyFileSync(srcPath, destPath);
70099
+ fs38.copyFileSync(srcPath, destPath);
69629
70100
  if (file.executable) {
69630
- fs35.chmodSync(destPath, 493);
70101
+ fs38.chmodSync(destPath, 493);
69631
70102
  }
69632
70103
  return { written: true, path: file.dest };
69633
70104
  }
69634
70105
  function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
69635
- const packagePath = path40.join(repoPath, "package.json");
69636
- if (!fs35.existsSync(packagePath)) {
70106
+ const packagePath = path43.join(repoPath, "package.json");
70107
+ if (!fs38.existsSync(packagePath)) {
69637
70108
  throw new Error("No package.json in repo root. Add one before applying this plugin.");
69638
70109
  }
69639
- const fragmentPath = path40.join(pluginDir, fragmentRelPath);
69640
- if (!fs35.existsSync(fragmentPath)) {
70110
+ const fragmentPath = path43.join(pluginDir, fragmentRelPath);
70111
+ if (!fs38.existsSync(fragmentPath)) {
69641
70112
  throw new Error(`Package fragment missing: ${fragmentRelPath}`);
69642
70113
  }
69643
- const pkg = JSON.parse(fs35.readFileSync(packagePath, "utf8"));
69644
- const fragment = JSON.parse(fs35.readFileSync(fragmentPath, "utf8"));
70114
+ const pkg = JSON.parse(fs38.readFileSync(packagePath, "utf8"));
70115
+ const fragment = JSON.parse(fs38.readFileSync(fragmentPath, "utf8"));
69645
70116
  for (const section of ["scripts", "devDependencies"]) {
69646
70117
  const existing = pkg[section] ?? {};
69647
70118
  const incoming = fragment[section] ?? {};
@@ -69654,35 +70125,37 @@ function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
69654
70125
  }
69655
70126
  pkg[section] = existing;
69656
70127
  }
69657
- fs35.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
70128
+ fs38.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
69658
70129
  }
69659
- function patchStageRegistry(repoPath, manifest, force) {
70130
+ function patchStageRegistry(repoPath, stages, verificationStageIds, force) {
69660
70131
  const registry2 = readStageRegistry(repoPath);
69661
- const stage = HarnessStageSchema.parse(manifest.stage);
69662
- const existing = registry2.stages.find((s) => s.id === stage.id);
69663
- if (existing && !force) {
69664
- throw new Error(
69665
- `Stage "${stage.id}" already registered in .har/stages.json. Use --force to replace.`
69666
- );
70132
+ let nextStages = [...registry2.stages];
70133
+ for (const stage of stages) {
70134
+ const existing = nextStages.find((s) => s.id === stage.id);
70135
+ if (existing && !force) {
70136
+ throw new Error(
70137
+ `Stage "${stage.id}" already registered in .har/stages.json. Use --force to replace.`
70138
+ );
70139
+ }
70140
+ nextStages = existing ? nextStages.map((s) => s.id === stage.id ? stage : s) : [...nextStages, stage];
69667
70141
  }
69668
- const stages = existing ? registry2.stages.map((s) => s.id === stage.id ? stage : s) : [...registry2.stages, stage];
69669
70142
  const verificationStages = [...registry2.verificationStages ?? []];
69670
- for (const id of manifest.verificationStages) {
70143
+ for (const id of verificationStageIds) {
69671
70144
  if (!verificationStages.includes(id)) {
69672
70145
  verificationStages.push(id);
69673
70146
  }
69674
70147
  }
69675
- const verifyIdx = stages.findIndex((s) => s.id === "verify");
70148
+ const verifyIdx = nextStages.findIndex((s) => s.id === "verify");
69676
70149
  if (verifyIdx >= 0) {
69677
- stages[verifyIdx] = {
69678
- ...stages[verifyIdx],
70150
+ nextStages[verifyIdx] = {
70151
+ ...nextStages[verifyIdx],
69679
70152
  description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`,
69680
70153
  acceptsArgs: ["--full"]
69681
70154
  };
69682
70155
  }
69683
70156
  const updated = {
69684
70157
  ...registry2,
69685
- stages,
70158
+ stages: nextStages,
69686
70159
  verificationStages
69687
70160
  };
69688
70161
  writeStageRegistry(repoPath, updated);
@@ -69692,30 +70165,33 @@ function assertHarnessPresent(repoPath) {
69692
70165
  throw new Error('No .har/ harness found. Run "har env init" first.');
69693
70166
  }
69694
70167
  }
69695
- function assertStageNotPresent(repoPath, stageId, force) {
70168
+ function assertStagesNotPresent(repoPath, stageIds, force) {
69696
70169
  if (force) return;
69697
- const scriptPath = path40.join(repoPath, ".har", "stages", `${stageId}.sh`);
69698
- if (fs35.existsSync(scriptPath)) {
69699
- throw new Error(
69700
- `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
69701
- );
69702
- }
69703
70170
  const registry2 = readStageRegistry(repoPath);
69704
- if (registry2.stages.some((s) => s.id === stageId)) {
69705
- throw new Error(
69706
- `Stage "${stageId}" already registered in .har/stages.json. Use --force to replace.`
69707
- );
70171
+ for (const stageId of stageIds) {
70172
+ const scriptPath = path43.join(repoPath, ".har", "stages", `${stageId}.sh`);
70173
+ if (fs38.existsSync(scriptPath)) {
70174
+ throw new Error(
70175
+ `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
70176
+ );
70177
+ }
70178
+ if (registry2.stages.some((s) => s.id === stageId)) {
70179
+ throw new Error(
70180
+ `Stage "${stageId}" already registered in .har/stages.json. Use --force to replace.`
70181
+ );
70182
+ }
69708
70183
  }
69709
70184
  }
69710
- function applyPlugin(repoPath, pluginId, options = {}) {
69711
- const resolved = path40.resolve(repoPath);
70185
+ function applyPluginFromDir(repoPath, pluginDir, options, meta) {
70186
+ const resolved = path43.resolve(repoPath);
69712
70187
  const force = options.force ?? false;
69713
70188
  const warnings = [];
69714
70189
  const filesWritten = [];
69715
70190
  assertHarnessPresent(resolved);
69716
- const manifest = readPluginManifest(pluginId);
69717
- assertStageNotPresent(resolved, manifest.stageId, force);
69718
- const pluginDir = resolvePluginDir(pluginId);
70191
+ const manifest = readPluginManifestFromDir(pluginDir);
70192
+ const stages = normalizePluginStages(manifest);
70193
+ const stageIds = stages.map((s) => s.id);
70194
+ assertStagesNotPresent(resolved, stageIds, force);
69719
70195
  for (const file of manifest.files) {
69720
70196
  const result = copyPluginFile(pluginDir, file, resolved, force);
69721
70197
  if (result.written) {
@@ -69727,7 +70203,7 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69727
70203
  if (file.skipFlag === "skipCi" && options.skipCi) {
69728
70204
  continue;
69729
70205
  }
69730
- if (fs35.existsSync(path40.join(resolved, file.dest)) && !force) {
70206
+ if (fs38.existsSync(path43.join(resolved, file.dest)) && !force) {
69731
70207
  warnings.push(`Skipped optional file (exists): ${file.dest}`);
69732
70208
  continue;
69733
70209
  }
@@ -69743,9 +70219,18 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69743
70219
  filesWritten.push("package.json");
69744
70220
  }
69745
70221
  }
69746
- patchStageRegistry(resolved, manifest, force);
69747
- success(`Applied plugin: ${pluginId}`);
69748
- info(`Registered stage: ${manifest.stageId}`);
70222
+ patchStageRegistry(resolved, stages, manifest.verificationStages, force);
70223
+ upsertPluginLedgerEntry(resolved, {
70224
+ id: manifest.id,
70225
+ source: meta.source,
70226
+ spec: meta.spec,
70227
+ version: meta.version,
70228
+ stageIds,
70229
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
70230
+ });
70231
+ const primary = primaryStageId(manifest);
70232
+ success(`Applied plugin: ${manifest.id}`);
70233
+ info(`Registered stage(s): ${stageIds.join(", ")}`);
69749
70234
  for (const file of filesWritten) {
69750
70235
  info(` + ${file}`);
69751
70236
  }
@@ -69753,50 +70238,88 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69753
70238
  warn(` \u26A0 ${warning}`);
69754
70239
  }
69755
70240
  return {
69756
- pluginId,
69757
- stageId: manifest.stageId,
70241
+ pluginId: manifest.id,
70242
+ stageId: primary,
70243
+ stageIds,
69758
70244
  filesWritten,
69759
70245
  warnings,
69760
70246
  nextSteps: manifest.nextSteps,
69761
- docsPath: manifest.docsPath
70247
+ docsPath: manifest.docsPath,
70248
+ source: meta.source
69762
70249
  };
69763
70250
  }
70251
+ function applyPlugin(repoPath, pluginSpec, options = {}) {
70252
+ const spec = options.spec ?? pluginSpec;
70253
+ const source = resolvePluginSource(spec);
70254
+ try {
70255
+ return applyPluginFromDir(repoPath, source.dir, options, {
70256
+ source: source.kind,
70257
+ spec: source.spec,
70258
+ version: source.version
70259
+ });
70260
+ } finally {
70261
+ cleanupResolvedPlugin(source);
70262
+ }
70263
+ }
69764
70264
  function listPluginIds() {
69765
- const root = path40.join(resolveTemplatesDir(), "plugins");
69766
- if (!fs35.existsSync(root)) return [...PLUGIN_IDS];
69767
- return fs35.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => PLUGIN_IDS.includes(name));
70265
+ return listBundledPluginIds();
69768
70266
  }
69769
70267
 
69770
70268
  // src/harness/plugin-drift.ts
69771
70269
  init_stages();
69772
70270
  var PACKAGE_SECTIONS = ["scripts", "devDependencies"];
69773
70271
  function detectInstalledPlugins(repoPath) {
69774
- const resolved = path41.resolve(repoPath);
69775
- const registryPath = path41.join(resolved, ".har", "stages.json");
69776
- if (!fs36.existsSync(registryPath)) return [];
70272
+ const resolved = path44.resolve(repoPath);
70273
+ const ledger = readPluginLedger(resolved);
70274
+ if (ledger && ledger.plugins.length > 0) {
70275
+ return ledger.plugins.map((p) => p.id).sort();
70276
+ }
70277
+ const registryPath = path44.join(resolved, ".har", "stages.json");
70278
+ if (!fs39.existsSync(registryPath)) return [];
69777
70279
  const registry2 = readStageRegistry(resolved);
69778
70280
  const stageIds = new Set(registry2.stages.map((stage) => stage.id));
69779
70281
  const installed = [];
69780
- for (const pluginId of PLUGIN_IDS) {
69781
- const manifest = readPluginManifest(pluginId);
69782
- if (stageIds.has(manifest.stageId)) {
69783
- installed.push(pluginId);
70282
+ for (const pluginId of listPluginIds()) {
70283
+ try {
70284
+ const manifest = readPluginManifest(pluginId);
70285
+ const primary = primaryStageId(manifest);
70286
+ if (stageIds.has(primary)) {
70287
+ installed.push(pluginId);
70288
+ }
70289
+ } catch {
69784
70290
  }
69785
70291
  }
69786
- return installed;
70292
+ return installed.sort();
69787
70293
  }
69788
70294
  function pluginTemplatePath(pluginId, srcRel) {
69789
- return path41.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
70295
+ return path44.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
70296
+ }
70297
+ function resolvePluginTemplateDir(repoPath, pluginId) {
70298
+ const ledger = readPluginLedger(repoPath);
70299
+ const entry = ledger?.plugins.find((p) => p.id === pluginId);
70300
+ if (entry && entry.source !== "bundled") {
70301
+ try {
70302
+ const source = resolvePluginSource(entry.spec);
70303
+ cleanupResolvedPlugin(source);
70304
+ } catch {
70305
+ }
70306
+ }
70307
+ const bundled = path44.join(resolveTemplatesDir(), "plugins", pluginId);
70308
+ if (fs39.existsSync(path44.join(bundled, "template.manifest.json"))) {
70309
+ return bundled;
70310
+ }
70311
+ return null;
69790
70312
  }
69791
70313
  function readBundledPluginFile(pluginId, srcRel) {
69792
- return fs36.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
70314
+ return fs39.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
69793
70315
  }
69794
70316
  function listPluginDestPaths(pluginId, repoPath) {
69795
- const manifest = readPluginManifest(pluginId);
70317
+ const templateDir = resolvePluginTemplateDir(repoPath, pluginId);
70318
+ const manifest = templateDir ? readPluginManifestFromDir(templateDir) : readPluginManifest(pluginId);
69796
70319
  const paths = manifest.files.map((file) => file.dest);
69797
70320
  if (manifest.optionalFiles) {
69798
70321
  for (const file of manifest.optionalFiles) {
69799
- if (fs36.existsSync(path41.join(repoPath, file.dest))) {
70322
+ if (fs39.existsSync(path44.join(repoPath, file.dest))) {
69800
70323
  paths.push(file.dest);
69801
70324
  }
69802
70325
  }
@@ -69855,9 +70378,9 @@ function readTemplatePluginFile(pluginId, dest) {
69855
70378
  return null;
69856
70379
  }
69857
70380
  function readInstalledPluginFile(repoPath, pluginId, dest) {
69858
- const installedPath = path41.join(path41.resolve(repoPath), dest);
69859
- if (!fs36.existsSync(installedPath)) return null;
69860
- const content = fs36.readFileSync(installedPath, "utf8");
70381
+ const installedPath = path44.join(path44.resolve(repoPath), dest);
70382
+ if (!fs39.existsSync(installedPath)) return null;
70383
+ const content = fs39.readFileSync(installedPath, "utf8");
69861
70384
  const manifest = readPluginManifest(pluginId);
69862
70385
  const fragmentRel = manifest.merge?.[dest];
69863
70386
  if (fragmentRel) {
@@ -69876,7 +70399,7 @@ function pluginActionHint(pluginId, dest, kind) {
69876
70399
  }
69877
70400
  function comparePluginToTemplate(repoPath, pluginId) {
69878
70401
  const manifest = readPluginManifest(pluginId);
69879
- const resolved = path41.resolve(repoPath);
70402
+ const resolved = path44.resolve(repoPath);
69880
70403
  const destPaths = listPluginDestPaths(pluginId, resolved);
69881
70404
  const missing = [];
69882
70405
  const checksumMismatch = [];
@@ -69891,8 +70414,8 @@ function comparePluginToTemplate(repoPath, pluginId) {
69891
70414
  }
69892
70415
  if (dest === "package.json") {
69893
70416
  const fragmentRel = manifest.merge?.[dest];
69894
- const installedPath = path41.join(resolved, dest);
69895
- const installedRaw = fs36.readFileSync(installedPath, "utf8");
70417
+ const installedPath = path44.join(resolved, dest);
70418
+ const installedRaw = fs39.readFileSync(installedPath, "utf8");
69896
70419
  if (fragmentRel && pluginKeysMatch(installedRaw, readBundledPluginFile(pluginId, fragmentRel))) {
69897
70420
  unchanged.push(dest);
69898
70421
  } else if (computeFileChecksum(installedContent) === computeFileChecksum(templateContent)) {
@@ -69910,16 +70433,17 @@ function comparePluginToTemplate(repoPath, pluginId) {
69910
70433
  }
69911
70434
  return {
69912
70435
  pluginId,
69913
- stageId: manifest.stageId,
70436
+ stageId: primaryStageId(manifest),
69914
70437
  missing,
69915
70438
  checksumMismatch,
69916
70439
  unchanged
69917
70440
  };
69918
70441
  }
69919
70442
  function compareInstalledPluginsToTemplate(repoPath) {
69920
- return detectInstalledPlugins(repoPath).map(
69921
- (pluginId) => comparePluginToTemplate(repoPath, pluginId)
69922
- );
70443
+ return detectInstalledPlugins(repoPath).filter((pluginId) => {
70444
+ const bundled = path44.join(resolveTemplatesDir(), "plugins", pluginId, "template.manifest.json");
70445
+ return fs39.existsSync(bundled);
70446
+ }).map((pluginId) => comparePluginToTemplate(repoPath, pluginId));
69923
70447
  }
69924
70448
  function buildPluginDriftActions(repoPath, pluginDrift) {
69925
70449
  const actions = [];
@@ -69952,23 +70476,18 @@ function buildPluginDriftActions(repoPath, pluginDrift) {
69952
70476
 
69953
70477
  // src/harness/maintain-bundle.ts
69954
70478
  var MAINTAIN_DIR = "maintain";
69955
- var PROFILE_DIRS3 = {
69956
- default: "har-boilerplate",
69957
- cli: "har-boilerplate-cli",
69958
- ios: "har-boilerplate-ios"
69959
- };
69960
70479
  function substituteProjectName2(content, projectName) {
69961
70480
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
69962
70481
  }
69963
70482
  function projectNameFromRepo(repoPath) {
69964
- return path42.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
70483
+ return path45.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69965
70484
  }
69966
70485
  function boilerplateDirForProfile(profile) {
69967
- return path42.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
70486
+ return path45.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
69968
70487
  }
69969
70488
  function readBundledTemplateContent(repoPath, profile, file) {
69970
- const templatePath = path42.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
69971
- let content = fs37.readFileSync(templatePath, "utf8");
70489
+ const templatePath = path45.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
70490
+ let content = fs40.readFileSync(templatePath, "utf8");
69972
70491
  if (file === "harness.env") {
69973
70492
  content = substituteProjectName2(content, projectNameFromRepo(repoPath));
69974
70493
  }
@@ -69999,12 +70518,12 @@ function actionHint(file, kind) {
69999
70518
  return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
70000
70519
  }
70001
70520
  function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
70002
- const tmpDir = fs37.mkdtempSync(path42.join(os15.tmpdir(), "har-maintain-diff-"));
70521
+ const tmpDir = fs40.mkdtempSync(path45.join(os16.tmpdir(), "har-maintain-diff-"));
70003
70522
  try {
70004
- const installedPath = path42.join(tmpDir, "installed");
70005
- const templatePath = path42.join(tmpDir, "template");
70006
- fs37.writeFileSync(installedPath, installedContent);
70007
- fs37.writeFileSync(templatePath, templateContent);
70523
+ const installedPath = path45.join(tmpDir, "installed");
70524
+ const templatePath = path45.join(tmpDir, "template");
70525
+ fs40.writeFileSync(installedPath, installedContent);
70526
+ fs40.writeFileSync(templatePath, templateContent);
70008
70527
  const result = run(`diff -u "${installedPath}" "${templatePath}"`);
70009
70528
  const body = result.stdout.trim();
70010
70529
  if (!body) return "";
@@ -70013,7 +70532,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
70013
70532
  ${body.split("\n").slice(2).join("\n")}
70014
70533
  `;
70015
70534
  } finally {
70016
- fs37.rmSync(tmpDir, { recursive: true, force: true });
70535
+ fs40.rmSync(tmpDir, { recursive: true, force: true });
70017
70536
  }
70018
70537
  }
70019
70538
  function buildActions(repoPath, profile, drift) {
@@ -70028,7 +70547,7 @@ function buildActions(repoPath, profile, drift) {
70028
70547
  });
70029
70548
  }
70030
70549
  for (const file of drift.checksumMismatch) {
70031
- const installedPath = path42.join(harnessDir, file);
70550
+ const installedPath = path45.join(harnessDir, file);
70032
70551
  const installedRel = `maintain/installed/${file}`;
70033
70552
  const templateRel = `maintain/templates/${file}`;
70034
70553
  const diffRel = `maintain/diffs/${file}.diff`;
@@ -70036,7 +70555,7 @@ function buildActions(repoPath, profile, drift) {
70036
70555
  file,
70037
70556
  kind: "drift",
70038
70557
  template: templateRel,
70039
- installed: fs37.existsSync(installedPath) ? installedRel : void 0,
70558
+ installed: fs40.existsSync(installedPath) ? installedRel : void 0,
70040
70559
  diff: diffRel,
70041
70560
  hint: actionHint(file, "drift")
70042
70561
  });
@@ -70188,28 +70707,28 @@ function buildReadme(report) {
70188
70707
  }
70189
70708
  function writeBundleArtifacts(repoPath, profile, drift, report) {
70190
70709
  const harnessDir = getHarnessDir(repoPath);
70191
- const bundleDir = path42.join(harnessDir, MAINTAIN_DIR);
70192
- if (fs37.existsSync(bundleDir)) {
70193
- fs37.rmSync(bundleDir, { recursive: true, force: true });
70710
+ const bundleDir = path45.join(harnessDir, MAINTAIN_DIR);
70711
+ if (fs40.existsSync(bundleDir)) {
70712
+ fs40.rmSync(bundleDir, { recursive: true, force: true });
70194
70713
  }
70195
70714
  const dirs = [
70196
70715
  bundleDir,
70197
- path42.join(bundleDir, "templates"),
70198
- path42.join(bundleDir, "installed"),
70199
- path42.join(bundleDir, "diffs"),
70200
- path42.join(bundleDir, "stale")
70716
+ path45.join(bundleDir, "templates"),
70717
+ path45.join(bundleDir, "installed"),
70718
+ path45.join(bundleDir, "diffs"),
70719
+ path45.join(bundleDir, "stale")
70201
70720
  ];
70202
70721
  for (const dir of dirs) {
70203
- fs37.mkdirSync(dir, { recursive: true });
70722
+ fs40.mkdirSync(dir, { recursive: true });
70204
70723
  }
70205
70724
  const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
70206
70725
  for (const file of affectedFiles) {
70207
70726
  const templateContent = readBundledTemplateContent(repoPath, profile, file);
70208
- writeFileSafe(path42.join(bundleDir, "templates", file), templateContent);
70209
- const harnessPath = path42.join(harnessDir, file);
70210
- if (fs37.existsSync(harnessPath)) {
70211
- const installedContent = fs37.readFileSync(harnessPath, "utf8");
70212
- writeFileSafe(path42.join(bundleDir, "installed", file), installedContent);
70727
+ writeFileSafe(path45.join(bundleDir, "templates", file), templateContent);
70728
+ const harnessPath = path45.join(harnessDir, file);
70729
+ if (fs40.existsSync(harnessPath)) {
70730
+ const installedContent = fs40.readFileSync(harnessPath, "utf8");
70731
+ writeFileSafe(path45.join(bundleDir, "installed", file), installedContent);
70213
70732
  const diff = createUnifiedDiff(
70214
70733
  installedContent,
70215
70734
  templateContent,
@@ -70217,14 +70736,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
70217
70736
  `templates/${file}`
70218
70737
  );
70219
70738
  if (diff) {
70220
- writeFileSafe(path42.join(bundleDir, "diffs", `${file}.diff`), diff);
70739
+ writeFileSafe(path45.join(bundleDir, "diffs", `${file}.diff`), diff);
70221
70740
  }
70222
70741
  }
70223
70742
  }
70224
- writeFileSafe(path42.join(bundleDir, "README.md"), buildReadme(report));
70225
- writeFileSafe(path42.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
70743
+ writeFileSafe(path45.join(bundleDir, "README.md"), buildReadme(report));
70744
+ writeFileSafe(path45.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
70226
70745
  writeFileSafe(
70227
- path42.join(bundleDir, "validation.json"),
70746
+ path45.join(bundleDir, "validation.json"),
70228
70747
  JSON.stringify(report.validation, null, 2) + "\n"
70229
70748
  );
70230
70749
  if (report.stale.length > 0) {
@@ -70236,34 +70755,34 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
70236
70755
  ...report.stale.map((s) => `- **${s.file}** \u2014 ${s.hint}`),
70237
70756
  ""
70238
70757
  ];
70239
- writeFileSafe(path42.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
70758
+ writeFileSafe(path45.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
70240
70759
  }
70241
70760
  return bundleDir;
70242
70761
  }
70243
70762
  function ensureParentDir2(filePath) {
70244
- const parent = path42.dirname(filePath);
70245
- if (!fs37.existsSync(parent)) {
70246
- fs37.mkdirSync(parent, { recursive: true });
70763
+ const parent = path45.dirname(filePath);
70764
+ if (!fs40.existsSync(parent)) {
70765
+ fs40.mkdirSync(parent, { recursive: true });
70247
70766
  }
70248
70767
  }
70249
70768
  function writePluginBundleArtifacts(repoPath, bundleDir, report) {
70250
70769
  if (report.pluginActions.length === 0) return;
70251
70770
  for (const action of report.pluginActions) {
70252
- const pluginRoot = path42.join(bundleDir, "plugins", action.pluginId);
70253
- const templatesDir = path42.join(pluginRoot, "templates");
70254
- const installedDir = path42.join(pluginRoot, "installed");
70255
- const diffsDir = path42.join(pluginRoot, "diffs");
70771
+ const pluginRoot = path45.join(bundleDir, "plugins", action.pluginId);
70772
+ const templatesDir = path45.join(pluginRoot, "templates");
70773
+ const installedDir = path45.join(pluginRoot, "installed");
70774
+ const diffsDir = path45.join(pluginRoot, "diffs");
70256
70775
  for (const dir of [templatesDir, installedDir, diffsDir]) {
70257
- fs37.mkdirSync(dir, { recursive: true });
70776
+ fs40.mkdirSync(dir, { recursive: true });
70258
70777
  }
70259
70778
  const templateContent = readTemplatePluginFile(action.pluginId, action.file);
70260
70779
  if (templateContent === null) continue;
70261
- const templatePath = path42.join(templatesDir, action.file);
70780
+ const templatePath = path45.join(templatesDir, action.file);
70262
70781
  ensureParentDir2(templatePath);
70263
70782
  writeFileSafe(templatePath, templateContent);
70264
70783
  const installedContent = readInstalledPluginFile(repoPath, action.pluginId, action.file);
70265
70784
  if (installedContent !== null) {
70266
- const installedPath = path42.join(installedDir, action.file);
70785
+ const installedPath = path45.join(installedDir, action.file);
70267
70786
  ensureParentDir2(installedPath);
70268
70787
  writeFileSafe(installedPath, installedContent);
70269
70788
  const diff = createUnifiedDiff(
@@ -70273,7 +70792,7 @@ function writePluginBundleArtifacts(repoPath, bundleDir, report) {
70273
70792
  `templates/${action.file}`
70274
70793
  );
70275
70794
  if (diff) {
70276
- const diffPath = path42.join(diffsDir, `${action.file}.diff`);
70795
+ const diffPath = path45.join(diffsDir, `${action.file}.diff`);
70277
70796
  ensureParentDir2(diffPath);
70278
70797
  writeFileSafe(diffPath, diff);
70279
70798
  }
@@ -70309,9 +70828,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
70309
70828
  return { bundleDir, report };
70310
70829
  }
70311
70830
  function removeMaintainBundle(repoPath) {
70312
- const bundleDir = path42.join(getHarnessDir(repoPath), MAINTAIN_DIR);
70313
- if (fs37.existsSync(bundleDir)) {
70314
- fs37.rmSync(bundleDir, { recursive: true, force: true });
70831
+ const bundleDir = path45.join(getHarnessDir(repoPath), MAINTAIN_DIR);
70832
+ if (fs40.existsSync(bundleDir)) {
70833
+ fs40.rmSync(bundleDir, { recursive: true, force: true });
70315
70834
  }
70316
70835
  }
70317
70836
  function formatMaintainBundlePromptSection(report) {
@@ -70400,11 +70919,11 @@ init_manifest();
70400
70919
  init_stages();
70401
70920
  function listHarnessScripts(repoPath) {
70402
70921
  const harnessDir = getHarnessDir(repoPath);
70403
- if (!fs38.existsSync(harnessDir)) return [];
70404
- return fs38.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
70922
+ if (!fs41.existsSync(harnessDir)) return [];
70923
+ return fs41.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
70405
70924
  }
70406
70925
  function describeProject(repoPath) {
70407
- const resolved = path43.resolve(repoPath);
70926
+ const resolved = path46.resolve(repoPath);
70408
70927
  const manifest = readManifest(resolved);
70409
70928
  const present = harnessExists(resolved);
70410
70929
  return {
@@ -70424,8 +70943,8 @@ function describeProject(repoPath) {
70424
70943
  };
70425
70944
  }
70426
70945
  async function initHarness(options) {
70427
- const repoPath = path43.resolve(options.repoPath);
70428
- if (!fs38.existsSync(repoPath)) {
70946
+ const repoPath = path46.resolve(options.repoPath);
70947
+ if (!fs41.existsSync(repoPath)) {
70429
70948
  throw new Error(`Path not found: ${repoPath}`);
70430
70949
  }
70431
70950
  const scaffold = scaffoldHarnessBoilerplate(repoPath, {
@@ -70445,9 +70964,9 @@ async function initHarness(options) {
70445
70964
  };
70446
70965
  }
70447
70966
  async function maintainHarness(options) {
70448
- const repoPath = path43.resolve(options.repoPath);
70967
+ const repoPath = path46.resolve(options.repoPath);
70449
70968
  const harnessDir = getHarnessDir(repoPath);
70450
- if (!fs38.existsSync(harnessDir)) {
70969
+ if (!fs41.existsSync(harnessDir)) {
70451
70970
  throw new Error('No .har/ found. Run "har env init" first.');
70452
70971
  }
70453
70972
  const drift = compareHarnessToTemplate(repoPath);
@@ -70482,13 +71001,13 @@ function addPlugin(repoPath, pluginId, options = {}) {
70482
71001
  }
70483
71002
 
70484
71003
  // src/harness/custom-stage.ts
70485
- var fs39 = __toESM(require("fs"));
70486
- var path44 = __toESM(require("path"));
71004
+ var fs42 = __toESM(require("fs"));
71005
+ var path47 = __toESM(require("path"));
70487
71006
  init_schema2();
70488
71007
  init_stages();
70489
71008
  var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
70490
71009
  function addCustomStage(repoPath, options) {
70491
- const resolved = path44.resolve(repoPath);
71010
+ const resolved = path47.resolve(repoPath);
70492
71011
  if (!harnessExists(resolved)) {
70493
71012
  throw new Error('No .har/ harness found. Run "har env init" first.');
70494
71013
  }
@@ -70525,20 +71044,20 @@ function addCustomStage(repoPath, options) {
70525
71044
  if (options.command) {
70526
71045
  stage.command = options.command;
70527
71046
  } else {
70528
- const scriptRel = path44.join("stages", `${id}.sh`);
70529
- const scriptAbs = path44.join(resolved, ".har", scriptRel);
70530
- if (fs39.existsSync(scriptAbs) && !options.force) {
71047
+ const scriptRel = path47.join("stages", `${id}.sh`);
71048
+ const scriptAbs = path47.join(resolved, ".har", scriptRel);
71049
+ if (fs42.existsSync(scriptAbs) && !options.force) {
70531
71050
  throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
70532
71051
  }
70533
- const skeletonPath = path44.join(
71052
+ const skeletonPath = path47.join(
70534
71053
  resolveTemplatesDir(),
70535
71054
  "plugins",
70536
71055
  "custom-stage-skeleton.sh"
70537
71056
  );
70538
- const skeleton = fs39.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind).replace(/__STAGE_DESCRIPTION__/g, stage.description);
70539
- fs39.mkdirSync(path44.dirname(scriptAbs), { recursive: true });
70540
- fs39.writeFileSync(scriptAbs, skeleton);
70541
- fs39.chmodSync(scriptAbs, 493);
71057
+ const skeleton = fs42.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind).replace(/__STAGE_DESCRIPTION__/g, stage.description);
71058
+ fs42.mkdirSync(path47.dirname(scriptAbs), { recursive: true });
71059
+ fs42.writeFileSync(scriptAbs, skeleton);
71060
+ fs42.chmodSync(scriptAbs, 493);
70542
71061
  filesWritten.push(`.har/${scriptRel}`);
70543
71062
  stage.script = scriptRel;
70544
71063
  stage.artifacts = [
@@ -70586,18 +71105,18 @@ function addCustomStage(repoPath, options) {
70586
71105
  }
70587
71106
 
70588
71107
  // src/harness/adaptation-prompt.ts
70589
- var fs40 = __toESM(require("fs"));
70590
- var path45 = __toESM(require("path"));
71108
+ var fs43 = __toESM(require("fs"));
71109
+ var path48 = __toESM(require("path"));
70591
71110
  var readline5 = __toESM(require("readline"));
70592
71111
 
70593
71112
  // src/utils/clipboard.ts
70594
- var import_child_process10 = require("child_process");
71113
+ var import_child_process11 = require("child_process");
70595
71114
  function commandExists2(command2) {
70596
- const probe = process.platform === "win32" ? (0, import_child_process10.spawnSync)("where", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }) : (0, import_child_process10.spawnSync)("which", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] });
71115
+ const probe = process.platform === "win32" ? (0, import_child_process11.spawnSync)("where", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }) : (0, import_child_process11.spawnSync)("which", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] });
70597
71116
  return probe.status === 0;
70598
71117
  }
70599
71118
  function tryPipeCommand(command2, args, text) {
70600
- const result = (0, import_child_process10.spawnSync)(command2, args, {
71119
+ const result = (0, import_child_process11.spawnSync)(command2, args, {
70601
71120
  input: text,
70602
71121
  encoding: "utf8",
70603
71122
  stdio: ["pipe", "ignore", "ignore"]
@@ -70617,7 +71136,7 @@ function copyViaPlatformTools(text) {
70617
71136
  }
70618
71137
  if (process.platform === "win32") {
70619
71138
  if (commandExists2("powershell")) {
70620
- const result = (0, import_child_process10.spawnSync)(
71139
+ const result = (0, import_child_process11.spawnSync)(
70621
71140
  "powershell",
70622
71141
  [
70623
71142
  "-NoProfile",
@@ -70677,7 +71196,7 @@ function loadTemplate(name) {
70677
71196
  if (!filePath) {
70678
71197
  throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
70679
71198
  }
70680
- return fs40.readFileSync(filePath, "utf8");
71199
+ return fs43.readFileSync(filePath, "utf8");
70681
71200
  }
70682
71201
  function applyProfilePlaceholders(content, profile) {
70683
71202
  return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
@@ -70699,7 +71218,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
70699
71218
  }
70700
71219
  function writeAdaptationPrompt(repoPath, content) {
70701
71220
  const harnessDir = getHarnessDir(repoPath);
70702
- const filePath = path45.join(harnessDir, ADAPTATION_PROMPT_FILE);
71221
+ const filePath = path48.join(harnessDir, ADAPTATION_PROMPT_FILE);
70703
71222
  writeFileSafe(filePath, content);
70704
71223
  return filePath;
70705
71224
  }
@@ -70718,12 +71237,12 @@ function printAdaptationPrompt(content) {
70718
71237
  }
70719
71238
  function askYesNo2(question) {
70720
71239
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
70721
- return new Promise((resolve47) => {
71240
+ return new Promise((resolve50) => {
70722
71241
  process.stderr.write(`${question} `);
70723
71242
  rl.once("line", (answer) => {
70724
71243
  rl.close();
70725
71244
  const trimmed = answer.trim();
70726
- resolve47(trimmed === "" || /^y(es)?$/i.test(trimmed));
71245
+ resolve50(trimmed === "" || /^y(es)?$/i.test(trimmed));
70727
71246
  });
70728
71247
  });
70729
71248
  }
@@ -70754,27 +71273,27 @@ async function offerAdaptationPromptClipboard(content, options = {}) {
70754
71273
  }
70755
71274
 
70756
71275
  // src/harness/cursor-rule.ts
70757
- var fs41 = __toESM(require("fs"));
70758
- var path46 = __toESM(require("path"));
71276
+ var fs44 = __toESM(require("fs"));
71277
+ var path49 = __toESM(require("path"));
70759
71278
  var readline6 = __toESM(require("readline"));
70760
71279
  init_file_ops();
70761
71280
  init_logging();
70762
71281
  var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
70763
71282
  function getCursorRulePath(repoPath) {
70764
- return path46.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
71283
+ return path49.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
70765
71284
  }
70766
71285
  function isCursorWorkspace(repoPath) {
70767
- return fs41.existsSync(path46.join(repoPath, ".cursor"));
71286
+ return fs44.existsSync(path49.join(repoPath, ".cursor"));
70768
71287
  }
70769
71288
  function cursorRuleExists(repoPath) {
70770
- return fs41.existsSync(getCursorRulePath(repoPath));
71289
+ return fs44.existsSync(getCursorRulePath(repoPath));
70771
71290
  }
70772
71291
  function scaffoldCursorRule(repoPath) {
70773
71292
  const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
70774
71293
  if (!templatePath) {
70775
71294
  throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
70776
71295
  }
70777
- const content = fs41.readFileSync(templatePath, "utf8");
71296
+ const content = fs44.readFileSync(templatePath, "utf8");
70778
71297
  writeFileSafe(getCursorRulePath(repoPath), content);
70779
71298
  }
70780
71299
  async function handleCursorRule(options) {
@@ -70812,16 +71331,16 @@ async function promptScaffoldCursorRule(exists) {
70812
71331
  }
70813
71332
  async function askYesNo3(question) {
70814
71333
  const rl = readline6.createInterface({ input: process.stdin, output: process.stderr });
70815
- return new Promise((resolve47) => {
71334
+ return new Promise((resolve50) => {
70816
71335
  process.stderr.write(`${question} `);
70817
71336
  rl.once("line", (answer) => {
70818
71337
  rl.close();
70819
71338
  const trimmed = answer.trim();
70820
71339
  if (trimmed === "") {
70821
- resolve47(true);
71340
+ resolve50(true);
70822
71341
  return;
70823
71342
  }
70824
- resolve47(/^y(es)?$/i.test(trimmed));
71343
+ resolve50(/^y(es)?$/i.test(trimmed));
70825
71344
  });
70826
71345
  });
70827
71346
  }
@@ -70863,8 +71382,8 @@ init_validation();
70863
71382
  init_control_config();
70864
71383
 
70865
71384
  // src/core/control-lifecycle.ts
70866
- var import_child_process11 = require("child_process");
70867
- var path47 = __toESM(require("path"));
71385
+ var import_child_process12 = require("child_process");
71386
+ var path50 = __toESM(require("path"));
70868
71387
 
70869
71388
  // src/core/control-image.ts
70870
71389
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -70890,10 +71409,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
70890
71409
  var CONTROL_CONTAINER_PORT = 3847;
70891
71410
  var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
70892
71411
  function resolveControlDir() {
70893
- return path47.resolve(__dirname, "..", "control");
71412
+ return path50.resolve(__dirname, "..", "control");
70894
71413
  }
70895
71414
  function resolveControlBuildContext() {
70896
- return path47.resolve(resolveControlDir(), "..");
71415
+ return path50.resolve(resolveControlDir(), "..");
70897
71416
  }
70898
71417
  function buildDockerRunArgs(options) {
70899
71418
  const name = options.containerName ?? CONTROL_CONTAINER_NAME;
@@ -70916,7 +71435,7 @@ function buildDockerRunArgs(options) {
70916
71435
  return args;
70917
71436
  }
70918
71437
  function runDocker(args) {
70919
- const result = (0, import_child_process11.spawnSync)("docker", args, {
71438
+ const result = (0, import_child_process12.spawnSync)("docker", args, {
70920
71439
  stdio: "inherit",
70921
71440
  env: process.env
70922
71441
  });
@@ -70926,7 +71445,7 @@ function runDocker(args) {
70926
71445
  return result.status ?? 1;
70927
71446
  }
70928
71447
  function controlContainerExists(name = CONTROL_CONTAINER_NAME) {
70929
- const result = (0, import_child_process11.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
71448
+ const result = (0, import_child_process12.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
70930
71449
  encoding: "utf8",
70931
71450
  stdio: ["pipe", "pipe", "ignore"]
70932
71451
  });
@@ -70949,7 +71468,7 @@ async function startMissionControl(options) {
70949
71468
  "-t",
70950
71469
  imageRef,
70951
71470
  "-f",
70952
- path47.join(resolveControlDir(), "Dockerfile"),
71471
+ path50.join(resolveControlDir(), "Dockerfile"),
70953
71472
  resolveControlBuildContext()
70954
71473
  ]);
70955
71474
  if (buildCode !== 0) {
@@ -71096,7 +71615,7 @@ async function ensureTelemetryInfrastructure(options) {
71096
71615
  // src/core/run-service.ts
71097
71616
  init_telemetry_config();
71098
71617
  init_telemetry_env();
71099
- var path49 = __toESM(require("path"));
71618
+ var path52 = __toESM(require("path"));
71100
71619
  init_local_executor();
71101
71620
  function extractShellOutput(result) {
71102
71621
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
@@ -71261,7 +71780,8 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
71261
71780
  source: options.source,
71262
71781
  sourceUrl: options.sourceUrl,
71263
71782
  title: options.title,
71264
- parentWorkUnitId: options.parentWorkUnitId
71783
+ parentWorkUnitId: options.parentWorkUnitId,
71784
+ relatedLinks: options.relatedLinks
71265
71785
  });
71266
71786
  createWorkAttempt(harnessRoot, {
71267
71787
  attemptId,
@@ -71301,12 +71821,12 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
71301
71821
  suffix: session.suffix,
71302
71822
  createdAt: session.createdAt
71303
71823
  });
71304
- const envFile = path49.join(session.workDir, `.env.agent.${options.agentId}`);
71824
+ const envFile = path52.join(session.workDir, `.env.agent.${options.agentId}`);
71305
71825
  try {
71306
71826
  appendTelemetryEnvToFile(envFile, {
71307
71827
  sessionKey,
71308
71828
  agentId: options.agentId,
71309
- repoPath: path49.resolve(options.repoPath),
71829
+ repoPath: path52.resolve(options.repoPath),
71310
71830
  workDir: session.workDir,
71311
71831
  branch: session.branch,
71312
71832
  suffix: session.suffix,
@@ -71538,21 +72058,319 @@ var getEnvironmentLogs = defaultRunService.getEnvironmentLogs.bind(defaultRunSer
71538
72058
 
71539
72059
  // src/cli/commands/env.ts
71540
72060
  init_runs();
72061
+ init_work_units();
72062
+ init_manifest();
71541
72063
  init_slot_status();
71542
72064
 
72065
+ // src/core/cleanup-service.ts
72066
+ var fs46 = __toESM(require("fs"));
72067
+ var os18 = __toESM(require("os"));
72068
+ var path53 = __toESM(require("path"));
72069
+ var import_child_process14 = require("child_process");
72070
+ init_control_sync();
72071
+ init_control_repo_path();
72072
+ init_slot_status();
72073
+ init_manifest();
72074
+ var DEFAULT_STALE_DAYS = 7;
72075
+ function getWorktreesRoot() {
72076
+ if (process.env.HAR_WORKTREES_ROOT) {
72077
+ return path53.resolve(process.env.HAR_WORKTREES_ROOT);
72078
+ }
72079
+ return path53.join(process.env.HOME || os18.homedir(), "worktrees");
72080
+ }
72081
+ function runGit3(cwd, args) {
72082
+ try {
72083
+ return (0, import_child_process14.execSync)(`git ${args}`, {
72084
+ cwd,
72085
+ encoding: "utf8",
72086
+ stdio: ["pipe", "pipe", "ignore"]
72087
+ }).trim();
72088
+ } catch {
72089
+ return void 0;
72090
+ }
72091
+ }
72092
+ function basenameRepo(repoPath) {
72093
+ return path53.basename(canonicalizeControlRepoPath(repoPath));
72094
+ }
72095
+ function ageDaysFrom(iso) {
72096
+ if (!iso) return void 0;
72097
+ const created = Date.parse(iso);
72098
+ if (Number.isNaN(created)) return void 0;
72099
+ return Math.floor((Date.now() - created) / (24 * 60 * 60 * 1e3));
72100
+ }
72101
+ function matchesKeep(candidate, keep) {
72102
+ if (!keep?.length) return false;
72103
+ for (const pin of keep) {
72104
+ if (pin.worktreePath && candidate.worktreePath) {
72105
+ if (path53.resolve(pin.worktreePath) === path53.resolve(candidate.worktreePath)) return true;
72106
+ }
72107
+ if (pin.repoPath && pin.agentId !== void 0 && candidate.agentId !== void 0) {
72108
+ const repo = canonicalizeControlRepoPath(pin.repoPath);
72109
+ const candidateRepo = path53.resolve(candidate.repoPath);
72110
+ if ((repo === candidateRepo || path53.resolve(pin.repoPath) === candidateRepo) && pin.agentId === candidate.agentId) {
72111
+ return true;
72112
+ }
72113
+ }
72114
+ if (pin.repoPath && pin.agentId === void 0 && !pin.worktreePath) {
72115
+ if (canonicalizeControlRepoPath(pin.repoPath) === candidate.repoPath) return true;
72116
+ }
72117
+ }
72118
+ return false;
72119
+ }
72120
+ function classifySlotCandidate(repoPath, projectName, slot, options) {
72121
+ if (!slot.active && !slot.worktreePath) return void 0;
72122
+ const candidate = {
72123
+ kind: "active_slot",
72124
+ repoPath,
72125
+ projectName,
72126
+ agentId: slot.agentId,
72127
+ worktreePath: slot.worktreePath ?? slot.workDir,
72128
+ branch: slot.branch,
72129
+ active: slot.active,
72130
+ dirty: slot.dirty,
72131
+ ageDays: ageDaysFrom(slot.sessionCreatedAt),
72132
+ sessionCreatedAt: slot.sessionCreatedAt,
72133
+ recommendation: "review",
72134
+ reason: "active session"
72135
+ };
72136
+ if (matchesKeep(candidate, options?.keep)) {
72137
+ candidate.recommendation = "keep";
72138
+ candidate.reason = "pinned with --keep";
72139
+ return candidate;
72140
+ }
72141
+ if (slot.dirty) {
72142
+ candidate.recommendation = "review";
72143
+ candidate.reason = "uncommitted changes \u2014 review before teardown";
72144
+ return candidate;
72145
+ }
72146
+ if (slot.sessionStatus === "failed" || slot.sessionStatus === "starting") {
72147
+ candidate.recommendation = "review";
72148
+ candidate.reason = "failed or resumable session \u2014 recover or teardown manually";
72149
+ return candidate;
72150
+ }
72151
+ const staleDays = options?.staleDays ?? DEFAULT_STALE_DAYS;
72152
+ const age = candidate.ageDays;
72153
+ if (age !== void 0 && age >= staleDays) {
72154
+ candidate.recommendation = "teardown";
72155
+ candidate.reason = `idle ${age}d, clean`;
72156
+ return candidate;
72157
+ }
72158
+ if (slot.active) {
72159
+ candidate.recommendation = "review";
72160
+ candidate.reason = age !== void 0 ? `active session (${age}d) \u2014 keep unless finished` : "active session \u2014 keep unless finished";
72161
+ }
72162
+ return candidate;
72163
+ }
72164
+ function resolveRepoForWorktree(worktreePath) {
72165
+ if (!fs46.existsSync(worktreePath)) return void 0;
72166
+ const commonDir = runGit3(worktreePath, "rev-parse --git-common-dir");
72167
+ if (!commonDir) return void 0;
72168
+ const gitRoot = path53.resolve(worktreePath, commonDir, "..");
72169
+ if (!readManifest(gitRoot)) return void 0;
72170
+ return canonicalizeControlRepoPath(gitRoot);
72171
+ }
72172
+ function discoverOrphanWorktrees(knownPaths) {
72173
+ const worktreesRoot = getWorktreesRoot();
72174
+ if (!fs46.existsSync(worktreesRoot)) return [];
72175
+ const orphans = [];
72176
+ for (const entry of fs46.readdirSync(worktreesRoot, { withFileTypes: true })) {
72177
+ if (!entry.isDirectory() || !entry.name.includes("-har-agent-")) continue;
72178
+ const worktreePath = path53.resolve(path53.join(worktreesRoot, entry.name));
72179
+ if (knownPaths.has(worktreePath)) continue;
72180
+ const repoPath = resolveRepoForWorktree(worktreePath) ?? worktreesRoot;
72181
+ const porcelain = runGit3(worktreePath, "status --porcelain");
72182
+ const dirty = porcelain !== void 0 ? porcelain.length > 0 : void 0;
72183
+ const branch = runGit3(worktreePath, "rev-parse --abbrev-ref HEAD");
72184
+ orphans.push({
72185
+ kind: "orphan_worktree",
72186
+ repoPath,
72187
+ projectName: basenameRepo(repoPath),
72188
+ worktreePath,
72189
+ branch: branch && branch !== "HEAD" ? branch : void 0,
72190
+ active: false,
72191
+ dirty,
72192
+ recommendation: dirty ? "review" : "remove_orphan",
72193
+ reason: dirty ? "orphan worktree with uncommitted changes" : "orphan worktree \u2014 no slot registry"
72194
+ });
72195
+ }
72196
+ return orphans.sort((a, b2) => (a.worktreePath ?? "").localeCompare(b2.worktreePath ?? ""));
72197
+ }
72198
+ async function discoverCleanupCandidates(options = {}) {
72199
+ const repoPaths = options.repoPaths ?? await discoverHarRepos({ cwd: options.cwd ?? process.cwd() });
72200
+ const candidates = [];
72201
+ const knownWorktreePaths = /* @__PURE__ */ new Set();
72202
+ for (const repoPath of repoPaths) {
72203
+ if (!fs46.existsSync(repoPath) || !readManifest(repoPath)) continue;
72204
+ const harnessRoot = resolveHarnessRoot(repoPath);
72205
+ const canonicalRepo = canonicalizeControlRepoPath(harnessRoot);
72206
+ const status = collectEnvironmentStatus(harnessRoot);
72207
+ const projectName = basenameRepo(harnessRoot);
72208
+ for (const slot of status.slots) {
72209
+ if (slot.worktreePath) knownWorktreePaths.add(path53.resolve(slot.worktreePath));
72210
+ if (slot.workDir) knownWorktreePaths.add(path53.resolve(slot.workDir));
72211
+ const candidate = classifySlotCandidate(canonicalRepo, projectName, slot, {
72212
+ staleDays: options.staleDays,
72213
+ keep: options.keep
72214
+ });
72215
+ if (!candidate) continue;
72216
+ if (candidate.recommendation === "review" && !options.includeReview) {
72217
+ }
72218
+ candidates.push(candidate);
72219
+ }
72220
+ }
72221
+ if (options.orphans !== false) {
72222
+ for (const orphan of discoverOrphanWorktrees(knownWorktreePaths)) {
72223
+ if (matchesKeep(orphan, options.keep)) {
72224
+ orphan.recommendation = "keep";
72225
+ orphan.reason = "pinned with --keep";
72226
+ }
72227
+ candidates.push(orphan);
72228
+ }
72229
+ }
72230
+ return {
72231
+ candidates: candidates.sort((a, b2) => {
72232
+ const repo = a.repoPath.localeCompare(b2.repoPath);
72233
+ if (repo !== 0) return repo;
72234
+ return (a.agentId ?? 0) - (b2.agentId ?? 0);
72235
+ }),
72236
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
72237
+ };
72238
+ }
72239
+ function selectAutoApprovedCandidates(plan, options) {
72240
+ return plan.candidates.filter((candidate) => {
72241
+ if (candidate.recommendation === "keep") return false;
72242
+ if (candidate.recommendation === "review") return options?.includeReview === true;
72243
+ return candidate.recommendation === "teardown" || candidate.recommendation === "remove_orphan";
72244
+ });
72245
+ }
72246
+ function formatCleanupPlan(plan) {
72247
+ if (plan.candidates.length === 0) {
72248
+ return "No session worktrees or orphans found.";
72249
+ }
72250
+ const lines2 = [];
72251
+ lines2.push(
72252
+ `${"REC".padEnd(8)} ${"REPO".padEnd(18)} ${"SLOT".padEnd(6)} ${"AGE".padEnd(5)} WORKTREE / REASON`
72253
+ );
72254
+ lines2.push("-".repeat(100));
72255
+ for (const candidate of plan.candidates) {
72256
+ const slot = candidate.agentId !== void 0 ? String(candidate.agentId) : "-";
72257
+ const age = candidate.ageDays !== void 0 ? `${candidate.ageDays}d` : "-";
72258
+ const target = candidate.worktreePath ?? candidate.repoPath;
72259
+ lines2.push(
72260
+ `${candidate.recommendation.padEnd(8)} ${candidate.projectName.padEnd(18)} ${slot.padEnd(6)} ${age.padEnd(5)} ${target}`
72261
+ );
72262
+ lines2.push(` ${candidate.reason}`);
72263
+ }
72264
+ return lines2.join("\n");
72265
+ }
72266
+ async function askYesNo4(question) {
72267
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
72268
+ const readline8 = await import("readline");
72269
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stderr });
72270
+ const answer = await new Promise((resolve50) => {
72271
+ rl.question(question, resolve50);
72272
+ });
72273
+ rl.close();
72274
+ return /^[Yy]$/.test(answer.trim());
72275
+ }
72276
+ async function confirmCleanupSelection(candidates) {
72277
+ if (candidates.length === 0) return [];
72278
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
72279
+ throw new Error("Non-interactive terminal \u2014 pass --yes (and optionally --include-review)");
72280
+ }
72281
+ const approved = [];
72282
+ for (const candidate of candidates) {
72283
+ if (candidate.recommendation === "keep") continue;
72284
+ const label = candidate.kind === "orphan_worktree" ? `orphan ${candidate.worktreePath}` : `${candidate.projectName} agent ${candidate.agentId}`;
72285
+ const action = candidate.recommendation === "remove_orphan" ? "remove" : "teardown";
72286
+ const ok = await askYesNo4(`${action} ${label}? [${candidate.recommendation === "teardown" || candidate.recommendation === "remove_orphan" ? "Y" : "y"}/n] `);
72287
+ if (ok) approved.push(candidate);
72288
+ }
72289
+ return approved;
72290
+ }
72291
+ async function executeCleanupCandidates(candidates, options = {}) {
72292
+ const service = createRunService();
72293
+ const outcomes = [];
72294
+ for (const candidate of candidates) {
72295
+ if (candidate.recommendation === "keep") continue;
72296
+ if (options.dryRun) {
72297
+ outcomes.push({ candidate, ok: true });
72298
+ continue;
72299
+ }
72300
+ try {
72301
+ if (candidate.kind === "active_slot" && candidate.agentId !== void 0) {
72302
+ const result = await service.teardownEnvironment({
72303
+ repoPath: candidate.repoPath,
72304
+ agentId: candidate.agentId,
72305
+ capture: false,
72306
+ trigger: "cli"
72307
+ });
72308
+ outcomes.push({
72309
+ candidate,
72310
+ ok: result.code === 0,
72311
+ error: result.code === 0 ? void 0 : result.stderr?.trim() || `exit ${result.code}`
72312
+ });
72313
+ continue;
72314
+ }
72315
+ if (candidate.kind === "orphan_worktree" && candidate.worktreePath) {
72316
+ if (fs46.existsSync(candidate.worktreePath)) {
72317
+ const repoRoot = candidate.repoPath !== getWorktreesRoot() ? candidate.repoPath : resolveRepoForWorktree(candidate.worktreePath);
72318
+ if (repoRoot) {
72319
+ try {
72320
+ (0, import_child_process14.execSync)(`git worktree remove --force ${JSON.stringify(candidate.worktreePath)}`, {
72321
+ cwd: repoRoot,
72322
+ stdio: ["pipe", "pipe", "ignore"]
72323
+ });
72324
+ } catch {
72325
+ }
72326
+ }
72327
+ if (fs46.existsSync(candidate.worktreePath)) {
72328
+ fs46.rmSync(candidate.worktreePath, { recursive: true, force: true });
72329
+ }
72330
+ }
72331
+ outcomes.push({ candidate, ok: true });
72332
+ continue;
72333
+ }
72334
+ outcomes.push({ candidate, ok: false, error: "unsupported candidate" });
72335
+ } catch (err) {
72336
+ outcomes.push({
72337
+ candidate,
72338
+ ok: false,
72339
+ error: err instanceof Error ? err.message : String(err)
72340
+ });
72341
+ }
72342
+ }
72343
+ return outcomes;
72344
+ }
72345
+ function parseCleanupKeepPins(values) {
72346
+ if (!values?.length) return [];
72347
+ return values.map((raw) => {
72348
+ const agentMatch = raw.match(/^(.+):(\d+)$/);
72349
+ if (agentMatch) {
72350
+ return { repoPath: agentMatch[1], agentId: Number(agentMatch[2]) };
72351
+ }
72352
+ if (raw.startsWith("/") || raw.startsWith("~")) {
72353
+ return {
72354
+ worktreePath: raw.startsWith("~") ? path53.join(process.env.HOME || os18.homedir(), raw.slice(2)) : path53.resolve(raw)
72355
+ };
72356
+ }
72357
+ return { repoPath: raw };
72358
+ });
72359
+ }
72360
+
71543
72361
  // src/core/commit-gate-onboarding.ts
71544
72362
  var readline7 = __toESM(require("readline"));
71545
72363
  init_hooks();
71546
72364
  init_stages();
71547
72365
  init_logging();
71548
- function askYesNo4(question) {
72366
+ function askYesNo5(question) {
71549
72367
  const rl = readline7.createInterface({ input: process.stdin, output: process.stderr });
71550
- return new Promise((resolve47) => {
72368
+ return new Promise((resolve50) => {
71551
72369
  process.stderr.write(`${question} `);
71552
72370
  rl.once("line", (answer) => {
71553
72371
  rl.close();
71554
72372
  const trimmed = answer.trim();
71555
- resolve47(trimmed === "" || /^y(es)?$/i.test(trimmed));
72373
+ resolve50(trimmed === "" || /^y(es)?$/i.test(trimmed));
71556
72374
  });
71557
72375
  });
71558
72376
  }
@@ -71577,7 +72395,7 @@ async function handleCommitGateOnboarding(options) {
71577
72395
  const status = getHooksStatus(options.repoPath);
71578
72396
  let shouldInstall = status.preCommitInstalled || status.postCommitInstalled || options.install === "always" || options.autoYes === true;
71579
72397
  if (!shouldInstall && options.install === "prompt" && process.stdin.isTTY && process.stdout.isTTY) {
71580
- shouldInstall = await askYesNo4("Install the HAR commit gate for this repository? [Y/n]");
72398
+ shouldInstall = await askYesNo5("Install the HAR commit gate for this repository? [Y/n]");
71581
72399
  }
71582
72400
  if (!shouldInstall) {
71583
72401
  info("Commit gate is not installed; run `har hooks install` when ready.");
@@ -71594,21 +72412,21 @@ async function handleCommitGateOnboarding(options) {
71594
72412
  }
71595
72413
 
71596
72414
  // src/core/onboarding-preferences.ts
71597
- var fs43 = __toESM(require("fs"));
71598
- var os17 = __toESM(require("os"));
71599
- var path50 = __toESM(require("path"));
72415
+ var fs47 = __toESM(require("fs"));
72416
+ var os19 = __toESM(require("os"));
72417
+ var path54 = __toESM(require("path"));
71600
72418
  init_schema2();
71601
72419
  function getOnboardingPreferencesPath() {
71602
72420
  if (process.env.HAR_PREFERENCES_PATH) {
71603
- return path50.resolve(process.env.HAR_PREFERENCES_PATH);
72421
+ return path54.resolve(process.env.HAR_PREFERENCES_PATH);
71604
72422
  }
71605
- return path50.join(os17.homedir(), ".har", "preferences.json");
72423
+ return path54.join(os19.homedir(), ".har", "preferences.json");
71606
72424
  }
71607
72425
  function readOnboardingPreferences() {
71608
72426
  const preferencePath = getOnboardingPreferencesPath();
71609
72427
  try {
71610
- if (!fs43.existsSync(preferencePath)) return OnboardingPreferencesSchema.parse({});
71611
- return OnboardingPreferencesSchema.parse(JSON.parse(fs43.readFileSync(preferencePath, "utf8")));
72428
+ if (!fs47.existsSync(preferencePath)) return OnboardingPreferencesSchema.parse({});
72429
+ return OnboardingPreferencesSchema.parse(JSON.parse(fs47.readFileSync(preferencePath, "utf8")));
71612
72430
  } catch {
71613
72431
  return OnboardingPreferencesSchema.parse({});
71614
72432
  }
@@ -71633,8 +72451,8 @@ function writeOnboardingPreferences(overrides) {
71633
72451
  };
71634
72452
  const parsed = OnboardingPreferencesSchema.parse(preference);
71635
72453
  const preferencePath = getOnboardingPreferencesPath();
71636
- fs43.mkdirSync(path50.dirname(preferencePath), { recursive: true });
71637
- fs43.writeFileSync(preferencePath, `${JSON.stringify(parsed, null, 2)}
72454
+ fs47.mkdirSync(path54.dirname(preferencePath), { recursive: true });
72455
+ fs47.writeFileSync(preferencePath, `${JSON.stringify(parsed, null, 2)}
71638
72456
  `);
71639
72457
  return parsed;
71640
72458
  }
@@ -71698,6 +72516,14 @@ feature branch.
71698
72516
  Failed launch: har env recover 1 # or: har env launch 1 --resume`;
71699
72517
 
71700
72518
  // src/cli/commands/env.ts
72519
+ var workLinkOptions = (y) => y.option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("work-id", {
72520
+ type: "string",
72521
+ demandOption: true,
72522
+ describe: "Repo-scoped work unit id (same as --work-id at launch)"
72523
+ }).option("link", {
72524
+ type: "string",
72525
+ describe: "Link as source|url or source|url|label"
72526
+ }).option("source", { type: "string", describe: "Tracker provider (e.g. github, jira)" }).option("url", { type: "string", describe: "Canonical URL for the link" }).option("label", { type: "string", describe: "Optional display label" });
71701
72527
  var envCommand = {
71702
72528
  command: "env <subcommand>",
71703
72529
  describe: "Manage agent environments (launch \u2192 verify \u2192 complete)",
@@ -71768,14 +72594,14 @@ var envCommand = {
71768
72594
  handleMaintain
71769
72595
  ).command(
71770
72596
  "add-plugin [plugin]",
71771
- `Install a verification plugin (${PLUGIN_IDS.join(", ")}) that registers stages`,
72597
+ "Install a verification plugin (bundled id, path, npm package, or git URL) that registers stages",
71772
72598
  (y) => y.positional("plugin", {
71773
72599
  type: "string",
71774
- describe: `Plugin id (${PLUGIN_IDS.join(", ")})`
72600
+ describe: "Bundled plugin id, local path (./plugin), npm package (@org/pkg), or git URL (github:org/repo)"
71775
72601
  }).option("list", {
71776
72602
  type: "boolean",
71777
72603
  default: false,
71778
- describe: "List available plugins and exit"
72604
+ describe: "List bundled plugins and exit"
71779
72605
  }).option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("force", {
71780
72606
  type: "boolean",
71781
72607
  default: false,
@@ -71791,7 +72617,7 @@ var envCommand = {
71791
72617
  `Register a custom stage (--custom), or install a plugin (deprecated alias for add-plugin)`,
71792
72618
  (y) => y.positional("template", {
71793
72619
  type: "string",
71794
- describe: `Custom stage id with --custom, or a plugin id (${PLUGIN_IDS.join(", ")}) as a deprecated alias`
72620
+ describe: "Custom stage id with --custom, or a plugin id / path / npm / git spec as a deprecated alias"
71795
72621
  }).option("list", {
71796
72622
  type: "boolean",
71797
72623
  default: false,
@@ -71857,8 +72683,17 @@ var envCommand = {
71857
72683
  }).option("parent-work-id", {
71858
72684
  type: "string",
71859
72685
  describe: "Optional parent work unit identifier"
72686
+ }).option("work-link", {
72687
+ type: "array",
72688
+ string: true,
72689
+ describe: "Additional tracker link: source|url or source|url|label (repeatable)"
71860
72690
  }).epilog(LAUNCH_EPILOG),
71861
72691
  handleLaunch
72692
+ ).command(
72693
+ "work-link",
72694
+ "Append a related external link to an existing work unit",
72695
+ workLinkOptions,
72696
+ handleWorkLink
71862
72697
  ).command(
71863
72698
  "recover <id>",
71864
72699
  "Resume a failed or partial launch (alias for launch --resume)",
@@ -71897,6 +72732,38 @@ var envCommand = {
71897
72732
  "Show status of all running agents",
71898
72733
  (y) => y.option("repo", { type: "string", default: "." }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" }),
71899
72734
  handleStatus
72735
+ ).command(
72736
+ "cleanup",
72737
+ "Discover stale session worktrees across registered repos and tear them down",
72738
+ (y) => y.option("repo", {
72739
+ type: "string",
72740
+ describe: "Limit cleanup scan to one repository path"
72741
+ }).option("dry-run", {
72742
+ type: "boolean",
72743
+ default: false,
72744
+ describe: "Show the cleanup plan without executing"
72745
+ }).option("yes", {
72746
+ alias: "y",
72747
+ type: "boolean",
72748
+ default: false,
72749
+ describe: "Approve teardown/remove for recommended rows without prompting"
72750
+ }).option("keep", {
72751
+ type: "string",
72752
+ describe: "Pin sessions to keep (comma-separated repoPath:agentId or worktree paths)"
72753
+ }).option("stale", {
72754
+ type: "number",
72755
+ default: 7,
72756
+ describe: "Age in days before a clean idle session is recommended for teardown"
72757
+ }).option("orphans", {
72758
+ type: "boolean",
72759
+ default: true,
72760
+ describe: "Include orphan worktree directories under ~/worktrees"
72761
+ }).option("include-review", {
72762
+ type: "boolean",
72763
+ default: false,
72764
+ describe: "With --yes, also teardown/remove rows marked review (dirty or recent active)"
72765
+ }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" }),
72766
+ handleCleanup
71900
72767
  ).command(
71901
72768
  "runs",
71902
72769
  "Inspect harness run history",
@@ -71919,13 +72786,13 @@ var envCommand = {
71919
72786
  }
71920
72787
  ).demandCommand(
71921
72788
  1,
71922
- "Please specify a subcommand: init, maintain, add-stage, launch, recover, verify, complete, teardown, status, runs"
72789
+ "Please specify a subcommand: init, maintain, add-stage, launch, recover, verify, complete, teardown, status, cleanup, runs"
71923
72790
  ).epilog(HAR_ENV_EPILOG),
71924
72791
  handler: () => {
71925
72792
  }
71926
72793
  };
71927
72794
  async function handleInit(argv) {
71928
- const repoPath = path51.resolve(argv.repo);
72795
+ const repoPath = path55.resolve(argv.repo);
71929
72796
  const onboarding = resolveOnboardingOptions(argv);
71930
72797
  header("har env init");
71931
72798
  info(`Repository: ${repoPath}`);
@@ -71971,7 +72838,7 @@ async function handleInit(argv) {
71971
72838
  }
71972
72839
  }
71973
72840
  async function handleMaintain(argv) {
71974
- const repoPath = path51.resolve(argv.repo);
72841
+ const repoPath = path55.resolve(argv.repo);
71975
72842
  const onboarding = resolveOnboardingOptions(argv);
71976
72843
  header("har env maintain");
71977
72844
  info(`Repository: ${repoPath}`);
@@ -72102,7 +72969,7 @@ async function applyAgentIntegrations(options) {
72102
72969
  }
72103
72970
  }
72104
72971
  async function handleAddPlugin(argv) {
72105
- const repoPath = path51.resolve(argv.repo);
72972
+ const repoPath = path55.resolve(argv.repo);
72106
72973
  const available = listPluginIds();
72107
72974
  if (argv.list) {
72108
72975
  for (const id of available) {
@@ -72110,9 +72977,9 @@ async function handleAddPlugin(argv) {
72110
72977
  }
72111
72978
  return;
72112
72979
  }
72113
- if (!argv.plugin || !available.includes(argv.plugin)) {
72980
+ if (!argv.plugin) {
72114
72981
  error(
72115
- `Unknown plugin: ${argv.plugin ?? "(missing)"}. Available: ${available.join(", ")}. For a project-specific stage, use: har env add-stage <id> --custom`
72982
+ `Missing plugin. Bundled: ${available.join(", ") || "(none)"}. Or pass a path, npm package, or git URL. For a project-specific stage, use: har env add-stage <id> --custom`
72116
72983
  );
72117
72984
  return finishCommand(1);
72118
72985
  }
@@ -72122,10 +72989,12 @@ async function handleAddPlugin(argv) {
72122
72989
  try {
72123
72990
  const result = addPlugin(repoPath, argv.plugin, {
72124
72991
  force: argv.force,
72125
- skipCi: argv.skipCi
72992
+ skipCi: argv.skipCi,
72993
+ spec: argv.plugin
72126
72994
  });
72127
72995
  divider();
72128
- success(`Plugin applied \u2014 registered stage: ${result.stageId}`);
72996
+ const stageLabel = result.stageIds.length > 1 ? `stages: ${result.stageIds.join(", ")}` : `stage: ${result.stageId}`;
72997
+ success(`Plugin applied \u2014 registered ${stageLabel} (source: ${result.source})`);
72129
72998
  console.error("");
72130
72999
  console.error(" Next steps:");
72131
73000
  for (const step of result.nextSteps) {
@@ -72140,7 +73009,7 @@ async function handleAddPlugin(argv) {
72140
73009
  }
72141
73010
  }
72142
73011
  async function handleAddStage(argv) {
72143
- const repoPath = path51.resolve(argv.repo);
73012
+ const repoPath = path55.resolve(argv.repo);
72144
73013
  const available = listPluginIds();
72145
73014
  if (argv.list) {
72146
73015
  warn("har env add-stage --list is deprecated; prefer: har env add-plugin --list");
@@ -72188,9 +73057,9 @@ async function handleAddStage(argv) {
72188
73057
  }
72189
73058
  return;
72190
73059
  }
72191
- if (!argv.template || !available.includes(argv.template)) {
73060
+ if (!argv.template) {
72192
73061
  error(
72193
- `Unknown plugin: ${argv.template ?? "(missing)"}. Available: ${available.join(", ")}. Prefer: har env add-plugin <id>. For a project-specific stage, use: har env add-stage <id> --custom`
73062
+ `Unknown plugin: (missing). Available: ${available.join(", ")}. Prefer: har env add-plugin <id>. For a project-specific stage, use: har env add-stage <id> --custom`
72194
73063
  );
72195
73064
  return finishCommand(1);
72196
73065
  }
@@ -72206,7 +73075,7 @@ async function handleAddStage(argv) {
72206
73075
  });
72207
73076
  }
72208
73077
  async function handlePreflight(argv) {
72209
- const repo = path51.resolve(argv.repo);
73078
+ const repo = path55.resolve(argv.repo);
72210
73079
  const agentId = validateAgentId(argv.id, repo);
72211
73080
  const result = await preflightEnvironment({
72212
73081
  repoPath: repo,
@@ -72222,7 +73091,7 @@ async function handlePreflight(argv) {
72222
73091
  return finishCommand(result.code);
72223
73092
  }
72224
73093
  async function handleLaunch(argv) {
72225
- const repo = path51.resolve(argv.repo);
73094
+ const repo = path55.resolve(argv.repo);
72226
73095
  const agentId = validateAgentId(argv.id, repo);
72227
73096
  if (!argv.resume) {
72228
73097
  const guard = checkLaunchGuard2(repo, agentId, { worktree: argv.worktree });
@@ -72245,6 +73114,7 @@ async function handleLaunch(argv) {
72245
73114
  sourceUrl: argv.workUrl,
72246
73115
  title: argv.workTitle,
72247
73116
  parentWorkUnitId: argv.parentWorkId,
73117
+ relatedLinks: argv.workLink?.map(parseWorkLinkSpec),
72248
73118
  capture: false
72249
73119
  });
72250
73120
  if (result.blocked) {
@@ -72265,6 +73135,22 @@ async function handleLaunch(argv) {
72265
73135
  }
72266
73136
  return finishCommand(result.code);
72267
73137
  }
73138
+ async function handleWorkLink(argv) {
73139
+ if (!argv.link && !(argv.source && argv.url)) {
73140
+ error("Provide --link or both --source and --url");
73141
+ return finishCommand(1);
73142
+ }
73143
+ const harnessRoot = resolveHarnessRoot(path55.resolve(argv.repo));
73144
+ const links = argv.link ? [parseWorkLinkSpec(argv.link)] : [{ source: argv.source, url: argv.url, label: argv.label }];
73145
+ try {
73146
+ const record2 = addWorkUnitLinks(harnessRoot, argv.workId, links);
73147
+ success(`Added link to work unit ${record2.workUnitId}`);
73148
+ return finishCommand(0);
73149
+ } catch (err) {
73150
+ error(err instanceof Error ? err.message : String(err));
73151
+ return finishCommand(1);
73152
+ }
73153
+ }
72268
73154
  async function handleRecover(argv) {
72269
73155
  return handleLaunch({
72270
73156
  id: argv.id,
@@ -72280,7 +73166,7 @@ async function handleRecover(argv) {
72280
73166
  });
72281
73167
  }
72282
73168
  async function handleVerify(argv) {
72283
- const repo = path51.resolve(argv.repo);
73169
+ const repo = path55.resolve(argv.repo);
72284
73170
  const agentId = validateAgentId(argv.id, repo);
72285
73171
  const result = await runVerification({
72286
73172
  repoPath: repo,
@@ -72292,7 +73178,7 @@ async function handleVerify(argv) {
72292
73178
  return finishCommand(result.code);
72293
73179
  }
72294
73180
  async function handleTeardown(argv) {
72295
- const repo = path51.resolve(argv.repo);
73181
+ const repo = path55.resolve(argv.repo);
72296
73182
  const agentId = validateAgentId(argv.id, repo);
72297
73183
  const result = await teardownEnvironment({
72298
73184
  repoPath: repo,
@@ -72303,7 +73189,7 @@ async function handleTeardown(argv) {
72303
73189
  return finishCommand(result.code);
72304
73190
  }
72305
73191
  async function handleComplete(argv) {
72306
- const repo = path51.resolve(argv.repo);
73192
+ const repo = path55.resolve(argv.repo);
72307
73193
  const agentId = validateAgentId(argv.id, repo);
72308
73194
  const result = await completeEnvironment({
72309
73195
  repoPath: repo,
@@ -72324,7 +73210,7 @@ async function handleComplete(argv) {
72324
73210
  return finishCommand(result.code);
72325
73211
  }
72326
73212
  async function handleStatus(argv) {
72327
- const repoPath = path51.resolve(argv.repo);
73213
+ const repoPath = path55.resolve(argv.repo);
72328
73214
  if (argv.json) {
72329
73215
  const status = collectEnvironmentStatus(repoPath);
72330
73216
  const output = EnvironmentStatusSchema.parse(status);
@@ -72336,8 +73222,59 @@ async function handleStatus(argv) {
72336
73222
  capture: false
72337
73223
  });
72338
73224
  }
73225
+ async function handleCleanup(argv) {
73226
+ const keepValues = argv.keep?.split(",").map((value) => value.trim()).filter(Boolean);
73227
+ const keep = parseCleanupKeepPins(keepValues);
73228
+ const repoPaths = argv.repo ? [path55.resolve(argv.repo)] : void 0;
73229
+ const plan = await discoverCleanupCandidates({
73230
+ cwd: process.cwd(),
73231
+ repoPaths,
73232
+ keep,
73233
+ staleDays: argv.stale,
73234
+ orphans: argv.orphans
73235
+ });
73236
+ if (argv.json) {
73237
+ process.stdout.write(JSON.stringify(plan, null, 2) + "\n");
73238
+ if (argv.dryRun) return;
73239
+ } else {
73240
+ header("har env cleanup");
73241
+ process.stdout.write(`${formatCleanupPlan(plan)}
73242
+
73243
+ `);
73244
+ if (argv.dryRun) {
73245
+ info("Dry run \u2014 no changes made.");
73246
+ return;
73247
+ }
73248
+ }
73249
+ let selected = selectAutoApprovedCandidates(plan, { includeReview: argv.includeReview });
73250
+ if (!argv.yes) {
73251
+ try {
73252
+ const interactive = await confirmCleanupSelection(plan.candidates);
73253
+ selected = interactive;
73254
+ } catch (err) {
73255
+ error(err instanceof Error ? err.message : String(err));
73256
+ return finishCommand(1);
73257
+ }
73258
+ }
73259
+ if (selected.length === 0) {
73260
+ info("Nothing selected for cleanup.");
73261
+ return;
73262
+ }
73263
+ const outcomes = await executeCleanupCandidates(selected, { dryRun: false });
73264
+ let failed = 0;
73265
+ for (const outcome of outcomes) {
73266
+ const label = outcome.candidate.kind === "orphan_worktree" ? outcome.candidate.worktreePath : `${outcome.candidate.projectName} agent ${outcome.candidate.agentId}`;
73267
+ if (outcome.ok) {
73268
+ success(`Cleaned ${label}`);
73269
+ } else {
73270
+ failed++;
73271
+ error(`${label}: ${outcome.error ?? "failed"}`);
73272
+ }
73273
+ }
73274
+ return finishCommand(failed > 0 ? 1 : 0);
73275
+ }
72339
73276
  async function handleRunsList(argv) {
72340
- const repoPath = path51.resolve(argv.repo);
73277
+ const repoPath = path55.resolve(argv.repo);
72341
73278
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
72342
73279
  if (argv.json) {
72343
73280
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -72354,7 +73291,7 @@ async function handleRunsList(argv) {
72354
73291
  }
72355
73292
  }
72356
73293
  async function handleRunsGet(argv) {
72357
- const run2 = getRun(path51.resolve(argv.repo), argv.runId);
73294
+ const run2 = getRun(path55.resolve(argv.repo), argv.runId);
72358
73295
  if (!run2) {
72359
73296
  error(`Run not found: ${argv.runId}`);
72360
73297
  return finishCommand(1);
@@ -72436,27 +73373,27 @@ function printNextSteps() {
72436
73373
  }
72437
73374
 
72438
73375
  // src/cli/commands/control.ts
72439
- var path55 = __toESM(require("path"));
73376
+ var path59 = __toESM(require("path"));
72440
73377
  init_control_config();
72441
73378
  init_control_port();
72442
73379
  init_control_sync();
72443
73380
 
72444
73381
  // src/core/control-sync-selection.ts
72445
- var fs44 = __toESM(require("fs"));
72446
- var os18 = __toESM(require("os"));
72447
- var path52 = __toESM(require("path"));
73382
+ var fs48 = __toESM(require("fs"));
73383
+ var os20 = __toESM(require("os"));
73384
+ var path56 = __toESM(require("path"));
72448
73385
  init_control_repo_path();
72449
73386
  function getSelectionPath() {
72450
73387
  if (process.env.HAR_CONTROL_SYNC_SELECTION_PATH) {
72451
- return path52.resolve(process.env.HAR_CONTROL_SYNC_SELECTION_PATH);
73388
+ return path56.resolve(process.env.HAR_CONTROL_SYNC_SELECTION_PATH);
72452
73389
  }
72453
- return path52.join(os18.homedir(), ".har", "sync-selection.json");
73390
+ return path56.join(os20.homedir(), ".har", "sync-selection.json");
72454
73391
  }
72455
73392
  function readSyncSelection() {
72456
73393
  const selectionPath = getSelectionPath();
72457
73394
  try {
72458
- if (!fs44.existsSync(selectionPath)) return null;
72459
- const parsed = JSON.parse(fs44.readFileSync(selectionPath, "utf8"));
73395
+ if (!fs48.existsSync(selectionPath)) return null;
73396
+ const parsed = JSON.parse(fs48.readFileSync(selectionPath, "utf8"));
72460
73397
  if (!Array.isArray(parsed.repos)) return null;
72461
73398
  return parsed.repos.map((repoPath) => canonicalizeControlRepoPath(repoPath));
72462
73399
  } catch {
@@ -72466,8 +73403,8 @@ function readSyncSelection() {
72466
73403
  function writeSyncSelection(repos) {
72467
73404
  const selectionPath = getSelectionPath();
72468
73405
  const canonical = Array.from(new Set(repos.map((repoPath) => canonicalizeControlRepoPath(repoPath))));
72469
- fs44.mkdirSync(path52.dirname(selectionPath), { recursive: true });
72470
- fs44.writeFileSync(selectionPath, JSON.stringify({ repos: canonical }, null, 2) + "\n");
73406
+ fs48.mkdirSync(path56.dirname(selectionPath), { recursive: true });
73407
+ fs48.writeFileSync(selectionPath, JSON.stringify({ repos: canonical }, null, 2) + "\n");
72471
73408
  }
72472
73409
  function resolveSyncSelection(input) {
72473
73410
  const { discovered, stored, forceSelect, isTTY } = input;
@@ -72499,13 +73436,13 @@ init_portal_credentials();
72499
73436
  // src/core/portal-login.ts
72500
73437
  var http = __toESM(require("http"));
72501
73438
  var import_crypto = require("crypto");
72502
- var import_child_process13 = require("child_process");
73439
+ var import_child_process15 = require("child_process");
72503
73440
  var CALLBACK_PATH = "/callback";
72504
73441
  var LOGIN_TIMEOUT_MS = 3 * 6e4;
72505
73442
  function openBrowser(url) {
72506
73443
  const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
72507
73444
  try {
72508
- const child = (0, import_child_process13.spawn)(cmd, args, { stdio: "ignore", detached: true });
73445
+ const child = (0, import_child_process15.spawn)(cmd, args, { stdio: "ignore", detached: true });
72509
73446
  child.on("error", () => {
72510
73447
  });
72511
73448
  child.unref();
@@ -72514,7 +73451,7 @@ function openBrowser(url) {
72514
73451
  }
72515
73452
  async function loginViaBrowser(portalUrl, openUrl = openBrowser) {
72516
73453
  const state = (0, import_crypto.randomUUID)();
72517
- return new Promise((resolve47, reject) => {
73454
+ return new Promise((resolve50, reject) => {
72518
73455
  let timer;
72519
73456
  const server = http.createServer((req, res) => {
72520
73457
  const requestUrl = new URL(req.url ?? "", "http://127.0.0.1");
@@ -72539,7 +73476,7 @@ async function loginViaBrowser(portalUrl, openUrl = openBrowser) {
72539
73476
  res.writeHead(200, { "Content-Type": "text/html" });
72540
73477
  res.end("<h1>Logged in</h1><p>You can close this tab and return to the terminal.</p>");
72541
73478
  cleanup();
72542
- resolve47({
73479
+ resolve50({
72543
73480
  portalUrl,
72544
73481
  token,
72545
73482
  workspace,
@@ -72574,8 +73511,8 @@ If your browser did not open, paste that URL.
72574
73511
  }
72575
73512
 
72576
73513
  // src/core/control-unregister.ts
72577
- var fs45 = __toESM(require("fs"));
72578
- var path53 = __toESM(require("path"));
73514
+ var fs49 = __toESM(require("fs"));
73515
+ var path57 = __toESM(require("path"));
72579
73516
  init_control_repo_path();
72580
73517
  init_control_registry();
72581
73518
  init_control_config();
@@ -72585,9 +73522,9 @@ function listUnregisterWorktreeCandidates(repoPath) {
72585
73522
  const harnessRoot = canonicalizeControlRepoPath(repoPath);
72586
73523
  const byPath = /* @__PURE__ */ new Map();
72587
73524
  const add = (partial2) => {
72588
- const resolved = path53.resolve(partial2.worktreePath);
73525
+ const resolved = path57.resolve(partial2.worktreePath);
72589
73526
  if (!resolved || resolved === harnessRoot) return;
72590
- const exists = partial2.exists ?? fs45.existsSync(resolved);
73527
+ const exists = partial2.exists ?? fs49.existsSync(resolved);
72591
73528
  const prev = byPath.get(resolved);
72592
73529
  if (prev) {
72593
73530
  byPath.set(resolved, {
@@ -72634,12 +73571,12 @@ function listUnregisterWorktreeCandidates(repoPath) {
72634
73571
  }
72635
73572
  return [...byPath.values()].sort((a, b2) => a.agentId - b2.agentId);
72636
73573
  }
72637
- async function askYesNo5(question) {
73574
+ async function askYesNo6(question) {
72638
73575
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
72639
73576
  const readline8 = await import("readline");
72640
73577
  const rl = readline8.createInterface({ input: process.stdin, output: process.stderr });
72641
- const answer = await new Promise((resolve47) => {
72642
- rl.question(question, resolve47);
73578
+ const answer = await new Promise((resolve50) => {
73579
+ rl.question(question, resolve50);
72643
73580
  });
72644
73581
  rl.close();
72645
73582
  return /^[Yy]$/.test(answer.trim());
@@ -72651,7 +73588,7 @@ async function confirmUnregister(options) {
72651
73588
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
72652
73589
  throw new Error("Non-interactive terminal \u2014 pass --yes (and optionally --delete-worktrees)");
72653
73590
  }
72654
- const proceed = await askYesNo5("Unregister this repository from Mission Control? [y/N] ");
73591
+ const proceed = await askYesNo6("Unregister this repository from Mission Control? [y/N] ");
72655
73592
  if (!proceed) return { proceed: false, deleteWorktrees: false };
72656
73593
  const existing = options.worktrees.filter((w2) => w2.exists);
72657
73594
  if (existing.length === 0) {
@@ -72663,7 +73600,7 @@ async function confirmUnregister(options) {
72663
73600
  process.stderr.write(` agent-${wt.agentId}: ${wt.worktreePath}${dirty}
72664
73601
  `);
72665
73602
  }
72666
- const deleteWorktrees = await askYesNo5(
73603
+ const deleteWorktrees = await askYesNo6(
72667
73604
  `Also delete ${existing.length} session worktree(s) on disk? [y/N] `
72668
73605
  );
72669
73606
  return { proceed: true, deleteWorktrees };
@@ -72701,28 +73638,28 @@ async function teardownWorktrees(repoPath, candidates, dryRun) {
72701
73638
  trigger: "cli"
72702
73639
  });
72703
73640
  if (result.code !== 0) {
72704
- if (fs45.existsSync(candidate.worktreePath)) {
72705
- fs45.rmSync(candidate.worktreePath, { recursive: true, force: true });
73641
+ if (fs49.existsSync(candidate.worktreePath)) {
73642
+ fs49.rmSync(candidate.worktreePath, { recursive: true, force: true });
72706
73643
  }
72707
73644
  }
72708
73645
  outcomes.push({
72709
73646
  path: candidate.worktreePath,
72710
73647
  agentId: candidate.agentId,
72711
- deleted: !fs45.existsSync(candidate.worktreePath),
72712
- error: result.code !== 0 && fs45.existsSync(candidate.worktreePath) ? result.stderr : void 0
73648
+ deleted: !fs49.existsSync(candidate.worktreePath),
73649
+ error: result.code !== 0 && fs49.existsSync(candidate.worktreePath) ? result.stderr : void 0
72713
73650
  });
72714
73651
  } catch (err) {
72715
73652
  const message = err instanceof Error ? err.message : String(err);
72716
73653
  try {
72717
- if (fs45.existsSync(candidate.worktreePath)) {
72718
- fs45.rmSync(candidate.worktreePath, { recursive: true, force: true });
73654
+ if (fs49.existsSync(candidate.worktreePath)) {
73655
+ fs49.rmSync(candidate.worktreePath, { recursive: true, force: true });
72719
73656
  }
72720
73657
  } catch {
72721
73658
  }
72722
73659
  outcomes.push({
72723
73660
  path: candidate.worktreePath,
72724
73661
  agentId: candidate.agentId,
72725
- deleted: !fs45.existsSync(candidate.worktreePath),
73662
+ deleted: !fs49.existsSync(candidate.worktreePath),
72726
73663
  error: message
72727
73664
  });
72728
73665
  }
@@ -72785,16 +73722,16 @@ init_control_config();
72785
73722
  init_control_registry();
72786
73723
 
72787
73724
  // src/core/harness-scrub.ts
72788
- var fs46 = __toESM(require("fs"));
72789
- var path54 = __toESM(require("path"));
73725
+ var fs50 = __toESM(require("fs"));
73726
+ var path58 = __toESM(require("path"));
72790
73727
  var HARNESS_SCRUB_DIRS = ["runs", "validations", "state", "slots"];
72791
73728
  function scrubLocalHarnessDirs(repoPath) {
72792
73729
  const results = [];
72793
- const resolved = path54.resolve(repoPath);
72794
- if (!fs46.existsSync(resolved)) {
73730
+ const resolved = path58.resolve(repoPath);
73731
+ if (!fs50.existsSync(resolved)) {
72795
73732
  for (const directory of HARNESS_SCRUB_DIRS) {
72796
73733
  results.push({
72797
- path: path54.join(resolved, ".har", directory),
73734
+ path: path58.join(resolved, ".har", directory),
72798
73735
  directory,
72799
73736
  deleted: false,
72800
73737
  error: "repository path does not exist"
@@ -72803,24 +73740,24 @@ function scrubLocalHarnessDirs(repoPath) {
72803
73740
  return results;
72804
73741
  }
72805
73742
  for (const directory of HARNESS_SCRUB_DIRS) {
72806
- const target = path54.join(resolved, ".har", directory);
72807
- if (!fs46.existsSync(target)) {
73743
+ const target = path58.join(resolved, ".har", directory);
73744
+ if (!fs50.existsSync(target)) {
72808
73745
  results.push({ path: target, directory, deleted: true });
72809
73746
  continue;
72810
73747
  }
72811
73748
  try {
72812
- fs46.rmSync(target, { recursive: true, force: true });
73749
+ fs50.rmSync(target, { recursive: true, force: true });
72813
73750
  results.push({
72814
73751
  path: target,
72815
73752
  directory,
72816
- deleted: !fs46.existsSync(target),
72817
- error: fs46.existsSync(target) ? "failed to remove directory" : void 0
73753
+ deleted: !fs50.existsSync(target),
73754
+ error: fs50.existsSync(target) ? "failed to remove directory" : void 0
72818
73755
  });
72819
73756
  } catch (err) {
72820
73757
  results.push({
72821
73758
  path: target,
72822
73759
  directory,
72823
- deleted: !fs46.existsSync(target),
73760
+ deleted: !fs50.existsSync(target),
72824
73761
  error: err instanceof Error ? err.message : String(err)
72825
73762
  });
72826
73763
  }
@@ -72831,7 +73768,7 @@ function scrubLocalHarnessForRepos(repoPaths) {
72831
73768
  const results = [];
72832
73769
  const seen = /* @__PURE__ */ new Set();
72833
73770
  for (const repoPath of repoPaths) {
72834
- const key = path54.resolve(repoPath);
73771
+ const key = path58.resolve(repoPath);
72835
73772
  if (seen.has(key)) continue;
72836
73773
  seen.add(key);
72837
73774
  results.push(...scrubLocalHarnessDirs(key));
@@ -72840,12 +73777,12 @@ function scrubLocalHarnessForRepos(repoPaths) {
72840
73777
  }
72841
73778
 
72842
73779
  // src/core/control-reset.ts
72843
- async function askYesNo6(question) {
73780
+ async function askYesNo7(question) {
72844
73781
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
72845
73782
  const readline8 = await import("readline");
72846
73783
  const rl = readline8.createInterface({ input: process.stdin, output: process.stderr });
72847
- const answer = await new Promise((resolve47) => {
72848
- rl.question(question, resolve47);
73784
+ const answer = await new Promise((resolve50) => {
73785
+ rl.question(question, resolve50);
72849
73786
  });
72850
73787
  rl.close();
72851
73788
  return /^[Yy]$/.test(answer.trim());
@@ -72855,7 +73792,7 @@ async function confirmControlReset(options) {
72855
73792
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
72856
73793
  throw new Error("Non-interactive terminal \u2014 pass --yes");
72857
73794
  }
72858
- return askYesNo6(
73795
+ return askYesNo7(
72859
73796
  `Clear Mission Control data for ${options.repoCount} registered repositor${options.repoCount === 1 ? "y" : "ies"} (and scrub local .har history)? [y/N] `
72860
73797
  );
72861
73798
  }
@@ -73090,7 +74027,7 @@ async function handleDown() {
73090
74027
  return finishCommand(code);
73091
74028
  }
73092
74029
  async function handleRegister(argv) {
73093
- const repoPath = path55.resolve(argv.repo);
74030
+ const repoPath = path59.resolve(argv.repo);
73094
74031
  header("har control register");
73095
74032
  info(`Repository: ${repoPath}`);
73096
74033
  try {
@@ -73182,7 +74119,7 @@ async function handleReset(argv) {
73182
74119
  }
73183
74120
  }
73184
74121
  async function handleUnregister(argv) {
73185
- const repoPath = path55.resolve(argv.repo);
74122
+ const repoPath = path59.resolve(argv.repo);
73186
74123
  const worktrees = listUnregisterWorktreeCandidates(repoPath);
73187
74124
  try {
73188
74125
  let deleteWorktrees = argv.deleteWorktrees;
@@ -73364,12 +74301,12 @@ async function handleLogin(argv) {
73364
74301
  }
73365
74302
 
73366
74303
  // src/cli/commands/hooks.ts
73367
- var path57 = __toESM(require("path"));
74304
+ var path61 = __toESM(require("path"));
73368
74305
  init_hooks();
73369
74306
 
73370
74307
  // src/core/claude-hooks.ts
73371
- var fs47 = __toESM(require("fs"));
73372
- var path56 = __toESM(require("path"));
74308
+ var fs51 = __toESM(require("fs"));
74309
+ var path60 = __toESM(require("path"));
73373
74310
  var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
73374
74311
  var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
73375
74312
  var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
@@ -73380,8 +74317,8 @@ function isHarGuardEntry(entry) {
73380
74317
  );
73381
74318
  }
73382
74319
  function readSettings(settingsPath) {
73383
- if (!fs47.existsSync(settingsPath)) return {};
73384
- const raw = fs47.readFileSync(settingsPath, "utf8").trim();
74320
+ if (!fs51.existsSync(settingsPath)) return {};
74321
+ const raw = fs51.readFileSync(settingsPath, "utf8").trim();
73385
74322
  if (raw === "") return {};
73386
74323
  const parsed = JSON.parse(raw);
73387
74324
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -73390,19 +74327,19 @@ function readSettings(settingsPath) {
73390
74327
  return parsed;
73391
74328
  }
73392
74329
  function writeSettings(settingsPath, settings) {
73393
- fs47.mkdirSync(path56.dirname(settingsPath), { recursive: true });
73394
- fs47.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
74330
+ fs51.mkdirSync(path60.dirname(settingsPath), { recursive: true });
74331
+ fs51.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
73395
74332
  }
73396
74333
  function installClaudeGuard(repoPath) {
73397
74334
  const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
73398
74335
  if (!templatePath) {
73399
74336
  throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
73400
74337
  }
73401
- const guardScript = path56.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
73402
- fs47.mkdirSync(path56.dirname(guardScript), { recursive: true });
73403
- fs47.copyFileSync(templatePath, guardScript);
73404
- fs47.chmodSync(guardScript, 493);
73405
- const settingsPath = path56.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
74338
+ const guardScript = path60.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
74339
+ fs51.mkdirSync(path60.dirname(guardScript), { recursive: true });
74340
+ fs51.copyFileSync(templatePath, guardScript);
74341
+ fs51.chmodSync(guardScript, 493);
74342
+ const settingsPath = path60.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
73406
74343
  const settings = readSettings(settingsPath);
73407
74344
  const hooks = settings.hooks ?? {};
73408
74345
  const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
@@ -73416,15 +74353,15 @@ function installClaudeGuard(repoPath) {
73416
74353
  }
73417
74354
  function uninstallClaudeGuard(repoPath) {
73418
74355
  let removed = false;
73419
- const guardScript = path56.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
73420
- if (fs47.existsSync(guardScript)) {
73421
- fs47.rmSync(guardScript);
74356
+ const guardScript = path60.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
74357
+ if (fs51.existsSync(guardScript)) {
74358
+ fs51.rmSync(guardScript);
73422
74359
  removed = true;
73423
- const parent = path56.dirname(guardScript);
73424
- if (fs47.existsSync(parent) && fs47.readdirSync(parent).length === 0) fs47.rmdirSync(parent);
74360
+ const parent = path60.dirname(guardScript);
74361
+ if (fs51.existsSync(parent) && fs51.readdirSync(parent).length === 0) fs51.rmdirSync(parent);
73425
74362
  }
73426
- const settingsPath = path56.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
73427
- if (fs47.existsSync(settingsPath)) {
74363
+ const settingsPath = path60.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
74364
+ if (fs51.existsSync(settingsPath)) {
73428
74365
  const settings = readSettings(settingsPath);
73429
74366
  const preToolUse = settings.hooks?.PreToolUse;
73430
74367
  if (preToolUse) {
@@ -73449,8 +74386,8 @@ function uninstallClaudeGuard(repoPath) {
73449
74386
  return { removed };
73450
74387
  }
73451
74388
  function claudeGuardInstalled(repoPath) {
73452
- const settingsPath = path56.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
73453
- if (!fs47.existsSync(settingsPath)) return false;
74389
+ const settingsPath = path60.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
74390
+ if (!fs51.existsSync(settingsPath)) return false;
73454
74391
  try {
73455
74392
  const settings = readSettings(settingsPath);
73456
74393
  return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
@@ -73467,7 +74404,7 @@ function repoOption(y) {
73467
74404
  function handleInstall2(argv) {
73468
74405
  try {
73469
74406
  if (argv.claude) {
73470
- const result2 = installClaudeGuard(path57.resolve(argv.repo));
74407
+ const result2 = installClaudeGuard(path61.resolve(argv.repo));
73471
74408
  success("Claude Code worktree guard installed.");
73472
74409
  info(`Guard script: ${result2.guardScript}`);
73473
74410
  info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
@@ -73475,7 +74412,7 @@ function handleInstall2(argv) {
73475
74412
  info("Commit both files so the guard travels with the repo.");
73476
74413
  return;
73477
74414
  }
73478
- const result = installHooks({ repoPath: path57.resolve(argv.repo), force: argv.force });
74415
+ const result = installHooks({ repoPath: path61.resolve(argv.repo), force: argv.force });
73479
74416
  success(`Commit gate installed in ${result.hooksDir}`);
73480
74417
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
73481
74418
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -73488,7 +74425,7 @@ function handleInstall2(argv) {
73488
74425
  function handleUninstall(argv) {
73489
74426
  try {
73490
74427
  if (argv.claude) {
73491
- const result2 = uninstallClaudeGuard(path57.resolve(argv.repo));
74428
+ const result2 = uninstallClaudeGuard(path61.resolve(argv.repo));
73492
74429
  if (result2.removed) {
73493
74430
  success("Claude Code worktree guard removed.");
73494
74431
  } else {
@@ -73496,7 +74433,7 @@ function handleUninstall(argv) {
73496
74433
  }
73497
74434
  return;
73498
74435
  }
73499
- const result = uninstallHooks(path57.resolve(argv.repo));
74436
+ const result = uninstallHooks(path61.resolve(argv.repo));
73500
74437
  if (result.removed) {
73501
74438
  success(`Commit gate removed from ${result.hooksDir}`);
73502
74439
  } else {
@@ -73509,7 +74446,7 @@ function handleUninstall(argv) {
73509
74446
  }
73510
74447
  function handleStatus2(argv) {
73511
74448
  try {
73512
- const status = getHooksStatus(path57.resolve(argv.repo));
74449
+ const status = getHooksStatus(path61.resolve(argv.repo));
73513
74450
  if (argv.json) {
73514
74451
  process.stdout.write(`${JSON.stringify(status, null, 2)}
73515
74452
  `);
@@ -73527,7 +74464,7 @@ function handleStatus2(argv) {
73527
74464
  );
73528
74465
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
73529
74466
  info(
73530
- `Claude guard: ${claudeGuardInstalled(path57.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
74467
+ `Claude guard: ${claudeGuardInstalled(path61.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
73531
74468
  );
73532
74469
  } catch (err) {
73533
74470
  error(err instanceof Error ? err.message : String(err));
@@ -73788,10 +74725,10 @@ function assignProp(target, prop, value) {
73788
74725
  configurable: true
73789
74726
  });
73790
74727
  }
73791
- function getElementAtPath(obj, path62) {
73792
- if (!path62)
74728
+ function getElementAtPath(obj, path66) {
74729
+ if (!path66)
73793
74730
  return obj;
73794
- return path62.reduce((acc, key) => acc?.[key], obj);
74731
+ return path66.reduce((acc, key) => acc?.[key], obj);
73795
74732
  }
73796
74733
  function promiseAllObject(promisesObj) {
73797
74734
  const keys = Object.keys(promisesObj);
@@ -74111,11 +75048,11 @@ function aborted(x, startIndex = 0) {
74111
75048
  }
74112
75049
  return false;
74113
75050
  }
74114
- function prefixIssues(path62, issues) {
75051
+ function prefixIssues(path66, issues) {
74115
75052
  return issues.map((iss) => {
74116
75053
  var _a2;
74117
75054
  (_a2 = iss).path ?? (_a2.path = []);
74118
- iss.path.unshift(path62);
75055
+ iss.path.unshift(path66);
74119
75056
  return iss;
74120
75057
  });
74121
75058
  }
@@ -79438,7 +80375,7 @@ var Protocol = class {
79438
80375
  return;
79439
80376
  }
79440
80377
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
79441
- await new Promise((resolve47) => setTimeout(resolve47, pollInterval));
80378
+ await new Promise((resolve50) => setTimeout(resolve50, pollInterval));
79442
80379
  options?.signal?.throwIfAborted();
79443
80380
  }
79444
80381
  } catch (error3) {
@@ -79455,7 +80392,7 @@ var Protocol = class {
79455
80392
  */
79456
80393
  request(request, resultSchema, options) {
79457
80394
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
79458
- return new Promise((resolve47, reject) => {
80395
+ return new Promise((resolve50, reject) => {
79459
80396
  const earlyReject = (error3) => {
79460
80397
  reject(error3);
79461
80398
  };
@@ -79533,7 +80470,7 @@ var Protocol = class {
79533
80470
  if (!parseResult.success) {
79534
80471
  reject(parseResult.error);
79535
80472
  } else {
79536
- resolve47(parseResult.data);
80473
+ resolve50(parseResult.data);
79537
80474
  }
79538
80475
  } catch (error3) {
79539
80476
  reject(error3);
@@ -79794,12 +80731,12 @@ var Protocol = class {
79794
80731
  }
79795
80732
  } catch {
79796
80733
  }
79797
- return new Promise((resolve47, reject) => {
80734
+ return new Promise((resolve50, reject) => {
79798
80735
  if (signal.aborted) {
79799
80736
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
79800
80737
  return;
79801
80738
  }
79802
- const timeoutId = setTimeout(resolve47, interval);
80739
+ const timeoutId = setTimeout(resolve50, interval);
79803
80740
  signal.addEventListener("abort", () => {
79804
80741
  clearTimeout(timeoutId);
79805
80742
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -80589,7 +81526,7 @@ var Server = class extends Protocol {
80589
81526
  };
80590
81527
 
80591
81528
  // src/mcp/server.ts
80592
- var path58 = __toESM(require("path"));
81529
+ var path62 = __toESM(require("path"));
80593
81530
 
80594
81531
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
80595
81532
  var import_node_process3 = __toESM(require("node:process"), 1);
@@ -80672,12 +81609,12 @@ var StdioServerTransport = class {
80672
81609
  this.onclose?.();
80673
81610
  }
80674
81611
  send(message) {
80675
- return new Promise((resolve47) => {
81612
+ return new Promise((resolve50) => {
80676
81613
  const json = serializeMessage(message);
80677
81614
  if (this._stdout.write(json)) {
80678
- resolve47();
81615
+ resolve50();
80679
81616
  } else {
80680
- this._stdout.once("drain", resolve47);
81617
+ this._stdout.once("drain", resolve50);
80681
81618
  }
80682
81619
  });
80683
81620
  }
@@ -80686,6 +81623,8 @@ var StdioServerTransport = class {
80686
81623
  // src/mcp/server.ts
80687
81624
  init_telemetry_config();
80688
81625
  init_runs();
81626
+ init_work_units();
81627
+ init_manifest();
80689
81628
 
80690
81629
  // src/mcp/schema-tools.ts
80691
81630
  init_schema2();
@@ -80770,7 +81709,21 @@ var LaunchEnvironmentInputSchema = external_exports.object({
80770
81709
  source: external_exports.string().min(1).max(64).optional(),
80771
81710
  sourceUrl: external_exports.string().url().optional(),
80772
81711
  title: external_exports.string().min(1).max(256).optional(),
80773
- parentWorkUnitId: external_exports.string().min(1).max(128).optional()
81712
+ parentWorkUnitId: external_exports.string().min(1).max(128).optional(),
81713
+ relatedLinks: external_exports.array(
81714
+ external_exports.object({
81715
+ source: external_exports.string().min(1).max(64),
81716
+ url: external_exports.string().url(),
81717
+ label: external_exports.string().min(1).max(128).optional()
81718
+ })
81719
+ ).optional()
81720
+ });
81721
+ var AddWorkUnitLinkInputSchema = external_exports.object({
81722
+ repo: external_exports.string().default("."),
81723
+ workUnitId: external_exports.string().min(1).max(128),
81724
+ source: external_exports.string().min(1).max(64),
81725
+ url: external_exports.string().url(),
81726
+ label: external_exports.string().min(1).max(128).optional()
80774
81727
  });
80775
81728
  var LaunchEnvironmentOutputSchema = ShellRunOutputSchema.extend({
80776
81729
  previewUrls: external_exports.record(external_exports.string()).optional(),
@@ -80917,11 +81870,41 @@ var HAR_MCP_TOOLS = [
80917
81870
  parentWorkUnitId: {
80918
81871
  type: "string",
80919
81872
  description: "Optional parent work unit identifier."
81873
+ },
81874
+ relatedLinks: {
81875
+ type: "array",
81876
+ description: "Additional tracker links (append-only). Each item: source, url, optional label.",
81877
+ items: {
81878
+ type: "object",
81879
+ properties: {
81880
+ source: { type: "string" },
81881
+ url: { type: "string" },
81882
+ label: { type: "string" }
81883
+ },
81884
+ required: ["source", "url"]
81885
+ }
80920
81886
  }
80921
81887
  },
80922
81888
  ["agentId"]
80923
81889
  )
80924
81890
  },
81891
+ {
81892
+ name: "har_add_work_unit_link",
81893
+ description: "Append a related external link (PR, mirrored issue, alternate tracker) to an existing work unit.",
81894
+ inputSchema: objectJsonSchema(
81895
+ {
81896
+ repo: repoJsonProperty,
81897
+ workUnitId: {
81898
+ type: "string",
81899
+ description: "Repo-scoped work unit id bound at launch."
81900
+ },
81901
+ source: { type: "string", description: "Tracker provider (e.g. github, jira)." },
81902
+ url: { type: "string", description: "Canonical URL for the link." },
81903
+ label: { type: "string", description: "Optional display label." }
81904
+ },
81905
+ ["workUnitId", "source", "url"]
81906
+ )
81907
+ },
80925
81908
  {
80926
81909
  name: "har_recover_environment",
80927
81910
  description: "Resume a failed or partial agent launch without replacing the worktree. Alias for har_launch_environment with resume=true.",
@@ -81092,6 +82075,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
81092
82075
  sourceUrl: input.sourceUrl,
81093
82076
  title: input.title,
81094
82077
  parentWorkUnitId: input.parentWorkUnitId,
82078
+ relatedLinks: input.relatedLinks,
81095
82079
  capture: true
81096
82080
  });
81097
82081
  const parsed = LaunchEnvironmentOutputSchema.parse(result);
@@ -81114,6 +82098,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
81114
82098
  sourceUrl: input.sourceUrl,
81115
82099
  title: input.title,
81116
82100
  parentWorkUnitId: input.parentWorkUnitId,
82101
+ relatedLinks: input.relatedLinks,
81117
82102
  capture: true
81118
82103
  });
81119
82104
  const parsed = LaunchEnvironmentOutputSchema.parse(result);
@@ -81122,6 +82107,28 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
81122
82107
  ...result.blocked ? { isError: true } : {}
81123
82108
  };
81124
82109
  }
82110
+ case "har_add_work_unit_link": {
82111
+ const input = AddWorkUnitLinkInputSchema.parse({ ...args, repo });
82112
+ const harnessRoot = resolveHarnessRoot(repo);
82113
+ try {
82114
+ const record2 = addWorkUnitLinks(harnessRoot, input.workUnitId, [
82115
+ { source: input.source, url: input.url, label: input.label }
82116
+ ]);
82117
+ return jsonContent({
82118
+ ok: true,
82119
+ workUnitId: record2.workUnitId,
82120
+ relatedLinks: record2.relatedLinks
82121
+ });
82122
+ } catch (err) {
82123
+ return {
82124
+ ...jsonContent({
82125
+ ok: false,
82126
+ error: err instanceof Error ? err.message : String(err)
82127
+ }),
82128
+ isError: true
82129
+ };
82130
+ }
82131
+ }
81125
82132
  case "har_preflight_environment": {
81126
82133
  const input = PreflightEnvironmentInputSchema.parse({ ...args, repo });
81127
82134
  const agentId = validateAgentId(input.agentId, repo);
@@ -81232,7 +82239,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
81232
82239
  const input = ControlUpInputSchema.parse({ ...args, repo });
81233
82240
  const result = await startControlAndSync({
81234
82241
  detach: input.detach,
81235
- cwd: path58.resolve(input.repo)
82242
+ cwd: path62.resolve(input.repo)
81236
82243
  });
81237
82244
  if (result.code !== 0) {
81238
82245
  return {
@@ -81296,11 +82303,11 @@ var mcpCommand = {
81296
82303
  };
81297
82304
 
81298
82305
  // src/cli/commands/onboard.ts
81299
- var path60 = __toESM(require("path"));
82306
+ var path64 = __toESM(require("path"));
81300
82307
  init_stages();
81301
82308
 
81302
82309
  // src/core/onboarding.ts
81303
- var path59 = __toESM(require("path"));
82310
+ var path63 = __toESM(require("path"));
81304
82311
  init_control_config();
81305
82312
  init_control_sync();
81306
82313
  init_otel_hooks();
@@ -81345,7 +82352,8 @@ function listPluginChoices() {
81345
82352
  return listPluginIds().map((id) => {
81346
82353
  try {
81347
82354
  const manifest = readPluginManifest(id);
81348
- const description = typeof manifest.stage.description === "string" ? manifest.stage.description : `Adds stage ${manifest.stageId}`;
82355
+ const stages = normalizePluginStages(manifest);
82356
+ const description = typeof stages[0]?.description === "string" ? stages[0].description : `Adds stage ${primaryStageId(manifest)}`;
81349
82357
  return {
81350
82358
  id,
81351
82359
  label: `${id} \u2014 ${description}`,
@@ -81435,7 +82443,7 @@ async function defaultEnsureControl(options) {
81435
82443
  return { started: true, apiUrl: result.apiUrl };
81436
82444
  }
81437
82445
  async function runOnboarding(options, deps = {}) {
81438
- const repoPath = path59.resolve(options.repoPath);
82446
+ const repoPath = path63.resolve(options.repoPath);
81439
82447
  const applyTelemetry = deps.applyTelemetry ?? applyOnboardingTelemetry;
81440
82448
  const ensureControl = deps.ensureControl ?? defaultEnsureControl;
81441
82449
  const init = deps.initHarness ?? initHarness;
@@ -81623,7 +82631,7 @@ async function promptChoices(args) {
81623
82631
  };
81624
82632
  }
81625
82633
  const pluginChoices = listPluginChoices();
81626
- const repoPath = path60.resolve(args.repo);
82634
+ const repoPath = path64.resolve(args.repo);
81627
82635
  const alreadyPresent = harnessExists(repoPath);
81628
82636
  const needProfile = !alreadyPresent && !args.skipInit && args.profile === void 0;
81629
82637
  const needAgentSlots = shouldConfigureSlots && flaggedSlots === void 0;
@@ -81740,7 +82748,7 @@ function printSummary(result) {
81740
82748
  console.error("");
81741
82749
  }
81742
82750
  async function handleOnboard(argv) {
81743
- const repoPath = path60.resolve(argv.repo);
82751
+ const repoPath = path64.resolve(argv.repo);
81744
82752
  header("har onboard");
81745
82753
  info(`Repository: ${repoPath}`);
81746
82754
  try {
@@ -81971,7 +82979,7 @@ var preferencesCommand = {
81971
82979
  };
81972
82980
 
81973
82981
  // src/cli/commands/telemetry.ts
81974
- var path61 = __toESM(require("path"));
82982
+ var path65 = __toESM(require("path"));
81975
82983
  init_control_config();
81976
82984
  init_control_sync();
81977
82985
  init_manifest();
@@ -82064,7 +83072,7 @@ async function handleOff() {
82064
83072
  info("Mission Control will not auto-start. Existing usage rows are kept.");
82065
83073
  }
82066
83074
  async function handleWriteEnv(argv) {
82067
- const repoPath = path61.resolve(argv.repo);
83075
+ const repoPath = path65.resolve(argv.repo);
82068
83076
  const harnessRoot = resolveHarnessRoot(repoPath);
82069
83077
  const session = readSlotRegistry(harnessRoot, argv.agentId);
82070
83078
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
@@ -82076,7 +83084,7 @@ async function handleWriteEnv(argv) {
82076
83084
  suffix,
82077
83085
  createdAt: session?.createdAt
82078
83086
  });
82079
- const envFile = argv.envFile ?? path61.join(workDir, `.env.agent.${argv.agentId}`);
83087
+ const envFile = argv.envFile ?? path65.join(workDir, `.env.agent.${argv.agentId}`);
82080
83088
  const attrs = {
82081
83089
  sessionKey,
82082
83090
  agentId: argv.agentId,
@@ -82092,7 +83100,7 @@ async function handleWriteEnv(argv) {
82092
83100
  success(`Wrote session attribution to ${envFile}`);
82093
83101
  }
82094
83102
  function handlePrintEnv(argv) {
82095
- const repoPath = path61.resolve(argv.repo);
83103
+ const repoPath = path65.resolve(argv.repo);
82096
83104
  const workDir = argv.workDir ?? repoPath;
82097
83105
  const sessionKey = buildSessionKey({
82098
83106
  branch: argv.branch,