@osfactory/har 0.54.0 → 0.55.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
@@ -4517,6 +4517,8 @@ var init_schema = __esm({
4517
4517
  remediations: external_exports.array(external_exports.string()),
4518
4518
  ports: external_exports.record(external_exports.number()).optional(),
4519
4519
  allocatedPorts: external_exports.boolean().optional(),
4520
+ /** A warning already states why this port was chosen — suppresses the generic note. */
4521
+ portChoiceExplained: external_exports.boolean().optional(),
4520
4522
  /** Non-blocking notices (e.g. har control up holds the default port but an alternate was picked). */
4521
4523
  warnings: external_exports.array(external_exports.string()).optional()
4522
4524
  });
@@ -29790,9 +29792,546 @@ var init_slot_launch_guard_occupied = __esm({
29790
29792
  }
29791
29793
  });
29792
29794
 
29795
+ // src/core/change-batch.ts
29796
+ function git(checkoutDir, args, env3) {
29797
+ const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
29798
+ if (result.code !== 0) {
29799
+ throw new Error(`git ${args} failed (${result.code}): ${result.stderr.trim()}`);
29800
+ }
29801
+ return result.stdout.trim();
29802
+ }
29803
+ function tryGit2(checkoutDir, args) {
29804
+ const result = run(`git ${args}`, { cwd: checkoutDir });
29805
+ return result.code === 0 ? result.stdout.trim() : void 0;
29806
+ }
29807
+ function isCheckoutRoot(checkoutDir) {
29808
+ const toplevel = tryGit2(checkoutDir, "rev-parse --show-toplevel");
29809
+ if (!toplevel) return false;
29810
+ try {
29811
+ return fs16.realpathSync(toplevel) === fs16.realpathSync(checkoutDir);
29812
+ } catch {
29813
+ return false;
29814
+ }
29815
+ }
29816
+ function getHeadTree(checkoutDir) {
29817
+ return tryGit2(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
29818
+ }
29819
+ function getHeadSha(checkoutDir) {
29820
+ return tryGit2(checkoutDir, "rev-parse -q --verify HEAD");
29821
+ }
29822
+ function getCurrentBranch(checkoutDir) {
29823
+ const branch = tryGit2(checkoutDir, "rev-parse --abbrev-ref HEAD");
29824
+ return branch && branch !== "HEAD" ? branch : void 0;
29825
+ }
29826
+ function getEmptyTree(checkoutDir) {
29827
+ return git(checkoutDir, "hash-object -t tree /dev/null");
29828
+ }
29829
+ function isMergeOrRebaseInProgress(checkoutDir) {
29830
+ if (tryGit2(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
29831
+ for (const dir of ["rebase-merge", "rebase-apply"]) {
29832
+ const gitPath = tryGit2(checkoutDir, `rev-parse --git-path ${dir}`);
29833
+ if (gitPath && fs16.existsSync(path16.resolve(checkoutDir, gitPath))) return true;
29834
+ }
29835
+ return false;
29836
+ }
29837
+ function computeStagedTree(checkoutDir) {
29838
+ return git(checkoutDir, "write-tree");
29839
+ }
29840
+ function parseNameStatus(output) {
29841
+ const files = [];
29842
+ for (const line of output.split("\n")) {
29843
+ if (!line.trim()) continue;
29844
+ const parts = line.split(" ");
29845
+ const status = parts[0]?.charAt(0);
29846
+ if (!status) continue;
29847
+ if ((status === "R" || status === "C") && parts.length >= 3) {
29848
+ files.push({ path: parts[2], status, oldPath: parts[1] });
29849
+ } else if (parts.length >= 2) {
29850
+ files.push({ path: parts[1], status });
29851
+ }
29852
+ }
29853
+ return files;
29854
+ }
29855
+ function computeWorktreeSnapshot(checkoutDir) {
29856
+ const tmpDir = fs16.mkdtempSync(path16.join(os6.tmpdir(), "har-idx-"));
29857
+ const tmpIndex = path16.join(tmpDir, "index");
29858
+ const env3 = { GIT_INDEX_FILE: tmpIndex };
29859
+ try {
29860
+ const headTree = getHeadTree(checkoutDir);
29861
+ if (headTree) {
29862
+ git(checkoutDir, "read-tree HEAD", env3);
29863
+ } else {
29864
+ git(checkoutDir, "read-tree --empty", env3);
29865
+ }
29866
+ git(checkoutDir, "add -A .", env3);
29867
+ const treeHash = git(checkoutDir, "write-tree", env3);
29868
+ const baseTree = headTree ?? getEmptyTree(checkoutDir);
29869
+ const diffOutput = baseTree === treeHash ? "" : git(
29870
+ checkoutDir,
29871
+ `diff-tree -r -M --name-status --ignore-submodules=all ${baseTree} ${treeHash}`
29872
+ );
29873
+ return {
29874
+ treeHash,
29875
+ headSha: getHeadSha(checkoutDir),
29876
+ branch: getCurrentBranch(checkoutDir),
29877
+ changedFiles: parseNameStatus(diffOutput)
29878
+ };
29879
+ } finally {
29880
+ fs16.rmSync(tmpDir, { recursive: true, force: true });
29881
+ }
29882
+ }
29883
+ var fs16, os6, path16;
29884
+ var init_change_batch = __esm({
29885
+ "src/core/change-batch.ts"() {
29886
+ "use strict";
29887
+ fs16 = __toESM(require("fs"));
29888
+ os6 = __toESM(require("os"));
29889
+ path16 = __toESM(require("path"));
29890
+ init_shell();
29891
+ }
29892
+ });
29893
+
29894
+ // src/core/validations.ts
29895
+ function resolveValidationCheckoutDir(input) {
29896
+ const candidates = [input.worktreePath, input.workDir, input.harnessRoot].filter(
29897
+ (value) => Boolean(value)
29898
+ );
29899
+ return candidates.find(isCheckoutRoot) ?? candidates[0] ?? input.harnessRoot;
29900
+ }
29901
+ function getValidationsDir(checkoutDir) {
29902
+ return path17.join(checkoutDir, ".har", VALIDATIONS_DIR);
29903
+ }
29904
+ function validationPath(checkoutDir, treeHash) {
29905
+ return path17.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29906
+ }
29907
+ function writeRecord(checkoutDir, record2) {
29908
+ const dir = getValidationsDir(checkoutDir);
29909
+ fs17.mkdirSync(dir, { recursive: true });
29910
+ fs17.writeFileSync(
29911
+ validationPath(checkoutDir, record2.treeHash),
29912
+ `${JSON.stringify(record2, null, 2)}
29913
+ `
29914
+ );
29915
+ }
29916
+ function ensureValidationsIgnored(checkoutDir) {
29917
+ const harDir = path17.join(checkoutDir, ".har");
29918
+ if (!fs17.existsSync(harDir)) return;
29919
+ const gitignorePath = path17.join(harDir, ".gitignore");
29920
+ const content = fs17.existsSync(gitignorePath) ? fs17.readFileSync(gitignorePath, "utf8") : "";
29921
+ if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
29922
+ const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
29923
+ fs17.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29924
+ `);
29925
+ }
29926
+ function recordValidation(input) {
29927
+ if (!isCheckoutRoot(input.checkoutDir)) {
29928
+ throw new Error(`Not the root of a git checkout: ${input.checkoutDir}`);
29929
+ }
29930
+ ensureValidationsIgnored(input.checkoutDir);
29931
+ ensureValidationsIgnored(input.harnessRoot);
29932
+ const snapshot = computeWorktreeSnapshot(input.checkoutDir);
29933
+ const now = (/* @__PURE__ */ new Date()).toISOString();
29934
+ const existing = findValidation(input.checkoutDir, snapshot.treeHash);
29935
+ const record2 = ValidationRecordSchema.parse({
29936
+ validationId: existing?.validationId ?? crypto3.randomUUID(),
29937
+ treeHash: snapshot.treeHash,
29938
+ headSha: snapshot.headSha,
29939
+ branch: snapshot.branch,
29940
+ workDir: path17.resolve(input.checkoutDir),
29941
+ harnessRoot: path17.resolve(input.harnessRoot),
29942
+ agentId: input.agentId,
29943
+ status: input.status,
29944
+ full: input.full,
29945
+ runId: input.runId,
29946
+ changedFiles: snapshot.changedFiles,
29947
+ createdAt: existing?.createdAt ?? now,
29948
+ updatedAt: now,
29949
+ commitSha: existing?.commitSha,
29950
+ committedAt: existing?.committedAt
29951
+ });
29952
+ writeRecord(input.checkoutDir, record2);
29953
+ if (path17.resolve(input.harnessRoot) !== path17.resolve(input.checkoutDir)) {
29954
+ writeRecord(input.harnessRoot, record2);
29955
+ }
29956
+ markDirty(input.checkoutDir);
29957
+ return record2;
29958
+ }
29959
+ function findValidation(checkoutDir, treeHash) {
29960
+ const file = validationPath(checkoutDir, treeHash);
29961
+ if (!fs17.existsSync(file)) return void 0;
29962
+ try {
29963
+ return ValidationRecordSchema.parse(JSON.parse(fs17.readFileSync(file, "utf8")));
29964
+ } catch {
29965
+ return void 0;
29966
+ }
29967
+ }
29968
+ function attachCommit(checkoutDir, treeHash, commitSha) {
29969
+ const record2 = findValidation(checkoutDir, treeHash);
29970
+ if (!record2) return void 0;
29971
+ const updated = {
29972
+ ...record2,
29973
+ commitSha,
29974
+ committedAt: (/* @__PURE__ */ new Date()).toISOString(),
29975
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29976
+ };
29977
+ writeRecord(checkoutDir, updated);
29978
+ if (record2.harnessRoot && path17.resolve(record2.harnessRoot) !== path17.resolve(checkoutDir) && fs17.existsSync(path17.join(record2.harnessRoot, ".har"))) {
29979
+ writeRecord(record2.harnessRoot, updated);
29980
+ }
29981
+ return updated;
29982
+ }
29983
+ function listValidations(harnessRoot) {
29984
+ const dir = getValidationsDir(harnessRoot);
29985
+ if (!fs17.existsSync(dir)) return [];
29986
+ const records = [];
29987
+ for (const entry of fs17.readdirSync(dir)) {
29988
+ if (!entry.endsWith(".json")) continue;
29989
+ try {
29990
+ records.push(ValidationRecordSchema.parse(JSON.parse(fs17.readFileSync(path17.join(dir, entry), "utf8"))));
29991
+ } catch {
29992
+ }
29993
+ }
29994
+ return records.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
29995
+ }
29996
+ var crypto3, fs17, path17, VALIDATIONS_DIR;
29997
+ var init_validations = __esm({
29998
+ "src/core/validations.ts"() {
29999
+ "use strict";
30000
+ crypto3 = __toESM(require("crypto"));
30001
+ fs17 = __toESM(require("fs"));
30002
+ path17 = __toESM(require("path"));
30003
+ init_schema2();
30004
+ init_change_batch();
30005
+ init_sync_context();
30006
+ VALIDATIONS_DIR = "validations";
30007
+ }
30008
+ });
30009
+
30010
+ // src/core/hooks.ts
30011
+ function tryGit3(cwd, args) {
30012
+ const result = run(`git ${args}`, { cwd });
30013
+ return result.code === 0 ? result.stdout.trim() : void 0;
30014
+ }
30015
+ function resolveCheckoutRoot(cwd) {
30016
+ return tryGit3(cwd, "rev-parse --show-toplevel");
30017
+ }
30018
+ function resolveHooksDir(checkoutDir) {
30019
+ const hooksPath = tryGit3(checkoutDir, "rev-parse --git-path hooks");
30020
+ if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
30021
+ return path18.resolve(checkoutDir, hooksPath);
30022
+ }
30023
+ function getConfiguredHooksPath(checkoutDir) {
30024
+ return tryGit3(checkoutDir, "config core.hooksPath") || void 0;
30025
+ }
30026
+ function defaultHarInvocation() {
30027
+ const entry = process.argv[1];
30028
+ if (entry && entry.endsWith(".js")) {
30029
+ return `"${process.execPath}" "${path18.resolve(entry)}"`;
30030
+ }
30031
+ if (entry) return `"${path18.resolve(entry)}"`;
30032
+ return "har";
30033
+ }
30034
+ function buildHookScript(harInvocation, subcommand, failOpenNotice) {
30035
+ return `#!/bin/sh
30036
+ # Managed by \`har hooks\` \u2014 do not edit; reinstall with \`har hooks install\`.
30037
+ if ${harInvocation} --version >/dev/null 2>&1; then
30038
+ exec ${harInvocation} hooks ${subcommand}
30039
+ fi
30040
+ if command -v har >/dev/null 2>&1; then
30041
+ exec har hooks ${subcommand}
30042
+ fi
30043
+ toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
30044
+ if [ -n "$toplevel" ] && [ -x "$toplevel/node_modules/.bin/har" ]; then
30045
+ exec "$toplevel/node_modules/.bin/har" hooks ${subcommand}
30046
+ fi
30047
+ echo "${failOpenNotice}" >&2
30048
+ exit 0
30049
+ `;
30050
+ }
30051
+ function upsertMarkedBlock(filePath, block) {
30052
+ if (!fs18.existsSync(filePath)) {
30053
+ fs18.writeFileSync(filePath, `#!/bin/sh
30054
+ ${block}
30055
+ `, { mode: 493 });
30056
+ return "created";
30057
+ }
30058
+ const content = fs18.readFileSync(filePath, "utf8");
30059
+ if (content.includes(MARKER_START)) {
30060
+ const pattern = new RegExp(
30061
+ `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
30062
+ );
30063
+ fs18.writeFileSync(filePath, content.replace(pattern, block));
30064
+ fs18.chmodSync(filePath, 493);
30065
+ return "updated";
30066
+ }
30067
+ const suffix = content.endsWith("\n") ? "" : "\n";
30068
+ fs18.writeFileSync(filePath, `${content}${suffix}
30069
+ ${block}
30070
+ `);
30071
+ fs18.chmodSync(filePath, 493);
30072
+ return "appended";
30073
+ }
30074
+ function removeMarkedBlock(filePath) {
30075
+ if (!fs18.existsSync(filePath)) return false;
30076
+ const content = fs18.readFileSync(filePath, "utf8");
30077
+ if (!content.includes(MARKER_START)) return false;
30078
+ const pattern = new RegExp(
30079
+ `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
30080
+ );
30081
+ const stripped = content.replace(pattern, "\n");
30082
+ if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
30083
+ fs18.rmSync(filePath);
30084
+ } else {
30085
+ fs18.writeFileSync(filePath, stripped);
30086
+ }
30087
+ return true;
30088
+ }
30089
+ function escapeRegExp(value) {
30090
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30091
+ }
30092
+ function installHooks(options) {
30093
+ const checkout = resolveCheckoutRoot(options.repoPath);
30094
+ if (!checkout) throw new Error(`Not a git repository: ${options.repoPath}`);
30095
+ const configuredHooksPath = getConfiguredHooksPath(checkout);
30096
+ if (configuredHooksPath && !options.force) {
30097
+ throw new Error(
30098
+ `core.hooksPath is set to "${configuredHooksPath}" (managed hooks, e.g. husky).
30099
+ Add this to your managed pre-commit hook instead:
30100
+
30101
+ ${PRE_COMMIT_BLOCK.split("\n").join("\n ")}
30102
+
30103
+ Or re-run with --force to write into that directory anyway.`
30104
+ );
30105
+ }
30106
+ const hooksDir = resolveHooksDir(checkout);
30107
+ fs18.mkdirSync(hooksDir, { recursive: true });
30108
+ const invocation = options.harInvocation ?? defaultHarInvocation();
30109
+ fs18.writeFileSync(
30110
+ path18.join(hooksDir, "har-pre-commit"),
30111
+ buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
30112
+ { mode: 493 }
30113
+ );
30114
+ fs18.writeFileSync(
30115
+ path18.join(hooksDir, "har-post-commit"),
30116
+ buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
30117
+ { mode: 493 }
30118
+ );
30119
+ const preCommit = upsertMarkedBlock(path18.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
30120
+ const postCommit = upsertMarkedBlock(path18.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
30121
+ ensureValidationsIgnored(checkout);
30122
+ return { hooksDir, preCommit, postCommit };
30123
+ }
30124
+ function uninstallHooks(repoPath) {
30125
+ const checkout = resolveCheckoutRoot(repoPath);
30126
+ if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
30127
+ const hooksDir = resolveHooksDir(checkout);
30128
+ let removed = false;
30129
+ removed = removeMarkedBlock(path18.join(hooksDir, "pre-commit")) || removed;
30130
+ removed = removeMarkedBlock(path18.join(hooksDir, "post-commit")) || removed;
30131
+ for (const file of ["har-pre-commit", "har-post-commit"]) {
30132
+ const full = path18.join(hooksDir, file);
30133
+ if (fs18.existsSync(full)) {
30134
+ fs18.rmSync(full);
30135
+ removed = true;
30136
+ }
30137
+ }
30138
+ return { hooksDir, removed };
30139
+ }
30140
+ function getCommitGateConfig(checkoutDir) {
30141
+ try {
30142
+ const registry2 = readStageRegistry(checkoutDir);
30143
+ return CommitGateConfigSchema.parse(registry2.commitGate ?? {});
30144
+ } catch {
30145
+ return CommitGateConfigSchema.parse({});
30146
+ }
30147
+ }
30148
+ function isAgentWorktree(checkoutDir) {
30149
+ const branch = getCurrentBranch(checkoutDir);
30150
+ if (branch && (/^har-agent-\d+$/.test(branch) || /-har-agent-\d+-[a-z0-9]+$/.test(branch))) {
30151
+ return true;
30152
+ }
30153
+ const worktreesRoot = path18.join(os7.homedir(), "worktrees");
30154
+ const resolved = path18.resolve(checkoutDir);
30155
+ const name = path18.basename(resolved);
30156
+ return resolved.startsWith(`${worktreesRoot}${path18.sep}`) && (/-agent-\d+$/.test(name) || /-har-agent-\d+-[a-z0-9]+$/.test(name));
30157
+ }
30158
+ function resolveEffectiveMode(checkoutDir, gate) {
30159
+ if (!gate.enabled) return "off";
30160
+ if (gate.scope === "all") return gate.mode;
30161
+ return isAgentWorktree(checkoutDir) ? gate.mode : "warn";
30162
+ }
30163
+ function getHooksStatus(repoPath) {
30164
+ const checkout = resolveCheckoutRoot(repoPath);
30165
+ if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
30166
+ const hooksDir = resolveHooksDir(checkout);
30167
+ const gate = getCommitGateConfig(checkout);
30168
+ const hasBlock = (name) => {
30169
+ const file = path18.join(hooksDir, name);
30170
+ return fs18.existsSync(file) && fs18.readFileSync(file, "utf8").includes(MARKER_START);
30171
+ };
30172
+ return {
30173
+ checkout,
30174
+ hooksDir,
30175
+ configuredHooksPath: getConfiguredHooksPath(checkout),
30176
+ preCommitInstalled: hasBlock("pre-commit") && fs18.existsSync(path18.join(hooksDir, "har-pre-commit")),
30177
+ postCommitInstalled: hasBlock("post-commit") && fs18.existsSync(path18.join(hooksDir, "har-post-commit")),
30178
+ gate,
30179
+ effectiveMode: resolveEffectiveMode(checkout, gate)
30180
+ };
30181
+ }
30182
+ function checkCommitGate(cwd) {
30183
+ if (process.env.HAR_SKIP_GATE === "1") {
30184
+ return { exitCode: 0, messages: ["har: commit gate skipped (HAR_SKIP_GATE=1)"] };
30185
+ }
30186
+ const checkout = resolveCheckoutRoot(cwd);
30187
+ if (!checkout) return { exitCode: 0, messages: [] };
30188
+ if (!fs18.existsSync(path18.join(checkout, ".har", "stages.json"))) {
30189
+ return { exitCode: 0, messages: [] };
30190
+ }
30191
+ const gate = getCommitGateConfig(checkout);
30192
+ const mode = resolveEffectiveMode(checkout, gate);
30193
+ if (mode === "off") return { exitCode: 0, messages: [] };
30194
+ if (!getHeadSha(checkout)) {
30195
+ return { exitCode: 0, messages: ["har: commit gate skipped (initial commit)"] };
30196
+ }
30197
+ if (isMergeOrRebaseInProgress(checkout)) {
30198
+ return { exitCode: 0, messages: ["har: commit gate skipped (merge/rebase in progress)"] };
30199
+ }
30200
+ let stagedTree;
30201
+ try {
30202
+ stagedTree = computeStagedTree(checkout);
30203
+ } catch {
30204
+ return { exitCode: 0, messages: ["har: commit gate skipped (index has unmerged entries)"] };
30205
+ }
30206
+ if (stagedTree === getHeadTree(checkout)) {
30207
+ return { exitCode: 0, messages: [] };
30208
+ }
30209
+ const record2 = findValidation(checkout, stagedTree);
30210
+ if (record2 && record2.status === "pass" && record2.full) {
30211
+ return {
30212
+ exitCode: 0,
30213
+ messages: [
30214
+ `har: batch ${stagedTree.slice(0, 8)} verified${record2.runId ? ` by run ${record2.runId.slice(0, 8)}` : ""} \u2713`
30215
+ ]
30216
+ };
30217
+ }
30218
+ const messages = [];
30219
+ const verb = mode === "block" ? "commit blocked" : "warning";
30220
+ messages.push(
30221
+ `har: ${verb} \u2014 no passing verification for this exact change batch (tree ${stagedTree.slice(0, 8)}).`
30222
+ );
30223
+ if (record2 && record2.status === "fail") {
30224
+ messages.push(
30225
+ ` This exact batch was verified and FAILED${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}. Fix and re-run.`
30226
+ );
30227
+ } else if (record2 && !record2.full) {
30228
+ messages.push(
30229
+ ` This batch only passed a partial verify${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}; the gate requires --full.`
30230
+ );
30231
+ }
30232
+ messages.push(" Run the full pipeline: har env verify <agentId> --full");
30233
+ messages.push(
30234
+ " Note: verify hashes the WORKING TREE \u2014 stage exactly what you verified (git add -A)."
30235
+ );
30236
+ messages.push(" Bypass (humans only): HAR_SKIP_GATE=1 git commit \u2026 or git commit --no-verify");
30237
+ messages.push(" Configure via commitGate { mode: block|warn, scope: worktrees|all } in .har/stages.json");
30238
+ return { exitCode: mode === "block" ? 1 : 0, messages };
30239
+ }
30240
+ async function recordCommitAssociation(cwd) {
30241
+ try {
30242
+ const checkout = resolveCheckoutRoot(cwd);
30243
+ if (!checkout) return { attached: false };
30244
+ const commitSha = getHeadSha(checkout);
30245
+ const tree = tryGit3(checkout, "rev-parse HEAD^{tree}");
30246
+ if (!commitSha || !tree) return { attached: false };
30247
+ const updated = attachCommit(checkout, tree, commitSha);
30248
+ if (updated) {
30249
+ markDirty(updated.harnessRoot);
30250
+ return { attached: true, commitSha };
30251
+ }
30252
+ return { attached: false, commitSha };
30253
+ } catch {
30254
+ return { attached: false };
30255
+ }
30256
+ }
30257
+ var fs18, os7, path18, MARKER_START, MARKER_END, PRE_COMMIT_BLOCK, POST_COMMIT_BLOCK;
30258
+ var init_hooks = __esm({
30259
+ "src/core/hooks.ts"() {
30260
+ "use strict";
30261
+ fs18 = __toESM(require("fs"));
30262
+ os7 = __toESM(require("os"));
30263
+ path18 = __toESM(require("path"));
30264
+ init_shell();
30265
+ init_schema2();
30266
+ init_stages();
30267
+ init_change_batch();
30268
+ init_validations();
30269
+ init_sync_context();
30270
+ MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
30271
+ MARKER_END = "# <<< har commit gate <<<";
30272
+ PRE_COMMIT_BLOCK = [
30273
+ MARKER_START,
30274
+ 'if [ "$HAR_SKIP_GATE" != "1" ]; then',
30275
+ ' "$(git rev-parse --git-path hooks)/har-pre-commit" || exit 1',
30276
+ "fi",
30277
+ MARKER_END
30278
+ ].join("\n");
30279
+ POST_COMMIT_BLOCK = [
30280
+ MARKER_START,
30281
+ '"$(git rev-parse --git-path hooks)/har-post-commit" || true',
30282
+ MARKER_END
30283
+ ].join("\n");
30284
+ }
30285
+ });
30286
+
30287
+ // src/core/worktree-untracked.ts
30288
+ function listUntrackedAbsentFromWorktree(repoPath) {
30289
+ const toplevel = resolveCheckoutRoot(repoPath);
30290
+ if (!toplevel) return [];
30291
+ try {
30292
+ const raw = (0, import_child_process6.execSync)(
30293
+ "git --literal-pathspecs ls-files --others --exclude-standard --directory -z",
30294
+ {
30295
+ cwd: toplevel,
30296
+ encoding: "utf8",
30297
+ stdio: ["pipe", "pipe", "ignore"],
30298
+ timeout: 1e4,
30299
+ maxBuffer: 8 * 1024 * 1024
30300
+ }
30301
+ );
30302
+ return raw.split("\0").filter(Boolean).sort();
30303
+ } catch {
30304
+ return [];
30305
+ }
30306
+ }
30307
+ function formatUntrackedWorktreeWarning(paths, maxListed = MAX_LISTED) {
30308
+ if (paths.length === 0) return void 0;
30309
+ const shown = paths.slice(0, maxListed);
30310
+ const omitted = paths.length - shown.length;
30311
+ const listed = shown.join(", ") + (omitted > 0 ? ` (+${omitted} more)` : "");
30312
+ const n = paths.length;
30313
+ const noun = n === 1 ? "path" : "paths";
30314
+ const them = n === 1 ? "It is" : "They are";
30315
+ return `${n} untracked ${noun} will not appear in the session worktree: ${listed}. ${them} only in the main checkout \u2014 track them, or launch with --no-worktree.`;
30316
+ }
30317
+ function worktreeCheckEnabled(env3, worktree) {
30318
+ if (worktree === false) return false;
30319
+ if (worktree === true) return true;
30320
+ return (env3.HARNESS_USE_WORKTREE ?? "true").toLowerCase() !== "false";
30321
+ }
30322
+ var import_child_process6, MAX_LISTED;
30323
+ var init_worktree_untracked = __esm({
30324
+ "src/core/worktree-untracked.ts"() {
30325
+ "use strict";
30326
+ import_child_process6 = require("child_process");
30327
+ init_hooks();
30328
+ MAX_LISTED = 8;
30329
+ }
30330
+ });
30331
+
29793
30332
  // src/utils/package-runner.ts
29794
30333
  function commandExists(command2) {
29795
- const result = (0, import_child_process6.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
30334
+ const result = (0, import_child_process7.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
29796
30335
  encoding: "utf8"
29797
30336
  });
29798
30337
  return result.status === 0 && Boolean(result.stdout?.trim());
@@ -29803,11 +30342,11 @@ function packageRunner() {
29803
30342
  cached = available ?? RUNNERS[0];
29804
30343
  return cached;
29805
30344
  }
29806
- var import_child_process6, RUNNERS, cached;
30345
+ var import_child_process7, RUNNERS, cached;
29807
30346
  var init_package_runner = __esm({
29808
30347
  "src/utils/package-runner.ts"() {
29809
30348
  "use strict";
29810
- import_child_process6 = require("child_process");
30349
+ import_child_process7 = require("child_process");
29811
30350
  RUNNERS = ["npx --yes", "bunx", "pnpm dlx", "yarn dlx"];
29812
30351
  }
29813
30352
  });
@@ -29815,7 +30354,7 @@ var init_package_runner = __esm({
29815
30354
  // src/core/slot-preflight.ts
29816
30355
  function listPm2Processes() {
29817
30356
  try {
29818
- const raw = (0, import_child_process7.execSync)(`${packageRunner()} pm2 jlist`, {
30357
+ const raw = (0, import_child_process8.execSync)(`${packageRunner()} pm2 jlist`, {
29819
30358
  encoding: "utf8",
29820
30359
  stdio: ["pipe", "pipe", "ignore"],
29821
30360
  timeout: 3e3
@@ -29860,10 +30399,14 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
29860
30399
  }
29861
30400
  return { port };
29862
30401
  }
30402
+ function scanUntrackedWorktreePaths(repoPath, env3) {
30403
+ if (!worktreeCheckEnabled(env3, void 0)) return [];
30404
+ return listUntrackedAbsentFromWorktree(repoPath);
30405
+ }
29863
30406
  function inspectSlotReadiness(repoPath, agentId, options = {}) {
29864
30407
  const harnessRoot = resolveHarnessRoot(repoPath);
29865
30408
  const env3 = readHarnessEnv(harnessRoot);
29866
- const projectName = env3.HARNESS_PROJECT_NAME ?? path16.basename(harnessRoot);
30409
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path19.basename(harnessRoot);
29867
30410
  const usesPm2 = harnessUsesPm2(repoPath);
29868
30411
  const blockers = [];
29869
30412
  const remediations = [];
@@ -29917,6 +30460,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29917
30460
  }
29918
30461
  }
29919
30462
  let allocatedPorts = false;
30463
+ let portChoiceExplained = false;
29920
30464
  if (usesPm2 && (options.allocatePorts ?? true)) {
29921
30465
  const alloc = allocateAppPorts(repoPath, agentId);
29922
30466
  if ("error" in alloc) {
@@ -29936,7 +30480,9 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29936
30480
  agentId,
29937
30481
  portStep(env3)
29938
30482
  );
29939
- warnings.push(...controlDefaultPortWarnings(containers, feDefault, alloc.frontend));
30483
+ const portWarnings = controlDefaultPortWarnings(containers, feDefault, alloc.frontend);
30484
+ portChoiceExplained = portWarnings.length > 0;
30485
+ warnings.push(...portWarnings);
29940
30486
  for (const [label, port] of Object.entries({ frontend: alloc.frontend, api: alloc.api })) {
29941
30487
  const control = controlContainerOnPort(containers, port);
29942
30488
  if (control) {
@@ -29988,6 +30534,11 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29988
30534
  }
29989
30535
  }
29990
30536
  }
30537
+ if (worktreeCheckEnabled(env3, options.worktree)) {
30538
+ const untracked = options.untrackedPaths ?? scanUntrackedWorktreePaths(repoPath, env3);
30539
+ const untrackedWarning = formatUntrackedWorktreeWarning(untracked);
30540
+ if (untrackedWarning) warnings.push(untrackedWarning);
30541
+ }
29991
30542
  const canLaunch = blockers.length === 0;
29992
30543
  return {
29993
30544
  canLaunch,
@@ -29996,6 +30547,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29996
30547
  remediations: [...new Set(remediations)],
29997
30548
  ports: Object.keys(ports).length > 0 ? ports : void 0,
29998
30549
  allocatedPorts: allocatedPorts || void 0,
30550
+ portChoiceExplained: portChoiceExplained || void 0,
29999
30551
  warnings: warnings.length > 0 ? warnings : void 0
30000
30552
  };
30001
30553
  }
@@ -30012,7 +30564,7 @@ function formatPreflightReport(agentId, readiness) {
30012
30564
  p.db !== void 0 ? `db=${p.db}` : void 0
30013
30565
  ].filter(Boolean);
30014
30566
  if (parts.length) lines2.push(` Ports: ${parts.join(" ")}`);
30015
- if (readiness.allocatedPorts && !readiness.warnings?.length) {
30567
+ if (readiness.allocatedPorts && !readiness.portChoiceExplained) {
30016
30568
  lines2.push(" (alternate ports selected \u2014 defaults were busy)");
30017
30569
  }
30018
30570
  }
@@ -30028,20 +30580,24 @@ function formatPreflightReport(agentId, readiness) {
30028
30580
  lines2.push(` [${b2.code}] ${b2.message}`);
30029
30581
  if (b2.remediation) lines2.push(` \u2192 ${b2.remediation}`);
30030
30582
  }
30583
+ for (const w2 of readiness.warnings ?? []) {
30584
+ lines2.push(` WARN: ${w2}`);
30585
+ }
30031
30586
  return lines2.join("\n");
30032
30587
  }
30033
- var path16, import_child_process7;
30588
+ var path19, import_child_process8;
30034
30589
  var init_slot_preflight = __esm({
30035
30590
  "src/core/slot-preflight.ts"() {
30036
30591
  "use strict";
30037
- path16 = __toESM(require("path"));
30592
+ path19 = __toESM(require("path"));
30038
30593
  init_env();
30039
30594
  init_manifest();
30040
30595
  init_control_port();
30041
30596
  init_slot_launch_guard_occupied();
30042
30597
  init_slot_ports();
30043
30598
  init_slot_registry();
30044
- import_child_process7 = require("child_process");
30599
+ init_worktree_untracked();
30600
+ import_child_process8 = require("child_process");
30045
30601
  init_package_runner();
30046
30602
  }
30047
30603
  });
@@ -30049,7 +30605,7 @@ var init_slot_preflight = __esm({
30049
30605
  // src/core/slot-status.ts
30050
30606
  function readGitRemote(repoPath) {
30051
30607
  try {
30052
- const remote = (0, import_child_process8.execSync)("git remote get-url origin", {
30608
+ const remote = (0, import_child_process9.execSync)("git remote get-url origin", {
30053
30609
  cwd: repoPath,
30054
30610
  encoding: "utf8",
30055
30611
  stdio: ["pipe", "pipe", "ignore"]
@@ -30061,7 +30617,7 @@ function readGitRemote(repoPath) {
30061
30617
  }
30062
30618
  function runGit2(cwd, args) {
30063
30619
  try {
30064
- return (0, import_child_process8.execSync)(`git ${args}`, {
30620
+ return (0, import_child_process9.execSync)(`git ${args}`, {
30065
30621
  cwd,
30066
30622
  encoding: "utf8",
30067
30623
  stdio: ["pipe", "pipe", "ignore"]
@@ -30075,7 +30631,7 @@ function readWorktreeBranch(worktreePath) {
30075
30631
  }
30076
30632
  function gitCommonDir3(cwd) {
30077
30633
  const out = runGit2(cwd, "rev-parse --git-common-dir");
30078
- return out ? path17.resolve(cwd, out) : void 0;
30634
+ return out ? path20.resolve(cwd, out) : void 0;
30079
30635
  }
30080
30636
  function sameGitCheckout3(a, b2) {
30081
30637
  const left2 = gitCommonDir3(a);
@@ -30087,12 +30643,12 @@ function gitPrefix2(cwd) {
30087
30643
  return out ?? "";
30088
30644
  }
30089
30645
  function discoverSessionWorktreePath(harnessRoot, agentId) {
30090
- const worktreesRoot = path17.join(os6.homedir(), "worktrees");
30091
- if (!fs16.existsSync(worktreesRoot)) return void 0;
30646
+ const worktreesRoot = path20.join(os8.homedir(), "worktrees");
30647
+ if (!fs19.existsSync(worktreesRoot)) return void 0;
30092
30648
  const suffix = `-har-agent-${agentId}-`;
30093
30649
  const relPrefix = gitPrefix2(harnessRoot);
30094
- const matches = fs16.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path17.join(worktreesRoot, entry.name)).filter(
30095
- (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs16.existsSync(path17.join(candidate, relPrefix, `.env.agent.${agentId}`))
30650
+ const matches = fs19.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path20.join(worktreesRoot, entry.name)).filter(
30651
+ (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs19.existsSync(path20.join(candidate, relPrefix, `.env.agent.${agentId}`))
30096
30652
  );
30097
30653
  return matches.sort()[0];
30098
30654
  }
@@ -30137,7 +30693,7 @@ function lastBuildPass(run2) {
30137
30693
  }
30138
30694
  function listPm2Processes2() {
30139
30695
  try {
30140
- const raw = (0, import_child_process8.execSync)(`${packageRunner()} pm2 jlist`, {
30696
+ const raw = (0, import_child_process9.execSync)(`${packageRunner()} pm2 jlist`, {
30141
30697
  encoding: "utf8",
30142
30698
  stdio: ["pipe", "pipe", "ignore"],
30143
30699
  timeout: 3e3
@@ -30165,20 +30721,20 @@ function detectPm2Issue(projectName, agentId, session, procs) {
30165
30721
  }
30166
30722
  return void 0;
30167
30723
  }
30168
- function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
30724
+ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs, untrackedPaths) {
30169
30725
  const env3 = readHarnessEnv(harnessRoot);
30170
- const projectName = env3.HARNESS_PROJECT_NAME ?? path17.basename(harnessRoot);
30726
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path20.basename(harnessRoot);
30171
30727
  const session = readSlotRegistry(harnessRoot, agentId);
30172
- const legacyWorktreePath = path17.join(
30173
- os6.homedir(),
30728
+ const legacyWorktreePath = path20.join(
30729
+ os8.homedir(),
30174
30730
  "worktrees",
30175
30731
  `${projectName}-agent-${agentId}`
30176
30732
  );
30177
- const worktreePath = session?.worktreePath && fs16.existsSync(session.worktreePath) ? session.worktreePath : fs16.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
30733
+ const worktreePath = session?.worktreePath && fs19.existsSync(session.worktreePath) ? session.worktreePath : fs19.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
30178
30734
  const workDir = resolveAgentWorkDir(harnessRoot, agentId);
30179
- const envInWorkDir = workDir ? fs16.existsSync(path17.join(workDir, `.env.agent.${agentId}`)) : false;
30180
- const envInRoot = fs16.existsSync(path17.join(harnessRoot, `.env.agent.${agentId}`));
30181
- const envInWorktree = worktreePath ? fs16.existsSync(path17.join(worktreePath, `.env.agent.${agentId}`)) : false;
30735
+ const envInWorkDir = workDir ? fs19.existsSync(path20.join(workDir, `.env.agent.${agentId}`)) : false;
30736
+ const envInRoot = fs19.existsSync(path20.join(harnessRoot, `.env.agent.${agentId}`));
30737
+ const envInWorktree = worktreePath ? fs19.existsSync(path20.join(worktreePath, `.env.agent.${agentId}`)) : false;
30182
30738
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
30183
30739
  const latest = latestRunForAgent(runs, agentId);
30184
30740
  const verifyRun = runs.find((r) => r.agentId === agentId && r.stageId === "verify");
@@ -30194,7 +30750,8 @@ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
30194
30750
  const pm2Issue = detectPm2Issue(projectName, agentId, session, pm2Procs);
30195
30751
  const readiness = inspectSlotReadiness(harnessRoot, agentId, {
30196
30752
  allocatePorts: true,
30197
- occupied: { active, dirty: drift.dirty }
30753
+ occupied: { active, dirty: drift.dirty },
30754
+ untrackedPaths: untrackedPaths ?? scanUntrackedWorktreePaths(harnessRoot, env3)
30198
30755
  });
30199
30756
  const resumeHint = session?.status === "failed" || session?.status === "starting" ? `har env launch ${agentId} --resume (alias: har env recover ${agentId})` : void 0;
30200
30757
  return {
@@ -30231,23 +30788,24 @@ function collectEnvironmentStatus(repoPath) {
30231
30788
  const manifest = readManifest(harnessRoot);
30232
30789
  const slotIds = getAgentSlotIds(harnessRoot);
30233
30790
  const pm2Procs = listPm2Processes2();
30791
+ const untrackedPaths = scanUntrackedWorktreePaths(harnessRoot, readHarnessEnv(harnessRoot));
30234
30792
  return {
30235
- repoPath: path17.resolve(repoPath),
30793
+ repoPath: path20.resolve(repoPath),
30236
30794
  harnessRoot,
30237
30795
  gitRemote: readGitRemote(harnessRoot),
30238
30796
  profile: manifest?.profile,
30239
- slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs)),
30797
+ slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs, untrackedPaths)),
30240
30798
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
30241
30799
  };
30242
30800
  }
30243
- var fs16, os6, path17, import_child_process8, BYPASS_WARNING_MS;
30801
+ var fs19, os8, path20, import_child_process9, BYPASS_WARNING_MS;
30244
30802
  var init_slot_status = __esm({
30245
30803
  "src/core/slot-status.ts"() {
30246
30804
  "use strict";
30247
- fs16 = __toESM(require("fs"));
30248
- os6 = __toESM(require("os"));
30249
- path17 = __toESM(require("path"));
30250
- import_child_process8 = require("child_process");
30805
+ fs19 = __toESM(require("fs"));
30806
+ os8 = __toESM(require("os"));
30807
+ path20 = __toESM(require("path"));
30808
+ import_child_process9 = require("child_process");
30251
30809
  init_env();
30252
30810
  init_manifest();
30253
30811
  init_stages();
@@ -30260,256 +30818,41 @@ var init_slot_status = __esm({
30260
30818
  }
30261
30819
  });
30262
30820
 
30263
- // src/core/change-batch.ts
30264
- function git(checkoutDir, args, env3) {
30265
- const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
30266
- if (result.code !== 0) {
30267
- throw new Error(`git ${args} failed (${result.code}): ${result.stderr.trim()}`);
30268
- }
30269
- return result.stdout.trim();
30270
- }
30271
- function tryGit2(checkoutDir, args) {
30272
- const result = run(`git ${args}`, { cwd: checkoutDir });
30273
- return result.code === 0 ? result.stdout.trim() : void 0;
30274
- }
30275
- function isCheckoutRoot(checkoutDir) {
30276
- const toplevel = tryGit2(checkoutDir, "rev-parse --show-toplevel");
30277
- if (!toplevel) return false;
30278
- try {
30279
- return fs17.realpathSync(toplevel) === fs17.realpathSync(checkoutDir);
30280
- } catch {
30281
- return false;
30282
- }
30283
- }
30284
- function getHeadTree(checkoutDir) {
30285
- return tryGit2(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
30286
- }
30287
- function getHeadSha(checkoutDir) {
30288
- return tryGit2(checkoutDir, "rev-parse -q --verify HEAD");
30289
- }
30290
- function getCurrentBranch(checkoutDir) {
30291
- const branch = tryGit2(checkoutDir, "rev-parse --abbrev-ref HEAD");
30292
- return branch && branch !== "HEAD" ? branch : void 0;
30293
- }
30294
- function getEmptyTree(checkoutDir) {
30295
- return git(checkoutDir, "hash-object -t tree /dev/null");
30296
- }
30297
- function isMergeOrRebaseInProgress(checkoutDir) {
30298
- if (tryGit2(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
30299
- for (const dir of ["rebase-merge", "rebase-apply"]) {
30300
- const gitPath = tryGit2(checkoutDir, `rev-parse --git-path ${dir}`);
30301
- if (gitPath && fs17.existsSync(path18.resolve(checkoutDir, gitPath))) return true;
30302
- }
30303
- return false;
30304
- }
30305
- function computeStagedTree(checkoutDir) {
30306
- return git(checkoutDir, "write-tree");
30307
- }
30308
- function parseNameStatus(output) {
30309
- const files = [];
30310
- for (const line of output.split("\n")) {
30311
- if (!line.trim()) continue;
30312
- const parts = line.split(" ");
30313
- const status = parts[0]?.charAt(0);
30314
- if (!status) continue;
30315
- if ((status === "R" || status === "C") && parts.length >= 3) {
30316
- files.push({ path: parts[2], status, oldPath: parts[1] });
30317
- } else if (parts.length >= 2) {
30318
- files.push({ path: parts[1], status });
30319
- }
30320
- }
30321
- return files;
30322
- }
30323
- function computeWorktreeSnapshot(checkoutDir) {
30324
- const tmpDir = fs17.mkdtempSync(path18.join(os7.tmpdir(), "har-idx-"));
30325
- const tmpIndex = path18.join(tmpDir, "index");
30326
- const env3 = { GIT_INDEX_FILE: tmpIndex };
30327
- try {
30328
- const headTree = getHeadTree(checkoutDir);
30329
- if (headTree) {
30330
- git(checkoutDir, "read-tree HEAD", env3);
30331
- } else {
30332
- git(checkoutDir, "read-tree --empty", env3);
30333
- }
30334
- git(checkoutDir, "add -A .", env3);
30335
- const treeHash = git(checkoutDir, "write-tree", env3);
30336
- const baseTree = headTree ?? getEmptyTree(checkoutDir);
30337
- const diffOutput = baseTree === treeHash ? "" : git(
30338
- checkoutDir,
30339
- `diff-tree -r -M --name-status --ignore-submodules=all ${baseTree} ${treeHash}`
30340
- );
30341
- return {
30342
- treeHash,
30343
- headSha: getHeadSha(checkoutDir),
30344
- branch: getCurrentBranch(checkoutDir),
30345
- changedFiles: parseNameStatus(diffOutput)
30346
- };
30347
- } finally {
30348
- fs17.rmSync(tmpDir, { recursive: true, force: true });
30349
- }
30350
- }
30351
- var fs17, os7, path18;
30352
- var init_change_batch = __esm({
30353
- "src/core/change-batch.ts"() {
30354
- "use strict";
30355
- fs17 = __toESM(require("fs"));
30356
- os7 = __toESM(require("os"));
30357
- path18 = __toESM(require("path"));
30358
- init_shell();
30359
- }
30360
- });
30361
-
30362
- // src/core/validations.ts
30363
- function resolveValidationCheckoutDir(input) {
30364
- const candidates = [input.worktreePath, input.workDir, input.harnessRoot].filter(
30365
- (value) => Boolean(value)
30366
- );
30367
- return candidates.find(isCheckoutRoot) ?? candidates[0] ?? input.harnessRoot;
30368
- }
30369
- function getValidationsDir(checkoutDir) {
30370
- return path19.join(checkoutDir, ".har", VALIDATIONS_DIR);
30371
- }
30372
- function validationPath(checkoutDir, treeHash) {
30373
- return path19.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
30374
- }
30375
- function writeRecord(checkoutDir, record2) {
30376
- const dir = getValidationsDir(checkoutDir);
30377
- fs18.mkdirSync(dir, { recursive: true });
30378
- fs18.writeFileSync(
30379
- validationPath(checkoutDir, record2.treeHash),
30380
- `${JSON.stringify(record2, null, 2)}
30381
- `
30382
- );
30383
- }
30384
- function ensureValidationsIgnored(checkoutDir) {
30385
- const harDir = path19.join(checkoutDir, ".har");
30386
- if (!fs18.existsSync(harDir)) return;
30387
- const gitignorePath = path19.join(harDir, ".gitignore");
30388
- const content = fs18.existsSync(gitignorePath) ? fs18.readFileSync(gitignorePath, "utf8") : "";
30389
- if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
30390
- const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
30391
- fs18.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
30392
- `);
30393
- }
30394
- function recordValidation(input) {
30395
- if (!isCheckoutRoot(input.checkoutDir)) {
30396
- throw new Error(`Not the root of a git checkout: ${input.checkoutDir}`);
30397
- }
30398
- ensureValidationsIgnored(input.checkoutDir);
30399
- ensureValidationsIgnored(input.harnessRoot);
30400
- const snapshot = computeWorktreeSnapshot(input.checkoutDir);
30401
- const now = (/* @__PURE__ */ new Date()).toISOString();
30402
- const existing = findValidation(input.checkoutDir, snapshot.treeHash);
30403
- const record2 = ValidationRecordSchema.parse({
30404
- validationId: existing?.validationId ?? crypto3.randomUUID(),
30405
- treeHash: snapshot.treeHash,
30406
- headSha: snapshot.headSha,
30407
- branch: snapshot.branch,
30408
- workDir: path19.resolve(input.checkoutDir),
30409
- harnessRoot: path19.resolve(input.harnessRoot),
30410
- agentId: input.agentId,
30411
- status: input.status,
30412
- full: input.full,
30413
- runId: input.runId,
30414
- changedFiles: snapshot.changedFiles,
30415
- createdAt: existing?.createdAt ?? now,
30416
- updatedAt: now,
30417
- commitSha: existing?.commitSha,
30418
- committedAt: existing?.committedAt
30419
- });
30420
- writeRecord(input.checkoutDir, record2);
30421
- if (path19.resolve(input.harnessRoot) !== path19.resolve(input.checkoutDir)) {
30422
- writeRecord(input.harnessRoot, record2);
30423
- }
30424
- markDirty(input.checkoutDir);
30425
- return record2;
30426
- }
30427
- function findValidation(checkoutDir, treeHash) {
30428
- const file = validationPath(checkoutDir, treeHash);
30429
- if (!fs18.existsSync(file)) return void 0;
30430
- try {
30431
- return ValidationRecordSchema.parse(JSON.parse(fs18.readFileSync(file, "utf8")));
30432
- } catch {
30433
- return void 0;
30434
- }
30435
- }
30436
- function attachCommit(checkoutDir, treeHash, commitSha) {
30437
- const record2 = findValidation(checkoutDir, treeHash);
30438
- if (!record2) return void 0;
30439
- const updated = {
30440
- ...record2,
30441
- commitSha,
30442
- committedAt: (/* @__PURE__ */ new Date()).toISOString(),
30443
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30444
- };
30445
- writeRecord(checkoutDir, updated);
30446
- if (record2.harnessRoot && path19.resolve(record2.harnessRoot) !== path19.resolve(checkoutDir) && fs18.existsSync(path19.join(record2.harnessRoot, ".har"))) {
30447
- writeRecord(record2.harnessRoot, updated);
30448
- }
30449
- return updated;
30450
- }
30451
- function listValidations(harnessRoot) {
30452
- const dir = getValidationsDir(harnessRoot);
30453
- if (!fs18.existsSync(dir)) return [];
30454
- const records = [];
30455
- for (const entry of fs18.readdirSync(dir)) {
30456
- if (!entry.endsWith(".json")) continue;
30457
- try {
30458
- records.push(ValidationRecordSchema.parse(JSON.parse(fs18.readFileSync(path19.join(dir, entry), "utf8"))));
30459
- } catch {
30460
- }
30461
- }
30462
- return records.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
30463
- }
30464
- var crypto3, fs18, path19, VALIDATIONS_DIR;
30465
- var init_validations = __esm({
30466
- "src/core/validations.ts"() {
30467
- "use strict";
30468
- crypto3 = __toESM(require("crypto"));
30469
- fs18 = __toESM(require("fs"));
30470
- path19 = __toESM(require("path"));
30471
- init_schema2();
30472
- init_change_batch();
30473
- init_sync_context();
30474
- VALIDATIONS_DIR = "validations";
30475
- }
30476
- });
30477
-
30478
30821
  // src/core/work-units.ts
30479
30822
  function harEvidenceDir(harnessRoot, name) {
30480
- return path20.join(harnessRoot, ".har", name);
30823
+ return path21.join(harnessRoot, ".har", name);
30481
30824
  }
30482
30825
  function hashedRecordPath(harnessRoot, dir, id) {
30483
30826
  const digest = crypto4.createHash("sha256").update(id).digest("hex");
30484
- return path20.join(harEvidenceDir(harnessRoot, dir), `${digest}.json`);
30827
+ return path21.join(harEvidenceDir(harnessRoot, dir), `${digest}.json`);
30485
30828
  }
30486
30829
  function writeRecord2(file, value) {
30487
- fs19.mkdirSync(path20.dirname(file), { recursive: true });
30830
+ fs20.mkdirSync(path21.dirname(file), { recursive: true });
30488
30831
  const temp = `${file}.${process.pid}.tmp`;
30489
- fs19.writeFileSync(temp, `${JSON.stringify(value, null, 2)}
30832
+ fs20.writeFileSync(temp, `${JSON.stringify(value, null, 2)}
30490
30833
  `);
30491
- fs19.renameSync(temp, file);
30834
+ fs20.renameSync(temp, file);
30492
30835
  }
30493
30836
  function ensureWorkEvidenceIgnored(harnessRoot) {
30494
- const harDir = path20.join(harnessRoot, ".har");
30495
- if (!fs19.existsSync(harDir)) return;
30496
- const gitignorePath = path20.join(harDir, ".gitignore");
30497
- const content = fs19.existsSync(gitignorePath) ? fs19.readFileSync(gitignorePath, "utf8") : "";
30837
+ const harDir = path21.join(harnessRoot, ".har");
30838
+ if (!fs20.existsSync(harDir)) return;
30839
+ const gitignorePath = path21.join(harDir, ".gitignore");
30840
+ const content = fs20.existsSync(gitignorePath) ? fs20.readFileSync(gitignorePath, "utf8") : "";
30498
30841
  const lines2 = new Set(content.split("\n").map((line) => line.trim()));
30499
30842
  const missing = EVIDENCE_DIRS.map((dir) => `${dir}/`).filter((dir) => !lines2.has(dir));
30500
30843
  if (missing.length === 0) return;
30501
30844
  const separator = content.length === 0 || content.endsWith("\n") ? "" : "\n";
30502
- fs19.writeFileSync(gitignorePath, `${content}${separator}${missing.join("\n")}
30845
+ fs20.writeFileSync(gitignorePath, `${content}${separator}${missing.join("\n")}
30503
30846
  `);
30504
30847
  }
30505
30848
  function listRecords(harnessRoot, dir, parse4) {
30506
30849
  const recordDir = harEvidenceDir(harnessRoot, dir);
30507
- if (!fs19.existsSync(recordDir)) return [];
30850
+ if (!fs20.existsSync(recordDir)) return [];
30508
30851
  const records = [];
30509
- for (const name of fs19.readdirSync(recordDir)) {
30852
+ for (const name of fs20.readdirSync(recordDir)) {
30510
30853
  if (!name.endsWith(".json")) continue;
30511
30854
  try {
30512
- records.push(parse4(JSON.parse(fs19.readFileSync(path20.join(recordDir, name), "utf8"))));
30855
+ records.push(parse4(JSON.parse(fs20.readFileSync(path21.join(recordDir, name), "utf8"))));
30513
30856
  } catch {
30514
30857
  }
30515
30858
  }
@@ -30518,9 +30861,9 @@ function listRecords(harnessRoot, dir, parse4) {
30518
30861
  function findWorkUnit(harnessRoot, workUnitId) {
30519
30862
  const metadata = WorkUnitMetadataSchema.parse({ workUnitId });
30520
30863
  const file = hashedRecordPath(harnessRoot, WORK_UNITS_DIR, metadata.workUnitId);
30521
- if (!fs19.existsSync(file)) return void 0;
30864
+ if (!fs20.existsSync(file)) return void 0;
30522
30865
  try {
30523
- return WorkUnitRecordSchema.parse(JSON.parse(fs19.readFileSync(file, "utf8")));
30866
+ return WorkUnitRecordSchema.parse(JSON.parse(fs20.readFileSync(file, "utf8")));
30524
30867
  } catch {
30525
30868
  return void 0;
30526
30869
  }
@@ -30598,10 +30941,10 @@ function bindValidationToAttempt(harnessRoot, input) {
30598
30941
  VALIDATION_BINDINGS_DIR,
30599
30942
  stableKey
30600
30943
  );
30601
- if (fs19.existsSync(existingFile)) {
30944
+ if (fs20.existsSync(existingFile)) {
30602
30945
  try {
30603
30946
  return ValidationBindingRecordSchema.parse(
30604
- JSON.parse(fs19.readFileSync(existingFile, "utf8"))
30947
+ JSON.parse(fs20.readFileSync(existingFile, "utf8"))
30605
30948
  );
30606
30949
  } catch {
30607
30950
  }
@@ -30636,13 +30979,13 @@ function decideWorkUnitOutcome(harnessRoot, workUnitId, outcome) {
30636
30979
  markDirty(harnessRoot);
30637
30980
  return record2;
30638
30981
  }
30639
- var crypto4, fs19, path20, WORK_UNITS_DIR, WORK_ATTEMPTS_DIR, VALIDATION_BINDINGS_DIR, EVIDENCE_DIRS;
30982
+ var crypto4, fs20, path21, WORK_UNITS_DIR, WORK_ATTEMPTS_DIR, VALIDATION_BINDINGS_DIR, EVIDENCE_DIRS;
30640
30983
  var init_work_units = __esm({
30641
30984
  "src/core/work-units.ts"() {
30642
30985
  "use strict";
30643
30986
  crypto4 = __toESM(require("crypto"));
30644
- fs19 = __toESM(require("fs"));
30645
- path20 = __toESM(require("path"));
30987
+ fs20 = __toESM(require("fs"));
30988
+ path21 = __toESM(require("path"));
30646
30989
  init_schema2();
30647
30990
  init_sync_context();
30648
30991
  WORK_UNITS_DIR = "work-units";
@@ -31265,8 +31608,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs) {
31265
31608
  ${TELEMETRY_MARKER_END}
31266
31609
  `;
31267
31610
  let existing = "";
31268
- if (fs20.existsSync(envFilePath)) {
31269
- existing = fs20.readFileSync(envFilePath, "utf8");
31611
+ if (fs21.existsSync(envFilePath)) {
31612
+ existing = fs21.readFileSync(envFilePath, "utf8");
31270
31613
  const start = existing.indexOf(TELEMETRY_MARKER_START);
31271
31614
  if (start >= 0) {
31272
31615
  const end = existing.indexOf(TELEMETRY_MARKER_END, start);
@@ -31277,16 +31620,16 @@ ${TELEMETRY_MARKER_END}
31277
31620
  }
31278
31621
  }
31279
31622
  }
31280
- fs20.mkdirSync(path21.dirname(envFilePath), { recursive: true });
31623
+ fs21.mkdirSync(path22.dirname(envFilePath), { recursive: true });
31281
31624
  const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
31282
- fs20.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
31625
+ fs21.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
31283
31626
  }
31284
- var fs20, path21, TELEMETRY_MARKER_START, TELEMETRY_MARKER_END;
31627
+ var fs21, path22, TELEMETRY_MARKER_START, TELEMETRY_MARKER_END;
31285
31628
  var init_telemetry_env = __esm({
31286
31629
  "src/core/telemetry-env.ts"() {
31287
31630
  "use strict";
31288
- fs20 = __toESM(require("fs"));
31289
- path21 = __toESM(require("path"));
31631
+ fs21 = __toESM(require("fs"));
31632
+ path22 = __toESM(require("path"));
31290
31633
  init_control_config();
31291
31634
  init_shell();
31292
31635
  TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
@@ -31299,32 +31642,32 @@ function stripTrailingSeparators(value) {
31299
31642
  return value.replace(/[/\\]+$/, "");
31300
31643
  }
31301
31644
  function isWorkspaceUnderPath(workspace, basePath) {
31302
- const workspaceNorm = stripTrailingSeparators(path22.resolve(workspace));
31303
- const baseNorm = stripTrailingSeparators(path22.resolve(basePath));
31645
+ const workspaceNorm = stripTrailingSeparators(path23.resolve(workspace));
31646
+ const baseNorm = stripTrailingSeparators(path23.resolve(basePath));
31304
31647
  if (!workspaceNorm || !baseNorm) return false;
31305
- return workspaceNorm === baseNorm || workspaceNorm.startsWith(`${baseNorm}${path22.sep}`);
31648
+ return workspaceNorm === baseNorm || workspaceNorm.startsWith(`${baseNorm}${path23.sep}`);
31306
31649
  }
31307
31650
  function workspaceMatchesTarget(workspace, targets) {
31308
31651
  return targets.some((target) => isWorkspaceUnderPath(workspace, target));
31309
31652
  }
31310
- var path22;
31653
+ var path23;
31311
31654
  var init_workspace_path_match = __esm({
31312
31655
  "src/core/workspace-path-match.ts"() {
31313
31656
  "use strict";
31314
- path22 = __toESM(require("path"));
31657
+ path23 = __toESM(require("path"));
31315
31658
  }
31316
31659
  });
31317
31660
 
31318
31661
  // src/core/usage-harvest/claude.ts
31319
31662
  function claudeProjectsRoot() {
31320
- return process.env.HAR_CLAUDE_PROJECTS_DIR ? path23.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path23.join(os9.homedir(), ".claude", "projects");
31663
+ return process.env.HAR_CLAUDE_PROJECTS_DIR ? path24.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path24.join(os10.homedir(), ".claude", "projects");
31321
31664
  }
31322
31665
  function encodeClaudeProjectDir(cwd) {
31323
31666
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
31324
31667
  }
31325
31668
  function readJsonlRecords(filePath) {
31326
- if (!fs21.existsSync(filePath)) return [];
31327
- const lines2 = fs21.readFileSync(filePath, "utf8").split(/\r?\n/);
31669
+ if (!fs22.existsSync(filePath)) return [];
31670
+ const lines2 = fs22.readFileSync(filePath, "utf8").split(/\r?\n/);
31328
31671
  const out = [];
31329
31672
  for (const line of lines2) {
31330
31673
  const trimmed = line.trim();
@@ -31389,18 +31732,18 @@ function findMatchingClaudeTranscripts(slot) {
31389
31732
  const fallbackTargets = slot.includeRepoPathFallback && slot.repoPath && !primaryTargets.includes(slot.repoPath) ? [slot.repoPath] : [];
31390
31733
  if (primaryTargets.length === 0 && fallbackTargets.length === 0) return [];
31391
31734
  const root = claudeProjectsRoot();
31392
- if (!fs21.existsSync(root)) return [];
31735
+ if (!fs22.existsSync(root)) return [];
31393
31736
  const primary = [];
31394
31737
  const fallback = [];
31395
- for (const entry of fs21.readdirSync(root, { withFileTypes: true })) {
31738
+ for (const entry of fs22.readdirSync(root, { withFileTypes: true })) {
31396
31739
  if (!entry.isDirectory()) continue;
31397
- const projectDir = path23.join(root, entry.name);
31740
+ const projectDir = path24.join(root, entry.name);
31398
31741
  const primaryEncoded = primaryTargets.some((t) => entry.name === encodeClaudeProjectDir(t));
31399
31742
  const fallbackEncoded = fallbackTargets.some((t) => entry.name === encodeClaudeProjectDir(t));
31400
- for (const file of fs21.readdirSync(projectDir)) {
31743
+ for (const file of fs22.readdirSync(projectDir)) {
31401
31744
  if (!file.endsWith(".jsonl")) continue;
31402
- const filePath = path23.join(projectDir, file);
31403
- const stat = fs21.statSync(filePath);
31745
+ const filePath = path24.join(projectDir, file);
31746
+ const stat = fs22.statSync(filePath);
31404
31747
  const records = readJsonlRecords(filePath);
31405
31748
  let primaryHit = primaryEncoded;
31406
31749
  let fallbackHit = fallbackEncoded;
@@ -31519,13 +31862,13 @@ function harvestClaudeEvents(slot) {
31519
31862
  });
31520
31863
  return extractPromptEvents(matches[0].records, sessionKey, slot).slice(-40);
31521
31864
  }
31522
- var fs21, os9, path23;
31865
+ var fs22, os10, path24;
31523
31866
  var init_claude = __esm({
31524
31867
  "src/core/usage-harvest/claude.ts"() {
31525
31868
  "use strict";
31526
- fs21 = __toESM(require("fs"));
31527
- os9 = __toESM(require("os"));
31528
- path23 = __toESM(require("path"));
31869
+ fs22 = __toESM(require("fs"));
31870
+ os10 = __toESM(require("os"));
31871
+ path24 = __toESM(require("path"));
31529
31872
  init_telemetry_config();
31530
31873
  init_telemetry_env();
31531
31874
  init_workspace_path_match();
@@ -31534,19 +31877,19 @@ var init_claude = __esm({
31534
31877
 
31535
31878
  // src/core/usage-harvest/codex.ts
31536
31879
  function codexSessionsRoot() {
31537
- return process.env.HAR_CODEX_SESSIONS_DIR ? path24.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path24.join(os10.homedir(), ".codex", "sessions");
31880
+ return process.env.HAR_CODEX_SESSIONS_DIR ? path25.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path25.join(os11.homedir(), ".codex", "sessions");
31538
31881
  }
31539
31882
  function walkJsonlFiles(dir, out = []) {
31540
- if (!fs22.existsSync(dir)) return out;
31541
- for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
31542
- const full = path24.join(dir, entry.name);
31883
+ if (!fs23.existsSync(dir)) return out;
31884
+ for (const entry of fs23.readdirSync(dir, { withFileTypes: true })) {
31885
+ const full = path25.join(dir, entry.name);
31543
31886
  if (entry.isDirectory()) walkJsonlFiles(full, out);
31544
31887
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
31545
31888
  }
31546
31889
  return out;
31547
31890
  }
31548
31891
  function readJsonl(filePath) {
31549
- const lines2 = fs22.readFileSync(filePath, "utf8").split(/\r?\n/);
31892
+ const lines2 = fs23.readFileSync(filePath, "utf8").split(/\r?\n/);
31550
31893
  const out = [];
31551
31894
  for (const line of lines2) {
31552
31895
  const trimmed = line.trim();
@@ -31613,7 +31956,7 @@ function harvestCodexUsage(slot) {
31613
31956
  const extracted = extractCodexTokens(records);
31614
31957
  if (!extracted.cwd) continue;
31615
31958
  if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
31616
- const mtime = fs22.statSync(file).mtimeMs;
31959
+ const mtime = fs23.statSync(file).mtimeMs;
31617
31960
  if (workspaceMatchesTarget(extracted.cwd, primaryTargets)) {
31618
31961
  if (mtime >= bestPrimaryMtime) {
31619
31962
  bestPrimaryMtime = mtime;
@@ -31648,13 +31991,13 @@ function harvestCodexUsage(slot) {
31648
31991
  lastSeenAt: now
31649
31992
  };
31650
31993
  }
31651
- var fs22, os10, path24;
31994
+ var fs23, os11, path25;
31652
31995
  var init_codex = __esm({
31653
31996
  "src/core/usage-harvest/codex.ts"() {
31654
31997
  "use strict";
31655
- fs22 = __toESM(require("fs"));
31656
- os10 = __toESM(require("os"));
31657
- path24 = __toESM(require("path"));
31998
+ fs23 = __toESM(require("fs"));
31999
+ os11 = __toESM(require("os"));
32000
+ path25 = __toESM(require("path"));
31658
32001
  init_telemetry_env();
31659
32002
  init_workspace_path_match();
31660
32003
  }
@@ -31702,15 +32045,15 @@ var init_usage_harvest = __esm({
31702
32045
  // src/core/portal-watermark.ts
31703
32046
  function getStatePath() {
31704
32047
  if (process.env.HAR_PORTAL_SYNC_STATE_PATH) {
31705
- return path25.resolve(process.env.HAR_PORTAL_SYNC_STATE_PATH);
32048
+ return path26.resolve(process.env.HAR_PORTAL_SYNC_STATE_PATH);
31706
32049
  }
31707
- return path25.join(os11.homedir(), ".har", "portal-sync-state.json");
32050
+ return path26.join(os12.homedir(), ".har", "portal-sync-state.json");
31708
32051
  }
31709
32052
  function readState() {
31710
32053
  const statePath = getStatePath();
31711
32054
  try {
31712
- if (!fs23.existsSync(statePath)) return { states: [] };
31713
- const parsed = JSON.parse(fs23.readFileSync(statePath, "utf8"));
32055
+ if (!fs24.existsSync(statePath)) return { states: [] };
32056
+ const parsed = JSON.parse(fs24.readFileSync(statePath, "utf8"));
31714
32057
  return Array.isArray(parsed.states) ? parsed : { states: [] };
31715
32058
  } catch {
31716
32059
  return { states: [] };
@@ -31718,8 +32061,8 @@ function readState() {
31718
32061
  }
31719
32062
  function writeState(state) {
31720
32063
  const statePath = getStatePath();
31721
- fs23.mkdirSync(path25.dirname(statePath), { recursive: true });
31722
- fs23.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
32064
+ fs24.mkdirSync(path26.dirname(statePath), { recursive: true });
32065
+ fs24.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
31723
32066
  }
31724
32067
  function readPortalWatermark(repoPath, portalUrl) {
31725
32068
  const canonical = canonicalizeControlRepoPath(repoPath);
@@ -31773,13 +32116,13 @@ function selectSince(rows, since, getTimestamp) {
31773
32116
  }
31774
32117
  return { selected, maxSyncedAt };
31775
32118
  }
31776
- var fs23, os11, path25;
32119
+ var fs24, os12, path26;
31777
32120
  var init_portal_watermark = __esm({
31778
32121
  "src/core/portal-watermark.ts"() {
31779
32122
  "use strict";
31780
- fs23 = __toESM(require("fs"));
31781
- os11 = __toESM(require("os"));
31782
- path25 = __toESM(require("path"));
32123
+ fs24 = __toESM(require("fs"));
32124
+ os12 = __toESM(require("os"));
32125
+ path26 = __toESM(require("path"));
31783
32126
  init_control_repo_path();
31784
32127
  }
31785
32128
  });
@@ -31800,8 +32143,8 @@ async function getJson(url) {
31800
32143
  async function resolveControlRepoId(apiUrl, repoPath) {
31801
32144
  const repos = await getJson(`${apiUrl}/api/repos`);
31802
32145
  if (!Array.isArray(repos)) return null;
31803
- const target = path26.resolve(repoPath);
31804
- return repos.find((repo) => path26.resolve(repo.path) === target)?.id ?? null;
32146
+ const target = path27.resolve(repoPath);
32147
+ return repos.find((repo) => path27.resolve(repo.path) === target)?.id ?? null;
31805
32148
  }
31806
32149
  function optionalString(value) {
31807
32150
  return value == null ? void 0 : value;
@@ -31870,11 +32213,11 @@ async function fetchPersistedPortalTelemetry(repoPath, apiUrl, options) {
31870
32213
  maxSyncedAt: maxTimestamp(usage2.maxSyncedAt, events.maxSyncedAt)
31871
32214
  };
31872
32215
  }
31873
- var path26, FETCH_TIMEOUT_MS;
32216
+ var path27, FETCH_TIMEOUT_MS;
31874
32217
  var init_control_persisted_usage = __esm({
31875
32218
  "src/core/control-persisted-usage.ts"() {
31876
32219
  "use strict";
31877
- path26 = __toESM(require("path"));
32220
+ path27 = __toESM(require("path"));
31878
32221
  init_portal_watermark();
31879
32222
  FETCH_TIMEOUT_MS = 3e3;
31880
32223
  }
@@ -32206,7 +32549,7 @@ async function buildPortalPayload(repoPath, controlApiUrl, since, full = false)
32206
32549
  );
32207
32550
  if (full && isTelemetryEnabled() && usage2.length === 0 && runs.some((r) => r.agentId != null)) {
32208
32551
  warn(
32209
- `${path27.basename(repoPath)}: agent runs found but no usage harvested \u2014 attribution will be missing. This happens when agent sessions ran outside the har worktree slot (e.g. in the main checkout). Run agents inside \`har env launch\`.`
32552
+ `${path28.basename(repoPath)}: agent runs found but no usage harvested \u2014 attribution will be missing. This happens when agent sessions ran outside the har worktree slot (e.g. in the main checkout). Run agents inside \`har env launch\`.`
32210
32553
  );
32211
32554
  }
32212
32555
  const syncBody = {
@@ -32333,7 +32676,7 @@ async function syncRepoWithLocalControl(options) {
32333
32676
  const listResponse = await fetch(`${apiUrl}/api/repos`);
32334
32677
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
32335
32678
  const repos = await listResponse.json();
32336
- const existing = repos.find((r) => path27.resolve(r.path) === repoPath);
32679
+ const existing = repos.find((r) => path28.resolve(r.path) === repoPath);
32337
32680
  if (!existing) {
32338
32681
  return;
32339
32682
  }
@@ -32546,7 +32889,7 @@ async function syncRunWithLocalControl(repoPath, run2, apiUrl) {
32546
32889
  const listResponse = await fetch(`${apiUrl}/api/repos`);
32547
32890
  if (!listResponse.ok) return;
32548
32891
  const repos = await listResponse.json();
32549
- repoId = repos.find((r) => path27.resolve(r.path) === repoPath)?.id;
32892
+ repoId = repos.find((r) => path28.resolve(r.path) === repoPath)?.id;
32550
32893
  }
32551
32894
  if (!repoId) return;
32552
32895
  const runsBody = SyncRunsInputSchema.parse({ runs: [run2] });
@@ -32576,11 +32919,11 @@ async function syncRunWithControlAsync(repoPath, run2) {
32576
32919
  `);
32577
32920
  }
32578
32921
  }
32579
- var path27, DEFAULT_MAX_BATCH_BYTES, DEFAULT_SYNC_OVERLAP_MS, MAX_FETCH_ATTEMPTS, SYNC_TIMEOUT_MS, EXPLICIT_SYNC_TIMEOUT_MS;
32922
+ var path28, DEFAULT_MAX_BATCH_BYTES, DEFAULT_SYNC_OVERLAP_MS, MAX_FETCH_ATTEMPTS, SYNC_TIMEOUT_MS, EXPLICIT_SYNC_TIMEOUT_MS;
32580
32923
  var init_control_sync = __esm({
32581
32924
  "src/core/control-sync.ts"() {
32582
32925
  "use strict";
32583
- path27 = __toESM(require("path"));
32926
+ path28 = __toESM(require("path"));
32584
32927
  init_manifest();
32585
32928
  init_stages();
32586
32929
  init_schema2();
@@ -32612,7 +32955,7 @@ var init_control_sync = __esm({
32612
32955
  // src/core/sync-context.ts
32613
32956
  function markDirty(repoPath) {
32614
32957
  if (!repoPath) return;
32615
- dirtyRepos.add(path28.resolve(repoPath));
32958
+ dirtyRepos.add(path29.resolve(repoPath));
32616
32959
  }
32617
32960
  function markAllRegisteredDirty() {
32618
32961
  syncAllRegistered = true;
@@ -32637,11 +32980,11 @@ async function syncDirtyRepos() {
32637
32980
  } catch {
32638
32981
  }
32639
32982
  }
32640
- var path28, dirtyRepos, syncAllRegistered;
32983
+ var path29, dirtyRepos, syncAllRegistered;
32641
32984
  var init_sync_context = __esm({
32642
32985
  "src/core/sync-context.ts"() {
32643
32986
  "use strict";
32644
- path28 = __toESM(require("path"));
32987
+ path29 = __toESM(require("path"));
32645
32988
  dirtyRepos = /* @__PURE__ */ new Set();
32646
32989
  syncAllRegistered = false;
32647
32990
  }
@@ -53412,32 +53755,32 @@ __export(otel_hooks_exports, {
53412
53755
  });
53413
53756
  function getOtelHooksHome() {
53414
53757
  if (process.env.HAR_OTEL_HOOKS_HOME) {
53415
- return path47.resolve(process.env.HAR_OTEL_HOOKS_HOME);
53758
+ return path48.resolve(process.env.HAR_OTEL_HOOKS_HOME);
53416
53759
  }
53417
- return path47.join(os15.homedir(), ".har", "otel-hooks");
53760
+ return path48.join(os16.homedir(), ".har", "otel-hooks");
53418
53761
  }
53419
53762
  function getOtelHooksConfigPath(hooksHome = getOtelHooksHome()) {
53420
- return path47.join(hooksHome, "otel_config.json");
53763
+ return path48.join(hooksHome, "otel_config.json");
53421
53764
  }
53422
53765
  function getOtelHooksWrapperPath(hooksHome = getOtelHooksHome()) {
53423
- return path47.join(hooksHome, "run-otel-hook.sh");
53766
+ return path48.join(hooksHome, "run-otel-hook.sh");
53424
53767
  }
53425
53768
  function getOtelHooksStateDir(hooksHome = getOtelHooksHome()) {
53426
- return path47.join(hooksHome, "state");
53769
+ return path48.join(hooksHome, "state");
53427
53770
  }
53428
53771
  function localOtelHookBin(hooksHome) {
53429
- return path47.join(hooksHome, "node_modules", ".bin", "otel-hook");
53772
+ return path48.join(hooksHome, "node_modules", ".bin", "otel-hook");
53430
53773
  }
53431
53774
  function installedPackageVersion(hooksHome) {
53432
53775
  try {
53433
- const pkgPath = path47.join(
53776
+ const pkgPath = path48.join(
53434
53777
  hooksHome,
53435
53778
  "node_modules",
53436
53779
  "@osfactory",
53437
53780
  "otel-hook",
53438
53781
  "package.json"
53439
53782
  );
53440
- const pkg = JSON.parse(fs41.readFileSync(pkgPath, "utf8"));
53783
+ const pkg = JSON.parse(fs42.readFileSync(pkgPath, "utf8"));
53441
53784
  return typeof pkg.version === "string" ? pkg.version : null;
53442
53785
  } catch {
53443
53786
  return null;
@@ -53488,15 +53831,15 @@ function buildOtelHooksConfig(options) {
53488
53831
  };
53489
53832
  }
53490
53833
  function writeOtelHooksConfig(config2, hooksHome = getOtelHooksHome()) {
53491
- fs41.mkdirSync(hooksHome, { recursive: true });
53834
+ fs42.mkdirSync(hooksHome, { recursive: true });
53492
53835
  const configPath = getOtelHooksConfigPath(hooksHome);
53493
- fs41.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
53836
+ fs42.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
53494
53837
  `);
53495
53838
  return configPath;
53496
53839
  }
53497
53840
  function writeOtelHooksWrapper(otelHookCommand, hooksHome = getOtelHooksHome()) {
53498
- fs41.mkdirSync(hooksHome, { recursive: true });
53499
- fs41.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
53841
+ fs42.mkdirSync(hooksHome, { recursive: true });
53842
+ fs42.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
53500
53843
  const wrapperPath = getOtelHooksWrapperPath(hooksHome);
53501
53844
  const configPath = getOtelHooksConfigPath(hooksHome);
53502
53845
  const stateDir = getOtelHooksStateDir(hooksHome);
@@ -53508,22 +53851,22 @@ exec "${otelHookCommand}" run \\
53508
53851
  --state-dir "${stateDir}" \\
53509
53852
  "$@"
53510
53853
  `;
53511
- fs41.writeFileSync(wrapperPath, script, { mode: 493 });
53854
+ fs42.writeFileSync(wrapperPath, script, { mode: 493 });
53512
53855
  try {
53513
- fs41.chmodSync(wrapperPath, 493);
53856
+ fs42.chmodSync(wrapperPath, 493);
53514
53857
  } catch {
53515
53858
  }
53516
53859
  return wrapperPath;
53517
53860
  }
53518
53861
  function commandExists3(command2) {
53519
- const result = (0, import_child_process11.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
53862
+ const result = (0, import_child_process12.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
53520
53863
  encoding: "utf8"
53521
53864
  });
53522
53865
  return result.status === 0 && Boolean(result.stdout?.trim());
53523
53866
  }
53524
53867
  function resolveOtelHookCommand(hooksHome) {
53525
53868
  const local = localOtelHookBin(hooksHome);
53526
- if (fs41.existsSync(local)) return local;
53869
+ if (fs42.existsSync(local)) return local;
53527
53870
  return null;
53528
53871
  }
53529
53872
  function resolveHooksInstaller(hooksHome) {
@@ -53538,7 +53881,7 @@ function resolveHooksInstaller(hooksHome) {
53538
53881
  return candidates.find((candidate) => commandExists3(candidate.command)) ?? null;
53539
53882
  }
53540
53883
  function tryInstallPackage(hooksHome) {
53541
- fs41.mkdirSync(hooksHome, { recursive: true });
53884
+ fs42.mkdirSync(hooksHome, { recursive: true });
53542
53885
  const installer = resolveHooksInstaller(hooksHome);
53543
53886
  if (!installer) {
53544
53887
  return {
@@ -53547,11 +53890,11 @@ function tryInstallPackage(hooksHome) {
53547
53890
  };
53548
53891
  }
53549
53892
  const described = `${installer.command} ${installer.args.join(" ")}`;
53550
- const result = (0, import_child_process11.spawnSync)(installer.command, installer.args, {
53893
+ const result = (0, import_child_process12.spawnSync)(installer.command, installer.args, {
53551
53894
  encoding: "utf8",
53552
53895
  timeout: 18e4
53553
53896
  });
53554
- if (result.status === 0 && fs41.existsSync(localOtelHookBin(hooksHome))) {
53897
+ if (result.status === 0 && fs42.existsSync(localOtelHookBin(hooksHome))) {
53555
53898
  return { ok: true, detail: described };
53556
53899
  }
53557
53900
  return {
@@ -53569,7 +53912,7 @@ function uninstallLegacyPythonHooks(hooksHome) {
53569
53912
  for (const [cmd, args] of attempts) {
53570
53913
  if (!commandExists3(cmd)) continue;
53571
53914
  try {
53572
- (0, import_child_process11.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
53915
+ (0, import_child_process12.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
53573
53916
  } catch {
53574
53917
  }
53575
53918
  }
@@ -53577,14 +53920,14 @@ function uninstallLegacyPythonHooks(hooksHome) {
53577
53920
  }
53578
53921
  function removeLegacyPythonArtifacts(hooksHome) {
53579
53922
  const legacyPaths = [
53580
- path47.join(hooksHome, ".venv"),
53581
- path47.join(hooksHome, "otel_config.yaml"),
53582
- path47.join(hooksHome, "venv")
53923
+ path48.join(hooksHome, ".venv"),
53924
+ path48.join(hooksHome, "otel_config.yaml"),
53925
+ path48.join(hooksHome, "venv")
53583
53926
  ];
53584
53927
  for (const target of legacyPaths) {
53585
53928
  try {
53586
- if (fs41.existsSync(target)) {
53587
- fs41.rmSync(target, { recursive: true, force: true });
53929
+ if (fs42.existsSync(target)) {
53930
+ fs42.rmSync(target, { recursive: true, force: true });
53588
53931
  }
53589
53932
  } catch {
53590
53933
  }
@@ -53601,7 +53944,7 @@ function normalizeLegacyProviderFlagsInCommand(command2) {
53601
53944
  }
53602
53945
  function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
53603
53946
  const hookCommand = `${wrapperPath} --provider ${providerId}`;
53604
- const result = (0, import_child_process11.spawnSync)(
53947
+ const result = (0, import_child_process12.spawnSync)(
53605
53948
  otelHookCommand,
53606
53949
  [
53607
53950
  "setup",
@@ -53625,10 +53968,10 @@ function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
53625
53968
  };
53626
53969
  }
53627
53970
  function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
53628
- if (!fs41.existsSync(filePath)) return false;
53971
+ if (!fs42.existsSync(filePath)) return false;
53629
53972
  let raw;
53630
53973
  try {
53631
- raw = fs41.readFileSync(filePath, "utf8");
53974
+ raw = fs42.readFileSync(filePath, "utf8");
53632
53975
  } catch {
53633
53976
  return false;
53634
53977
  }
@@ -53675,15 +54018,15 @@ function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
53675
54018
  return changed;
53676
54019
  };
53677
54020
  if (!walk(parsed)) return false;
53678
- fs41.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54021
+ fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
53679
54022
  `);
53680
54023
  return true;
53681
54024
  }
53682
54025
  function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
53683
- if (!fs41.existsSync(filePath)) return false;
54026
+ if (!fs42.existsSync(filePath)) return false;
53684
54027
  let raw;
53685
54028
  try {
53686
- raw = fs41.readFileSync(filePath, "utf8");
54029
+ raw = fs42.readFileSync(filePath, "utf8");
53687
54030
  } catch {
53688
54031
  return false;
53689
54032
  }
@@ -53718,21 +54061,21 @@ function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
53718
54061
  }
53719
54062
  }
53720
54063
  if (!changed) return false;
53721
- fs41.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54064
+ fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
53722
54065
  `);
53723
54066
  return true;
53724
54067
  }
53725
54068
  function rewriteKnownHookRegistrations(wrapperPath) {
53726
- const home = os15.homedir();
54069
+ const home = os16.homedir();
53727
54070
  const candidates = [
53728
- path47.join(home, ".cursor", "hooks.json"),
53729
- path47.join(home, ".claude", "settings.json"),
53730
- path47.join(home, ".codex", "hooks.json")
54071
+ path48.join(home, ".cursor", "hooks.json"),
54072
+ path48.join(home, ".claude", "settings.json"),
54073
+ path48.join(home, ".codex", "hooks.json")
53731
54074
  ];
53732
54075
  for (const filePath of candidates) {
53733
54076
  try {
53734
54077
  rewriteHookCommandsToWrapper(filePath, wrapperPath);
53735
- if (filePath.endsWith(`${path47.sep}.cursor${path47.sep}hooks.json`)) {
54078
+ if (filePath.endsWith(`${path48.sep}.cursor${path48.sep}hooks.json`)) {
53736
54079
  pruneLegacyCursorHookEvents(filePath, wrapperPath);
53737
54080
  }
53738
54081
  } catch {
@@ -53748,7 +54091,7 @@ function ensureOtelHooks(options) {
53748
54091
  const hooksHome = getOtelHooksHome();
53749
54092
  const errors = [];
53750
54093
  const providersSetup = [];
53751
- fs41.mkdirSync(hooksHome, { recursive: true });
54094
+ fs42.mkdirSync(hooksHome, { recursive: true });
53752
54095
  let otelHookCommand = resolveOtelHookCommand(hooksHome);
53753
54096
  const needsInstall = !otelHookCommand || installedPackageVersion(hooksHome) !== pinnedVersion();
53754
54097
  if (needsInstall) {
@@ -53842,14 +54185,14 @@ function ensureOtelHooks(options) {
53842
54185
  function disableOtelHooksExport() {
53843
54186
  return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
53844
54187
  }
53845
- var import_child_process11, fs41, os15, path47, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
54188
+ var import_child_process12, fs42, os16, path48, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
53846
54189
  var init_otel_hooks = __esm({
53847
54190
  "src/core/otel-hooks.ts"() {
53848
54191
  "use strict";
53849
- import_child_process11 = require("child_process");
53850
- fs41 = __toESM(require("fs"));
53851
- os15 = __toESM(require("os"));
53852
- path47 = __toESM(require("path"));
54192
+ import_child_process12 = require("child_process");
54193
+ fs42 = __toESM(require("fs"));
54194
+ os16 = __toESM(require("os"));
54195
+ path48 = __toESM(require("path"));
53853
54196
  init_control_config();
53854
54197
  init_telemetry_config();
53855
54198
  OTEL_HOOKS_PACKAGE = "@osfactory/otel-hook@0.1.2";
@@ -65603,34 +65946,34 @@ init_telemetry_config();
65603
65946
  init_sync_context();
65604
65947
 
65605
65948
  // src/cli/commands/agents.ts
65606
- var path31 = __toESM(require("path"));
65949
+ var path32 = __toESM(require("path"));
65607
65950
 
65608
65951
  // src/harness/agent-skills.ts
65609
- var fs25 = __toESM(require("fs"));
65610
- var os12 = __toESM(require("os"));
65611
- var path30 = __toESM(require("path"));
65952
+ var fs26 = __toESM(require("fs"));
65953
+ var os13 = __toESM(require("os"));
65954
+ var path31 = __toESM(require("path"));
65612
65955
  var readline = __toESM(require("readline"));
65613
65956
  init_file_ops();
65614
65957
  init_logging();
65615
65958
 
65616
65959
  // src/utils/paths.ts
65617
- var fs24 = __toESM(require("fs"));
65618
- var path29 = __toESM(require("path"));
65960
+ var fs25 = __toESM(require("fs"));
65961
+ var path30 = __toESM(require("path"));
65619
65962
  function resolveTemplatesDir() {
65620
65963
  const candidates = [
65621
- path29.join(__dirname, "templates"),
65622
- path29.join(__dirname, "..", "templates"),
65623
- path29.join(__dirname, "..", "harness", "templates")
65964
+ path30.join(__dirname, "templates"),
65965
+ path30.join(__dirname, "..", "templates"),
65966
+ path30.join(__dirname, "..", "harness", "templates")
65624
65967
  ];
65625
65968
  for (const dir of candidates) {
65626
- if (fs24.existsSync(path29.join(dir, "har-boilerplate"))) return dir;
65969
+ if (fs25.existsSync(path30.join(dir, "har-boilerplate"))) return dir;
65627
65970
  }
65628
65971
  throw new Error("Templates directory not found. Run npm run build.");
65629
65972
  }
65630
65973
  function resolveTemplateFile(name) {
65631
65974
  const dir = resolveTemplatesDir();
65632
- const filePath = path29.join(dir, name);
65633
- return fs24.existsSync(filePath) ? filePath : null;
65975
+ const filePath = path30.join(dir, name);
65976
+ return fs25.existsSync(filePath) ? filePath : null;
65634
65977
  }
65635
65978
 
65636
65979
  // src/harness/agent-skills.ts
@@ -65638,18 +65981,18 @@ init_manifest();
65638
65981
  var AGENT_SKILL_TARGETS = ["claude", "cursor", "codex"];
65639
65982
  var MANAGED_HEADER = "<!-- managed by har \u2014 regenerated by `har agents install` / `har env maintain`; edit at your own risk -->";
65640
65983
  function agentSkillsTemplateDir() {
65641
- const dir = path30.join(resolveTemplatesDir(), "agent-skills");
65642
- if (!fs25.existsSync(dir)) {
65984
+ const dir = path31.join(resolveTemplatesDir(), "agent-skills");
65985
+ if (!fs26.existsSync(dir)) {
65643
65986
  throw new Error("Agent skills templates not found (templates/agent-skills). Run npm run build.");
65644
65987
  }
65645
65988
  return dir;
65646
65989
  }
65647
65990
  function readSkillsManifest() {
65648
- const manifestPath = path30.join(agentSkillsTemplateDir(), "skills.manifest.json");
65649
- return JSON.parse(fs25.readFileSync(manifestPath, "utf8"));
65991
+ const manifestPath = path31.join(agentSkillsTemplateDir(), "skills.manifest.json");
65992
+ return JSON.parse(fs26.readFileSync(manifestPath, "utf8"));
65650
65993
  }
65651
65994
  function readSkillBody(entry) {
65652
- return fs25.readFileSync(path30.join(agentSkillsTemplateDir(), entry.body), "utf8");
65995
+ return fs26.readFileSync(path31.join(agentSkillsTemplateDir(), entry.body), "utf8");
65653
65996
  }
65654
65997
  function renderYamlValue(value) {
65655
65998
  if (typeof value === "string") return JSON.stringify(value);
@@ -65681,7 +66024,7 @@ function renderSkillFiles(agents) {
65681
66024
  agent: "claude",
65682
66025
  skill: entry.id,
65683
66026
  scope: "repo",
65684
- relPath: path30.join(".claude", "skills", entry.id, "SKILL.md"),
66027
+ relPath: path31.join(".claude", "skills", entry.id, "SKILL.md"),
65685
66028
  content: renderClaudeSkill(entry, body)
65686
66029
  });
65687
66030
  }
@@ -65690,7 +66033,7 @@ function renderSkillFiles(agents) {
65690
66033
  agent: "cursor",
65691
66034
  skill: entry.id,
65692
66035
  scope: "repo",
65693
- relPath: path30.join(".cursor", "commands", `${entry.id}.md`),
66036
+ relPath: path31.join(".cursor", "commands", `${entry.id}.md`),
65694
66037
  content: renderPlainSkill(body)
65695
66038
  });
65696
66039
  }
@@ -65699,7 +66042,7 @@ function renderSkillFiles(agents) {
65699
66042
  agent: "codex",
65700
66043
  skill: entry.id,
65701
66044
  scope: "global",
65702
- relPath: path30.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
66045
+ relPath: path31.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
65703
66046
  content: renderPlainSkill(body)
65704
66047
  });
65705
66048
  }
@@ -65708,12 +66051,12 @@ function renderSkillFiles(agents) {
65708
66051
  }
65709
66052
  function resolveScaffoldPath(repoPath, file) {
65710
66053
  if (file.scope === "global") {
65711
- return path30.join(os12.homedir(), file.relPath.replace(/^~\//, ""));
66054
+ return path31.join(os13.homedir(), file.relPath.replace(/^~\//, ""));
65712
66055
  }
65713
- return path30.join(repoPath, file.relPath);
66056
+ return path31.join(repoPath, file.relPath);
65714
66057
  }
65715
66058
  function isHarManaged(filePath) {
65716
- return fs25.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
66059
+ return fs26.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
65717
66060
  }
65718
66061
  function scaffoldAgentSkills(repoPath, agents, options = {}) {
65719
66062
  const written = [];
@@ -65721,7 +66064,7 @@ function scaffoldAgentSkills(repoPath, agents, options = {}) {
65721
66064
  const records = [];
65722
66065
  for (const file of renderSkillFiles(agents)) {
65723
66066
  const destPath = resolveScaffoldPath(repoPath, file);
65724
- if (fs25.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
66067
+ if (fs26.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
65725
66068
  warn(`Skipped ${file.relPath} \u2014 exists with user modifications (use --force to overwrite)`);
65726
66069
  skipped.push(file.relPath);
65727
66070
  continue;
@@ -65755,16 +66098,16 @@ function removeAgentSkills(repoPath, agents) {
65755
66098
  const removed = [];
65756
66099
  for (const file of renderSkillFiles(agents)) {
65757
66100
  const destPath = resolveScaffoldPath(repoPath, file);
65758
- if (!fs25.existsSync(destPath)) continue;
66101
+ if (!fs26.existsSync(destPath)) continue;
65759
66102
  if (!isHarManaged(destPath)) {
65760
66103
  warn(`Kept ${file.relPath} \u2014 has user modifications`);
65761
66104
  continue;
65762
66105
  }
65763
- fs25.rmSync(destPath);
66106
+ fs26.rmSync(destPath);
65764
66107
  removed.push(file.relPath);
65765
- const parent = path30.dirname(destPath);
65766
- if (fs25.existsSync(parent) && fs25.readdirSync(parent).length === 0) {
65767
- fs25.rmdirSync(parent);
66108
+ const parent = path31.dirname(destPath);
66109
+ if (fs26.existsSync(parent) && fs26.readdirSync(parent).length === 0) {
66110
+ fs26.rmdirSync(parent);
65768
66111
  }
65769
66112
  }
65770
66113
  const manifest = readManifest(repoPath);
@@ -65783,9 +66126,9 @@ function detectAgentTargets(repoPath) {
65783
66126
  const manifest = readManifest(repoPath);
65784
66127
  const scaffolded = new Set((manifest?.scaffoldedAgentFiles ?? []).map((entry) => entry.agent));
65785
66128
  const detected = new Set(scaffolded);
65786
- if (fs25.existsSync(path30.join(repoPath, ".claude"))) detected.add("claude");
65787
- if (fs25.existsSync(path30.join(repoPath, ".cursor"))) detected.add("cursor");
65788
- if (fs25.existsSync(path30.join(os12.homedir(), ".codex"))) detected.add("codex");
66129
+ if (fs26.existsSync(path31.join(repoPath, ".claude"))) detected.add("claude");
66130
+ if (fs26.existsSync(path31.join(repoPath, ".cursor"))) detected.add("cursor");
66131
+ if (fs26.existsSync(path31.join(os13.homedir(), ".codex"))) detected.add("codex");
65789
66132
  return AGENT_SKILL_TARGETS.filter((agent) => detected.has(agent));
65790
66133
  }
65791
66134
  function parseAgentTargets(raw) {
@@ -65872,7 +66215,7 @@ var agentsCommand = {
65872
66215
  }
65873
66216
  };
65874
66217
  function handleInstall(argv) {
65875
- const repoPath = path31.resolve(argv.repo);
66218
+ const repoPath = path32.resolve(argv.repo);
65876
66219
  header("har agents install");
65877
66220
  try {
65878
66221
  const targets = resolveTargets(argv, repoPath);
@@ -65898,7 +66241,7 @@ function handleInstall(argv) {
65898
66241
  }
65899
66242
  }
65900
66243
  function handleRemove(argv) {
65901
- const repoPath = path31.resolve(argv.repo);
66244
+ const repoPath = path32.resolve(argv.repo);
65902
66245
  header("har agents remove");
65903
66246
  try {
65904
66247
  const targets = resolveTargets(argv, repoPath);
@@ -65928,25 +66271,25 @@ async function finishCommand(code) {
65928
66271
  }
65929
66272
 
65930
66273
  // src/core/harness.ts
65931
- var fs37 = __toESM(require("fs"));
65932
- var path42 = __toESM(require("path"));
66274
+ var fs38 = __toESM(require("fs"));
66275
+ var path43 = __toESM(require("path"));
65933
66276
 
65934
66277
  // src/harness/generator.ts
65935
- var fs28 = __toESM(require("fs"));
65936
- var path34 = __toESM(require("path"));
66278
+ var fs29 = __toESM(require("fs"));
66279
+ var path35 = __toESM(require("path"));
65937
66280
  init_file_ops();
65938
66281
  init_logging();
65939
66282
 
65940
66283
  // src/core/gitignore.ts
65941
- var fs26 = __toESM(require("fs"));
65942
- var path32 = __toESM(require("path"));
66284
+ var fs27 = __toESM(require("fs"));
66285
+ var path33 = __toESM(require("path"));
65943
66286
  var HARNESS_ROOT_GITIGNORE_PATTERNS = [
65944
66287
  ".env.agent.*",
65945
66288
  "ecosystem.agent.*.config.cjs"
65946
66289
  ];
65947
66290
  function ensureRootGitignorePatterns(checkoutDir) {
65948
- const gitignorePath = path32.join(checkoutDir, ".gitignore");
65949
- const content = fs26.existsSync(gitignorePath) ? fs26.readFileSync(gitignorePath, "utf8") : "";
66291
+ const gitignorePath = path33.join(checkoutDir, ".gitignore");
66292
+ const content = fs27.existsSync(gitignorePath) ? fs27.readFileSync(gitignorePath, "utf8") : "";
65950
66293
  const lines2 = content.split("\n").map((line) => line.trim());
65951
66294
  const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines2.includes(pattern));
65952
66295
  if (missing.length === 0) return;
@@ -65955,12 +66298,12 @@ function ensureRootGitignorePatterns(checkoutDir) {
65955
66298
  const block = lines2.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
65956
66299
  ` : `${header2}${missing.join("\n")}
65957
66300
  `;
65958
- fs26.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
66301
+ fs27.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
65959
66302
  }
65960
66303
 
65961
66304
  // src/harness/gitignore-template.ts
65962
- var fs27 = __toESM(require("fs"));
65963
- var path33 = __toESM(require("path"));
66305
+ var fs28 = __toESM(require("fs"));
66306
+ var path34 = __toESM(require("path"));
65964
66307
  var HARNESS_GITIGNORE_TEMPLATE = "gitignore.template";
65965
66308
  var HARNESS_GITIGNORE_FILE = ".gitignore";
65966
66309
  function harnessFileForTemplate(templateFile) {
@@ -65970,16 +66313,16 @@ function templateFileForHarness(harnessFile) {
65970
66313
  return harnessFile === HARNESS_GITIGNORE_FILE ? HARNESS_GITIGNORE_TEMPLATE : harnessFile;
65971
66314
  }
65972
66315
  function resolveGitignoreTemplateSource(boilerplateDir) {
65973
- const primary = path33.join(boilerplateDir, HARNESS_GITIGNORE_TEMPLATE);
65974
- if (fs27.existsSync(primary)) return primary;
65975
- const legacy = path33.join(boilerplateDir, HARNESS_GITIGNORE_FILE);
65976
- if (fs27.existsSync(legacy)) return legacy;
66316
+ const primary = path34.join(boilerplateDir, HARNESS_GITIGNORE_TEMPLATE);
66317
+ if (fs28.existsSync(primary)) return primary;
66318
+ const legacy = path34.join(boilerplateDir, HARNESS_GITIGNORE_FILE);
66319
+ if (fs28.existsSync(legacy)) return legacy;
65977
66320
  return null;
65978
66321
  }
65979
66322
  function writeHarnessGitignore(harnessDir, boilerplateDir) {
65980
66323
  const src = resolveGitignoreTemplateSource(boilerplateDir);
65981
66324
  if (!src) return;
65982
- fs27.writeFileSync(path33.join(harnessDir, HARNESS_GITIGNORE_FILE), fs27.readFileSync(src, "utf8"));
66325
+ fs28.writeFileSync(path34.join(harnessDir, HARNESS_GITIGNORE_FILE), fs28.readFileSync(src, "utf8"));
65983
66326
  }
65984
66327
  function isExpectedHarnessOnlyFile(harnessFile, templateFiles) {
65985
66328
  return harnessFile === HARNESS_GITIGNORE_FILE && templateFiles.includes(HARNESS_GITIGNORE_TEMPLATE);
@@ -66000,27 +66343,27 @@ var CLI_PRUNE_FILES = [
66000
66343
  ];
66001
66344
  function pruneCliProfile(harnessDir) {
66002
66345
  for (const file of CLI_PRUNE_FILES) {
66003
- const filePath = path34.join(harnessDir, file);
66004
- if (fs28.existsSync(filePath)) {
66005
- fs28.unlinkSync(filePath);
66346
+ const filePath = path35.join(harnessDir, file);
66347
+ if (fs29.existsSync(filePath)) {
66348
+ fs29.unlinkSync(filePath);
66006
66349
  }
66007
66350
  }
66008
66351
  }
66009
66352
  function scaffoldHarnessBoilerplate(repoPath, options = {}) {
66010
- const harnessDir = path34.join(repoPath, DEFAULT_HAR_DIR);
66011
- const projectName = path34.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
66353
+ const harnessDir = path35.join(repoPath, DEFAULT_HAR_DIR);
66354
+ const projectName = path35.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
66012
66355
  const profile = options.profile ?? "default";
66013
- const boilerplateDir = path34.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
66014
- if (fs28.existsSync(harnessDir) && !options.force) {
66356
+ const boilerplateDir = path35.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
66357
+ if (fs29.existsSync(harnessDir) && !options.force) {
66015
66358
  throw new Error(
66016
66359
  '.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
66017
66360
  );
66018
66361
  }
66019
- if (!fs28.existsSync(boilerplateDir)) {
66362
+ if (!fs29.existsSync(boilerplateDir)) {
66020
66363
  throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
66021
66364
  }
66022
- if (options.force && fs28.existsSync(harnessDir)) {
66023
- fs28.rmSync(harnessDir, { recursive: true, force: true });
66365
+ if (options.force && fs29.existsSync(harnessDir)) {
66366
+ fs29.rmSync(harnessDir, { recursive: true, force: true });
66024
66367
  }
66025
66368
  copyDirRecursive(boilerplateDir, harnessDir);
66026
66369
  writeHarnessGitignore(harnessDir, boilerplateDir);
@@ -66028,11 +66371,11 @@ function scaffoldHarnessBoilerplate(repoPath, options = {}) {
66028
66371
  pruneCliProfile(harnessDir);
66029
66372
  }
66030
66373
  syncAgentSlotsToHarnessEnv(repoPath);
66031
- const harnessEnvPath = path34.join(harnessDir, "harness.env");
66032
- if (fs28.existsSync(harnessEnvPath)) {
66033
- let content = fs28.readFileSync(harnessEnvPath, "utf8");
66374
+ const harnessEnvPath = path35.join(harnessDir, "harness.env");
66375
+ if (fs29.existsSync(harnessEnvPath)) {
66376
+ let content = fs29.readFileSync(harnessEnvPath, "utf8");
66034
66377
  content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
66035
- fs28.writeFileSync(harnessEnvPath, content);
66378
+ fs29.writeFileSync(harnessEnvPath, content);
66036
66379
  }
66037
66380
  const manifest = createManifest(
66038
66381
  repoPath,
@@ -66054,16 +66397,16 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
66054
66397
  }
66055
66398
 
66056
66399
  // src/harness/agent-md.ts
66057
- var fs30 = __toESM(require("fs"));
66058
- var path36 = __toESM(require("path"));
66400
+ var fs31 = __toESM(require("fs"));
66401
+ var path37 = __toESM(require("path"));
66059
66402
  init_file_ops();
66060
66403
  init_logging();
66061
66404
  init_manifest();
66062
66405
 
66063
66406
  // src/harness/instruction-files.ts
66064
- var fs29 = __toESM(require("fs"));
66065
- var os13 = __toESM(require("os"));
66066
- var path35 = __toESM(require("path"));
66407
+ var fs30 = __toESM(require("fs"));
66408
+ var os14 = __toESM(require("os"));
66409
+ var path36 = __toESM(require("path"));
66067
66410
 
66068
66411
  // node_modules/@inquirer/core/dist/esm/lib/key.mjs
66069
66412
  var isUpKey = (key) => (
@@ -68396,7 +68739,7 @@ var AgentsMdShrinkError = class extends Error {
68396
68739
  this.name = "AgentsMdShrinkError";
68397
68740
  }
68398
68741
  };
68399
- function escapeRegExp(value) {
68742
+ function escapeRegExp2(value) {
68400
68743
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68401
68744
  }
68402
68745
  function countNonEmptyLines(text) {
@@ -68405,7 +68748,7 @@ function countNonEmptyLines(text) {
68405
68748
  function stripClaudePointerFromAgentsMd(content) {
68406
68749
  if (!content.includes(CLAUDE_HAR_SECTION_START)) return content;
68407
68750
  const pattern = new RegExp(
68408
- `${escapeRegExp(CLAUDE_HAR_SECTION_START)}[\\s\\S]*?${escapeRegExp(CLAUDE_HAR_SECTION_END)}\\n?`,
68751
+ `${escapeRegExp2(CLAUDE_HAR_SECTION_START)}[\\s\\S]*?${escapeRegExp2(CLAUDE_HAR_SECTION_END)}\\n?`,
68409
68752
  "g"
68410
68753
  );
68411
68754
  return content.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
@@ -68457,12 +68800,12 @@ function outsideContentShrinkMessage(beforeLines, afterLines) {
68457
68800
  }
68458
68801
  function detectInstructionFiles(repoPath) {
68459
68802
  return {
68460
- agentsMd: fs29.existsSync(path35.join(repoPath, AGENTS_MD)),
68461
- agentMd: fs29.existsSync(path35.join(repoPath, LEGACY_AGENT_MD)),
68462
- claudeMd: fs29.existsSync(path35.join(repoPath, CLAUDE_MD)),
68463
- cursorDir: fs29.existsSync(path35.join(repoPath, ".cursor")),
68464
- claudeDir: fs29.existsSync(path35.join(repoPath, ".claude")),
68465
- codexHome: fs29.existsSync(path35.join(os13.homedir(), ".codex"))
68803
+ agentsMd: fs30.existsSync(path36.join(repoPath, AGENTS_MD)),
68804
+ agentMd: fs30.existsSync(path36.join(repoPath, LEGACY_AGENT_MD)),
68805
+ claudeMd: fs30.existsSync(path36.join(repoPath, CLAUDE_MD)),
68806
+ cursorDir: fs30.existsSync(path36.join(repoPath, ".cursor")),
68807
+ claudeDir: fs30.existsSync(path36.join(repoPath, ".claude")),
68808
+ codexHome: fs30.existsSync(path36.join(os14.homedir(), ".codex"))
68466
68809
  };
68467
68810
  }
68468
68811
  function formatDetectionReport(detection) {
@@ -68517,7 +68860,7 @@ function loadHarAgentsSectionFromTemplate() {
68517
68860
  if (!templatePath) {
68518
68861
  throw new Error("AGENTS.md.template not found. Run npm run build.");
68519
68862
  }
68520
- const content = fs29.readFileSync(templatePath, "utf8");
68863
+ const content = fs30.readFileSync(templatePath, "utf8");
68521
68864
  const start = content.indexOf(HAR_SECTION_START);
68522
68865
  const end = content.indexOf(HAR_SECTION_END);
68523
68866
  if (start === -1 || end === -1 || end < start) {
@@ -68530,9 +68873,9 @@ function loadAgentsMdTemplate() {
68530
68873
  if (!templatePath) {
68531
68874
  throw new Error("AGENTS.md.template not found. Run npm run build.");
68532
68875
  }
68533
- return fs29.readFileSync(templatePath, "utf8");
68876
+ return fs30.readFileSync(templatePath, "utf8");
68534
68877
  }
68535
- function upsertMarkedBlock(content, block, startMarker, endMarker) {
68878
+ function upsertMarkedBlock2(content, block, startMarker, endMarker) {
68536
68879
  const existingBlock = findLastCompleteMarkedBlock(content, startMarker, endMarker);
68537
68880
  if (existingBlock) {
68538
68881
  const newContent = content.slice(0, existingBlock.start) + block + content.slice(existingBlock.end);
@@ -68554,7 +68897,7 @@ function mergeAgentsMdContent(existing, incoming) {
68554
68897
  harSection = sanitizedIncoming.slice(start, end + HAR_SECTION_END.length);
68555
68898
  }
68556
68899
  }
68557
- const { content } = upsertMarkedBlock(
68900
+ const { content } = upsertMarkedBlock2(
68558
68901
  sanitizedExisting,
68559
68902
  harSection,
68560
68903
  HAR_SECTION_START,
@@ -68563,15 +68906,15 @@ function mergeAgentsMdContent(existing, incoming) {
68563
68906
  return content;
68564
68907
  }
68565
68908
  function upsertAgentsMdHarSection(repoPath, options = {}) {
68566
- const dest = path35.join(repoPath, AGENTS_MD);
68909
+ const dest = path36.join(repoPath, AGENTS_MD);
68567
68910
  const section = loadHarAgentsSectionFromTemplate();
68568
- if (!fs29.existsSync(dest)) {
68911
+ if (!fs30.existsSync(dest)) {
68569
68912
  writeFileSafe(dest, loadAgentsMdTemplate());
68570
68913
  return "created";
68571
68914
  }
68572
- const existing = fs29.readFileSync(dest, "utf8");
68915
+ const existing = fs30.readFileSync(dest, "utf8");
68573
68916
  const sanitized = stripClaudePointerFromAgentsMd(existing);
68574
- const { content, action } = upsertMarkedBlock(
68917
+ const { content, action } = upsertMarkedBlock2(
68575
68918
  sanitized,
68576
68919
  section,
68577
68920
  HAR_SECTION_START,
@@ -68594,15 +68937,15 @@ function upsertAgentsMdHarSection(repoPath, options = {}) {
68594
68937
  return action;
68595
68938
  }
68596
68939
  function migrateLegacyAgentMd(repoPath) {
68597
- const legacyPath = path35.join(repoPath, LEGACY_AGENT_MD);
68598
- const agentsPath = path35.join(repoPath, AGENTS_MD);
68599
- if (!fs29.existsSync(legacyPath)) return false;
68600
- const legacy = fs29.readFileSync(legacyPath, "utf8");
68601
- if (!fs29.existsSync(agentsPath)) {
68940
+ const legacyPath = path36.join(repoPath, LEGACY_AGENT_MD);
68941
+ const agentsPath = path36.join(repoPath, AGENTS_MD);
68942
+ if (!fs30.existsSync(legacyPath)) return false;
68943
+ const legacy = fs30.readFileSync(legacyPath, "utf8");
68944
+ if (!fs30.existsSync(agentsPath)) {
68602
68945
  writeFileSafe(agentsPath, legacy);
68603
68946
  upsertAgentsMdHarSection(repoPath);
68604
68947
  } else {
68605
- const agents = fs29.readFileSync(agentsPath, "utf8");
68948
+ const agents = fs30.readFileSync(agentsPath, "utf8");
68606
68949
  const legacyTrimmed = legacy.trim();
68607
68950
  if (legacyTrimmed.length > 0 && !agents.includes(legacyTrimmed)) {
68608
68951
  const suffix = agents.endsWith("\n") ? "" : "\n";
@@ -68612,7 +68955,7 @@ ${legacyTrimmed}
68612
68955
  }
68613
68956
  upsertAgentsMdHarSection(repoPath);
68614
68957
  }
68615
- fs29.unlinkSync(legacyPath);
68958
+ fs30.unlinkSync(legacyPath);
68616
68959
  info(`Migrated ${LEGACY_AGENT_MD} \u2192 ${AGENTS_MD} (removed legacy file)`);
68617
68960
  return true;
68618
68961
  }
@@ -68622,7 +68965,7 @@ function loadClaudeMdTemplate(projectName) {
68622
68965
  throw new Error("CLAUDE.md.template not found. Run npm run build.");
68623
68966
  }
68624
68967
  const displayName = projectName.replace(/_/g, " ");
68625
- return fs29.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
68968
+ return fs30.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
68626
68969
  }
68627
68970
  function isThinClaudePointer(content) {
68628
68971
  const trimmed = content.trim();
@@ -68636,21 +68979,21 @@ This repository uses a \`.har/\` harness. Read [AGENTS.md](./AGENTS.md) and
68636
68979
  [\`.har/README.md\`](./.har/README.md) before making changes.
68637
68980
  ${CLAUDE_HAR_SECTION_END}`;
68638
68981
  function ensureClaudeMdPointer(repoPath, options = {}) {
68639
- const dest = path35.join(repoPath, CLAUDE_MD);
68640
- const projectName = path35.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
68982
+ const dest = path36.join(repoPath, CLAUDE_MD);
68983
+ const projectName = path36.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
68641
68984
  const thin = loadClaudeMdTemplate(projectName);
68642
- if (!fs29.existsSync(dest)) {
68985
+ if (!fs30.existsSync(dest)) {
68643
68986
  writeFileSafe(dest, thin);
68644
68987
  return "created";
68645
68988
  }
68646
- const existing = fs29.readFileSync(dest, "utf8");
68989
+ const existing = fs30.readFileSync(dest, "utf8");
68647
68990
  if (isThinClaudePointer(existing) || options.force) {
68648
68991
  writeFileSafe(dest, thin);
68649
68992
  return "updated";
68650
68993
  }
68651
68994
  if (existing.includes(CLAUDE_HAR_SECTION_START) || /\[AGENTS\.md\]/.test(existing)) {
68652
68995
  if (existing.includes(CLAUDE_HAR_SECTION_START)) {
68653
- const { content: content2, action: action2 } = upsertMarkedBlock(
68996
+ const { content: content2, action: action2 } = upsertMarkedBlock2(
68654
68997
  existing,
68655
68998
  CLAUDE_POINTER_BLOCK,
68656
68999
  CLAUDE_HAR_SECTION_START,
@@ -68661,7 +69004,7 @@ function ensureClaudeMdPointer(repoPath, options = {}) {
68661
69004
  }
68662
69005
  return "skipped";
68663
69006
  }
68664
- const { content, action } = upsertMarkedBlock(
69007
+ const { content, action } = upsertMarkedBlock2(
68665
69008
  existing,
68666
69009
  CLAUDE_POINTER_BLOCK,
68667
69010
  CLAUDE_HAR_SECTION_START,
@@ -68792,19 +69135,19 @@ var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
68792
69135
  var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
68793
69136
  function readAgentMdProposal(repoPath) {
68794
69137
  const harnessDir = getHarnessDir(repoPath);
68795
- const proposalPath = path36.join(harnessDir, AGENTS_MD_PROPOSAL);
68796
- const metaPath = path36.join(harnessDir, AGENTS_MD_PROPOSAL_META);
68797
- const legacyProposalPath = path36.join(harnessDir, AGENT_MD_PROPOSAL);
68798
- const legacyMetaPath = path36.join(harnessDir, AGENT_MD_PROPOSAL_META);
68799
- const resolvedProposal = fs30.existsSync(proposalPath) ? proposalPath : fs30.existsSync(legacyProposalPath) ? legacyProposalPath : null;
69138
+ const proposalPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL);
69139
+ const metaPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL_META);
69140
+ const legacyProposalPath = path37.join(harnessDir, AGENT_MD_PROPOSAL);
69141
+ const legacyMetaPath = path37.join(harnessDir, AGENT_MD_PROPOSAL_META);
69142
+ const resolvedProposal = fs31.existsSync(proposalPath) ? proposalPath : fs31.existsSync(legacyProposalPath) ? legacyProposalPath : null;
68800
69143
  if (!resolvedProposal) return null;
68801
69144
  const resolvedMeta = resolvedProposal === proposalPath ? metaPath : legacyMetaPath;
68802
- const content = fs30.readFileSync(resolvedProposal, "utf8");
69145
+ const content = fs31.readFileSync(resolvedProposal, "utf8");
68803
69146
  let rationale = "";
68804
69147
  let createdAt = (/* @__PURE__ */ new Date()).toISOString();
68805
- if (fs30.existsSync(resolvedMeta)) {
69148
+ if (fs31.existsSync(resolvedMeta)) {
68806
69149
  try {
68807
- const meta = JSON.parse(fs30.readFileSync(resolvedMeta, "utf8"));
69150
+ const meta = JSON.parse(fs31.readFileSync(resolvedMeta, "utf8"));
68808
69151
  rationale = meta.rationale ?? "";
68809
69152
  createdAt = meta.createdAt ?? createdAt;
68810
69153
  } catch {
@@ -68821,8 +69164,8 @@ function clearAgentMdProposal(repoPath) {
68821
69164
  AGENT_MD_PROPOSAL,
68822
69165
  AGENT_MD_PROPOSAL_META
68823
69166
  ]) {
68824
- const p = path36.join(harnessDir, file);
68825
- if (fs30.existsSync(p)) fs30.unlinkSync(p);
69167
+ const p = path37.join(harnessDir, file);
69168
+ if (fs31.existsSync(p)) fs31.unlinkSync(p);
68826
69169
  }
68827
69170
  }
68828
69171
  function wouldProposalShrinkExisting(existing, proposal) {
@@ -68835,9 +69178,9 @@ function wouldProposalShrinkExisting(existing, proposal) {
68835
69178
  function applyAgentMdProposalMerge(repoPath, options = {}) {
68836
69179
  const proposal = readAgentMdProposal(repoPath);
68837
69180
  if (!proposal) return false;
68838
- const agentsMdPath = path36.join(repoPath, AGENTS_MD);
68839
- if (fs30.existsSync(agentsMdPath)) {
68840
- const existing = fs30.readFileSync(agentsMdPath, "utf8");
69181
+ const agentsMdPath = path37.join(repoPath, AGENTS_MD);
69182
+ if (fs31.existsSync(agentsMdPath)) {
69183
+ const existing = fs31.readFileSync(agentsMdPath, "utf8");
68841
69184
  if (wouldProposalShrinkExisting(existing, proposal.content)) {
68842
69185
  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.`;
68843
69186
  if (options.rejectSignificantShrink) {
@@ -68860,8 +69203,8 @@ function finalizeAgentsMdInstructionFiles(repoPath) {
68860
69203
  }
68861
69204
 
68862
69205
  // src/harness/validator.ts
68863
- var fs31 = __toESM(require("fs"));
68864
- var path37 = __toESM(require("path"));
69206
+ var fs32 = __toESM(require("fs"));
69207
+ var path38 = __toESM(require("path"));
68865
69208
  init_shell();
68866
69209
  init_manifest();
68867
69210
  init_stages();
@@ -68916,22 +69259,22 @@ var SHELL_SCRIPTS = [
68916
69259
  function validateHarness(repoPath) {
68917
69260
  const harnessDir = getHarnessDir(repoPath);
68918
69261
  const issues = [];
68919
- if (!fs31.existsSync(harnessDir)) {
69262
+ if (!fs32.existsSync(harnessDir)) {
68920
69263
  return {
68921
69264
  pass: false,
68922
69265
  issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
68923
69266
  };
68924
69267
  }
68925
69268
  for (const file of getRequiredFiles(repoPath)) {
68926
- const filePath = path37.join(harnessDir, file);
68927
- if (!fs31.existsSync(filePath)) {
69269
+ const filePath = path38.join(harnessDir, file);
69270
+ if (!fs32.existsSync(filePath)) {
68928
69271
  issues.push({ file, message: "Required file missing", severity: "error" });
68929
69272
  }
68930
69273
  }
68931
69274
  for (const script of SHELL_SCRIPTS) {
68932
- const scriptPath = path37.join(harnessDir, script);
68933
- if (!fs31.existsSync(scriptPath)) continue;
68934
- const stat = fs31.statSync(scriptPath);
69275
+ const scriptPath = path38.join(harnessDir, script);
69276
+ if (!fs32.existsSync(scriptPath)) continue;
69277
+ const stat = fs32.statSync(scriptPath);
68935
69278
  if (!(stat.mode & 73)) {
68936
69279
  issues.push({ file: script, message: "Script is not executable", severity: "warning" });
68937
69280
  }
@@ -68946,9 +69289,9 @@ function validateHarness(repoPath) {
68946
69289
  }
68947
69290
  const manifest = readManifest(repoPath);
68948
69291
  const profile = manifest?.profile ?? "default";
68949
- const ecosystemPath = path37.join(harnessDir, "ecosystem.agent.template.cjs");
68950
- if (profile !== "cli" && fs31.existsSync(ecosystemPath)) {
68951
- const content = fs31.readFileSync(ecosystemPath, "utf8");
69292
+ const ecosystemPath = path38.join(harnessDir, "ecosystem.agent.template.cjs");
69293
+ if (profile !== "cli" && fs32.existsSync(ecosystemPath)) {
69294
+ const content = fs32.readFileSync(ecosystemPath, "utf8");
68952
69295
  if (!content.includes("module.exports")) {
68953
69296
  issues.push({
68954
69297
  file: "ecosystem.agent.template.cjs",
@@ -68957,9 +69300,9 @@ function validateHarness(repoPath) {
68957
69300
  });
68958
69301
  }
68959
69302
  }
68960
- const harnessEnvPath = path37.join(harnessDir, "harness.env");
68961
- if (fs31.existsSync(harnessEnvPath)) {
68962
- const content = fs31.readFileSync(harnessEnvPath, "utf8");
69303
+ const harnessEnvPath = path38.join(harnessDir, "harness.env");
69304
+ if (fs32.existsSync(harnessEnvPath)) {
69305
+ const content = fs32.readFileSync(harnessEnvPath, "utf8");
68963
69306
  if (content.includes("TODO: set migrate command")) {
68964
69307
  issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
68965
69308
  }
@@ -68967,17 +69310,17 @@ function validateHarness(repoPath) {
68967
69310
  issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
68968
69311
  }
68969
69312
  }
68970
- const verifyPath = path37.join(harnessDir, "verify.sh");
68971
- if (fs31.existsSync(verifyPath)) {
68972
- const content = fs31.readFileSync(verifyPath, "utf8");
69313
+ const verifyPath = path38.join(harnessDir, "verify.sh");
69314
+ if (fs32.existsSync(verifyPath)) {
69315
+ const content = fs32.readFileSync(verifyPath, "utf8");
68973
69316
  if (content.includes("echo 'TODO:")) {
68974
69317
  issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
68975
69318
  }
68976
69319
  }
68977
- const stagesPath = path37.join(harnessDir, "stages.json");
68978
- if (fs31.existsSync(stagesPath)) {
69320
+ const stagesPath = path38.join(harnessDir, "stages.json");
69321
+ if (fs32.existsSync(stagesPath)) {
68979
69322
  try {
68980
- JSON.parse(fs31.readFileSync(stagesPath, "utf8"));
69323
+ JSON.parse(fs32.readFileSync(stagesPath, "utf8"));
68981
69324
  const registry2 = readStageRegistry(repoPath);
68982
69325
  if (registry2.stages.length === 0) {
68983
69326
  issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
@@ -68992,9 +69335,9 @@ function validateHarness(repoPath) {
68992
69335
  } else {
68993
69336
  issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
68994
69337
  }
68995
- const readmePath = path37.join(harnessDir, "README.md");
68996
- if (fs31.existsSync(readmePath)) {
68997
- const content = fs31.readFileSync(readmePath, "utf8");
69338
+ const readmePath = path38.join(harnessDir, "README.md");
69339
+ if (fs32.existsSync(readmePath)) {
69340
+ const content = fs32.readFileSync(readmePath, "utf8");
68998
69341
  if (!content.includes(".har")) {
68999
69342
  issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
69000
69343
  }
@@ -69008,8 +69351,8 @@ function validateHarness(repoPath) {
69008
69351
  async function smokeTestHarness(repoPath) {
69009
69352
  const harnessDir = getHarnessDir(repoPath);
69010
69353
  const issues = [];
69011
- const setupScript = path37.join(harnessDir, "setup-infra.sh");
69012
- if (fs31.existsSync(setupScript)) {
69354
+ const setupScript = path38.join(harnessDir, "setup-infra.sh");
69355
+ if (fs32.existsSync(setupScript)) {
69013
69356
  const result = run(`bash "${setupScript}"`, { cwd: repoPath });
69014
69357
  if (result.code !== 0) {
69015
69358
  issues.push({
@@ -69024,8 +69367,8 @@ async function smokeTestHarness(repoPath) {
69024
69367
  }
69025
69368
 
69026
69369
  // src/harness/drift.ts
69027
- var fs32 = __toESM(require("fs"));
69028
- var path38 = __toESM(require("path"));
69370
+ var fs33 = __toESM(require("fs"));
69371
+ var path39 = __toESM(require("path"));
69029
69372
  init_env();
69030
69373
  init_manifest();
69031
69374
  init_stages();
@@ -69096,16 +69439,16 @@ function substituteProjectName(content, projectName) {
69096
69439
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
69097
69440
  }
69098
69441
  function listBoilerplateFiles(boilerplateDir) {
69099
- if (!fs32.existsSync(boilerplateDir)) return [];
69100
- return fs32.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
69442
+ if (!fs33.existsSync(boilerplateDir)) return [];
69443
+ return fs33.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
69101
69444
  }
69102
69445
  function compareHarnessToTemplate(repoPath) {
69103
- const resolved = path38.resolve(repoPath);
69446
+ const resolved = path39.resolve(repoPath);
69104
69447
  const manifest = readManifest(resolved);
69105
69448
  const profile = manifest?.profile ?? "default";
69106
69449
  const harnessDir = getHarnessDir(resolved);
69107
- const projectName = path38.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
69108
- const boilerplateDir = path38.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
69450
+ const projectName = path39.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
69451
+ const boilerplateDir = path39.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
69109
69452
  const templateFiles = listBoilerplateFiles(boilerplateDir);
69110
69453
  const missing = [];
69111
69454
  const checksumMismatch = [];
@@ -69113,17 +69456,17 @@ function compareHarnessToTemplate(repoPath) {
69113
69456
  const unchanged = [];
69114
69457
  for (const file of templateFiles) {
69115
69458
  const harnessFile = harnessFileForTemplate(file);
69116
- const templatePath = path38.join(boilerplateDir, file);
69117
- const harnessPath = path38.join(harnessDir, harnessFile);
69118
- let templateContent = fs32.readFileSync(templatePath, "utf8");
69459
+ const templatePath = path39.join(boilerplateDir, file);
69460
+ const harnessPath = path39.join(harnessDir, harnessFile);
69461
+ let templateContent = fs33.readFileSync(templatePath, "utf8");
69119
69462
  if (file === "harness.env") {
69120
69463
  templateContent = substituteProjectName(templateContent, projectName);
69121
69464
  }
69122
- if (!fs32.existsSync(harnessPath)) {
69465
+ if (!fs33.existsSync(harnessPath)) {
69123
69466
  missing.push(harnessFile);
69124
69467
  continue;
69125
69468
  }
69126
- const harnessChecksum = computeFileChecksum(fs32.readFileSync(harnessPath, "utf8"));
69469
+ const harnessChecksum = computeFileChecksum(fs33.readFileSync(harnessPath, "utf8"));
69127
69470
  const templateChecksum = computeFileChecksum(templateContent);
69128
69471
  if (harnessChecksum === templateChecksum) {
69129
69472
  unchanged.push(harnessFile);
@@ -69131,10 +69474,10 @@ function compareHarnessToTemplate(repoPath) {
69131
69474
  checksumMismatch.push(harnessFile);
69132
69475
  }
69133
69476
  }
69134
- if (fs32.existsSync(harnessDir)) {
69135
- for (const file of fs32.readdirSync(harnessDir)) {
69136
- const full = path38.join(harnessDir, file);
69137
- if (!fs32.statSync(full).isFile()) continue;
69477
+ if (fs33.existsSync(harnessDir)) {
69478
+ for (const file of fs33.readdirSync(harnessDir)) {
69479
+ const full = path39.join(harnessDir, file);
69480
+ if (!fs33.statSync(full).isFile()) continue;
69138
69481
  if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
69139
69482
  if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
69140
69483
  extra.push(file);
@@ -69165,29 +69508,29 @@ function compareHarnessToTemplate(repoPath) {
69165
69508
  }
69166
69509
 
69167
69510
  // src/harness/maintain-bundle.ts
69168
- var fs36 = __toESM(require("fs"));
69169
- var os14 = __toESM(require("os"));
69170
- var path41 = __toESM(require("path"));
69511
+ var fs37 = __toESM(require("fs"));
69512
+ var os15 = __toESM(require("os"));
69513
+ var path42 = __toESM(require("path"));
69171
69514
  init_file_ops();
69172
69515
  init_shell();
69173
69516
  init_manifest();
69174
69517
 
69175
69518
  // src/harness/plugin-drift.ts
69176
- var fs35 = __toESM(require("fs"));
69177
- var path40 = __toESM(require("path"));
69519
+ var fs36 = __toESM(require("fs"));
69520
+ var path41 = __toESM(require("path"));
69178
69521
  init_manifest();
69179
69522
 
69180
69523
  // src/harness/plugins.ts
69181
- var fs34 = __toESM(require("fs"));
69182
- var path39 = __toESM(require("path"));
69524
+ var fs35 = __toESM(require("fs"));
69525
+ var path40 = __toESM(require("path"));
69183
69526
  init_zod();
69184
69527
  init_logging();
69185
69528
 
69186
69529
  // src/harness/parser.ts
69187
- var fs33 = __toESM(require("fs"));
69530
+ var fs34 = __toESM(require("fs"));
69188
69531
  init_manifest();
69189
69532
  function harnessExists(repoPath) {
69190
- return fs33.existsSync(`${repoPath}/.har/setup-infra.sh`);
69533
+ return fs34.existsSync(`${repoPath}/.har/setup-infra.sh`);
69191
69534
  }
69192
69535
 
69193
69536
  // src/harness/plugins.ts
@@ -69212,16 +69555,16 @@ var PluginManifestSchema = external_exports.object({
69212
69555
  docsPath: external_exports.string().min(1)
69213
69556
  });
69214
69557
  function resolvePluginDir(pluginId) {
69215
- const dir = path39.join(resolveTemplatesDir(), "plugins", pluginId);
69216
- if (!fs34.existsSync(dir)) {
69558
+ const dir = path40.join(resolveTemplatesDir(), "plugins", pluginId);
69559
+ if (!fs35.existsSync(dir)) {
69217
69560
  throw new Error(`Plugin not found: ${pluginId}. Run npm run build.`);
69218
69561
  }
69219
69562
  return dir;
69220
69563
  }
69221
69564
  function readPluginManifest(pluginId) {
69222
- const manifestPath = path39.join(resolvePluginDir(pluginId), "template.manifest.json");
69565
+ const manifestPath = path40.join(resolvePluginDir(pluginId), "template.manifest.json");
69223
69566
  const parsed = PluginManifestSchema.safeParse(
69224
- JSON.parse(fs34.readFileSync(manifestPath, "utf8"))
69567
+ JSON.parse(fs35.readFileSync(manifestPath, "utf8"))
69225
69568
  );
69226
69569
  if (!parsed.success) {
69227
69570
  throw new Error(`Invalid plugin manifest for ${pluginId}: ${parsed.error.message}`);
@@ -69232,40 +69575,40 @@ function readPluginManifest(pluginId) {
69232
69575
  return parsed.data;
69233
69576
  }
69234
69577
  function ensureParentDir(filePath) {
69235
- const parent = path39.dirname(filePath);
69236
- if (!fs34.existsSync(parent)) {
69237
- fs34.mkdirSync(parent, { recursive: true });
69578
+ const parent = path40.dirname(filePath);
69579
+ if (!fs35.existsSync(parent)) {
69580
+ fs35.mkdirSync(parent, { recursive: true });
69238
69581
  }
69239
69582
  }
69240
69583
  function copyPluginFile(pluginDir, file, repoPath, force) {
69241
- const srcPath = path39.join(pluginDir, file.src);
69242
- const destPath = path39.join(repoPath, file.dest);
69243
- if (!fs34.existsSync(srcPath)) {
69584
+ const srcPath = path40.join(pluginDir, file.src);
69585
+ const destPath = path40.join(repoPath, file.dest);
69586
+ if (!fs35.existsSync(srcPath)) {
69244
69587
  throw new Error(`Plugin file missing: ${file.src}`);
69245
69588
  }
69246
- if (fs34.existsSync(destPath) && !force) {
69589
+ if (fs35.existsSync(destPath) && !force) {
69247
69590
  throw new Error(
69248
69591
  `File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
69249
69592
  );
69250
69593
  }
69251
69594
  ensureParentDir(destPath);
69252
- fs34.copyFileSync(srcPath, destPath);
69595
+ fs35.copyFileSync(srcPath, destPath);
69253
69596
  if (file.executable) {
69254
- fs34.chmodSync(destPath, 493);
69597
+ fs35.chmodSync(destPath, 493);
69255
69598
  }
69256
69599
  return { written: true, path: file.dest };
69257
69600
  }
69258
69601
  function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
69259
- const packagePath = path39.join(repoPath, "package.json");
69260
- if (!fs34.existsSync(packagePath)) {
69602
+ const packagePath = path40.join(repoPath, "package.json");
69603
+ if (!fs35.existsSync(packagePath)) {
69261
69604
  throw new Error("No package.json in repo root. Add one before applying this plugin.");
69262
69605
  }
69263
- const fragmentPath = path39.join(pluginDir, fragmentRelPath);
69264
- if (!fs34.existsSync(fragmentPath)) {
69606
+ const fragmentPath = path40.join(pluginDir, fragmentRelPath);
69607
+ if (!fs35.existsSync(fragmentPath)) {
69265
69608
  throw new Error(`Package fragment missing: ${fragmentRelPath}`);
69266
69609
  }
69267
- const pkg = JSON.parse(fs34.readFileSync(packagePath, "utf8"));
69268
- const fragment = JSON.parse(fs34.readFileSync(fragmentPath, "utf8"));
69610
+ const pkg = JSON.parse(fs35.readFileSync(packagePath, "utf8"));
69611
+ const fragment = JSON.parse(fs35.readFileSync(fragmentPath, "utf8"));
69269
69612
  for (const section of ["scripts", "devDependencies"]) {
69270
69613
  const existing = pkg[section] ?? {};
69271
69614
  const incoming = fragment[section] ?? {};
@@ -69278,7 +69621,7 @@ function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
69278
69621
  }
69279
69622
  pkg[section] = existing;
69280
69623
  }
69281
- fs34.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
69624
+ fs35.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
69282
69625
  }
69283
69626
  function patchStageRegistry(repoPath, manifest, force) {
69284
69627
  const registry2 = readStageRegistry(repoPath);
@@ -69318,8 +69661,8 @@ function assertHarnessPresent(repoPath) {
69318
69661
  }
69319
69662
  function assertStageNotPresent(repoPath, stageId, force) {
69320
69663
  if (force) return;
69321
- const scriptPath = path39.join(repoPath, ".har", "stages", `${stageId}.sh`);
69322
- if (fs34.existsSync(scriptPath)) {
69664
+ const scriptPath = path40.join(repoPath, ".har", "stages", `${stageId}.sh`);
69665
+ if (fs35.existsSync(scriptPath)) {
69323
69666
  throw new Error(
69324
69667
  `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
69325
69668
  );
@@ -69332,7 +69675,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
69332
69675
  }
69333
69676
  }
69334
69677
  function applyPlugin(repoPath, pluginId, options = {}) {
69335
- const resolved = path39.resolve(repoPath);
69678
+ const resolved = path40.resolve(repoPath);
69336
69679
  const force = options.force ?? false;
69337
69680
  const warnings = [];
69338
69681
  const filesWritten = [];
@@ -69351,7 +69694,7 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69351
69694
  if (file.skipFlag === "skipCi" && options.skipCi) {
69352
69695
  continue;
69353
69696
  }
69354
- if (fs34.existsSync(path39.join(resolved, file.dest)) && !force) {
69697
+ if (fs35.existsSync(path40.join(resolved, file.dest)) && !force) {
69355
69698
  warnings.push(`Skipped optional file (exists): ${file.dest}`);
69356
69699
  continue;
69357
69700
  }
@@ -69386,18 +69729,18 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69386
69729
  };
69387
69730
  }
69388
69731
  function listPluginIds() {
69389
- const root = path39.join(resolveTemplatesDir(), "plugins");
69390
- if (!fs34.existsSync(root)) return [...PLUGIN_IDS];
69391
- return fs34.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => PLUGIN_IDS.includes(name));
69732
+ const root = path40.join(resolveTemplatesDir(), "plugins");
69733
+ if (!fs35.existsSync(root)) return [...PLUGIN_IDS];
69734
+ return fs35.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => PLUGIN_IDS.includes(name));
69392
69735
  }
69393
69736
 
69394
69737
  // src/harness/plugin-drift.ts
69395
69738
  init_stages();
69396
69739
  var PACKAGE_SECTIONS = ["scripts", "devDependencies"];
69397
69740
  function detectInstalledPlugins(repoPath) {
69398
- const resolved = path40.resolve(repoPath);
69399
- const registryPath = path40.join(resolved, ".har", "stages.json");
69400
- if (!fs35.existsSync(registryPath)) return [];
69741
+ const resolved = path41.resolve(repoPath);
69742
+ const registryPath = path41.join(resolved, ".har", "stages.json");
69743
+ if (!fs36.existsSync(registryPath)) return [];
69401
69744
  const registry2 = readStageRegistry(resolved);
69402
69745
  const stageIds = new Set(registry2.stages.map((stage) => stage.id));
69403
69746
  const installed = [];
@@ -69410,17 +69753,17 @@ function detectInstalledPlugins(repoPath) {
69410
69753
  return installed;
69411
69754
  }
69412
69755
  function pluginTemplatePath(pluginId, srcRel) {
69413
- return path40.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
69756
+ return path41.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
69414
69757
  }
69415
69758
  function readBundledPluginFile(pluginId, srcRel) {
69416
- return fs35.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
69759
+ return fs36.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
69417
69760
  }
69418
69761
  function listPluginDestPaths(pluginId, repoPath) {
69419
69762
  const manifest = readPluginManifest(pluginId);
69420
69763
  const paths = manifest.files.map((file) => file.dest);
69421
69764
  if (manifest.optionalFiles) {
69422
69765
  for (const file of manifest.optionalFiles) {
69423
- if (fs35.existsSync(path40.join(repoPath, file.dest))) {
69766
+ if (fs36.existsSync(path41.join(repoPath, file.dest))) {
69424
69767
  paths.push(file.dest);
69425
69768
  }
69426
69769
  }
@@ -69479,9 +69822,9 @@ function readTemplatePluginFile(pluginId, dest) {
69479
69822
  return null;
69480
69823
  }
69481
69824
  function readInstalledPluginFile(repoPath, pluginId, dest) {
69482
- const installedPath = path40.join(path40.resolve(repoPath), dest);
69483
- if (!fs35.existsSync(installedPath)) return null;
69484
- const content = fs35.readFileSync(installedPath, "utf8");
69825
+ const installedPath = path41.join(path41.resolve(repoPath), dest);
69826
+ if (!fs36.existsSync(installedPath)) return null;
69827
+ const content = fs36.readFileSync(installedPath, "utf8");
69485
69828
  const manifest = readPluginManifest(pluginId);
69486
69829
  const fragmentRel = manifest.merge?.[dest];
69487
69830
  if (fragmentRel) {
@@ -69500,7 +69843,7 @@ function pluginActionHint(pluginId, dest, kind) {
69500
69843
  }
69501
69844
  function comparePluginToTemplate(repoPath, pluginId) {
69502
69845
  const manifest = readPluginManifest(pluginId);
69503
- const resolved = path40.resolve(repoPath);
69846
+ const resolved = path41.resolve(repoPath);
69504
69847
  const destPaths = listPluginDestPaths(pluginId, resolved);
69505
69848
  const missing = [];
69506
69849
  const checksumMismatch = [];
@@ -69515,8 +69858,8 @@ function comparePluginToTemplate(repoPath, pluginId) {
69515
69858
  }
69516
69859
  if (dest === "package.json") {
69517
69860
  const fragmentRel = manifest.merge?.[dest];
69518
- const installedPath = path40.join(resolved, dest);
69519
- const installedRaw = fs35.readFileSync(installedPath, "utf8");
69861
+ const installedPath = path41.join(resolved, dest);
69862
+ const installedRaw = fs36.readFileSync(installedPath, "utf8");
69520
69863
  if (fragmentRel && pluginKeysMatch(installedRaw, readBundledPluginFile(pluginId, fragmentRel))) {
69521
69864
  unchanged.push(dest);
69522
69865
  } else if (computeFileChecksum(installedContent) === computeFileChecksum(templateContent)) {
@@ -69585,14 +69928,14 @@ function substituteProjectName2(content, projectName) {
69585
69928
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
69586
69929
  }
69587
69930
  function projectNameFromRepo(repoPath) {
69588
- return path41.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69931
+ return path42.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69589
69932
  }
69590
69933
  function boilerplateDirForProfile(profile) {
69591
- return path41.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
69934
+ return path42.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
69592
69935
  }
69593
69936
  function readBundledTemplateContent(repoPath, profile, file) {
69594
- const templatePath = path41.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
69595
- let content = fs36.readFileSync(templatePath, "utf8");
69937
+ const templatePath = path42.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
69938
+ let content = fs37.readFileSync(templatePath, "utf8");
69596
69939
  if (file === "harness.env") {
69597
69940
  content = substituteProjectName2(content, projectNameFromRepo(repoPath));
69598
69941
  }
@@ -69623,12 +69966,12 @@ function actionHint(file, kind) {
69623
69966
  return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
69624
69967
  }
69625
69968
  function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
69626
- const tmpDir = fs36.mkdtempSync(path41.join(os14.tmpdir(), "har-maintain-diff-"));
69969
+ const tmpDir = fs37.mkdtempSync(path42.join(os15.tmpdir(), "har-maintain-diff-"));
69627
69970
  try {
69628
- const installedPath = path41.join(tmpDir, "installed");
69629
- const templatePath = path41.join(tmpDir, "template");
69630
- fs36.writeFileSync(installedPath, installedContent);
69631
- fs36.writeFileSync(templatePath, templateContent);
69971
+ const installedPath = path42.join(tmpDir, "installed");
69972
+ const templatePath = path42.join(tmpDir, "template");
69973
+ fs37.writeFileSync(installedPath, installedContent);
69974
+ fs37.writeFileSync(templatePath, templateContent);
69632
69975
  const result = run(`diff -u "${installedPath}" "${templatePath}"`);
69633
69976
  const body = result.stdout.trim();
69634
69977
  if (!body) return "";
@@ -69637,7 +69980,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
69637
69980
  ${body.split("\n").slice(2).join("\n")}
69638
69981
  `;
69639
69982
  } finally {
69640
- fs36.rmSync(tmpDir, { recursive: true, force: true });
69983
+ fs37.rmSync(tmpDir, { recursive: true, force: true });
69641
69984
  }
69642
69985
  }
69643
69986
  function buildActions(repoPath, profile, drift) {
@@ -69652,7 +69995,7 @@ function buildActions(repoPath, profile, drift) {
69652
69995
  });
69653
69996
  }
69654
69997
  for (const file of drift.checksumMismatch) {
69655
- const installedPath = path41.join(harnessDir, file);
69998
+ const installedPath = path42.join(harnessDir, file);
69656
69999
  const installedRel = `maintain/installed/${file}`;
69657
70000
  const templateRel = `maintain/templates/${file}`;
69658
70001
  const diffRel = `maintain/diffs/${file}.diff`;
@@ -69660,7 +70003,7 @@ function buildActions(repoPath, profile, drift) {
69660
70003
  file,
69661
70004
  kind: "drift",
69662
70005
  template: templateRel,
69663
- installed: fs36.existsSync(installedPath) ? installedRel : void 0,
70006
+ installed: fs37.existsSync(installedPath) ? installedRel : void 0,
69664
70007
  diff: diffRel,
69665
70008
  hint: actionHint(file, "drift")
69666
70009
  });
@@ -69812,28 +70155,28 @@ function buildReadme(report) {
69812
70155
  }
69813
70156
  function writeBundleArtifacts(repoPath, profile, drift, report) {
69814
70157
  const harnessDir = getHarnessDir(repoPath);
69815
- const bundleDir = path41.join(harnessDir, MAINTAIN_DIR);
69816
- if (fs36.existsSync(bundleDir)) {
69817
- fs36.rmSync(bundleDir, { recursive: true, force: true });
70158
+ const bundleDir = path42.join(harnessDir, MAINTAIN_DIR);
70159
+ if (fs37.existsSync(bundleDir)) {
70160
+ fs37.rmSync(bundleDir, { recursive: true, force: true });
69818
70161
  }
69819
70162
  const dirs = [
69820
70163
  bundleDir,
69821
- path41.join(bundleDir, "templates"),
69822
- path41.join(bundleDir, "installed"),
69823
- path41.join(bundleDir, "diffs"),
69824
- path41.join(bundleDir, "stale")
70164
+ path42.join(bundleDir, "templates"),
70165
+ path42.join(bundleDir, "installed"),
70166
+ path42.join(bundleDir, "diffs"),
70167
+ path42.join(bundleDir, "stale")
69825
70168
  ];
69826
70169
  for (const dir of dirs) {
69827
- fs36.mkdirSync(dir, { recursive: true });
70170
+ fs37.mkdirSync(dir, { recursive: true });
69828
70171
  }
69829
70172
  const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
69830
70173
  for (const file of affectedFiles) {
69831
70174
  const templateContent = readBundledTemplateContent(repoPath, profile, file);
69832
- writeFileSafe(path41.join(bundleDir, "templates", file), templateContent);
69833
- const harnessPath = path41.join(harnessDir, file);
69834
- if (fs36.existsSync(harnessPath)) {
69835
- const installedContent = fs36.readFileSync(harnessPath, "utf8");
69836
- writeFileSafe(path41.join(bundleDir, "installed", file), installedContent);
70175
+ writeFileSafe(path42.join(bundleDir, "templates", file), templateContent);
70176
+ const harnessPath = path42.join(harnessDir, file);
70177
+ if (fs37.existsSync(harnessPath)) {
70178
+ const installedContent = fs37.readFileSync(harnessPath, "utf8");
70179
+ writeFileSafe(path42.join(bundleDir, "installed", file), installedContent);
69837
70180
  const diff = createUnifiedDiff(
69838
70181
  installedContent,
69839
70182
  templateContent,
@@ -69841,14 +70184,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
69841
70184
  `templates/${file}`
69842
70185
  );
69843
70186
  if (diff) {
69844
- writeFileSafe(path41.join(bundleDir, "diffs", `${file}.diff`), diff);
70187
+ writeFileSafe(path42.join(bundleDir, "diffs", `${file}.diff`), diff);
69845
70188
  }
69846
70189
  }
69847
70190
  }
69848
- writeFileSafe(path41.join(bundleDir, "README.md"), buildReadme(report));
69849
- writeFileSafe(path41.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
70191
+ writeFileSafe(path42.join(bundleDir, "README.md"), buildReadme(report));
70192
+ writeFileSafe(path42.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
69850
70193
  writeFileSafe(
69851
- path41.join(bundleDir, "validation.json"),
70194
+ path42.join(bundleDir, "validation.json"),
69852
70195
  JSON.stringify(report.validation, null, 2) + "\n"
69853
70196
  );
69854
70197
  if (report.stale.length > 0) {
@@ -69860,34 +70203,34 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
69860
70203
  ...report.stale.map((s) => `- **${s.file}** \u2014 ${s.hint}`),
69861
70204
  ""
69862
70205
  ];
69863
- writeFileSafe(path41.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
70206
+ writeFileSafe(path42.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
69864
70207
  }
69865
70208
  return bundleDir;
69866
70209
  }
69867
70210
  function ensureParentDir2(filePath) {
69868
- const parent = path41.dirname(filePath);
69869
- if (!fs36.existsSync(parent)) {
69870
- fs36.mkdirSync(parent, { recursive: true });
70211
+ const parent = path42.dirname(filePath);
70212
+ if (!fs37.existsSync(parent)) {
70213
+ fs37.mkdirSync(parent, { recursive: true });
69871
70214
  }
69872
70215
  }
69873
70216
  function writePluginBundleArtifacts(repoPath, bundleDir, report) {
69874
70217
  if (report.pluginActions.length === 0) return;
69875
70218
  for (const action of report.pluginActions) {
69876
- const pluginRoot = path41.join(bundleDir, "plugins", action.pluginId);
69877
- const templatesDir = path41.join(pluginRoot, "templates");
69878
- const installedDir = path41.join(pluginRoot, "installed");
69879
- const diffsDir = path41.join(pluginRoot, "diffs");
70219
+ const pluginRoot = path42.join(bundleDir, "plugins", action.pluginId);
70220
+ const templatesDir = path42.join(pluginRoot, "templates");
70221
+ const installedDir = path42.join(pluginRoot, "installed");
70222
+ const diffsDir = path42.join(pluginRoot, "diffs");
69880
70223
  for (const dir of [templatesDir, installedDir, diffsDir]) {
69881
- fs36.mkdirSync(dir, { recursive: true });
70224
+ fs37.mkdirSync(dir, { recursive: true });
69882
70225
  }
69883
70226
  const templateContent = readTemplatePluginFile(action.pluginId, action.file);
69884
70227
  if (templateContent === null) continue;
69885
- const templatePath = path41.join(templatesDir, action.file);
70228
+ const templatePath = path42.join(templatesDir, action.file);
69886
70229
  ensureParentDir2(templatePath);
69887
70230
  writeFileSafe(templatePath, templateContent);
69888
70231
  const installedContent = readInstalledPluginFile(repoPath, action.pluginId, action.file);
69889
70232
  if (installedContent !== null) {
69890
- const installedPath = path41.join(installedDir, action.file);
70233
+ const installedPath = path42.join(installedDir, action.file);
69891
70234
  ensureParentDir2(installedPath);
69892
70235
  writeFileSafe(installedPath, installedContent);
69893
70236
  const diff = createUnifiedDiff(
@@ -69897,7 +70240,7 @@ function writePluginBundleArtifacts(repoPath, bundleDir, report) {
69897
70240
  `templates/${action.file}`
69898
70241
  );
69899
70242
  if (diff) {
69900
- const diffPath = path41.join(diffsDir, `${action.file}.diff`);
70243
+ const diffPath = path42.join(diffsDir, `${action.file}.diff`);
69901
70244
  ensureParentDir2(diffPath);
69902
70245
  writeFileSafe(diffPath, diff);
69903
70246
  }
@@ -69933,9 +70276,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
69933
70276
  return { bundleDir, report };
69934
70277
  }
69935
70278
  function removeMaintainBundle(repoPath) {
69936
- const bundleDir = path41.join(getHarnessDir(repoPath), MAINTAIN_DIR);
69937
- if (fs36.existsSync(bundleDir)) {
69938
- fs36.rmSync(bundleDir, { recursive: true, force: true });
70279
+ const bundleDir = path42.join(getHarnessDir(repoPath), MAINTAIN_DIR);
70280
+ if (fs37.existsSync(bundleDir)) {
70281
+ fs37.rmSync(bundleDir, { recursive: true, force: true });
69939
70282
  }
69940
70283
  }
69941
70284
  function formatMaintainBundlePromptSection(report) {
@@ -70024,11 +70367,11 @@ init_manifest();
70024
70367
  init_stages();
70025
70368
  function listHarnessScripts(repoPath) {
70026
70369
  const harnessDir = getHarnessDir(repoPath);
70027
- if (!fs37.existsSync(harnessDir)) return [];
70028
- return fs37.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
70370
+ if (!fs38.existsSync(harnessDir)) return [];
70371
+ return fs38.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
70029
70372
  }
70030
70373
  function describeProject(repoPath) {
70031
- const resolved = path42.resolve(repoPath);
70374
+ const resolved = path43.resolve(repoPath);
70032
70375
  const manifest = readManifest(resolved);
70033
70376
  const present = harnessExists(resolved);
70034
70377
  return {
@@ -70048,8 +70391,8 @@ function describeProject(repoPath) {
70048
70391
  };
70049
70392
  }
70050
70393
  async function initHarness(options) {
70051
- const repoPath = path42.resolve(options.repoPath);
70052
- if (!fs37.existsSync(repoPath)) {
70394
+ const repoPath = path43.resolve(options.repoPath);
70395
+ if (!fs38.existsSync(repoPath)) {
70053
70396
  throw new Error(`Path not found: ${repoPath}`);
70054
70397
  }
70055
70398
  const scaffold = scaffoldHarnessBoilerplate(repoPath, {
@@ -70069,9 +70412,9 @@ async function initHarness(options) {
70069
70412
  };
70070
70413
  }
70071
70414
  async function maintainHarness(options) {
70072
- const repoPath = path42.resolve(options.repoPath);
70415
+ const repoPath = path43.resolve(options.repoPath);
70073
70416
  const harnessDir = getHarnessDir(repoPath);
70074
- if (!fs37.existsSync(harnessDir)) {
70417
+ if (!fs38.existsSync(harnessDir)) {
70075
70418
  throw new Error('No .har/ found. Run "har env init" first.');
70076
70419
  }
70077
70420
  const drift = compareHarnessToTemplate(repoPath);
@@ -70106,13 +70449,13 @@ function addPlugin(repoPath, pluginId, options = {}) {
70106
70449
  }
70107
70450
 
70108
70451
  // src/harness/custom-stage.ts
70109
- var fs38 = __toESM(require("fs"));
70110
- var path43 = __toESM(require("path"));
70452
+ var fs39 = __toESM(require("fs"));
70453
+ var path44 = __toESM(require("path"));
70111
70454
  init_schema2();
70112
70455
  init_stages();
70113
70456
  var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
70114
70457
  function addCustomStage(repoPath, options) {
70115
- const resolved = path43.resolve(repoPath);
70458
+ const resolved = path44.resolve(repoPath);
70116
70459
  if (!harnessExists(resolved)) {
70117
70460
  throw new Error('No .har/ harness found. Run "har env init" first.');
70118
70461
  }
@@ -70149,20 +70492,20 @@ function addCustomStage(repoPath, options) {
70149
70492
  if (options.command) {
70150
70493
  stage.command = options.command;
70151
70494
  } else {
70152
- const scriptRel = path43.join("stages", `${id}.sh`);
70153
- const scriptAbs = path43.join(resolved, ".har", scriptRel);
70154
- if (fs38.existsSync(scriptAbs) && !options.force) {
70495
+ const scriptRel = path44.join("stages", `${id}.sh`);
70496
+ const scriptAbs = path44.join(resolved, ".har", scriptRel);
70497
+ if (fs39.existsSync(scriptAbs) && !options.force) {
70155
70498
  throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
70156
70499
  }
70157
- const skeletonPath = path43.join(
70500
+ const skeletonPath = path44.join(
70158
70501
  resolveTemplatesDir(),
70159
70502
  "plugins",
70160
70503
  "custom-stage-skeleton.sh"
70161
70504
  );
70162
- const skeleton = fs38.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind).replace(/__STAGE_DESCRIPTION__/g, stage.description);
70163
- fs38.mkdirSync(path43.dirname(scriptAbs), { recursive: true });
70164
- fs38.writeFileSync(scriptAbs, skeleton);
70165
- fs38.chmodSync(scriptAbs, 493);
70505
+ const skeleton = fs39.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind).replace(/__STAGE_DESCRIPTION__/g, stage.description);
70506
+ fs39.mkdirSync(path44.dirname(scriptAbs), { recursive: true });
70507
+ fs39.writeFileSync(scriptAbs, skeleton);
70508
+ fs39.chmodSync(scriptAbs, 493);
70166
70509
  filesWritten.push(`.har/${scriptRel}`);
70167
70510
  stage.script = scriptRel;
70168
70511
  stage.artifacts = [
@@ -70210,18 +70553,18 @@ function addCustomStage(repoPath, options) {
70210
70553
  }
70211
70554
 
70212
70555
  // src/harness/adaptation-prompt.ts
70213
- var fs39 = __toESM(require("fs"));
70214
- var path44 = __toESM(require("path"));
70556
+ var fs40 = __toESM(require("fs"));
70557
+ var path45 = __toESM(require("path"));
70215
70558
  var readline5 = __toESM(require("readline"));
70216
70559
 
70217
70560
  // src/utils/clipboard.ts
70218
- var import_child_process9 = require("child_process");
70561
+ var import_child_process10 = require("child_process");
70219
70562
  function commandExists2(command2) {
70220
- const probe = process.platform === "win32" ? (0, import_child_process9.spawnSync)("where", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }) : (0, import_child_process9.spawnSync)("which", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] });
70563
+ 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"] });
70221
70564
  return probe.status === 0;
70222
70565
  }
70223
70566
  function tryPipeCommand(command2, args, text) {
70224
- const result = (0, import_child_process9.spawnSync)(command2, args, {
70567
+ const result = (0, import_child_process10.spawnSync)(command2, args, {
70225
70568
  input: text,
70226
70569
  encoding: "utf8",
70227
70570
  stdio: ["pipe", "ignore", "ignore"]
@@ -70241,7 +70584,7 @@ function copyViaPlatformTools(text) {
70241
70584
  }
70242
70585
  if (process.platform === "win32") {
70243
70586
  if (commandExists2("powershell")) {
70244
- const result = (0, import_child_process9.spawnSync)(
70587
+ const result = (0, import_child_process10.spawnSync)(
70245
70588
  "powershell",
70246
70589
  [
70247
70590
  "-NoProfile",
@@ -70292,16 +70635,16 @@ init_logging();
70292
70635
  init_manifest();
70293
70636
  var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
70294
70637
  var PROFILE_HINTS = {
70295
- default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared. Install the Semgrep plugin (har env add-plugin semgrep) when the repo needs a SAST verification stage.",
70296
- cli: "CLI/library profile (typical SWE-bench) \u2014 no PM2. Optional Docker Compose via HARNESS_INFRA_SERVICES. Git worktree by default. Launch provisions toolchain declaratively (HARNESS_ECOSYSTEM auto-detects common ecosystems); verify must use resolved tool paths from .env.agent.<id>, never hardcoded interpreter or package-manager paths. Install the Semgrep plugin (har env add-plugin semgrep) when the repo needs a SAST verification stage.",
70297
- ios: "iOS mobile app profile \u2014 xcodebuild + iOS Simulator in an isolated git worktree. Set HARNESS_XCODE_SCHEME, workspace/project, HARNESS_SIMULATOR_NAME, HARNESS_BUNDLE_ID. Launch writes XCODEBUILD_BIN to .env.agent.<id>; verify uses it. Install RocketSim plugin (har env add-plugin rocketsim) for user-flow validation."
70638
+ default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.",
70639
+ cli: "CLI/library profile (typical SWE-bench) \u2014 no PM2. Optional Docker Compose via HARNESS_INFRA_SERVICES. Git worktree by default. Launch provisions toolchain declaratively (HARNESS_ECOSYSTEM auto-detects common ecosystems); verify must use resolved tool paths from .env.agent.<id>, never hardcoded interpreter or package-manager paths.",
70640
+ ios: "iOS mobile app profile \u2014 xcodebuild + iOS Simulator in an isolated git worktree. Set HARNESS_XCODE_SCHEME, workspace/project, HARNESS_SIMULATOR_NAME, HARNESS_BUNDLE_ID. Launch writes XCODEBUILD_BIN to .env.agent.<id>; verify uses it."
70298
70641
  };
70299
70642
  function loadTemplate(name) {
70300
70643
  const filePath = resolveTemplateFile(name);
70301
70644
  if (!filePath) {
70302
70645
  throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
70303
70646
  }
70304
- return fs39.readFileSync(filePath, "utf8");
70647
+ return fs40.readFileSync(filePath, "utf8");
70305
70648
  }
70306
70649
  function applyProfilePlaceholders(content, profile) {
70307
70650
  return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
@@ -70323,7 +70666,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
70323
70666
  }
70324
70667
  function writeAdaptationPrompt(repoPath, content) {
70325
70668
  const harnessDir = getHarnessDir(repoPath);
70326
- const filePath = path44.join(harnessDir, ADAPTATION_PROMPT_FILE);
70669
+ const filePath = path45.join(harnessDir, ADAPTATION_PROMPT_FILE);
70327
70670
  writeFileSafe(filePath, content);
70328
70671
  return filePath;
70329
70672
  }
@@ -70378,27 +70721,27 @@ async function offerAdaptationPromptClipboard(content, options = {}) {
70378
70721
  }
70379
70722
 
70380
70723
  // src/harness/cursor-rule.ts
70381
- var fs40 = __toESM(require("fs"));
70382
- var path45 = __toESM(require("path"));
70724
+ var fs41 = __toESM(require("fs"));
70725
+ var path46 = __toESM(require("path"));
70383
70726
  var readline6 = __toESM(require("readline"));
70384
70727
  init_file_ops();
70385
70728
  init_logging();
70386
70729
  var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
70387
70730
  function getCursorRulePath(repoPath) {
70388
- return path45.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
70731
+ return path46.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
70389
70732
  }
70390
70733
  function isCursorWorkspace(repoPath) {
70391
- return fs40.existsSync(path45.join(repoPath, ".cursor"));
70734
+ return fs41.existsSync(path46.join(repoPath, ".cursor"));
70392
70735
  }
70393
70736
  function cursorRuleExists(repoPath) {
70394
- return fs40.existsSync(getCursorRulePath(repoPath));
70737
+ return fs41.existsSync(getCursorRulePath(repoPath));
70395
70738
  }
70396
70739
  function scaffoldCursorRule(repoPath) {
70397
70740
  const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
70398
70741
  if (!templatePath) {
70399
70742
  throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
70400
70743
  }
70401
- const content = fs40.readFileSync(templatePath, "utf8");
70744
+ const content = fs41.readFileSync(templatePath, "utf8");
70402
70745
  writeFileSafe(getCursorRulePath(repoPath), content);
70403
70746
  }
70404
70747
  async function handleCursorRule(options) {
@@ -70487,8 +70830,8 @@ init_validation();
70487
70830
  init_control_config();
70488
70831
 
70489
70832
  // src/core/control-lifecycle.ts
70490
- var import_child_process10 = require("child_process");
70491
- var path46 = __toESM(require("path"));
70833
+ var import_child_process11 = require("child_process");
70834
+ var path47 = __toESM(require("path"));
70492
70835
 
70493
70836
  // src/core/control-image.ts
70494
70837
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -70514,10 +70857,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
70514
70857
  var CONTROL_CONTAINER_PORT = 3847;
70515
70858
  var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
70516
70859
  function resolveControlDir() {
70517
- return path46.resolve(__dirname, "..", "control");
70860
+ return path47.resolve(__dirname, "..", "control");
70518
70861
  }
70519
70862
  function resolveControlBuildContext() {
70520
- return path46.resolve(resolveControlDir(), "..");
70863
+ return path47.resolve(resolveControlDir(), "..");
70521
70864
  }
70522
70865
  function buildDockerRunArgs(options) {
70523
70866
  const name = options.containerName ?? CONTROL_CONTAINER_NAME;
@@ -70540,7 +70883,7 @@ function buildDockerRunArgs(options) {
70540
70883
  return args;
70541
70884
  }
70542
70885
  function runDocker(args) {
70543
- const result = (0, import_child_process10.spawnSync)("docker", args, {
70886
+ const result = (0, import_child_process11.spawnSync)("docker", args, {
70544
70887
  stdio: "inherit",
70545
70888
  env: process.env
70546
70889
  });
@@ -70550,7 +70893,7 @@ function runDocker(args) {
70550
70893
  return result.status ?? 1;
70551
70894
  }
70552
70895
  function controlContainerExists(name = CONTROL_CONTAINER_NAME) {
70553
- const result = (0, import_child_process10.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
70896
+ const result = (0, import_child_process11.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
70554
70897
  encoding: "utf8",
70555
70898
  stdio: ["pipe", "pipe", "ignore"]
70556
70899
  });
@@ -70573,7 +70916,7 @@ async function startMissionControl(options) {
70573
70916
  "-t",
70574
70917
  imageRef,
70575
70918
  "-f",
70576
- path46.join(resolveControlDir(), "Dockerfile"),
70919
+ path47.join(resolveControlDir(), "Dockerfile"),
70577
70920
  resolveControlBuildContext()
70578
70921
  ]);
70579
70922
  if (buildCode !== 0) {
@@ -70720,7 +71063,7 @@ async function ensureTelemetryInfrastructure(options) {
70720
71063
  // src/core/run-service.ts
70721
71064
  init_telemetry_config();
70722
71065
  init_telemetry_env();
70723
- var path48 = __toESM(require("path"));
71066
+ var path49 = __toESM(require("path"));
70724
71067
  init_local_executor();
70725
71068
  function extractShellOutput(result) {
70726
71069
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
@@ -70819,7 +71162,8 @@ var RunService = class {
70819
71162
  }
70820
71163
  async launchEnvironment(options) {
70821
71164
  const guard = checkLaunchGuard2(options.repoPath, options.agentId, {
70822
- resume: options.resume
71165
+ resume: options.resume,
71166
+ worktree: options.worktree
70823
71167
  });
70824
71168
  if (!guard.allowed) {
70825
71169
  const slot = guard.slot;
@@ -70838,27 +71182,28 @@ var RunService = class {
70838
71182
  } : void 0
70839
71183
  };
70840
71184
  }
70841
- let telemetryBanner = "";
71185
+ let launchBanner = (guard.readiness?.warnings ?? []).map((warning) => `WARN: ${warning}
71186
+ `).join("");
70842
71187
  if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
70843
71188
  const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
70844
71189
  if (ensured.message) {
70845
- telemetryBanner += `${ensured.message}
71190
+ launchBanner += `${ensured.message}
70846
71191
  Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Worktrees. Disable: har telemetry off
70847
71192
  `;
70848
71193
  }
70849
71194
  if (ensured.warning) {
70850
- telemetryBanner += `${ensured.warning}
71195
+ launchBanner += `${ensured.warning}
70851
71196
  `;
70852
71197
  }
70853
71198
  try {
70854
71199
  const { ensureOtelHooks: ensureOtelHooks2 } = await Promise.resolve().then(() => (init_otel_hooks(), otel_hooks_exports));
70855
71200
  const hooks = ensureOtelHooks2({ setupAgents: true });
70856
- if (hooks.message) telemetryBanner += `${hooks.message}
71201
+ if (hooks.message) launchBanner += `${hooks.message}
70857
71202
  `;
70858
- if (hooks.warning) telemetryBanner += `${hooks.warning}
71203
+ if (hooks.warning) launchBanner += `${hooks.warning}
70859
71204
  `;
70860
71205
  } catch (err) {
70861
- telemetryBanner += `@osfactory/otel-hook setup skipped: ${err instanceof Error ? err.message : String(err)}
71206
+ launchBanner += `@osfactory/otel-hook setup skipped: ${err instanceof Error ? err.message : String(err)}
70862
71207
  `;
70863
71208
  }
70864
71209
  }
@@ -70923,12 +71268,12 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
70923
71268
  suffix: session.suffix,
70924
71269
  createdAt: session.createdAt
70925
71270
  });
70926
- const envFile = path48.join(session.workDir, `.env.agent.${options.agentId}`);
71271
+ const envFile = path49.join(session.workDir, `.env.agent.${options.agentId}`);
70927
71272
  try {
70928
71273
  appendTelemetryEnvToFile(envFile, {
70929
71274
  sessionKey,
70930
71275
  agentId: options.agentId,
70931
- repoPath: path48.resolve(options.repoPath),
71276
+ repoPath: path49.resolve(options.repoPath),
70932
71277
  workDir: session.workDir,
70933
71278
  branch: session.branch,
70934
71279
  suffix: session.suffix,
@@ -70951,8 +71296,8 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
70951
71296
  });
70952
71297
  }
70953
71298
  }
70954
- if (telemetryBanner) {
70955
- envResult.stderr = `${telemetryBanner}${envResult.stderr ?? ""}`;
71299
+ if (launchBanner) {
71300
+ envResult.stderr = `${launchBanner}${envResult.stderr ?? ""}`;
70956
71301
  }
70957
71302
  }
70958
71303
  return envResult;
@@ -71164,279 +71509,7 @@ init_slot_status();
71164
71509
 
71165
71510
  // src/core/commit-gate-onboarding.ts
71166
71511
  var readline7 = __toESM(require("readline"));
71167
-
71168
- // src/core/hooks.ts
71169
- var fs42 = __toESM(require("fs"));
71170
- var os16 = __toESM(require("os"));
71171
- var path49 = __toESM(require("path"));
71172
- init_shell();
71173
- init_schema2();
71174
- init_stages();
71175
- init_change_batch();
71176
- init_validations();
71177
- init_sync_context();
71178
- var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
71179
- var MARKER_END = "# <<< har commit gate <<<";
71180
- var PRE_COMMIT_BLOCK = [
71181
- MARKER_START,
71182
- 'if [ "$HAR_SKIP_GATE" != "1" ]; then',
71183
- ' "$(git rev-parse --git-path hooks)/har-pre-commit" || exit 1',
71184
- "fi",
71185
- MARKER_END
71186
- ].join("\n");
71187
- var POST_COMMIT_BLOCK = [
71188
- MARKER_START,
71189
- '"$(git rev-parse --git-path hooks)/har-post-commit" || true',
71190
- MARKER_END
71191
- ].join("\n");
71192
- function tryGit3(cwd, args) {
71193
- const result = run(`git ${args}`, { cwd });
71194
- return result.code === 0 ? result.stdout.trim() : void 0;
71195
- }
71196
- function resolveCheckoutRoot(cwd) {
71197
- return tryGit3(cwd, "rev-parse --show-toplevel");
71198
- }
71199
- function resolveHooksDir(checkoutDir) {
71200
- const hooksPath = tryGit3(checkoutDir, "rev-parse --git-path hooks");
71201
- if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
71202
- return path49.resolve(checkoutDir, hooksPath);
71203
- }
71204
- function getConfiguredHooksPath(checkoutDir) {
71205
- return tryGit3(checkoutDir, "config core.hooksPath") || void 0;
71206
- }
71207
- function defaultHarInvocation() {
71208
- const entry = process.argv[1];
71209
- if (entry && entry.endsWith(".js")) {
71210
- return `"${process.execPath}" "${path49.resolve(entry)}"`;
71211
- }
71212
- if (entry) return `"${path49.resolve(entry)}"`;
71213
- return "har";
71214
- }
71215
- function buildHookScript(harInvocation, subcommand, failOpenNotice) {
71216
- return `#!/bin/sh
71217
- # Managed by \`har hooks\` \u2014 do not edit; reinstall with \`har hooks install\`.
71218
- if ${harInvocation} --version >/dev/null 2>&1; then
71219
- exec ${harInvocation} hooks ${subcommand}
71220
- fi
71221
- if command -v har >/dev/null 2>&1; then
71222
- exec har hooks ${subcommand}
71223
- fi
71224
- toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
71225
- if [ -n "$toplevel" ] && [ -x "$toplevel/node_modules/.bin/har" ]; then
71226
- exec "$toplevel/node_modules/.bin/har" hooks ${subcommand}
71227
- fi
71228
- echo "${failOpenNotice}" >&2
71229
- exit 0
71230
- `;
71231
- }
71232
- function upsertMarkedBlock2(filePath, block) {
71233
- if (!fs42.existsSync(filePath)) {
71234
- fs42.writeFileSync(filePath, `#!/bin/sh
71235
- ${block}
71236
- `, { mode: 493 });
71237
- return "created";
71238
- }
71239
- const content = fs42.readFileSync(filePath, "utf8");
71240
- if (content.includes(MARKER_START)) {
71241
- const pattern = new RegExp(
71242
- `${escapeRegExp2(MARKER_START)}[\\s\\S]*?${escapeRegExp2(MARKER_END)}`
71243
- );
71244
- fs42.writeFileSync(filePath, content.replace(pattern, block));
71245
- fs42.chmodSync(filePath, 493);
71246
- return "updated";
71247
- }
71248
- const suffix = content.endsWith("\n") ? "" : "\n";
71249
- fs42.writeFileSync(filePath, `${content}${suffix}
71250
- ${block}
71251
- `);
71252
- fs42.chmodSync(filePath, 493);
71253
- return "appended";
71254
- }
71255
- function removeMarkedBlock(filePath) {
71256
- if (!fs42.existsSync(filePath)) return false;
71257
- const content = fs42.readFileSync(filePath, "utf8");
71258
- if (!content.includes(MARKER_START)) return false;
71259
- const pattern = new RegExp(
71260
- `\\n?${escapeRegExp2(MARKER_START)}[\\s\\S]*?${escapeRegExp2(MARKER_END)}\\n?`
71261
- );
71262
- const stripped = content.replace(pattern, "\n");
71263
- if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
71264
- fs42.rmSync(filePath);
71265
- } else {
71266
- fs42.writeFileSync(filePath, stripped);
71267
- }
71268
- return true;
71269
- }
71270
- function escapeRegExp2(value) {
71271
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
71272
- }
71273
- function installHooks(options) {
71274
- const checkout = resolveCheckoutRoot(options.repoPath);
71275
- if (!checkout) throw new Error(`Not a git repository: ${options.repoPath}`);
71276
- const configuredHooksPath = getConfiguredHooksPath(checkout);
71277
- if (configuredHooksPath && !options.force) {
71278
- throw new Error(
71279
- `core.hooksPath is set to "${configuredHooksPath}" (managed hooks, e.g. husky).
71280
- Add this to your managed pre-commit hook instead:
71281
-
71282
- ${PRE_COMMIT_BLOCK.split("\n").join("\n ")}
71283
-
71284
- Or re-run with --force to write into that directory anyway.`
71285
- );
71286
- }
71287
- const hooksDir = resolveHooksDir(checkout);
71288
- fs42.mkdirSync(hooksDir, { recursive: true });
71289
- const invocation = options.harInvocation ?? defaultHarInvocation();
71290
- fs42.writeFileSync(
71291
- path49.join(hooksDir, "har-pre-commit"),
71292
- buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
71293
- { mode: 493 }
71294
- );
71295
- fs42.writeFileSync(
71296
- path49.join(hooksDir, "har-post-commit"),
71297
- buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
71298
- { mode: 493 }
71299
- );
71300
- const preCommit = upsertMarkedBlock2(path49.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
71301
- const postCommit = upsertMarkedBlock2(path49.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
71302
- ensureValidationsIgnored(checkout);
71303
- return { hooksDir, preCommit, postCommit };
71304
- }
71305
- function uninstallHooks(repoPath) {
71306
- const checkout = resolveCheckoutRoot(repoPath);
71307
- if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
71308
- const hooksDir = resolveHooksDir(checkout);
71309
- let removed = false;
71310
- removed = removeMarkedBlock(path49.join(hooksDir, "pre-commit")) || removed;
71311
- removed = removeMarkedBlock(path49.join(hooksDir, "post-commit")) || removed;
71312
- for (const file of ["har-pre-commit", "har-post-commit"]) {
71313
- const full = path49.join(hooksDir, file);
71314
- if (fs42.existsSync(full)) {
71315
- fs42.rmSync(full);
71316
- removed = true;
71317
- }
71318
- }
71319
- return { hooksDir, removed };
71320
- }
71321
- function getCommitGateConfig(checkoutDir) {
71322
- try {
71323
- const registry2 = readStageRegistry(checkoutDir);
71324
- return CommitGateConfigSchema.parse(registry2.commitGate ?? {});
71325
- } catch {
71326
- return CommitGateConfigSchema.parse({});
71327
- }
71328
- }
71329
- function isAgentWorktree(checkoutDir) {
71330
- const branch = getCurrentBranch(checkoutDir);
71331
- if (branch && (/^har-agent-\d+$/.test(branch) || /-har-agent-\d+-[a-z0-9]+$/.test(branch))) {
71332
- return true;
71333
- }
71334
- const worktreesRoot = path49.join(os16.homedir(), "worktrees");
71335
- const resolved = path49.resolve(checkoutDir);
71336
- const name = path49.basename(resolved);
71337
- return resolved.startsWith(`${worktreesRoot}${path49.sep}`) && (/-agent-\d+$/.test(name) || /-har-agent-\d+-[a-z0-9]+$/.test(name));
71338
- }
71339
- function resolveEffectiveMode(checkoutDir, gate) {
71340
- if (!gate.enabled) return "off";
71341
- if (gate.scope === "all") return gate.mode;
71342
- return isAgentWorktree(checkoutDir) ? gate.mode : "warn";
71343
- }
71344
- function getHooksStatus(repoPath) {
71345
- const checkout = resolveCheckoutRoot(repoPath);
71346
- if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
71347
- const hooksDir = resolveHooksDir(checkout);
71348
- const gate = getCommitGateConfig(checkout);
71349
- const hasBlock = (name) => {
71350
- const file = path49.join(hooksDir, name);
71351
- return fs42.existsSync(file) && fs42.readFileSync(file, "utf8").includes(MARKER_START);
71352
- };
71353
- return {
71354
- checkout,
71355
- hooksDir,
71356
- configuredHooksPath: getConfiguredHooksPath(checkout),
71357
- preCommitInstalled: hasBlock("pre-commit") && fs42.existsSync(path49.join(hooksDir, "har-pre-commit")),
71358
- postCommitInstalled: hasBlock("post-commit") && fs42.existsSync(path49.join(hooksDir, "har-post-commit")),
71359
- gate,
71360
- effectiveMode: resolveEffectiveMode(checkout, gate)
71361
- };
71362
- }
71363
- function checkCommitGate(cwd) {
71364
- if (process.env.HAR_SKIP_GATE === "1") {
71365
- return { exitCode: 0, messages: ["har: commit gate skipped (HAR_SKIP_GATE=1)"] };
71366
- }
71367
- const checkout = resolveCheckoutRoot(cwd);
71368
- if (!checkout) return { exitCode: 0, messages: [] };
71369
- if (!fs42.existsSync(path49.join(checkout, ".har", "stages.json"))) {
71370
- return { exitCode: 0, messages: [] };
71371
- }
71372
- const gate = getCommitGateConfig(checkout);
71373
- const mode = resolveEffectiveMode(checkout, gate);
71374
- if (mode === "off") return { exitCode: 0, messages: [] };
71375
- if (!getHeadSha(checkout)) {
71376
- return { exitCode: 0, messages: ["har: commit gate skipped (initial commit)"] };
71377
- }
71378
- if (isMergeOrRebaseInProgress(checkout)) {
71379
- return { exitCode: 0, messages: ["har: commit gate skipped (merge/rebase in progress)"] };
71380
- }
71381
- let stagedTree;
71382
- try {
71383
- stagedTree = computeStagedTree(checkout);
71384
- } catch {
71385
- return { exitCode: 0, messages: ["har: commit gate skipped (index has unmerged entries)"] };
71386
- }
71387
- if (stagedTree === getHeadTree(checkout)) {
71388
- return { exitCode: 0, messages: [] };
71389
- }
71390
- const record2 = findValidation(checkout, stagedTree);
71391
- if (record2 && record2.status === "pass" && record2.full) {
71392
- return {
71393
- exitCode: 0,
71394
- messages: [
71395
- `har: batch ${stagedTree.slice(0, 8)} verified${record2.runId ? ` by run ${record2.runId.slice(0, 8)}` : ""} \u2713`
71396
- ]
71397
- };
71398
- }
71399
- const messages = [];
71400
- const verb = mode === "block" ? "commit blocked" : "warning";
71401
- messages.push(
71402
- `har: ${verb} \u2014 no passing verification for this exact change batch (tree ${stagedTree.slice(0, 8)}).`
71403
- );
71404
- if (record2 && record2.status === "fail") {
71405
- messages.push(
71406
- ` This exact batch was verified and FAILED${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}. Fix and re-run.`
71407
- );
71408
- } else if (record2 && !record2.full) {
71409
- messages.push(
71410
- ` This batch only passed a partial verify${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}; the gate requires --full.`
71411
- );
71412
- }
71413
- messages.push(" Run the full pipeline: har env verify <agentId> --full");
71414
- messages.push(
71415
- " Note: verify hashes the WORKING TREE \u2014 stage exactly what you verified (git add -A)."
71416
- );
71417
- messages.push(" Bypass (humans only): HAR_SKIP_GATE=1 git commit \u2026 or git commit --no-verify");
71418
- messages.push(" Configure via commitGate { mode: block|warn, scope: worktrees|all } in .har/stages.json");
71419
- return { exitCode: mode === "block" ? 1 : 0, messages };
71420
- }
71421
- async function recordCommitAssociation(cwd) {
71422
- try {
71423
- const checkout = resolveCheckoutRoot(cwd);
71424
- if (!checkout) return { attached: false };
71425
- const commitSha = getHeadSha(checkout);
71426
- const tree = tryGit3(checkout, "rev-parse HEAD^{tree}");
71427
- if (!commitSha || !tree) return { attached: false };
71428
- const updated = attachCommit(checkout, tree, commitSha);
71429
- if (updated) {
71430
- markDirty(updated.harnessRoot);
71431
- return { attached: true, commitSha };
71432
- }
71433
- return { attached: false, commitSha };
71434
- } catch {
71435
- return { attached: false };
71436
- }
71437
- }
71438
-
71439
- // src/core/commit-gate-onboarding.ts
71512
+ init_hooks();
71440
71513
  init_stages();
71441
71514
  init_logging();
71442
71515
  function askYesNo4(question) {
@@ -71564,7 +71637,7 @@ Occupied slots always block a new launch:
71564
71637
  For a new unrelated task: checkout main (or your intended base) on the main
71565
71638
  repo checkout, free the slot (complete/teardown), then launch.`;
71566
71639
  var HAR_ENV_EPILOG = `Environment lifecycle:
71567
- preflight <id> dry-run: ports, PM2, Docker, occupied-slot gates
71640
+ preflight <id> dry-run: ports, PM2, Docker, occupied-slot gates, untracked paths
71568
71641
  launch <id> fresh session worktree from the main checkout's current HEAD
71569
71642
  recover <id> alias for launch --resume (failed/starting only)
71570
71643
  verify <id> run checks (--full before declaring done)
@@ -71760,7 +71833,7 @@ var envCommand = {
71760
71833
  handleRecover
71761
71834
  ).command(
71762
71835
  "preflight <id>",
71763
- "Check whether a slot can launch now (ports, PM2, Docker, occupied slot)",
71836
+ "Check whether a slot can launch now (ports, PM2, Docker, occupied slot, untracked worktree paths)",
71764
71837
  (y) => y.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" }),
71765
71838
  handlePreflight
71766
71839
  ).command(
@@ -72119,11 +72192,14 @@ async function handleLaunch(argv) {
72119
72192
  const repo = path51.resolve(argv.repo);
72120
72193
  const agentId = validateAgentId(argv.id, repo);
72121
72194
  if (!argv.resume) {
72122
- const guard = checkLaunchGuard2(repo, agentId, {});
72195
+ const guard = checkLaunchGuard2(repo, agentId, { worktree: argv.worktree });
72123
72196
  if (!guard.allowed && guard.blocked) {
72124
72197
  error(guard.reason ?? `Slot ${agentId} is occupied.`);
72125
72198
  return finishCommand(2);
72126
72199
  }
72200
+ for (const warning of guard.readiness?.warnings ?? []) {
72201
+ warn(warning);
72202
+ }
72127
72203
  }
72128
72204
  const result = await launchEnvironment({
72129
72205
  repoPath: repo,
@@ -72390,13 +72466,13 @@ init_portal_credentials();
72390
72466
  // src/core/portal-login.ts
72391
72467
  var http = __toESM(require("http"));
72392
72468
  var import_crypto = require("crypto");
72393
- var import_child_process12 = require("child_process");
72469
+ var import_child_process13 = require("child_process");
72394
72470
  var CALLBACK_PATH = "/callback";
72395
72471
  var LOGIN_TIMEOUT_MS = 3 * 6e4;
72396
72472
  function openBrowser(url) {
72397
72473
  const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
72398
72474
  try {
72399
- const child = (0, import_child_process12.spawn)(cmd, args, { stdio: "ignore", detached: true });
72475
+ const child = (0, import_child_process13.spawn)(cmd, args, { stdio: "ignore", detached: true });
72400
72476
  child.on("error", () => {
72401
72477
  });
72402
72478
  child.unref();
@@ -73256,6 +73332,7 @@ async function handleLogin(argv) {
73256
73332
 
73257
73333
  // src/cli/commands/hooks.ts
73258
73334
  var path57 = __toESM(require("path"));
73335
+ init_hooks();
73259
73336
 
73260
73337
  // src/core/claude-hooks.ts
73261
73338
  var fs47 = __toESM(require("fs"));
@@ -80825,7 +80902,7 @@ var HAR_MCP_TOOLS = [
80825
80902
  },
80826
80903
  {
80827
80904
  name: "har_preflight_environment",
80828
- description: "Readiness gate before launch: checks ports, foreign PM2, Docker conflicts, and occupied slot. Returns canLaunch with actionable blockers. Call before har_launch_environment.",
80905
+ description: "Readiness gate before launch: checks ports, foreign PM2, Docker conflicts, occupied slot, and untracked paths that will be missing from a session worktree. Returns canLaunch with actionable blockers and warnings. Call before har_launch_environment.",
80829
80906
  inputSchema: objectJsonSchema(
80830
80907
  {
80831
80908
  repo: repoJsonProperty,