@osfactory/har 0.54.1 → 0.56.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
@@ -4248,7 +4248,7 @@ var init_zod = __esm({
4248
4248
  });
4249
4249
 
4250
4250
  // packages/schemas/src/schema.ts
4251
- var HAR_STAGE_KINDS, HAR_AGENT_SLOT_MIN, AgentSkillTargetSchema, ScaffoldedAgentFileSchema, HarnessManifestSchema, HarnessStageKindSchema, HarnessArtifactKindSchema, HarnessArtifactSchema, HarnessStageIdSchema, HarnessStageSchema, HarnessAgentSlotsSchema, CommitGateConfigSchema, OnboardingPreferencesSchema, HarnessStageRegistrySchema, HarnessVerificationStepSchema, ShellRunOutputSchema, HarnessVerificationResultSchema, HarnessStageRunStatusSchema, HarnessStageLogSchema, HarnessStageUrlSchema, HarnessStageRunResultSchema, StageKindSchema, VerificationResultSchema, StageResultSchema, RunRecordTriggerSchema, WorkUnitIdSchema, WorkAttemptIdSchema, WorkUnitMetadataSchema, WorkUnitOutcomeSchema, WorkUnitRecordSchema, WorkAttemptRecordSchema, RunRecordSchema, SlotRegistryEntrySchema, AgentSlotHarnessUsageSchema, PreflightBlockerSchema, SlotReadinessSchema, AgentSlotStatusSchema, EnvironmentStatusSchema, RegisterRepoInputSchema, UnregisterRepoInputSchema, UnregisterWorktreeResultSchema, UnregisterRepoResultSchema, ResetMissionControlInputSchema, HarnessScrubResultSchema, ResetMissionControlResultSchema, DeleteWorktreesInputSchema, DeleteWorktreeResultSchema, DeleteWorktreesResultSchema, SyncRunsInputSchema, SyncSlotsInputSchema, SyncWorkUnitsInputSchema, ChangedFileStatusSchema, ChangedFileSchema, ValidationRecordSchema, SyncValidationsInputSchema, ValidationBindingRecordSchema, SyncValidationBindingsInputSchema, AgentToolSchema, UsageSourceSchema, AgentSessionUsageSchema, SyncUsageInputSchema, AgentSessionEventSchema, SyncSessionEventsInputSchema;
4251
+ var HAR_STAGE_KINDS, HAR_AGENT_SLOT_MIN, AgentSkillTargetSchema, ScaffoldedAgentFileSchema, HarnessManifestSchema, HarnessStageKindSchema, HarnessArtifactKindSchema, HarnessArtifactSchema, HarnessStageIdSchema, HarnessStageSchema, HarnessAgentSlotsSchema, CommitGateConfigSchema, OnboardingPreferencesSchema, HarnessStageRegistrySchema, HarnessVerificationStepSchema, ShellRunOutputSchema, HarnessVerificationResultSchema, HarnessStageRunStatusSchema, HarnessStageLogSchema, HarnessStageUrlSchema, HarnessStageRunResultSchema, StageKindSchema, VerificationResultSchema, StageResultSchema, RunRecordTriggerSchema, WorkUnitIdSchema, WorkAttemptIdSchema, WorkUnitMetadataSchema, WorkUnitOutcomeSchema, WorkUnitRecordSchema, WorkAttemptRecordSchema, RunRecordSchema, SlotRegistryEntrySchema, AgentSlotHarnessUsageSchema, PreflightBlockerSchema, SlotReadinessSchema, AgentSlotStatusSchema, EnvironmentStatusSchema, RegisterRepoInputSchema, UnregisterRepoInputSchema, UnregisterWorktreeResultSchema, UnregisterRepoResultSchema, ResetMissionControlInputSchema, HarnessScrubResultSchema, ResetMissionControlResultSchema, DeleteWorktreesInputSchema, DeleteWorktreeResultSchema, DeleteWorktreesResultSchema, SyncRunsInputSchema, SyncSlotsInputSchema, SyncWorkUnitsInputSchema, ChangedFileStatusSchema, ChangedFileSchema, ValidationRecordSchema, SyncValidationsInputSchema, ValidationBindingRecordSchema, SyncValidationBindingsInputSchema, AgentToolSchema, UsageSourceSchema, AgentTrajectorySourceSchema, AgentTrajectoryContentDisclosureSchema, AgentTrajectoryRecordV1Schema, AgentSessionUsageSchema, SyncUsageInputSchema, AgentSessionEventSchema, SyncSessionEventsInputSchema;
4252
4252
  var init_schema = __esm({
4253
4253
  "packages/schemas/src/schema.ts"() {
4254
4254
  "use strict";
@@ -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
  });
@@ -4691,6 +4693,39 @@ var init_schema = __esm({
4691
4693
  });
4692
4694
  AgentToolSchema = external_exports.enum(["claude_code", "codex", "cursor"]);
4693
4695
  UsageSourceSchema = external_exports.enum(["otel", "harvest"]);
4696
+ AgentTrajectorySourceSchema = external_exports.enum(["otel", "harvest", "har"]);
4697
+ AgentTrajectoryContentDisclosureSchema = external_exports.enum([
4698
+ "full",
4699
+ "redacted",
4700
+ "masked",
4701
+ "truncated",
4702
+ "withheld",
4703
+ "metadata_only"
4704
+ ]);
4705
+ AgentTrajectoryRecordV1Schema = external_exports.object({
4706
+ version: external_exports.literal(1),
4707
+ source: AgentTrajectorySourceSchema,
4708
+ sourceEventId: external_exports.string().min(1),
4709
+ contentKey: external_exports.string().min(1),
4710
+ sessionKey: external_exports.string().min(1),
4711
+ agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
4712
+ agentTool: AgentToolSchema,
4713
+ eventType: external_exports.string().min(1),
4714
+ sequence: external_exports.number().int().nonnegative(),
4715
+ timestamp: external_exports.string(),
4716
+ payload: external_exports.record(external_exports.unknown()),
4717
+ contentKind: external_exports.string().min(1),
4718
+ contentDisclosure: AgentTrajectoryContentDisclosureSchema,
4719
+ contentLabel: external_exports.string().optional(),
4720
+ traceId: external_exports.string().optional(),
4721
+ spanId: external_exports.string().optional(),
4722
+ parentSpanId: external_exports.string().optional(),
4723
+ generationId: external_exports.string().optional(),
4724
+ toolCallId: external_exports.string().optional(),
4725
+ correlationId: external_exports.string().optional(),
4726
+ workUnitId: WorkUnitIdSchema.optional(),
4727
+ attemptId: WorkAttemptIdSchema.optional()
4728
+ }).passthrough();
4694
4729
  AgentSessionUsageSchema = external_exports.object({
4695
4730
  sessionKey: external_exports.string().min(1),
4696
4731
  agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
@@ -29790,9 +29825,546 @@ var init_slot_launch_guard_occupied = __esm({
29790
29825
  }
29791
29826
  });
29792
29827
 
29828
+ // src/core/change-batch.ts
29829
+ function git(checkoutDir, args, env3) {
29830
+ const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
29831
+ if (result.code !== 0) {
29832
+ throw new Error(`git ${args} failed (${result.code}): ${result.stderr.trim()}`);
29833
+ }
29834
+ return result.stdout.trim();
29835
+ }
29836
+ function tryGit2(checkoutDir, args) {
29837
+ const result = run(`git ${args}`, { cwd: checkoutDir });
29838
+ return result.code === 0 ? result.stdout.trim() : void 0;
29839
+ }
29840
+ function isCheckoutRoot(checkoutDir) {
29841
+ const toplevel = tryGit2(checkoutDir, "rev-parse --show-toplevel");
29842
+ if (!toplevel) return false;
29843
+ try {
29844
+ return fs16.realpathSync(toplevel) === fs16.realpathSync(checkoutDir);
29845
+ } catch {
29846
+ return false;
29847
+ }
29848
+ }
29849
+ function getHeadTree(checkoutDir) {
29850
+ return tryGit2(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
29851
+ }
29852
+ function getHeadSha(checkoutDir) {
29853
+ return tryGit2(checkoutDir, "rev-parse -q --verify HEAD");
29854
+ }
29855
+ function getCurrentBranch(checkoutDir) {
29856
+ const branch = tryGit2(checkoutDir, "rev-parse --abbrev-ref HEAD");
29857
+ return branch && branch !== "HEAD" ? branch : void 0;
29858
+ }
29859
+ function getEmptyTree(checkoutDir) {
29860
+ return git(checkoutDir, "hash-object -t tree /dev/null");
29861
+ }
29862
+ function isMergeOrRebaseInProgress(checkoutDir) {
29863
+ if (tryGit2(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
29864
+ for (const dir of ["rebase-merge", "rebase-apply"]) {
29865
+ const gitPath = tryGit2(checkoutDir, `rev-parse --git-path ${dir}`);
29866
+ if (gitPath && fs16.existsSync(path16.resolve(checkoutDir, gitPath))) return true;
29867
+ }
29868
+ return false;
29869
+ }
29870
+ function computeStagedTree(checkoutDir) {
29871
+ return git(checkoutDir, "write-tree");
29872
+ }
29873
+ function parseNameStatus(output) {
29874
+ const files = [];
29875
+ for (const line of output.split("\n")) {
29876
+ if (!line.trim()) continue;
29877
+ const parts = line.split(" ");
29878
+ const status = parts[0]?.charAt(0);
29879
+ if (!status) continue;
29880
+ if ((status === "R" || status === "C") && parts.length >= 3) {
29881
+ files.push({ path: parts[2], status, oldPath: parts[1] });
29882
+ } else if (parts.length >= 2) {
29883
+ files.push({ path: parts[1], status });
29884
+ }
29885
+ }
29886
+ return files;
29887
+ }
29888
+ function computeWorktreeSnapshot(checkoutDir) {
29889
+ const tmpDir = fs16.mkdtempSync(path16.join(os6.tmpdir(), "har-idx-"));
29890
+ const tmpIndex = path16.join(tmpDir, "index");
29891
+ const env3 = { GIT_INDEX_FILE: tmpIndex };
29892
+ try {
29893
+ const headTree = getHeadTree(checkoutDir);
29894
+ if (headTree) {
29895
+ git(checkoutDir, "read-tree HEAD", env3);
29896
+ } else {
29897
+ git(checkoutDir, "read-tree --empty", env3);
29898
+ }
29899
+ git(checkoutDir, "add -A .", env3);
29900
+ const treeHash = git(checkoutDir, "write-tree", env3);
29901
+ const baseTree = headTree ?? getEmptyTree(checkoutDir);
29902
+ const diffOutput = baseTree === treeHash ? "" : git(
29903
+ checkoutDir,
29904
+ `diff-tree -r -M --name-status --ignore-submodules=all ${baseTree} ${treeHash}`
29905
+ );
29906
+ return {
29907
+ treeHash,
29908
+ headSha: getHeadSha(checkoutDir),
29909
+ branch: getCurrentBranch(checkoutDir),
29910
+ changedFiles: parseNameStatus(diffOutput)
29911
+ };
29912
+ } finally {
29913
+ fs16.rmSync(tmpDir, { recursive: true, force: true });
29914
+ }
29915
+ }
29916
+ var fs16, os6, path16;
29917
+ var init_change_batch = __esm({
29918
+ "src/core/change-batch.ts"() {
29919
+ "use strict";
29920
+ fs16 = __toESM(require("fs"));
29921
+ os6 = __toESM(require("os"));
29922
+ path16 = __toESM(require("path"));
29923
+ init_shell();
29924
+ }
29925
+ });
29926
+
29927
+ // src/core/validations.ts
29928
+ function resolveValidationCheckoutDir(input) {
29929
+ const candidates = [input.worktreePath, input.workDir, input.harnessRoot].filter(
29930
+ (value) => Boolean(value)
29931
+ );
29932
+ return candidates.find(isCheckoutRoot) ?? candidates[0] ?? input.harnessRoot;
29933
+ }
29934
+ function getValidationsDir(checkoutDir) {
29935
+ return path17.join(checkoutDir, ".har", VALIDATIONS_DIR);
29936
+ }
29937
+ function validationPath(checkoutDir, treeHash) {
29938
+ return path17.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29939
+ }
29940
+ function writeRecord(checkoutDir, record2) {
29941
+ const dir = getValidationsDir(checkoutDir);
29942
+ fs17.mkdirSync(dir, { recursive: true });
29943
+ fs17.writeFileSync(
29944
+ validationPath(checkoutDir, record2.treeHash),
29945
+ `${JSON.stringify(record2, null, 2)}
29946
+ `
29947
+ );
29948
+ }
29949
+ function ensureValidationsIgnored(checkoutDir) {
29950
+ const harDir = path17.join(checkoutDir, ".har");
29951
+ if (!fs17.existsSync(harDir)) return;
29952
+ const gitignorePath = path17.join(harDir, ".gitignore");
29953
+ const content = fs17.existsSync(gitignorePath) ? fs17.readFileSync(gitignorePath, "utf8") : "";
29954
+ if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
29955
+ const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
29956
+ fs17.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29957
+ `);
29958
+ }
29959
+ function recordValidation(input) {
29960
+ if (!isCheckoutRoot(input.checkoutDir)) {
29961
+ throw new Error(`Not the root of a git checkout: ${input.checkoutDir}`);
29962
+ }
29963
+ ensureValidationsIgnored(input.checkoutDir);
29964
+ ensureValidationsIgnored(input.harnessRoot);
29965
+ const snapshot = computeWorktreeSnapshot(input.checkoutDir);
29966
+ const now = (/* @__PURE__ */ new Date()).toISOString();
29967
+ const existing = findValidation(input.checkoutDir, snapshot.treeHash);
29968
+ const record2 = ValidationRecordSchema.parse({
29969
+ validationId: existing?.validationId ?? crypto3.randomUUID(),
29970
+ treeHash: snapshot.treeHash,
29971
+ headSha: snapshot.headSha,
29972
+ branch: snapshot.branch,
29973
+ workDir: path17.resolve(input.checkoutDir),
29974
+ harnessRoot: path17.resolve(input.harnessRoot),
29975
+ agentId: input.agentId,
29976
+ status: input.status,
29977
+ full: input.full,
29978
+ runId: input.runId,
29979
+ changedFiles: snapshot.changedFiles,
29980
+ createdAt: existing?.createdAt ?? now,
29981
+ updatedAt: now,
29982
+ commitSha: existing?.commitSha,
29983
+ committedAt: existing?.committedAt
29984
+ });
29985
+ writeRecord(input.checkoutDir, record2);
29986
+ if (path17.resolve(input.harnessRoot) !== path17.resolve(input.checkoutDir)) {
29987
+ writeRecord(input.harnessRoot, record2);
29988
+ }
29989
+ markDirty(input.checkoutDir);
29990
+ return record2;
29991
+ }
29992
+ function findValidation(checkoutDir, treeHash) {
29993
+ const file = validationPath(checkoutDir, treeHash);
29994
+ if (!fs17.existsSync(file)) return void 0;
29995
+ try {
29996
+ return ValidationRecordSchema.parse(JSON.parse(fs17.readFileSync(file, "utf8")));
29997
+ } catch {
29998
+ return void 0;
29999
+ }
30000
+ }
30001
+ function attachCommit(checkoutDir, treeHash, commitSha) {
30002
+ const record2 = findValidation(checkoutDir, treeHash);
30003
+ if (!record2) return void 0;
30004
+ const updated = {
30005
+ ...record2,
30006
+ commitSha,
30007
+ committedAt: (/* @__PURE__ */ new Date()).toISOString(),
30008
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30009
+ };
30010
+ writeRecord(checkoutDir, updated);
30011
+ if (record2.harnessRoot && path17.resolve(record2.harnessRoot) !== path17.resolve(checkoutDir) && fs17.existsSync(path17.join(record2.harnessRoot, ".har"))) {
30012
+ writeRecord(record2.harnessRoot, updated);
30013
+ }
30014
+ return updated;
30015
+ }
30016
+ function listValidations(harnessRoot) {
30017
+ const dir = getValidationsDir(harnessRoot);
30018
+ if (!fs17.existsSync(dir)) return [];
30019
+ const records = [];
30020
+ for (const entry of fs17.readdirSync(dir)) {
30021
+ if (!entry.endsWith(".json")) continue;
30022
+ try {
30023
+ records.push(ValidationRecordSchema.parse(JSON.parse(fs17.readFileSync(path17.join(dir, entry), "utf8"))));
30024
+ } catch {
30025
+ }
30026
+ }
30027
+ return records.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
30028
+ }
30029
+ var crypto3, fs17, path17, VALIDATIONS_DIR;
30030
+ var init_validations = __esm({
30031
+ "src/core/validations.ts"() {
30032
+ "use strict";
30033
+ crypto3 = __toESM(require("crypto"));
30034
+ fs17 = __toESM(require("fs"));
30035
+ path17 = __toESM(require("path"));
30036
+ init_schema2();
30037
+ init_change_batch();
30038
+ init_sync_context();
30039
+ VALIDATIONS_DIR = "validations";
30040
+ }
30041
+ });
30042
+
30043
+ // src/core/hooks.ts
30044
+ function tryGit3(cwd, args) {
30045
+ const result = run(`git ${args}`, { cwd });
30046
+ return result.code === 0 ? result.stdout.trim() : void 0;
30047
+ }
30048
+ function resolveCheckoutRoot(cwd) {
30049
+ return tryGit3(cwd, "rev-parse --show-toplevel");
30050
+ }
30051
+ function resolveHooksDir(checkoutDir) {
30052
+ const hooksPath = tryGit3(checkoutDir, "rev-parse --git-path hooks");
30053
+ if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
30054
+ return path18.resolve(checkoutDir, hooksPath);
30055
+ }
30056
+ function getConfiguredHooksPath(checkoutDir) {
30057
+ return tryGit3(checkoutDir, "config core.hooksPath") || void 0;
30058
+ }
30059
+ function defaultHarInvocation() {
30060
+ const entry = process.argv[1];
30061
+ if (entry && entry.endsWith(".js")) {
30062
+ return `"${process.execPath}" "${path18.resolve(entry)}"`;
30063
+ }
30064
+ if (entry) return `"${path18.resolve(entry)}"`;
30065
+ return "har";
30066
+ }
30067
+ function buildHookScript(harInvocation, subcommand, failOpenNotice) {
30068
+ return `#!/bin/sh
30069
+ # Managed by \`har hooks\` \u2014 do not edit; reinstall with \`har hooks install\`.
30070
+ if ${harInvocation} --version >/dev/null 2>&1; then
30071
+ exec ${harInvocation} hooks ${subcommand}
30072
+ fi
30073
+ if command -v har >/dev/null 2>&1; then
30074
+ exec har hooks ${subcommand}
30075
+ fi
30076
+ toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
30077
+ if [ -n "$toplevel" ] && [ -x "$toplevel/node_modules/.bin/har" ]; then
30078
+ exec "$toplevel/node_modules/.bin/har" hooks ${subcommand}
30079
+ fi
30080
+ echo "${failOpenNotice}" >&2
30081
+ exit 0
30082
+ `;
30083
+ }
30084
+ function upsertMarkedBlock(filePath, block) {
30085
+ if (!fs18.existsSync(filePath)) {
30086
+ fs18.writeFileSync(filePath, `#!/bin/sh
30087
+ ${block}
30088
+ `, { mode: 493 });
30089
+ return "created";
30090
+ }
30091
+ const content = fs18.readFileSync(filePath, "utf8");
30092
+ if (content.includes(MARKER_START)) {
30093
+ const pattern = new RegExp(
30094
+ `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
30095
+ );
30096
+ fs18.writeFileSync(filePath, content.replace(pattern, block));
30097
+ fs18.chmodSync(filePath, 493);
30098
+ return "updated";
30099
+ }
30100
+ const suffix = content.endsWith("\n") ? "" : "\n";
30101
+ fs18.writeFileSync(filePath, `${content}${suffix}
30102
+ ${block}
30103
+ `);
30104
+ fs18.chmodSync(filePath, 493);
30105
+ return "appended";
30106
+ }
30107
+ function removeMarkedBlock(filePath) {
30108
+ if (!fs18.existsSync(filePath)) return false;
30109
+ const content = fs18.readFileSync(filePath, "utf8");
30110
+ if (!content.includes(MARKER_START)) return false;
30111
+ const pattern = new RegExp(
30112
+ `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
30113
+ );
30114
+ const stripped = content.replace(pattern, "\n");
30115
+ if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
30116
+ fs18.rmSync(filePath);
30117
+ } else {
30118
+ fs18.writeFileSync(filePath, stripped);
30119
+ }
30120
+ return true;
30121
+ }
30122
+ function escapeRegExp(value) {
30123
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30124
+ }
30125
+ function installHooks(options) {
30126
+ const checkout = resolveCheckoutRoot(options.repoPath);
30127
+ if (!checkout) throw new Error(`Not a git repository: ${options.repoPath}`);
30128
+ const configuredHooksPath = getConfiguredHooksPath(checkout);
30129
+ if (configuredHooksPath && !options.force) {
30130
+ throw new Error(
30131
+ `core.hooksPath is set to "${configuredHooksPath}" (managed hooks, e.g. husky).
30132
+ Add this to your managed pre-commit hook instead:
30133
+
30134
+ ${PRE_COMMIT_BLOCK.split("\n").join("\n ")}
30135
+
30136
+ Or re-run with --force to write into that directory anyway.`
30137
+ );
30138
+ }
30139
+ const hooksDir = resolveHooksDir(checkout);
30140
+ fs18.mkdirSync(hooksDir, { recursive: true });
30141
+ const invocation = options.harInvocation ?? defaultHarInvocation();
30142
+ fs18.writeFileSync(
30143
+ path18.join(hooksDir, "har-pre-commit"),
30144
+ buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
30145
+ { mode: 493 }
30146
+ );
30147
+ fs18.writeFileSync(
30148
+ path18.join(hooksDir, "har-post-commit"),
30149
+ buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
30150
+ { mode: 493 }
30151
+ );
30152
+ const preCommit = upsertMarkedBlock(path18.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
30153
+ const postCommit = upsertMarkedBlock(path18.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
30154
+ ensureValidationsIgnored(checkout);
30155
+ return { hooksDir, preCommit, postCommit };
30156
+ }
30157
+ function uninstallHooks(repoPath) {
30158
+ const checkout = resolveCheckoutRoot(repoPath);
30159
+ if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
30160
+ const hooksDir = resolveHooksDir(checkout);
30161
+ let removed = false;
30162
+ removed = removeMarkedBlock(path18.join(hooksDir, "pre-commit")) || removed;
30163
+ removed = removeMarkedBlock(path18.join(hooksDir, "post-commit")) || removed;
30164
+ for (const file of ["har-pre-commit", "har-post-commit"]) {
30165
+ const full = path18.join(hooksDir, file);
30166
+ if (fs18.existsSync(full)) {
30167
+ fs18.rmSync(full);
30168
+ removed = true;
30169
+ }
30170
+ }
30171
+ return { hooksDir, removed };
30172
+ }
30173
+ function getCommitGateConfig(checkoutDir) {
30174
+ try {
30175
+ const registry2 = readStageRegistry(checkoutDir);
30176
+ return CommitGateConfigSchema.parse(registry2.commitGate ?? {});
30177
+ } catch {
30178
+ return CommitGateConfigSchema.parse({});
30179
+ }
30180
+ }
30181
+ function isAgentWorktree(checkoutDir) {
30182
+ const branch = getCurrentBranch(checkoutDir);
30183
+ if (branch && (/^har-agent-\d+$/.test(branch) || /-har-agent-\d+-[a-z0-9]+$/.test(branch))) {
30184
+ return true;
30185
+ }
30186
+ const worktreesRoot = path18.join(os7.homedir(), "worktrees");
30187
+ const resolved = path18.resolve(checkoutDir);
30188
+ const name = path18.basename(resolved);
30189
+ return resolved.startsWith(`${worktreesRoot}${path18.sep}`) && (/-agent-\d+$/.test(name) || /-har-agent-\d+-[a-z0-9]+$/.test(name));
30190
+ }
30191
+ function resolveEffectiveMode(checkoutDir, gate) {
30192
+ if (!gate.enabled) return "off";
30193
+ if (gate.scope === "all") return gate.mode;
30194
+ return isAgentWorktree(checkoutDir) ? gate.mode : "warn";
30195
+ }
30196
+ function getHooksStatus(repoPath) {
30197
+ const checkout = resolveCheckoutRoot(repoPath);
30198
+ if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
30199
+ const hooksDir = resolveHooksDir(checkout);
30200
+ const gate = getCommitGateConfig(checkout);
30201
+ const hasBlock = (name) => {
30202
+ const file = path18.join(hooksDir, name);
30203
+ return fs18.existsSync(file) && fs18.readFileSync(file, "utf8").includes(MARKER_START);
30204
+ };
30205
+ return {
30206
+ checkout,
30207
+ hooksDir,
30208
+ configuredHooksPath: getConfiguredHooksPath(checkout),
30209
+ preCommitInstalled: hasBlock("pre-commit") && fs18.existsSync(path18.join(hooksDir, "har-pre-commit")),
30210
+ postCommitInstalled: hasBlock("post-commit") && fs18.existsSync(path18.join(hooksDir, "har-post-commit")),
30211
+ gate,
30212
+ effectiveMode: resolveEffectiveMode(checkout, gate)
30213
+ };
30214
+ }
30215
+ function checkCommitGate(cwd) {
30216
+ if (process.env.HAR_SKIP_GATE === "1") {
30217
+ return { exitCode: 0, messages: ["har: commit gate skipped (HAR_SKIP_GATE=1)"] };
30218
+ }
30219
+ const checkout = resolveCheckoutRoot(cwd);
30220
+ if (!checkout) return { exitCode: 0, messages: [] };
30221
+ if (!fs18.existsSync(path18.join(checkout, ".har", "stages.json"))) {
30222
+ return { exitCode: 0, messages: [] };
30223
+ }
30224
+ const gate = getCommitGateConfig(checkout);
30225
+ const mode = resolveEffectiveMode(checkout, gate);
30226
+ if (mode === "off") return { exitCode: 0, messages: [] };
30227
+ if (!getHeadSha(checkout)) {
30228
+ return { exitCode: 0, messages: ["har: commit gate skipped (initial commit)"] };
30229
+ }
30230
+ if (isMergeOrRebaseInProgress(checkout)) {
30231
+ return { exitCode: 0, messages: ["har: commit gate skipped (merge/rebase in progress)"] };
30232
+ }
30233
+ let stagedTree;
30234
+ try {
30235
+ stagedTree = computeStagedTree(checkout);
30236
+ } catch {
30237
+ return { exitCode: 0, messages: ["har: commit gate skipped (index has unmerged entries)"] };
30238
+ }
30239
+ if (stagedTree === getHeadTree(checkout)) {
30240
+ return { exitCode: 0, messages: [] };
30241
+ }
30242
+ const record2 = findValidation(checkout, stagedTree);
30243
+ if (record2 && record2.status === "pass" && record2.full) {
30244
+ return {
30245
+ exitCode: 0,
30246
+ messages: [
30247
+ `har: batch ${stagedTree.slice(0, 8)} verified${record2.runId ? ` by run ${record2.runId.slice(0, 8)}` : ""} \u2713`
30248
+ ]
30249
+ };
30250
+ }
30251
+ const messages = [];
30252
+ const verb = mode === "block" ? "commit blocked" : "warning";
30253
+ messages.push(
30254
+ `har: ${verb} \u2014 no passing verification for this exact change batch (tree ${stagedTree.slice(0, 8)}).`
30255
+ );
30256
+ if (record2 && record2.status === "fail") {
30257
+ messages.push(
30258
+ ` This exact batch was verified and FAILED${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}. Fix and re-run.`
30259
+ );
30260
+ } else if (record2 && !record2.full) {
30261
+ messages.push(
30262
+ ` This batch only passed a partial verify${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}; the gate requires --full.`
30263
+ );
30264
+ }
30265
+ messages.push(" Run the full pipeline: har env verify <agentId> --full");
30266
+ messages.push(
30267
+ " Note: verify hashes the WORKING TREE \u2014 stage exactly what you verified (git add -A)."
30268
+ );
30269
+ messages.push(" Bypass (humans only): HAR_SKIP_GATE=1 git commit \u2026 or git commit --no-verify");
30270
+ messages.push(" Configure via commitGate { mode: block|warn, scope: worktrees|all } in .har/stages.json");
30271
+ return { exitCode: mode === "block" ? 1 : 0, messages };
30272
+ }
30273
+ async function recordCommitAssociation(cwd) {
30274
+ try {
30275
+ const checkout = resolveCheckoutRoot(cwd);
30276
+ if (!checkout) return { attached: false };
30277
+ const commitSha = getHeadSha(checkout);
30278
+ const tree = tryGit3(checkout, "rev-parse HEAD^{tree}");
30279
+ if (!commitSha || !tree) return { attached: false };
30280
+ const updated = attachCommit(checkout, tree, commitSha);
30281
+ if (updated) {
30282
+ markDirty(updated.harnessRoot);
30283
+ return { attached: true, commitSha };
30284
+ }
30285
+ return { attached: false, commitSha };
30286
+ } catch {
30287
+ return { attached: false };
30288
+ }
30289
+ }
30290
+ var fs18, os7, path18, MARKER_START, MARKER_END, PRE_COMMIT_BLOCK, POST_COMMIT_BLOCK;
30291
+ var init_hooks = __esm({
30292
+ "src/core/hooks.ts"() {
30293
+ "use strict";
30294
+ fs18 = __toESM(require("fs"));
30295
+ os7 = __toESM(require("os"));
30296
+ path18 = __toESM(require("path"));
30297
+ init_shell();
30298
+ init_schema2();
30299
+ init_stages();
30300
+ init_change_batch();
30301
+ init_validations();
30302
+ init_sync_context();
30303
+ MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
30304
+ MARKER_END = "# <<< har commit gate <<<";
30305
+ PRE_COMMIT_BLOCK = [
30306
+ MARKER_START,
30307
+ 'if [ "$HAR_SKIP_GATE" != "1" ]; then',
30308
+ ' "$(git rev-parse --git-path hooks)/har-pre-commit" || exit 1',
30309
+ "fi",
30310
+ MARKER_END
30311
+ ].join("\n");
30312
+ POST_COMMIT_BLOCK = [
30313
+ MARKER_START,
30314
+ '"$(git rev-parse --git-path hooks)/har-post-commit" || true',
30315
+ MARKER_END
30316
+ ].join("\n");
30317
+ }
30318
+ });
30319
+
30320
+ // src/core/worktree-untracked.ts
30321
+ function listUntrackedAbsentFromWorktree(repoPath) {
30322
+ const toplevel = resolveCheckoutRoot(repoPath);
30323
+ if (!toplevel) return [];
30324
+ try {
30325
+ const raw = (0, import_child_process6.execSync)(
30326
+ "git --literal-pathspecs ls-files --others --exclude-standard --directory -z",
30327
+ {
30328
+ cwd: toplevel,
30329
+ encoding: "utf8",
30330
+ stdio: ["pipe", "pipe", "ignore"],
30331
+ timeout: 1e4,
30332
+ maxBuffer: 8 * 1024 * 1024
30333
+ }
30334
+ );
30335
+ return raw.split("\0").filter(Boolean).sort();
30336
+ } catch {
30337
+ return [];
30338
+ }
30339
+ }
30340
+ function formatUntrackedWorktreeWarning(paths, maxListed = MAX_LISTED) {
30341
+ if (paths.length === 0) return void 0;
30342
+ const shown = paths.slice(0, maxListed);
30343
+ const omitted = paths.length - shown.length;
30344
+ const listed = shown.join(", ") + (omitted > 0 ? ` (+${omitted} more)` : "");
30345
+ const n = paths.length;
30346
+ const noun = n === 1 ? "path" : "paths";
30347
+ const them = n === 1 ? "It is" : "They are";
30348
+ 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.`;
30349
+ }
30350
+ function worktreeCheckEnabled(env3, worktree) {
30351
+ if (worktree === false) return false;
30352
+ if (worktree === true) return true;
30353
+ return (env3.HARNESS_USE_WORKTREE ?? "true").toLowerCase() !== "false";
30354
+ }
30355
+ var import_child_process6, MAX_LISTED;
30356
+ var init_worktree_untracked = __esm({
30357
+ "src/core/worktree-untracked.ts"() {
30358
+ "use strict";
30359
+ import_child_process6 = require("child_process");
30360
+ init_hooks();
30361
+ MAX_LISTED = 8;
30362
+ }
30363
+ });
30364
+
29793
30365
  // src/utils/package-runner.ts
29794
30366
  function commandExists(command2) {
29795
- const result = (0, import_child_process6.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
30367
+ const result = (0, import_child_process7.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
29796
30368
  encoding: "utf8"
29797
30369
  });
29798
30370
  return result.status === 0 && Boolean(result.stdout?.trim());
@@ -29803,11 +30375,11 @@ function packageRunner() {
29803
30375
  cached = available ?? RUNNERS[0];
29804
30376
  return cached;
29805
30377
  }
29806
- var import_child_process6, RUNNERS, cached;
30378
+ var import_child_process7, RUNNERS, cached;
29807
30379
  var init_package_runner = __esm({
29808
30380
  "src/utils/package-runner.ts"() {
29809
30381
  "use strict";
29810
- import_child_process6 = require("child_process");
30382
+ import_child_process7 = require("child_process");
29811
30383
  RUNNERS = ["npx --yes", "bunx", "pnpm dlx", "yarn dlx"];
29812
30384
  }
29813
30385
  });
@@ -29815,7 +30387,7 @@ var init_package_runner = __esm({
29815
30387
  // src/core/slot-preflight.ts
29816
30388
  function listPm2Processes() {
29817
30389
  try {
29818
- const raw = (0, import_child_process7.execSync)(`${packageRunner()} pm2 jlist`, {
30390
+ const raw = (0, import_child_process8.execSync)(`${packageRunner()} pm2 jlist`, {
29819
30391
  encoding: "utf8",
29820
30392
  stdio: ["pipe", "pipe", "ignore"],
29821
30393
  timeout: 3e3
@@ -29860,10 +30432,14 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
29860
30432
  }
29861
30433
  return { port };
29862
30434
  }
30435
+ function scanUntrackedWorktreePaths(repoPath, env3) {
30436
+ if (!worktreeCheckEnabled(env3, void 0)) return [];
30437
+ return listUntrackedAbsentFromWorktree(repoPath);
30438
+ }
29863
30439
  function inspectSlotReadiness(repoPath, agentId, options = {}) {
29864
30440
  const harnessRoot = resolveHarnessRoot(repoPath);
29865
30441
  const env3 = readHarnessEnv(harnessRoot);
29866
- const projectName = env3.HARNESS_PROJECT_NAME ?? path16.basename(harnessRoot);
30442
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path19.basename(harnessRoot);
29867
30443
  const usesPm2 = harnessUsesPm2(repoPath);
29868
30444
  const blockers = [];
29869
30445
  const remediations = [];
@@ -29917,6 +30493,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29917
30493
  }
29918
30494
  }
29919
30495
  let allocatedPorts = false;
30496
+ let portChoiceExplained = false;
29920
30497
  if (usesPm2 && (options.allocatePorts ?? true)) {
29921
30498
  const alloc = allocateAppPorts(repoPath, agentId);
29922
30499
  if ("error" in alloc) {
@@ -29936,7 +30513,9 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29936
30513
  agentId,
29937
30514
  portStep(env3)
29938
30515
  );
29939
- warnings.push(...controlDefaultPortWarnings(containers, feDefault, alloc.frontend));
30516
+ const portWarnings = controlDefaultPortWarnings(containers, feDefault, alloc.frontend);
30517
+ portChoiceExplained = portWarnings.length > 0;
30518
+ warnings.push(...portWarnings);
29940
30519
  for (const [label, port] of Object.entries({ frontend: alloc.frontend, api: alloc.api })) {
29941
30520
  const control = controlContainerOnPort(containers, port);
29942
30521
  if (control) {
@@ -29988,6 +30567,11 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29988
30567
  }
29989
30568
  }
29990
30569
  }
30570
+ if (worktreeCheckEnabled(env3, options.worktree)) {
30571
+ const untracked = options.untrackedPaths ?? scanUntrackedWorktreePaths(repoPath, env3);
30572
+ const untrackedWarning = formatUntrackedWorktreeWarning(untracked);
30573
+ if (untrackedWarning) warnings.push(untrackedWarning);
30574
+ }
29991
30575
  const canLaunch = blockers.length === 0;
29992
30576
  return {
29993
30577
  canLaunch,
@@ -29996,6 +30580,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
29996
30580
  remediations: [...new Set(remediations)],
29997
30581
  ports: Object.keys(ports).length > 0 ? ports : void 0,
29998
30582
  allocatedPorts: allocatedPorts || void 0,
30583
+ portChoiceExplained: portChoiceExplained || void 0,
29999
30584
  warnings: warnings.length > 0 ? warnings : void 0
30000
30585
  };
30001
30586
  }
@@ -30012,7 +30597,7 @@ function formatPreflightReport(agentId, readiness) {
30012
30597
  p.db !== void 0 ? `db=${p.db}` : void 0
30013
30598
  ].filter(Boolean);
30014
30599
  if (parts.length) lines2.push(` Ports: ${parts.join(" ")}`);
30015
- if (readiness.allocatedPorts && !readiness.warnings?.length) {
30600
+ if (readiness.allocatedPorts && !readiness.portChoiceExplained) {
30016
30601
  lines2.push(" (alternate ports selected \u2014 defaults were busy)");
30017
30602
  }
30018
30603
  }
@@ -30028,20 +30613,24 @@ function formatPreflightReport(agentId, readiness) {
30028
30613
  lines2.push(` [${b2.code}] ${b2.message}`);
30029
30614
  if (b2.remediation) lines2.push(` \u2192 ${b2.remediation}`);
30030
30615
  }
30616
+ for (const w2 of readiness.warnings ?? []) {
30617
+ lines2.push(` WARN: ${w2}`);
30618
+ }
30031
30619
  return lines2.join("\n");
30032
30620
  }
30033
- var path16, import_child_process7;
30621
+ var path19, import_child_process8;
30034
30622
  var init_slot_preflight = __esm({
30035
30623
  "src/core/slot-preflight.ts"() {
30036
30624
  "use strict";
30037
- path16 = __toESM(require("path"));
30625
+ path19 = __toESM(require("path"));
30038
30626
  init_env();
30039
30627
  init_manifest();
30040
30628
  init_control_port();
30041
30629
  init_slot_launch_guard_occupied();
30042
30630
  init_slot_ports();
30043
30631
  init_slot_registry();
30044
- import_child_process7 = require("child_process");
30632
+ init_worktree_untracked();
30633
+ import_child_process8 = require("child_process");
30045
30634
  init_package_runner();
30046
30635
  }
30047
30636
  });
@@ -30049,7 +30638,7 @@ var init_slot_preflight = __esm({
30049
30638
  // src/core/slot-status.ts
30050
30639
  function readGitRemote(repoPath) {
30051
30640
  try {
30052
- const remote = (0, import_child_process8.execSync)("git remote get-url origin", {
30641
+ const remote = (0, import_child_process9.execSync)("git remote get-url origin", {
30053
30642
  cwd: repoPath,
30054
30643
  encoding: "utf8",
30055
30644
  stdio: ["pipe", "pipe", "ignore"]
@@ -30061,7 +30650,7 @@ function readGitRemote(repoPath) {
30061
30650
  }
30062
30651
  function runGit2(cwd, args) {
30063
30652
  try {
30064
- return (0, import_child_process8.execSync)(`git ${args}`, {
30653
+ return (0, import_child_process9.execSync)(`git ${args}`, {
30065
30654
  cwd,
30066
30655
  encoding: "utf8",
30067
30656
  stdio: ["pipe", "pipe", "ignore"]
@@ -30075,7 +30664,7 @@ function readWorktreeBranch(worktreePath) {
30075
30664
  }
30076
30665
  function gitCommonDir3(cwd) {
30077
30666
  const out = runGit2(cwd, "rev-parse --git-common-dir");
30078
- return out ? path17.resolve(cwd, out) : void 0;
30667
+ return out ? path20.resolve(cwd, out) : void 0;
30079
30668
  }
30080
30669
  function sameGitCheckout3(a, b2) {
30081
30670
  const left2 = gitCommonDir3(a);
@@ -30087,12 +30676,12 @@ function gitPrefix2(cwd) {
30087
30676
  return out ?? "";
30088
30677
  }
30089
30678
  function discoverSessionWorktreePath(harnessRoot, agentId) {
30090
- const worktreesRoot = path17.join(os6.homedir(), "worktrees");
30091
- if (!fs16.existsSync(worktreesRoot)) return void 0;
30679
+ const worktreesRoot = path20.join(os8.homedir(), "worktrees");
30680
+ if (!fs19.existsSync(worktreesRoot)) return void 0;
30092
30681
  const suffix = `-har-agent-${agentId}-`;
30093
30682
  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}`))
30683
+ const matches = fs19.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path20.join(worktreesRoot, entry.name)).filter(
30684
+ (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs19.existsSync(path20.join(candidate, relPrefix, `.env.agent.${agentId}`))
30096
30685
  );
30097
30686
  return matches.sort()[0];
30098
30687
  }
@@ -30137,7 +30726,7 @@ function lastBuildPass(run2) {
30137
30726
  }
30138
30727
  function listPm2Processes2() {
30139
30728
  try {
30140
- const raw = (0, import_child_process8.execSync)(`${packageRunner()} pm2 jlist`, {
30729
+ const raw = (0, import_child_process9.execSync)(`${packageRunner()} pm2 jlist`, {
30141
30730
  encoding: "utf8",
30142
30731
  stdio: ["pipe", "pipe", "ignore"],
30143
30732
  timeout: 3e3
@@ -30165,20 +30754,20 @@ function detectPm2Issue(projectName, agentId, session, procs) {
30165
30754
  }
30166
30755
  return void 0;
30167
30756
  }
30168
- function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
30757
+ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs, untrackedPaths) {
30169
30758
  const env3 = readHarnessEnv(harnessRoot);
30170
- const projectName = env3.HARNESS_PROJECT_NAME ?? path17.basename(harnessRoot);
30759
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path20.basename(harnessRoot);
30171
30760
  const session = readSlotRegistry(harnessRoot, agentId);
30172
- const legacyWorktreePath = path17.join(
30173
- os6.homedir(),
30761
+ const legacyWorktreePath = path20.join(
30762
+ os8.homedir(),
30174
30763
  "worktrees",
30175
30764
  `${projectName}-agent-${agentId}`
30176
30765
  );
30177
- const worktreePath = session?.worktreePath && fs16.existsSync(session.worktreePath) ? session.worktreePath : fs16.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
30766
+ const worktreePath = session?.worktreePath && fs19.existsSync(session.worktreePath) ? session.worktreePath : fs19.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
30178
30767
  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;
30768
+ const envInWorkDir = workDir ? fs19.existsSync(path20.join(workDir, `.env.agent.${agentId}`)) : false;
30769
+ const envInRoot = fs19.existsSync(path20.join(harnessRoot, `.env.agent.${agentId}`));
30770
+ const envInWorktree = worktreePath ? fs19.existsSync(path20.join(worktreePath, `.env.agent.${agentId}`)) : false;
30182
30771
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
30183
30772
  const latest = latestRunForAgent(runs, agentId);
30184
30773
  const verifyRun = runs.find((r) => r.agentId === agentId && r.stageId === "verify");
@@ -30194,7 +30783,8 @@ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
30194
30783
  const pm2Issue = detectPm2Issue(projectName, agentId, session, pm2Procs);
30195
30784
  const readiness = inspectSlotReadiness(harnessRoot, agentId, {
30196
30785
  allocatePorts: true,
30197
- occupied: { active, dirty: drift.dirty }
30786
+ occupied: { active, dirty: drift.dirty },
30787
+ untrackedPaths: untrackedPaths ?? scanUntrackedWorktreePaths(harnessRoot, env3)
30198
30788
  });
30199
30789
  const resumeHint = session?.status === "failed" || session?.status === "starting" ? `har env launch ${agentId} --resume (alias: har env recover ${agentId})` : void 0;
30200
30790
  return {
@@ -30231,23 +30821,24 @@ function collectEnvironmentStatus(repoPath) {
30231
30821
  const manifest = readManifest(harnessRoot);
30232
30822
  const slotIds = getAgentSlotIds(harnessRoot);
30233
30823
  const pm2Procs = listPm2Processes2();
30824
+ const untrackedPaths = scanUntrackedWorktreePaths(harnessRoot, readHarnessEnv(harnessRoot));
30234
30825
  return {
30235
- repoPath: path17.resolve(repoPath),
30826
+ repoPath: path20.resolve(repoPath),
30236
30827
  harnessRoot,
30237
30828
  gitRemote: readGitRemote(harnessRoot),
30238
30829
  profile: manifest?.profile,
30239
- slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs)),
30830
+ slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs, untrackedPaths)),
30240
30831
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
30241
30832
  };
30242
30833
  }
30243
- var fs16, os6, path17, import_child_process8, BYPASS_WARNING_MS;
30834
+ var fs19, os8, path20, import_child_process9, BYPASS_WARNING_MS;
30244
30835
  var init_slot_status = __esm({
30245
30836
  "src/core/slot-status.ts"() {
30246
30837
  "use strict";
30247
- fs16 = __toESM(require("fs"));
30248
- os6 = __toESM(require("os"));
30249
- path17 = __toESM(require("path"));
30250
- import_child_process8 = require("child_process");
30838
+ fs19 = __toESM(require("fs"));
30839
+ os8 = __toESM(require("os"));
30840
+ path20 = __toESM(require("path"));
30841
+ import_child_process9 = require("child_process");
30251
30842
  init_env();
30252
30843
  init_manifest();
30253
30844
  init_stages();
@@ -30260,256 +30851,41 @@ var init_slot_status = __esm({
30260
30851
  }
30261
30852
  });
30262
30853
 
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
30854
  // src/core/work-units.ts
30479
30855
  function harEvidenceDir(harnessRoot, name) {
30480
- return path20.join(harnessRoot, ".har", name);
30856
+ return path21.join(harnessRoot, ".har", name);
30481
30857
  }
30482
30858
  function hashedRecordPath(harnessRoot, dir, id) {
30483
30859
  const digest = crypto4.createHash("sha256").update(id).digest("hex");
30484
- return path20.join(harEvidenceDir(harnessRoot, dir), `${digest}.json`);
30860
+ return path21.join(harEvidenceDir(harnessRoot, dir), `${digest}.json`);
30485
30861
  }
30486
30862
  function writeRecord2(file, value) {
30487
- fs19.mkdirSync(path20.dirname(file), { recursive: true });
30863
+ fs20.mkdirSync(path21.dirname(file), { recursive: true });
30488
30864
  const temp = `${file}.${process.pid}.tmp`;
30489
- fs19.writeFileSync(temp, `${JSON.stringify(value, null, 2)}
30865
+ fs20.writeFileSync(temp, `${JSON.stringify(value, null, 2)}
30490
30866
  `);
30491
- fs19.renameSync(temp, file);
30867
+ fs20.renameSync(temp, file);
30492
30868
  }
30493
30869
  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") : "";
30870
+ const harDir = path21.join(harnessRoot, ".har");
30871
+ if (!fs20.existsSync(harDir)) return;
30872
+ const gitignorePath = path21.join(harDir, ".gitignore");
30873
+ const content = fs20.existsSync(gitignorePath) ? fs20.readFileSync(gitignorePath, "utf8") : "";
30498
30874
  const lines2 = new Set(content.split("\n").map((line) => line.trim()));
30499
30875
  const missing = EVIDENCE_DIRS.map((dir) => `${dir}/`).filter((dir) => !lines2.has(dir));
30500
30876
  if (missing.length === 0) return;
30501
30877
  const separator = content.length === 0 || content.endsWith("\n") ? "" : "\n";
30502
- fs19.writeFileSync(gitignorePath, `${content}${separator}${missing.join("\n")}
30878
+ fs20.writeFileSync(gitignorePath, `${content}${separator}${missing.join("\n")}
30503
30879
  `);
30504
30880
  }
30505
30881
  function listRecords(harnessRoot, dir, parse4) {
30506
30882
  const recordDir = harEvidenceDir(harnessRoot, dir);
30507
- if (!fs19.existsSync(recordDir)) return [];
30883
+ if (!fs20.existsSync(recordDir)) return [];
30508
30884
  const records = [];
30509
- for (const name of fs19.readdirSync(recordDir)) {
30885
+ for (const name of fs20.readdirSync(recordDir)) {
30510
30886
  if (!name.endsWith(".json")) continue;
30511
30887
  try {
30512
- records.push(parse4(JSON.parse(fs19.readFileSync(path20.join(recordDir, name), "utf8"))));
30888
+ records.push(parse4(JSON.parse(fs20.readFileSync(path21.join(recordDir, name), "utf8"))));
30513
30889
  } catch {
30514
30890
  }
30515
30891
  }
@@ -30518,9 +30894,9 @@ function listRecords(harnessRoot, dir, parse4) {
30518
30894
  function findWorkUnit(harnessRoot, workUnitId) {
30519
30895
  const metadata = WorkUnitMetadataSchema.parse({ workUnitId });
30520
30896
  const file = hashedRecordPath(harnessRoot, WORK_UNITS_DIR, metadata.workUnitId);
30521
- if (!fs19.existsSync(file)) return void 0;
30897
+ if (!fs20.existsSync(file)) return void 0;
30522
30898
  try {
30523
- return WorkUnitRecordSchema.parse(JSON.parse(fs19.readFileSync(file, "utf8")));
30899
+ return WorkUnitRecordSchema.parse(JSON.parse(fs20.readFileSync(file, "utf8")));
30524
30900
  } catch {
30525
30901
  return void 0;
30526
30902
  }
@@ -30598,10 +30974,10 @@ function bindValidationToAttempt(harnessRoot, input) {
30598
30974
  VALIDATION_BINDINGS_DIR,
30599
30975
  stableKey
30600
30976
  );
30601
- if (fs19.existsSync(existingFile)) {
30977
+ if (fs20.existsSync(existingFile)) {
30602
30978
  try {
30603
30979
  return ValidationBindingRecordSchema.parse(
30604
- JSON.parse(fs19.readFileSync(existingFile, "utf8"))
30980
+ JSON.parse(fs20.readFileSync(existingFile, "utf8"))
30605
30981
  );
30606
30982
  } catch {
30607
30983
  }
@@ -30636,13 +31012,13 @@ function decideWorkUnitOutcome(harnessRoot, workUnitId, outcome) {
30636
31012
  markDirty(harnessRoot);
30637
31013
  return record2;
30638
31014
  }
30639
- var crypto4, fs19, path20, WORK_UNITS_DIR, WORK_ATTEMPTS_DIR, VALIDATION_BINDINGS_DIR, EVIDENCE_DIRS;
31015
+ var crypto4, fs20, path21, WORK_UNITS_DIR, WORK_ATTEMPTS_DIR, VALIDATION_BINDINGS_DIR, EVIDENCE_DIRS;
30640
31016
  var init_work_units = __esm({
30641
31017
  "src/core/work-units.ts"() {
30642
31018
  "use strict";
30643
31019
  crypto4 = __toESM(require("crypto"));
30644
- fs19 = __toESM(require("fs"));
30645
- path20 = __toESM(require("path"));
31020
+ fs20 = __toESM(require("fs"));
31021
+ path21 = __toESM(require("path"));
30646
31022
  init_schema2();
30647
31023
  init_sync_context();
30648
31024
  WORK_UNITS_DIR = "work-units";
@@ -31265,8 +31641,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs) {
31265
31641
  ${TELEMETRY_MARKER_END}
31266
31642
  `;
31267
31643
  let existing = "";
31268
- if (fs20.existsSync(envFilePath)) {
31269
- existing = fs20.readFileSync(envFilePath, "utf8");
31644
+ if (fs21.existsSync(envFilePath)) {
31645
+ existing = fs21.readFileSync(envFilePath, "utf8");
31270
31646
  const start = existing.indexOf(TELEMETRY_MARKER_START);
31271
31647
  if (start >= 0) {
31272
31648
  const end = existing.indexOf(TELEMETRY_MARKER_END, start);
@@ -31277,16 +31653,16 @@ ${TELEMETRY_MARKER_END}
31277
31653
  }
31278
31654
  }
31279
31655
  }
31280
- fs20.mkdirSync(path21.dirname(envFilePath), { recursive: true });
31656
+ fs21.mkdirSync(path22.dirname(envFilePath), { recursive: true });
31281
31657
  const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
31282
- fs20.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
31658
+ fs21.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
31283
31659
  }
31284
- var fs20, path21, TELEMETRY_MARKER_START, TELEMETRY_MARKER_END;
31660
+ var fs21, path22, TELEMETRY_MARKER_START, TELEMETRY_MARKER_END;
31285
31661
  var init_telemetry_env = __esm({
31286
31662
  "src/core/telemetry-env.ts"() {
31287
31663
  "use strict";
31288
- fs20 = __toESM(require("fs"));
31289
- path21 = __toESM(require("path"));
31664
+ fs21 = __toESM(require("fs"));
31665
+ path22 = __toESM(require("path"));
31290
31666
  init_control_config();
31291
31667
  init_shell();
31292
31668
  TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
@@ -31299,32 +31675,32 @@ function stripTrailingSeparators(value) {
31299
31675
  return value.replace(/[/\\]+$/, "");
31300
31676
  }
31301
31677
  function isWorkspaceUnderPath(workspace, basePath) {
31302
- const workspaceNorm = stripTrailingSeparators(path22.resolve(workspace));
31303
- const baseNorm = stripTrailingSeparators(path22.resolve(basePath));
31678
+ const workspaceNorm = stripTrailingSeparators(path23.resolve(workspace));
31679
+ const baseNorm = stripTrailingSeparators(path23.resolve(basePath));
31304
31680
  if (!workspaceNorm || !baseNorm) return false;
31305
- return workspaceNorm === baseNorm || workspaceNorm.startsWith(`${baseNorm}${path22.sep}`);
31681
+ return workspaceNorm === baseNorm || workspaceNorm.startsWith(`${baseNorm}${path23.sep}`);
31306
31682
  }
31307
31683
  function workspaceMatchesTarget(workspace, targets) {
31308
31684
  return targets.some((target) => isWorkspaceUnderPath(workspace, target));
31309
31685
  }
31310
- var path22;
31686
+ var path23;
31311
31687
  var init_workspace_path_match = __esm({
31312
31688
  "src/core/workspace-path-match.ts"() {
31313
31689
  "use strict";
31314
- path22 = __toESM(require("path"));
31690
+ path23 = __toESM(require("path"));
31315
31691
  }
31316
31692
  });
31317
31693
 
31318
31694
  // src/core/usage-harvest/claude.ts
31319
31695
  function claudeProjectsRoot() {
31320
- return process.env.HAR_CLAUDE_PROJECTS_DIR ? path23.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path23.join(os9.homedir(), ".claude", "projects");
31696
+ return process.env.HAR_CLAUDE_PROJECTS_DIR ? path24.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path24.join(os10.homedir(), ".claude", "projects");
31321
31697
  }
31322
31698
  function encodeClaudeProjectDir(cwd) {
31323
31699
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
31324
31700
  }
31325
31701
  function readJsonlRecords(filePath) {
31326
- if (!fs21.existsSync(filePath)) return [];
31327
- const lines2 = fs21.readFileSync(filePath, "utf8").split(/\r?\n/);
31702
+ if (!fs22.existsSync(filePath)) return [];
31703
+ const lines2 = fs22.readFileSync(filePath, "utf8").split(/\r?\n/);
31328
31704
  const out = [];
31329
31705
  for (const line of lines2) {
31330
31706
  const trimmed = line.trim();
@@ -31389,18 +31765,18 @@ function findMatchingClaudeTranscripts(slot) {
31389
31765
  const fallbackTargets = slot.includeRepoPathFallback && slot.repoPath && !primaryTargets.includes(slot.repoPath) ? [slot.repoPath] : [];
31390
31766
  if (primaryTargets.length === 0 && fallbackTargets.length === 0) return [];
31391
31767
  const root = claudeProjectsRoot();
31392
- if (!fs21.existsSync(root)) return [];
31768
+ if (!fs22.existsSync(root)) return [];
31393
31769
  const primary = [];
31394
31770
  const fallback = [];
31395
- for (const entry of fs21.readdirSync(root, { withFileTypes: true })) {
31771
+ for (const entry of fs22.readdirSync(root, { withFileTypes: true })) {
31396
31772
  if (!entry.isDirectory()) continue;
31397
- const projectDir = path23.join(root, entry.name);
31773
+ const projectDir = path24.join(root, entry.name);
31398
31774
  const primaryEncoded = primaryTargets.some((t) => entry.name === encodeClaudeProjectDir(t));
31399
31775
  const fallbackEncoded = fallbackTargets.some((t) => entry.name === encodeClaudeProjectDir(t));
31400
- for (const file of fs21.readdirSync(projectDir)) {
31776
+ for (const file of fs22.readdirSync(projectDir)) {
31401
31777
  if (!file.endsWith(".jsonl")) continue;
31402
- const filePath = path23.join(projectDir, file);
31403
- const stat = fs21.statSync(filePath);
31778
+ const filePath = path24.join(projectDir, file);
31779
+ const stat = fs22.statSync(filePath);
31404
31780
  const records = readJsonlRecords(filePath);
31405
31781
  let primaryHit = primaryEncoded;
31406
31782
  let fallbackHit = fallbackEncoded;
@@ -31519,13 +31895,13 @@ function harvestClaudeEvents(slot) {
31519
31895
  });
31520
31896
  return extractPromptEvents(matches[0].records, sessionKey, slot).slice(-40);
31521
31897
  }
31522
- var fs21, os9, path23;
31898
+ var fs22, os10, path24;
31523
31899
  var init_claude = __esm({
31524
31900
  "src/core/usage-harvest/claude.ts"() {
31525
31901
  "use strict";
31526
- fs21 = __toESM(require("fs"));
31527
- os9 = __toESM(require("os"));
31528
- path23 = __toESM(require("path"));
31902
+ fs22 = __toESM(require("fs"));
31903
+ os10 = __toESM(require("os"));
31904
+ path24 = __toESM(require("path"));
31529
31905
  init_telemetry_config();
31530
31906
  init_telemetry_env();
31531
31907
  init_workspace_path_match();
@@ -31534,19 +31910,19 @@ var init_claude = __esm({
31534
31910
 
31535
31911
  // src/core/usage-harvest/codex.ts
31536
31912
  function codexSessionsRoot() {
31537
- return process.env.HAR_CODEX_SESSIONS_DIR ? path24.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path24.join(os10.homedir(), ".codex", "sessions");
31913
+ return process.env.HAR_CODEX_SESSIONS_DIR ? path25.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path25.join(os11.homedir(), ".codex", "sessions");
31538
31914
  }
31539
31915
  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);
31916
+ if (!fs23.existsSync(dir)) return out;
31917
+ for (const entry of fs23.readdirSync(dir, { withFileTypes: true })) {
31918
+ const full = path25.join(dir, entry.name);
31543
31919
  if (entry.isDirectory()) walkJsonlFiles(full, out);
31544
31920
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
31545
31921
  }
31546
31922
  return out;
31547
31923
  }
31548
31924
  function readJsonl(filePath) {
31549
- const lines2 = fs22.readFileSync(filePath, "utf8").split(/\r?\n/);
31925
+ const lines2 = fs23.readFileSync(filePath, "utf8").split(/\r?\n/);
31550
31926
  const out = [];
31551
31927
  for (const line of lines2) {
31552
31928
  const trimmed = line.trim();
@@ -31613,7 +31989,7 @@ function harvestCodexUsage(slot) {
31613
31989
  const extracted = extractCodexTokens(records);
31614
31990
  if (!extracted.cwd) continue;
31615
31991
  if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
31616
- const mtime = fs22.statSync(file).mtimeMs;
31992
+ const mtime = fs23.statSync(file).mtimeMs;
31617
31993
  if (workspaceMatchesTarget(extracted.cwd, primaryTargets)) {
31618
31994
  if (mtime >= bestPrimaryMtime) {
31619
31995
  bestPrimaryMtime = mtime;
@@ -31648,13 +32024,13 @@ function harvestCodexUsage(slot) {
31648
32024
  lastSeenAt: now
31649
32025
  };
31650
32026
  }
31651
- var fs22, os10, path24;
32027
+ var fs23, os11, path25;
31652
32028
  var init_codex = __esm({
31653
32029
  "src/core/usage-harvest/codex.ts"() {
31654
32030
  "use strict";
31655
- fs22 = __toESM(require("fs"));
31656
- os10 = __toESM(require("os"));
31657
- path24 = __toESM(require("path"));
32031
+ fs23 = __toESM(require("fs"));
32032
+ os11 = __toESM(require("os"));
32033
+ path25 = __toESM(require("path"));
31658
32034
  init_telemetry_env();
31659
32035
  init_workspace_path_match();
31660
32036
  }
@@ -31702,15 +32078,15 @@ var init_usage_harvest = __esm({
31702
32078
  // src/core/portal-watermark.ts
31703
32079
  function getStatePath() {
31704
32080
  if (process.env.HAR_PORTAL_SYNC_STATE_PATH) {
31705
- return path25.resolve(process.env.HAR_PORTAL_SYNC_STATE_PATH);
32081
+ return path26.resolve(process.env.HAR_PORTAL_SYNC_STATE_PATH);
31706
32082
  }
31707
- return path25.join(os11.homedir(), ".har", "portal-sync-state.json");
32083
+ return path26.join(os12.homedir(), ".har", "portal-sync-state.json");
31708
32084
  }
31709
32085
  function readState() {
31710
32086
  const statePath = getStatePath();
31711
32087
  try {
31712
- if (!fs23.existsSync(statePath)) return { states: [] };
31713
- const parsed = JSON.parse(fs23.readFileSync(statePath, "utf8"));
32088
+ if (!fs24.existsSync(statePath)) return { states: [] };
32089
+ const parsed = JSON.parse(fs24.readFileSync(statePath, "utf8"));
31714
32090
  return Array.isArray(parsed.states) ? parsed : { states: [] };
31715
32091
  } catch {
31716
32092
  return { states: [] };
@@ -31718,8 +32094,8 @@ function readState() {
31718
32094
  }
31719
32095
  function writeState(state) {
31720
32096
  const statePath = getStatePath();
31721
- fs23.mkdirSync(path25.dirname(statePath), { recursive: true });
31722
- fs23.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
32097
+ fs24.mkdirSync(path26.dirname(statePath), { recursive: true });
32098
+ fs24.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
31723
32099
  }
31724
32100
  function readPortalWatermark(repoPath, portalUrl) {
31725
32101
  const canonical = canonicalizeControlRepoPath(repoPath);
@@ -31773,13 +32149,13 @@ function selectSince(rows, since, getTimestamp) {
31773
32149
  }
31774
32150
  return { selected, maxSyncedAt };
31775
32151
  }
31776
- var fs23, os11, path25;
32152
+ var fs24, os12, path26;
31777
32153
  var init_portal_watermark = __esm({
31778
32154
  "src/core/portal-watermark.ts"() {
31779
32155
  "use strict";
31780
- fs23 = __toESM(require("fs"));
31781
- os11 = __toESM(require("os"));
31782
- path25 = __toESM(require("path"));
32156
+ fs24 = __toESM(require("fs"));
32157
+ os12 = __toESM(require("os"));
32158
+ path26 = __toESM(require("path"));
31783
32159
  init_control_repo_path();
31784
32160
  }
31785
32161
  });
@@ -31800,8 +32176,8 @@ async function getJson(url) {
31800
32176
  async function resolveControlRepoId(apiUrl, repoPath) {
31801
32177
  const repos = await getJson(`${apiUrl}/api/repos`);
31802
32178
  if (!Array.isArray(repos)) return null;
31803
- const target = path26.resolve(repoPath);
31804
- return repos.find((repo) => path26.resolve(repo.path) === target)?.id ?? null;
32179
+ const target = path27.resolve(repoPath);
32180
+ return repos.find((repo) => path27.resolve(repo.path) === target)?.id ?? null;
31805
32181
  }
31806
32182
  function optionalString(value) {
31807
32183
  return value == null ? void 0 : value;
@@ -31870,11 +32246,11 @@ async function fetchPersistedPortalTelemetry(repoPath, apiUrl, options) {
31870
32246
  maxSyncedAt: maxTimestamp(usage2.maxSyncedAt, events.maxSyncedAt)
31871
32247
  };
31872
32248
  }
31873
- var path26, FETCH_TIMEOUT_MS;
32249
+ var path27, FETCH_TIMEOUT_MS;
31874
32250
  var init_control_persisted_usage = __esm({
31875
32251
  "src/core/control-persisted-usage.ts"() {
31876
32252
  "use strict";
31877
- path26 = __toESM(require("path"));
32253
+ path27 = __toESM(require("path"));
31878
32254
  init_portal_watermark();
31879
32255
  FETCH_TIMEOUT_MS = 3e3;
31880
32256
  }
@@ -32206,7 +32582,7 @@ async function buildPortalPayload(repoPath, controlApiUrl, since, full = false)
32206
32582
  );
32207
32583
  if (full && isTelemetryEnabled() && usage2.length === 0 && runs.some((r) => r.agentId != null)) {
32208
32584
  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\`.`
32585
+ `${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
32586
  );
32211
32587
  }
32212
32588
  const syncBody = {
@@ -32333,7 +32709,7 @@ async function syncRepoWithLocalControl(options) {
32333
32709
  const listResponse = await fetch(`${apiUrl}/api/repos`);
32334
32710
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
32335
32711
  const repos = await listResponse.json();
32336
- const existing = repos.find((r) => path27.resolve(r.path) === repoPath);
32712
+ const existing = repos.find((r) => path28.resolve(r.path) === repoPath);
32337
32713
  if (!existing) {
32338
32714
  return;
32339
32715
  }
@@ -32546,7 +32922,7 @@ async function syncRunWithLocalControl(repoPath, run2, apiUrl) {
32546
32922
  const listResponse = await fetch(`${apiUrl}/api/repos`);
32547
32923
  if (!listResponse.ok) return;
32548
32924
  const repos = await listResponse.json();
32549
- repoId = repos.find((r) => path27.resolve(r.path) === repoPath)?.id;
32925
+ repoId = repos.find((r) => path28.resolve(r.path) === repoPath)?.id;
32550
32926
  }
32551
32927
  if (!repoId) return;
32552
32928
  const runsBody = SyncRunsInputSchema.parse({ runs: [run2] });
@@ -32576,11 +32952,11 @@ async function syncRunWithControlAsync(repoPath, run2) {
32576
32952
  `);
32577
32953
  }
32578
32954
  }
32579
- var path27, DEFAULT_MAX_BATCH_BYTES, DEFAULT_SYNC_OVERLAP_MS, MAX_FETCH_ATTEMPTS, SYNC_TIMEOUT_MS, EXPLICIT_SYNC_TIMEOUT_MS;
32955
+ var path28, DEFAULT_MAX_BATCH_BYTES, DEFAULT_SYNC_OVERLAP_MS, MAX_FETCH_ATTEMPTS, SYNC_TIMEOUT_MS, EXPLICIT_SYNC_TIMEOUT_MS;
32580
32956
  var init_control_sync = __esm({
32581
32957
  "src/core/control-sync.ts"() {
32582
32958
  "use strict";
32583
- path27 = __toESM(require("path"));
32959
+ path28 = __toESM(require("path"));
32584
32960
  init_manifest();
32585
32961
  init_stages();
32586
32962
  init_schema2();
@@ -32612,7 +32988,7 @@ var init_control_sync = __esm({
32612
32988
  // src/core/sync-context.ts
32613
32989
  function markDirty(repoPath) {
32614
32990
  if (!repoPath) return;
32615
- dirtyRepos.add(path28.resolve(repoPath));
32991
+ dirtyRepos.add(path29.resolve(repoPath));
32616
32992
  }
32617
32993
  function markAllRegisteredDirty() {
32618
32994
  syncAllRegistered = true;
@@ -32637,11 +33013,11 @@ async function syncDirtyRepos() {
32637
33013
  } catch {
32638
33014
  }
32639
33015
  }
32640
- var path28, dirtyRepos, syncAllRegistered;
33016
+ var path29, dirtyRepos, syncAllRegistered;
32641
33017
  var init_sync_context = __esm({
32642
33018
  "src/core/sync-context.ts"() {
32643
33019
  "use strict";
32644
- path28 = __toESM(require("path"));
33020
+ path29 = __toESM(require("path"));
32645
33021
  dirtyRepos = /* @__PURE__ */ new Set();
32646
33022
  syncAllRegistered = false;
32647
33023
  }
@@ -53412,32 +53788,32 @@ __export(otel_hooks_exports, {
53412
53788
  });
53413
53789
  function getOtelHooksHome() {
53414
53790
  if (process.env.HAR_OTEL_HOOKS_HOME) {
53415
- return path47.resolve(process.env.HAR_OTEL_HOOKS_HOME);
53791
+ return path48.resolve(process.env.HAR_OTEL_HOOKS_HOME);
53416
53792
  }
53417
- return path47.join(os15.homedir(), ".har", "otel-hooks");
53793
+ return path48.join(os16.homedir(), ".har", "otel-hooks");
53418
53794
  }
53419
53795
  function getOtelHooksConfigPath(hooksHome = getOtelHooksHome()) {
53420
- return path47.join(hooksHome, "otel_config.json");
53796
+ return path48.join(hooksHome, "otel_config.json");
53421
53797
  }
53422
53798
  function getOtelHooksWrapperPath(hooksHome = getOtelHooksHome()) {
53423
- return path47.join(hooksHome, "run-otel-hook.sh");
53799
+ return path48.join(hooksHome, "run-otel-hook.sh");
53424
53800
  }
53425
53801
  function getOtelHooksStateDir(hooksHome = getOtelHooksHome()) {
53426
- return path47.join(hooksHome, "state");
53802
+ return path48.join(hooksHome, "state");
53427
53803
  }
53428
53804
  function localOtelHookBin(hooksHome) {
53429
- return path47.join(hooksHome, "node_modules", ".bin", "otel-hook");
53805
+ return path48.join(hooksHome, "node_modules", ".bin", "otel-hook");
53430
53806
  }
53431
53807
  function installedPackageVersion(hooksHome) {
53432
53808
  try {
53433
- const pkgPath = path47.join(
53809
+ const pkgPath = path48.join(
53434
53810
  hooksHome,
53435
53811
  "node_modules",
53436
53812
  "@osfactory",
53437
53813
  "otel-hook",
53438
53814
  "package.json"
53439
53815
  );
53440
- const pkg = JSON.parse(fs41.readFileSync(pkgPath, "utf8"));
53816
+ const pkg = JSON.parse(fs42.readFileSync(pkgPath, "utf8"));
53441
53817
  return typeof pkg.version === "string" ? pkg.version : null;
53442
53818
  } catch {
53443
53819
  return null;
@@ -53488,15 +53864,15 @@ function buildOtelHooksConfig(options) {
53488
53864
  };
53489
53865
  }
53490
53866
  function writeOtelHooksConfig(config2, hooksHome = getOtelHooksHome()) {
53491
- fs41.mkdirSync(hooksHome, { recursive: true });
53867
+ fs42.mkdirSync(hooksHome, { recursive: true });
53492
53868
  const configPath = getOtelHooksConfigPath(hooksHome);
53493
- fs41.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
53869
+ fs42.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
53494
53870
  `);
53495
53871
  return configPath;
53496
53872
  }
53497
53873
  function writeOtelHooksWrapper(otelHookCommand, hooksHome = getOtelHooksHome()) {
53498
- fs41.mkdirSync(hooksHome, { recursive: true });
53499
- fs41.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
53874
+ fs42.mkdirSync(hooksHome, { recursive: true });
53875
+ fs42.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
53500
53876
  const wrapperPath = getOtelHooksWrapperPath(hooksHome);
53501
53877
  const configPath = getOtelHooksConfigPath(hooksHome);
53502
53878
  const stateDir = getOtelHooksStateDir(hooksHome);
@@ -53508,22 +53884,22 @@ exec "${otelHookCommand}" run \\
53508
53884
  --state-dir "${stateDir}" \\
53509
53885
  "$@"
53510
53886
  `;
53511
- fs41.writeFileSync(wrapperPath, script, { mode: 493 });
53887
+ fs42.writeFileSync(wrapperPath, script, { mode: 493 });
53512
53888
  try {
53513
- fs41.chmodSync(wrapperPath, 493);
53889
+ fs42.chmodSync(wrapperPath, 493);
53514
53890
  } catch {
53515
53891
  }
53516
53892
  return wrapperPath;
53517
53893
  }
53518
53894
  function commandExists3(command2) {
53519
- const result = (0, import_child_process11.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
53895
+ const result = (0, import_child_process12.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
53520
53896
  encoding: "utf8"
53521
53897
  });
53522
53898
  return result.status === 0 && Boolean(result.stdout?.trim());
53523
53899
  }
53524
53900
  function resolveOtelHookCommand(hooksHome) {
53525
53901
  const local = localOtelHookBin(hooksHome);
53526
- if (fs41.existsSync(local)) return local;
53902
+ if (fs42.existsSync(local)) return local;
53527
53903
  return null;
53528
53904
  }
53529
53905
  function resolveHooksInstaller(hooksHome) {
@@ -53538,7 +53914,7 @@ function resolveHooksInstaller(hooksHome) {
53538
53914
  return candidates.find((candidate) => commandExists3(candidate.command)) ?? null;
53539
53915
  }
53540
53916
  function tryInstallPackage(hooksHome) {
53541
- fs41.mkdirSync(hooksHome, { recursive: true });
53917
+ fs42.mkdirSync(hooksHome, { recursive: true });
53542
53918
  const installer = resolveHooksInstaller(hooksHome);
53543
53919
  if (!installer) {
53544
53920
  return {
@@ -53547,11 +53923,11 @@ function tryInstallPackage(hooksHome) {
53547
53923
  };
53548
53924
  }
53549
53925
  const described = `${installer.command} ${installer.args.join(" ")}`;
53550
- const result = (0, import_child_process11.spawnSync)(installer.command, installer.args, {
53926
+ const result = (0, import_child_process12.spawnSync)(installer.command, installer.args, {
53551
53927
  encoding: "utf8",
53552
53928
  timeout: 18e4
53553
53929
  });
53554
- if (result.status === 0 && fs41.existsSync(localOtelHookBin(hooksHome))) {
53930
+ if (result.status === 0 && fs42.existsSync(localOtelHookBin(hooksHome))) {
53555
53931
  return { ok: true, detail: described };
53556
53932
  }
53557
53933
  return {
@@ -53569,7 +53945,7 @@ function uninstallLegacyPythonHooks(hooksHome) {
53569
53945
  for (const [cmd, args] of attempts) {
53570
53946
  if (!commandExists3(cmd)) continue;
53571
53947
  try {
53572
- (0, import_child_process11.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
53948
+ (0, import_child_process12.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
53573
53949
  } catch {
53574
53950
  }
53575
53951
  }
@@ -53577,14 +53953,14 @@ function uninstallLegacyPythonHooks(hooksHome) {
53577
53953
  }
53578
53954
  function removeLegacyPythonArtifacts(hooksHome) {
53579
53955
  const legacyPaths = [
53580
- path47.join(hooksHome, ".venv"),
53581
- path47.join(hooksHome, "otel_config.yaml"),
53582
- path47.join(hooksHome, "venv")
53956
+ path48.join(hooksHome, ".venv"),
53957
+ path48.join(hooksHome, "otel_config.yaml"),
53958
+ path48.join(hooksHome, "venv")
53583
53959
  ];
53584
53960
  for (const target of legacyPaths) {
53585
53961
  try {
53586
- if (fs41.existsSync(target)) {
53587
- fs41.rmSync(target, { recursive: true, force: true });
53962
+ if (fs42.existsSync(target)) {
53963
+ fs42.rmSync(target, { recursive: true, force: true });
53588
53964
  }
53589
53965
  } catch {
53590
53966
  }
@@ -53601,7 +53977,7 @@ function normalizeLegacyProviderFlagsInCommand(command2) {
53601
53977
  }
53602
53978
  function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
53603
53979
  const hookCommand = `${wrapperPath} --provider ${providerId}`;
53604
- const result = (0, import_child_process11.spawnSync)(
53980
+ const result = (0, import_child_process12.spawnSync)(
53605
53981
  otelHookCommand,
53606
53982
  [
53607
53983
  "setup",
@@ -53625,10 +54001,10 @@ function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
53625
54001
  };
53626
54002
  }
53627
54003
  function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
53628
- if (!fs41.existsSync(filePath)) return false;
54004
+ if (!fs42.existsSync(filePath)) return false;
53629
54005
  let raw;
53630
54006
  try {
53631
- raw = fs41.readFileSync(filePath, "utf8");
54007
+ raw = fs42.readFileSync(filePath, "utf8");
53632
54008
  } catch {
53633
54009
  return false;
53634
54010
  }
@@ -53675,15 +54051,15 @@ function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
53675
54051
  return changed;
53676
54052
  };
53677
54053
  if (!walk(parsed)) return false;
53678
- fs41.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54054
+ fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
53679
54055
  `);
53680
54056
  return true;
53681
54057
  }
53682
54058
  function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
53683
- if (!fs41.existsSync(filePath)) return false;
54059
+ if (!fs42.existsSync(filePath)) return false;
53684
54060
  let raw;
53685
54061
  try {
53686
- raw = fs41.readFileSync(filePath, "utf8");
54062
+ raw = fs42.readFileSync(filePath, "utf8");
53687
54063
  } catch {
53688
54064
  return false;
53689
54065
  }
@@ -53718,21 +54094,21 @@ function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
53718
54094
  }
53719
54095
  }
53720
54096
  if (!changed) return false;
53721
- fs41.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
54097
+ fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
53722
54098
  `);
53723
54099
  return true;
53724
54100
  }
53725
54101
  function rewriteKnownHookRegistrations(wrapperPath) {
53726
- const home = os15.homedir();
54102
+ const home = os16.homedir();
53727
54103
  const candidates = [
53728
- path47.join(home, ".cursor", "hooks.json"),
53729
- path47.join(home, ".claude", "settings.json"),
53730
- path47.join(home, ".codex", "hooks.json")
54104
+ path48.join(home, ".cursor", "hooks.json"),
54105
+ path48.join(home, ".claude", "settings.json"),
54106
+ path48.join(home, ".codex", "hooks.json")
53731
54107
  ];
53732
54108
  for (const filePath of candidates) {
53733
54109
  try {
53734
54110
  rewriteHookCommandsToWrapper(filePath, wrapperPath);
53735
- if (filePath.endsWith(`${path47.sep}.cursor${path47.sep}hooks.json`)) {
54111
+ if (filePath.endsWith(`${path48.sep}.cursor${path48.sep}hooks.json`)) {
53736
54112
  pruneLegacyCursorHookEvents(filePath, wrapperPath);
53737
54113
  }
53738
54114
  } catch {
@@ -53748,7 +54124,7 @@ function ensureOtelHooks(options) {
53748
54124
  const hooksHome = getOtelHooksHome();
53749
54125
  const errors = [];
53750
54126
  const providersSetup = [];
53751
- fs41.mkdirSync(hooksHome, { recursive: true });
54127
+ fs42.mkdirSync(hooksHome, { recursive: true });
53752
54128
  let otelHookCommand = resolveOtelHookCommand(hooksHome);
53753
54129
  const needsInstall = !otelHookCommand || installedPackageVersion(hooksHome) !== pinnedVersion();
53754
54130
  if (needsInstall) {
@@ -53842,14 +54218,14 @@ function ensureOtelHooks(options) {
53842
54218
  function disableOtelHooksExport() {
53843
54219
  return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
53844
54220
  }
53845
- var import_child_process11, fs41, os15, path47, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
54221
+ var import_child_process12, fs42, os16, path48, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
53846
54222
  var init_otel_hooks = __esm({
53847
54223
  "src/core/otel-hooks.ts"() {
53848
54224
  "use strict";
53849
- import_child_process11 = require("child_process");
53850
- fs41 = __toESM(require("fs"));
53851
- os15 = __toESM(require("os"));
53852
- path47 = __toESM(require("path"));
54225
+ import_child_process12 = require("child_process");
54226
+ fs42 = __toESM(require("fs"));
54227
+ os16 = __toESM(require("os"));
54228
+ path48 = __toESM(require("path"));
53853
54229
  init_control_config();
53854
54230
  init_telemetry_config();
53855
54231
  OTEL_HOOKS_PACKAGE = "@osfactory/otel-hook@0.1.2";
@@ -65603,34 +65979,34 @@ init_telemetry_config();
65603
65979
  init_sync_context();
65604
65980
 
65605
65981
  // src/cli/commands/agents.ts
65606
- var path31 = __toESM(require("path"));
65982
+ var path32 = __toESM(require("path"));
65607
65983
 
65608
65984
  // src/harness/agent-skills.ts
65609
- var fs25 = __toESM(require("fs"));
65610
- var os12 = __toESM(require("os"));
65611
- var path30 = __toESM(require("path"));
65985
+ var fs26 = __toESM(require("fs"));
65986
+ var os13 = __toESM(require("os"));
65987
+ var path31 = __toESM(require("path"));
65612
65988
  var readline = __toESM(require("readline"));
65613
65989
  init_file_ops();
65614
65990
  init_logging();
65615
65991
 
65616
65992
  // src/utils/paths.ts
65617
- var fs24 = __toESM(require("fs"));
65618
- var path29 = __toESM(require("path"));
65993
+ var fs25 = __toESM(require("fs"));
65994
+ var path30 = __toESM(require("path"));
65619
65995
  function resolveTemplatesDir() {
65620
65996
  const candidates = [
65621
- path29.join(__dirname, "templates"),
65622
- path29.join(__dirname, "..", "templates"),
65623
- path29.join(__dirname, "..", "harness", "templates")
65997
+ path30.join(__dirname, "templates"),
65998
+ path30.join(__dirname, "..", "templates"),
65999
+ path30.join(__dirname, "..", "harness", "templates")
65624
66000
  ];
65625
66001
  for (const dir of candidates) {
65626
- if (fs24.existsSync(path29.join(dir, "har-boilerplate"))) return dir;
66002
+ if (fs25.existsSync(path30.join(dir, "har-boilerplate"))) return dir;
65627
66003
  }
65628
66004
  throw new Error("Templates directory not found. Run npm run build.");
65629
66005
  }
65630
66006
  function resolveTemplateFile(name) {
65631
66007
  const dir = resolveTemplatesDir();
65632
- const filePath = path29.join(dir, name);
65633
- return fs24.existsSync(filePath) ? filePath : null;
66008
+ const filePath = path30.join(dir, name);
66009
+ return fs25.existsSync(filePath) ? filePath : null;
65634
66010
  }
65635
66011
 
65636
66012
  // src/harness/agent-skills.ts
@@ -65638,18 +66014,18 @@ init_manifest();
65638
66014
  var AGENT_SKILL_TARGETS = ["claude", "cursor", "codex"];
65639
66015
  var MANAGED_HEADER = "<!-- managed by har \u2014 regenerated by `har agents install` / `har env maintain`; edit at your own risk -->";
65640
66016
  function agentSkillsTemplateDir() {
65641
- const dir = path30.join(resolveTemplatesDir(), "agent-skills");
65642
- if (!fs25.existsSync(dir)) {
66017
+ const dir = path31.join(resolveTemplatesDir(), "agent-skills");
66018
+ if (!fs26.existsSync(dir)) {
65643
66019
  throw new Error("Agent skills templates not found (templates/agent-skills). Run npm run build.");
65644
66020
  }
65645
66021
  return dir;
65646
66022
  }
65647
66023
  function readSkillsManifest() {
65648
- const manifestPath = path30.join(agentSkillsTemplateDir(), "skills.manifest.json");
65649
- return JSON.parse(fs25.readFileSync(manifestPath, "utf8"));
66024
+ const manifestPath = path31.join(agentSkillsTemplateDir(), "skills.manifest.json");
66025
+ return JSON.parse(fs26.readFileSync(manifestPath, "utf8"));
65650
66026
  }
65651
66027
  function readSkillBody(entry) {
65652
- return fs25.readFileSync(path30.join(agentSkillsTemplateDir(), entry.body), "utf8");
66028
+ return fs26.readFileSync(path31.join(agentSkillsTemplateDir(), entry.body), "utf8");
65653
66029
  }
65654
66030
  function renderYamlValue(value) {
65655
66031
  if (typeof value === "string") return JSON.stringify(value);
@@ -65681,7 +66057,7 @@ function renderSkillFiles(agents) {
65681
66057
  agent: "claude",
65682
66058
  skill: entry.id,
65683
66059
  scope: "repo",
65684
- relPath: path30.join(".claude", "skills", entry.id, "SKILL.md"),
66060
+ relPath: path31.join(".claude", "skills", entry.id, "SKILL.md"),
65685
66061
  content: renderClaudeSkill(entry, body)
65686
66062
  });
65687
66063
  }
@@ -65690,7 +66066,7 @@ function renderSkillFiles(agents) {
65690
66066
  agent: "cursor",
65691
66067
  skill: entry.id,
65692
66068
  scope: "repo",
65693
- relPath: path30.join(".cursor", "commands", `${entry.id}.md`),
66069
+ relPath: path31.join(".cursor", "commands", `${entry.id}.md`),
65694
66070
  content: renderPlainSkill(body)
65695
66071
  });
65696
66072
  }
@@ -65699,7 +66075,7 @@ function renderSkillFiles(agents) {
65699
66075
  agent: "codex",
65700
66076
  skill: entry.id,
65701
66077
  scope: "global",
65702
- relPath: path30.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
66078
+ relPath: path31.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
65703
66079
  content: renderPlainSkill(body)
65704
66080
  });
65705
66081
  }
@@ -65708,12 +66084,12 @@ function renderSkillFiles(agents) {
65708
66084
  }
65709
66085
  function resolveScaffoldPath(repoPath, file) {
65710
66086
  if (file.scope === "global") {
65711
- return path30.join(os12.homedir(), file.relPath.replace(/^~\//, ""));
66087
+ return path31.join(os13.homedir(), file.relPath.replace(/^~\//, ""));
65712
66088
  }
65713
- return path30.join(repoPath, file.relPath);
66089
+ return path31.join(repoPath, file.relPath);
65714
66090
  }
65715
66091
  function isHarManaged(filePath) {
65716
- return fs25.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
66092
+ return fs26.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
65717
66093
  }
65718
66094
  function scaffoldAgentSkills(repoPath, agents, options = {}) {
65719
66095
  const written = [];
@@ -65721,7 +66097,7 @@ function scaffoldAgentSkills(repoPath, agents, options = {}) {
65721
66097
  const records = [];
65722
66098
  for (const file of renderSkillFiles(agents)) {
65723
66099
  const destPath = resolveScaffoldPath(repoPath, file);
65724
- if (fs25.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
66100
+ if (fs26.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
65725
66101
  warn(`Skipped ${file.relPath} \u2014 exists with user modifications (use --force to overwrite)`);
65726
66102
  skipped.push(file.relPath);
65727
66103
  continue;
@@ -65755,16 +66131,16 @@ function removeAgentSkills(repoPath, agents) {
65755
66131
  const removed = [];
65756
66132
  for (const file of renderSkillFiles(agents)) {
65757
66133
  const destPath = resolveScaffoldPath(repoPath, file);
65758
- if (!fs25.existsSync(destPath)) continue;
66134
+ if (!fs26.existsSync(destPath)) continue;
65759
66135
  if (!isHarManaged(destPath)) {
65760
66136
  warn(`Kept ${file.relPath} \u2014 has user modifications`);
65761
66137
  continue;
65762
66138
  }
65763
- fs25.rmSync(destPath);
66139
+ fs26.rmSync(destPath);
65764
66140
  removed.push(file.relPath);
65765
- const parent = path30.dirname(destPath);
65766
- if (fs25.existsSync(parent) && fs25.readdirSync(parent).length === 0) {
65767
- fs25.rmdirSync(parent);
66141
+ const parent = path31.dirname(destPath);
66142
+ if (fs26.existsSync(parent) && fs26.readdirSync(parent).length === 0) {
66143
+ fs26.rmdirSync(parent);
65768
66144
  }
65769
66145
  }
65770
66146
  const manifest = readManifest(repoPath);
@@ -65783,9 +66159,9 @@ function detectAgentTargets(repoPath) {
65783
66159
  const manifest = readManifest(repoPath);
65784
66160
  const scaffolded = new Set((manifest?.scaffoldedAgentFiles ?? []).map((entry) => entry.agent));
65785
66161
  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");
66162
+ if (fs26.existsSync(path31.join(repoPath, ".claude"))) detected.add("claude");
66163
+ if (fs26.existsSync(path31.join(repoPath, ".cursor"))) detected.add("cursor");
66164
+ if (fs26.existsSync(path31.join(os13.homedir(), ".codex"))) detected.add("codex");
65789
66165
  return AGENT_SKILL_TARGETS.filter((agent) => detected.has(agent));
65790
66166
  }
65791
66167
  function parseAgentTargets(raw) {
@@ -65872,7 +66248,7 @@ var agentsCommand = {
65872
66248
  }
65873
66249
  };
65874
66250
  function handleInstall(argv) {
65875
- const repoPath = path31.resolve(argv.repo);
66251
+ const repoPath = path32.resolve(argv.repo);
65876
66252
  header("har agents install");
65877
66253
  try {
65878
66254
  const targets = resolveTargets(argv, repoPath);
@@ -65898,7 +66274,7 @@ function handleInstall(argv) {
65898
66274
  }
65899
66275
  }
65900
66276
  function handleRemove(argv) {
65901
- const repoPath = path31.resolve(argv.repo);
66277
+ const repoPath = path32.resolve(argv.repo);
65902
66278
  header("har agents remove");
65903
66279
  try {
65904
66280
  const targets = resolveTargets(argv, repoPath);
@@ -65928,25 +66304,25 @@ async function finishCommand(code) {
65928
66304
  }
65929
66305
 
65930
66306
  // src/core/harness.ts
65931
- var fs37 = __toESM(require("fs"));
65932
- var path42 = __toESM(require("path"));
66307
+ var fs38 = __toESM(require("fs"));
66308
+ var path43 = __toESM(require("path"));
65933
66309
 
65934
66310
  // src/harness/generator.ts
65935
- var fs28 = __toESM(require("fs"));
65936
- var path34 = __toESM(require("path"));
66311
+ var fs29 = __toESM(require("fs"));
66312
+ var path35 = __toESM(require("path"));
65937
66313
  init_file_ops();
65938
66314
  init_logging();
65939
66315
 
65940
66316
  // src/core/gitignore.ts
65941
- var fs26 = __toESM(require("fs"));
65942
- var path32 = __toESM(require("path"));
66317
+ var fs27 = __toESM(require("fs"));
66318
+ var path33 = __toESM(require("path"));
65943
66319
  var HARNESS_ROOT_GITIGNORE_PATTERNS = [
65944
66320
  ".env.agent.*",
65945
66321
  "ecosystem.agent.*.config.cjs"
65946
66322
  ];
65947
66323
  function ensureRootGitignorePatterns(checkoutDir) {
65948
- const gitignorePath = path32.join(checkoutDir, ".gitignore");
65949
- const content = fs26.existsSync(gitignorePath) ? fs26.readFileSync(gitignorePath, "utf8") : "";
66324
+ const gitignorePath = path33.join(checkoutDir, ".gitignore");
66325
+ const content = fs27.existsSync(gitignorePath) ? fs27.readFileSync(gitignorePath, "utf8") : "";
65950
66326
  const lines2 = content.split("\n").map((line) => line.trim());
65951
66327
  const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines2.includes(pattern));
65952
66328
  if (missing.length === 0) return;
@@ -65955,12 +66331,12 @@ function ensureRootGitignorePatterns(checkoutDir) {
65955
66331
  const block = lines2.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
65956
66332
  ` : `${header2}${missing.join("\n")}
65957
66333
  `;
65958
- fs26.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
66334
+ fs27.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
65959
66335
  }
65960
66336
 
65961
66337
  // src/harness/gitignore-template.ts
65962
- var fs27 = __toESM(require("fs"));
65963
- var path33 = __toESM(require("path"));
66338
+ var fs28 = __toESM(require("fs"));
66339
+ var path34 = __toESM(require("path"));
65964
66340
  var HARNESS_GITIGNORE_TEMPLATE = "gitignore.template";
65965
66341
  var HARNESS_GITIGNORE_FILE = ".gitignore";
65966
66342
  function harnessFileForTemplate(templateFile) {
@@ -65970,16 +66346,16 @@ function templateFileForHarness(harnessFile) {
65970
66346
  return harnessFile === HARNESS_GITIGNORE_FILE ? HARNESS_GITIGNORE_TEMPLATE : harnessFile;
65971
66347
  }
65972
66348
  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;
66349
+ const primary = path34.join(boilerplateDir, HARNESS_GITIGNORE_TEMPLATE);
66350
+ if (fs28.existsSync(primary)) return primary;
66351
+ const legacy = path34.join(boilerplateDir, HARNESS_GITIGNORE_FILE);
66352
+ if (fs28.existsSync(legacy)) return legacy;
65977
66353
  return null;
65978
66354
  }
65979
66355
  function writeHarnessGitignore(harnessDir, boilerplateDir) {
65980
66356
  const src = resolveGitignoreTemplateSource(boilerplateDir);
65981
66357
  if (!src) return;
65982
- fs27.writeFileSync(path33.join(harnessDir, HARNESS_GITIGNORE_FILE), fs27.readFileSync(src, "utf8"));
66358
+ fs28.writeFileSync(path34.join(harnessDir, HARNESS_GITIGNORE_FILE), fs28.readFileSync(src, "utf8"));
65983
66359
  }
65984
66360
  function isExpectedHarnessOnlyFile(harnessFile, templateFiles) {
65985
66361
  return harnessFile === HARNESS_GITIGNORE_FILE && templateFiles.includes(HARNESS_GITIGNORE_TEMPLATE);
@@ -66000,27 +66376,27 @@ var CLI_PRUNE_FILES = [
66000
66376
  ];
66001
66377
  function pruneCliProfile(harnessDir) {
66002
66378
  for (const file of CLI_PRUNE_FILES) {
66003
- const filePath = path34.join(harnessDir, file);
66004
- if (fs28.existsSync(filePath)) {
66005
- fs28.unlinkSync(filePath);
66379
+ const filePath = path35.join(harnessDir, file);
66380
+ if (fs29.existsSync(filePath)) {
66381
+ fs29.unlinkSync(filePath);
66006
66382
  }
66007
66383
  }
66008
66384
  }
66009
66385
  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, "_");
66386
+ const harnessDir = path35.join(repoPath, DEFAULT_HAR_DIR);
66387
+ const projectName = path35.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
66012
66388
  const profile = options.profile ?? "default";
66013
- const boilerplateDir = path34.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
66014
- if (fs28.existsSync(harnessDir) && !options.force) {
66389
+ const boilerplateDir = path35.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
66390
+ if (fs29.existsSync(harnessDir) && !options.force) {
66015
66391
  throw new Error(
66016
66392
  '.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
66017
66393
  );
66018
66394
  }
66019
- if (!fs28.existsSync(boilerplateDir)) {
66395
+ if (!fs29.existsSync(boilerplateDir)) {
66020
66396
  throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
66021
66397
  }
66022
- if (options.force && fs28.existsSync(harnessDir)) {
66023
- fs28.rmSync(harnessDir, { recursive: true, force: true });
66398
+ if (options.force && fs29.existsSync(harnessDir)) {
66399
+ fs29.rmSync(harnessDir, { recursive: true, force: true });
66024
66400
  }
66025
66401
  copyDirRecursive(boilerplateDir, harnessDir);
66026
66402
  writeHarnessGitignore(harnessDir, boilerplateDir);
@@ -66028,11 +66404,11 @@ function scaffoldHarnessBoilerplate(repoPath, options = {}) {
66028
66404
  pruneCliProfile(harnessDir);
66029
66405
  }
66030
66406
  syncAgentSlotsToHarnessEnv(repoPath);
66031
- const harnessEnvPath = path34.join(harnessDir, "harness.env");
66032
- if (fs28.existsSync(harnessEnvPath)) {
66033
- let content = fs28.readFileSync(harnessEnvPath, "utf8");
66407
+ const harnessEnvPath = path35.join(harnessDir, "harness.env");
66408
+ if (fs29.existsSync(harnessEnvPath)) {
66409
+ let content = fs29.readFileSync(harnessEnvPath, "utf8");
66034
66410
  content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
66035
- fs28.writeFileSync(harnessEnvPath, content);
66411
+ fs29.writeFileSync(harnessEnvPath, content);
66036
66412
  }
66037
66413
  const manifest = createManifest(
66038
66414
  repoPath,
@@ -66054,16 +66430,16 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
66054
66430
  }
66055
66431
 
66056
66432
  // src/harness/agent-md.ts
66057
- var fs30 = __toESM(require("fs"));
66058
- var path36 = __toESM(require("path"));
66433
+ var fs31 = __toESM(require("fs"));
66434
+ var path37 = __toESM(require("path"));
66059
66435
  init_file_ops();
66060
66436
  init_logging();
66061
66437
  init_manifest();
66062
66438
 
66063
66439
  // src/harness/instruction-files.ts
66064
- var fs29 = __toESM(require("fs"));
66065
- var os13 = __toESM(require("os"));
66066
- var path35 = __toESM(require("path"));
66440
+ var fs30 = __toESM(require("fs"));
66441
+ var os14 = __toESM(require("os"));
66442
+ var path36 = __toESM(require("path"));
66067
66443
 
66068
66444
  // node_modules/@inquirer/core/dist/esm/lib/key.mjs
66069
66445
  var isUpKey = (key) => (
@@ -68396,7 +68772,7 @@ var AgentsMdShrinkError = class extends Error {
68396
68772
  this.name = "AgentsMdShrinkError";
68397
68773
  }
68398
68774
  };
68399
- function escapeRegExp(value) {
68775
+ function escapeRegExp2(value) {
68400
68776
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68401
68777
  }
68402
68778
  function countNonEmptyLines(text) {
@@ -68405,7 +68781,7 @@ function countNonEmptyLines(text) {
68405
68781
  function stripClaudePointerFromAgentsMd(content) {
68406
68782
  if (!content.includes(CLAUDE_HAR_SECTION_START)) return content;
68407
68783
  const pattern = new RegExp(
68408
- `${escapeRegExp(CLAUDE_HAR_SECTION_START)}[\\s\\S]*?${escapeRegExp(CLAUDE_HAR_SECTION_END)}\\n?`,
68784
+ `${escapeRegExp2(CLAUDE_HAR_SECTION_START)}[\\s\\S]*?${escapeRegExp2(CLAUDE_HAR_SECTION_END)}\\n?`,
68409
68785
  "g"
68410
68786
  );
68411
68787
  return content.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
@@ -68457,12 +68833,12 @@ function outsideContentShrinkMessage(beforeLines, afterLines) {
68457
68833
  }
68458
68834
  function detectInstructionFiles(repoPath) {
68459
68835
  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"))
68836
+ agentsMd: fs30.existsSync(path36.join(repoPath, AGENTS_MD)),
68837
+ agentMd: fs30.existsSync(path36.join(repoPath, LEGACY_AGENT_MD)),
68838
+ claudeMd: fs30.existsSync(path36.join(repoPath, CLAUDE_MD)),
68839
+ cursorDir: fs30.existsSync(path36.join(repoPath, ".cursor")),
68840
+ claudeDir: fs30.existsSync(path36.join(repoPath, ".claude")),
68841
+ codexHome: fs30.existsSync(path36.join(os14.homedir(), ".codex"))
68466
68842
  };
68467
68843
  }
68468
68844
  function formatDetectionReport(detection) {
@@ -68517,7 +68893,7 @@ function loadHarAgentsSectionFromTemplate() {
68517
68893
  if (!templatePath) {
68518
68894
  throw new Error("AGENTS.md.template not found. Run npm run build.");
68519
68895
  }
68520
- const content = fs29.readFileSync(templatePath, "utf8");
68896
+ const content = fs30.readFileSync(templatePath, "utf8");
68521
68897
  const start = content.indexOf(HAR_SECTION_START);
68522
68898
  const end = content.indexOf(HAR_SECTION_END);
68523
68899
  if (start === -1 || end === -1 || end < start) {
@@ -68530,9 +68906,9 @@ function loadAgentsMdTemplate() {
68530
68906
  if (!templatePath) {
68531
68907
  throw new Error("AGENTS.md.template not found. Run npm run build.");
68532
68908
  }
68533
- return fs29.readFileSync(templatePath, "utf8");
68909
+ return fs30.readFileSync(templatePath, "utf8");
68534
68910
  }
68535
- function upsertMarkedBlock(content, block, startMarker, endMarker) {
68911
+ function upsertMarkedBlock2(content, block, startMarker, endMarker) {
68536
68912
  const existingBlock = findLastCompleteMarkedBlock(content, startMarker, endMarker);
68537
68913
  if (existingBlock) {
68538
68914
  const newContent = content.slice(0, existingBlock.start) + block + content.slice(existingBlock.end);
@@ -68554,7 +68930,7 @@ function mergeAgentsMdContent(existing, incoming) {
68554
68930
  harSection = sanitizedIncoming.slice(start, end + HAR_SECTION_END.length);
68555
68931
  }
68556
68932
  }
68557
- const { content } = upsertMarkedBlock(
68933
+ const { content } = upsertMarkedBlock2(
68558
68934
  sanitizedExisting,
68559
68935
  harSection,
68560
68936
  HAR_SECTION_START,
@@ -68563,15 +68939,15 @@ function mergeAgentsMdContent(existing, incoming) {
68563
68939
  return content;
68564
68940
  }
68565
68941
  function upsertAgentsMdHarSection(repoPath, options = {}) {
68566
- const dest = path35.join(repoPath, AGENTS_MD);
68942
+ const dest = path36.join(repoPath, AGENTS_MD);
68567
68943
  const section = loadHarAgentsSectionFromTemplate();
68568
- if (!fs29.existsSync(dest)) {
68944
+ if (!fs30.existsSync(dest)) {
68569
68945
  writeFileSafe(dest, loadAgentsMdTemplate());
68570
68946
  return "created";
68571
68947
  }
68572
- const existing = fs29.readFileSync(dest, "utf8");
68948
+ const existing = fs30.readFileSync(dest, "utf8");
68573
68949
  const sanitized = stripClaudePointerFromAgentsMd(existing);
68574
- const { content, action } = upsertMarkedBlock(
68950
+ const { content, action } = upsertMarkedBlock2(
68575
68951
  sanitized,
68576
68952
  section,
68577
68953
  HAR_SECTION_START,
@@ -68594,15 +68970,15 @@ function upsertAgentsMdHarSection(repoPath, options = {}) {
68594
68970
  return action;
68595
68971
  }
68596
68972
  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)) {
68973
+ const legacyPath = path36.join(repoPath, LEGACY_AGENT_MD);
68974
+ const agentsPath = path36.join(repoPath, AGENTS_MD);
68975
+ if (!fs30.existsSync(legacyPath)) return false;
68976
+ const legacy = fs30.readFileSync(legacyPath, "utf8");
68977
+ if (!fs30.existsSync(agentsPath)) {
68602
68978
  writeFileSafe(agentsPath, legacy);
68603
68979
  upsertAgentsMdHarSection(repoPath);
68604
68980
  } else {
68605
- const agents = fs29.readFileSync(agentsPath, "utf8");
68981
+ const agents = fs30.readFileSync(agentsPath, "utf8");
68606
68982
  const legacyTrimmed = legacy.trim();
68607
68983
  if (legacyTrimmed.length > 0 && !agents.includes(legacyTrimmed)) {
68608
68984
  const suffix = agents.endsWith("\n") ? "" : "\n";
@@ -68612,7 +68988,7 @@ ${legacyTrimmed}
68612
68988
  }
68613
68989
  upsertAgentsMdHarSection(repoPath);
68614
68990
  }
68615
- fs29.unlinkSync(legacyPath);
68991
+ fs30.unlinkSync(legacyPath);
68616
68992
  info(`Migrated ${LEGACY_AGENT_MD} \u2192 ${AGENTS_MD} (removed legacy file)`);
68617
68993
  return true;
68618
68994
  }
@@ -68622,7 +68998,7 @@ function loadClaudeMdTemplate(projectName) {
68622
68998
  throw new Error("CLAUDE.md.template not found. Run npm run build.");
68623
68999
  }
68624
69000
  const displayName = projectName.replace(/_/g, " ");
68625
- return fs29.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
69001
+ return fs30.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
68626
69002
  }
68627
69003
  function isThinClaudePointer(content) {
68628
69004
  const trimmed = content.trim();
@@ -68636,21 +69012,21 @@ This repository uses a \`.har/\` harness. Read [AGENTS.md](./AGENTS.md) and
68636
69012
  [\`.har/README.md\`](./.har/README.md) before making changes.
68637
69013
  ${CLAUDE_HAR_SECTION_END}`;
68638
69014
  function ensureClaudeMdPointer(repoPath, options = {}) {
68639
- const dest = path35.join(repoPath, CLAUDE_MD);
68640
- const projectName = path35.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69015
+ const dest = path36.join(repoPath, CLAUDE_MD);
69016
+ const projectName = path36.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
68641
69017
  const thin = loadClaudeMdTemplate(projectName);
68642
- if (!fs29.existsSync(dest)) {
69018
+ if (!fs30.existsSync(dest)) {
68643
69019
  writeFileSafe(dest, thin);
68644
69020
  return "created";
68645
69021
  }
68646
- const existing = fs29.readFileSync(dest, "utf8");
69022
+ const existing = fs30.readFileSync(dest, "utf8");
68647
69023
  if (isThinClaudePointer(existing) || options.force) {
68648
69024
  writeFileSafe(dest, thin);
68649
69025
  return "updated";
68650
69026
  }
68651
69027
  if (existing.includes(CLAUDE_HAR_SECTION_START) || /\[AGENTS\.md\]/.test(existing)) {
68652
69028
  if (existing.includes(CLAUDE_HAR_SECTION_START)) {
68653
- const { content: content2, action: action2 } = upsertMarkedBlock(
69029
+ const { content: content2, action: action2 } = upsertMarkedBlock2(
68654
69030
  existing,
68655
69031
  CLAUDE_POINTER_BLOCK,
68656
69032
  CLAUDE_HAR_SECTION_START,
@@ -68661,7 +69037,7 @@ function ensureClaudeMdPointer(repoPath, options = {}) {
68661
69037
  }
68662
69038
  return "skipped";
68663
69039
  }
68664
- const { content, action } = upsertMarkedBlock(
69040
+ const { content, action } = upsertMarkedBlock2(
68665
69041
  existing,
68666
69042
  CLAUDE_POINTER_BLOCK,
68667
69043
  CLAUDE_HAR_SECTION_START,
@@ -68792,19 +69168,19 @@ var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
68792
69168
  var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
68793
69169
  function readAgentMdProposal(repoPath) {
68794
69170
  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;
69171
+ const proposalPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL);
69172
+ const metaPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL_META);
69173
+ const legacyProposalPath = path37.join(harnessDir, AGENT_MD_PROPOSAL);
69174
+ const legacyMetaPath = path37.join(harnessDir, AGENT_MD_PROPOSAL_META);
69175
+ const resolvedProposal = fs31.existsSync(proposalPath) ? proposalPath : fs31.existsSync(legacyProposalPath) ? legacyProposalPath : null;
68800
69176
  if (!resolvedProposal) return null;
68801
69177
  const resolvedMeta = resolvedProposal === proposalPath ? metaPath : legacyMetaPath;
68802
- const content = fs30.readFileSync(resolvedProposal, "utf8");
69178
+ const content = fs31.readFileSync(resolvedProposal, "utf8");
68803
69179
  let rationale = "";
68804
69180
  let createdAt = (/* @__PURE__ */ new Date()).toISOString();
68805
- if (fs30.existsSync(resolvedMeta)) {
69181
+ if (fs31.existsSync(resolvedMeta)) {
68806
69182
  try {
68807
- const meta = JSON.parse(fs30.readFileSync(resolvedMeta, "utf8"));
69183
+ const meta = JSON.parse(fs31.readFileSync(resolvedMeta, "utf8"));
68808
69184
  rationale = meta.rationale ?? "";
68809
69185
  createdAt = meta.createdAt ?? createdAt;
68810
69186
  } catch {
@@ -68821,8 +69197,8 @@ function clearAgentMdProposal(repoPath) {
68821
69197
  AGENT_MD_PROPOSAL,
68822
69198
  AGENT_MD_PROPOSAL_META
68823
69199
  ]) {
68824
- const p = path36.join(harnessDir, file);
68825
- if (fs30.existsSync(p)) fs30.unlinkSync(p);
69200
+ const p = path37.join(harnessDir, file);
69201
+ if (fs31.existsSync(p)) fs31.unlinkSync(p);
68826
69202
  }
68827
69203
  }
68828
69204
  function wouldProposalShrinkExisting(existing, proposal) {
@@ -68835,9 +69211,9 @@ function wouldProposalShrinkExisting(existing, proposal) {
68835
69211
  function applyAgentMdProposalMerge(repoPath, options = {}) {
68836
69212
  const proposal = readAgentMdProposal(repoPath);
68837
69213
  if (!proposal) return false;
68838
- const agentsMdPath = path36.join(repoPath, AGENTS_MD);
68839
- if (fs30.existsSync(agentsMdPath)) {
68840
- const existing = fs30.readFileSync(agentsMdPath, "utf8");
69214
+ const agentsMdPath = path37.join(repoPath, AGENTS_MD);
69215
+ if (fs31.existsSync(agentsMdPath)) {
69216
+ const existing = fs31.readFileSync(agentsMdPath, "utf8");
68841
69217
  if (wouldProposalShrinkExisting(existing, proposal.content)) {
68842
69218
  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
69219
  if (options.rejectSignificantShrink) {
@@ -68860,8 +69236,8 @@ function finalizeAgentsMdInstructionFiles(repoPath) {
68860
69236
  }
68861
69237
 
68862
69238
  // src/harness/validator.ts
68863
- var fs31 = __toESM(require("fs"));
68864
- var path37 = __toESM(require("path"));
69239
+ var fs32 = __toESM(require("fs"));
69240
+ var path38 = __toESM(require("path"));
68865
69241
  init_shell();
68866
69242
  init_manifest();
68867
69243
  init_stages();
@@ -68916,22 +69292,22 @@ var SHELL_SCRIPTS = [
68916
69292
  function validateHarness(repoPath) {
68917
69293
  const harnessDir = getHarnessDir(repoPath);
68918
69294
  const issues = [];
68919
- if (!fs31.existsSync(harnessDir)) {
69295
+ if (!fs32.existsSync(harnessDir)) {
68920
69296
  return {
68921
69297
  pass: false,
68922
69298
  issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
68923
69299
  };
68924
69300
  }
68925
69301
  for (const file of getRequiredFiles(repoPath)) {
68926
- const filePath = path37.join(harnessDir, file);
68927
- if (!fs31.existsSync(filePath)) {
69302
+ const filePath = path38.join(harnessDir, file);
69303
+ if (!fs32.existsSync(filePath)) {
68928
69304
  issues.push({ file, message: "Required file missing", severity: "error" });
68929
69305
  }
68930
69306
  }
68931
69307
  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);
69308
+ const scriptPath = path38.join(harnessDir, script);
69309
+ if (!fs32.existsSync(scriptPath)) continue;
69310
+ const stat = fs32.statSync(scriptPath);
68935
69311
  if (!(stat.mode & 73)) {
68936
69312
  issues.push({ file: script, message: "Script is not executable", severity: "warning" });
68937
69313
  }
@@ -68946,9 +69322,9 @@ function validateHarness(repoPath) {
68946
69322
  }
68947
69323
  const manifest = readManifest(repoPath);
68948
69324
  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");
69325
+ const ecosystemPath = path38.join(harnessDir, "ecosystem.agent.template.cjs");
69326
+ if (profile !== "cli" && fs32.existsSync(ecosystemPath)) {
69327
+ const content = fs32.readFileSync(ecosystemPath, "utf8");
68952
69328
  if (!content.includes("module.exports")) {
68953
69329
  issues.push({
68954
69330
  file: "ecosystem.agent.template.cjs",
@@ -68957,9 +69333,9 @@ function validateHarness(repoPath) {
68957
69333
  });
68958
69334
  }
68959
69335
  }
68960
- const harnessEnvPath = path37.join(harnessDir, "harness.env");
68961
- if (fs31.existsSync(harnessEnvPath)) {
68962
- const content = fs31.readFileSync(harnessEnvPath, "utf8");
69336
+ const harnessEnvPath = path38.join(harnessDir, "harness.env");
69337
+ if (fs32.existsSync(harnessEnvPath)) {
69338
+ const content = fs32.readFileSync(harnessEnvPath, "utf8");
68963
69339
  if (content.includes("TODO: set migrate command")) {
68964
69340
  issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
68965
69341
  }
@@ -68967,17 +69343,17 @@ function validateHarness(repoPath) {
68967
69343
  issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
68968
69344
  }
68969
69345
  }
68970
- const verifyPath = path37.join(harnessDir, "verify.sh");
68971
- if (fs31.existsSync(verifyPath)) {
68972
- const content = fs31.readFileSync(verifyPath, "utf8");
69346
+ const verifyPath = path38.join(harnessDir, "verify.sh");
69347
+ if (fs32.existsSync(verifyPath)) {
69348
+ const content = fs32.readFileSync(verifyPath, "utf8");
68973
69349
  if (content.includes("echo 'TODO:")) {
68974
69350
  issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
68975
69351
  }
68976
69352
  }
68977
- const stagesPath = path37.join(harnessDir, "stages.json");
68978
- if (fs31.existsSync(stagesPath)) {
69353
+ const stagesPath = path38.join(harnessDir, "stages.json");
69354
+ if (fs32.existsSync(stagesPath)) {
68979
69355
  try {
68980
- JSON.parse(fs31.readFileSync(stagesPath, "utf8"));
69356
+ JSON.parse(fs32.readFileSync(stagesPath, "utf8"));
68981
69357
  const registry2 = readStageRegistry(repoPath);
68982
69358
  if (registry2.stages.length === 0) {
68983
69359
  issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
@@ -68992,9 +69368,9 @@ function validateHarness(repoPath) {
68992
69368
  } else {
68993
69369
  issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
68994
69370
  }
68995
- const readmePath = path37.join(harnessDir, "README.md");
68996
- if (fs31.existsSync(readmePath)) {
68997
- const content = fs31.readFileSync(readmePath, "utf8");
69371
+ const readmePath = path38.join(harnessDir, "README.md");
69372
+ if (fs32.existsSync(readmePath)) {
69373
+ const content = fs32.readFileSync(readmePath, "utf8");
68998
69374
  if (!content.includes(".har")) {
68999
69375
  issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
69000
69376
  }
@@ -69008,8 +69384,8 @@ function validateHarness(repoPath) {
69008
69384
  async function smokeTestHarness(repoPath) {
69009
69385
  const harnessDir = getHarnessDir(repoPath);
69010
69386
  const issues = [];
69011
- const setupScript = path37.join(harnessDir, "setup-infra.sh");
69012
- if (fs31.existsSync(setupScript)) {
69387
+ const setupScript = path38.join(harnessDir, "setup-infra.sh");
69388
+ if (fs32.existsSync(setupScript)) {
69013
69389
  const result = run(`bash "${setupScript}"`, { cwd: repoPath });
69014
69390
  if (result.code !== 0) {
69015
69391
  issues.push({
@@ -69024,8 +69400,8 @@ async function smokeTestHarness(repoPath) {
69024
69400
  }
69025
69401
 
69026
69402
  // src/harness/drift.ts
69027
- var fs32 = __toESM(require("fs"));
69028
- var path38 = __toESM(require("path"));
69403
+ var fs33 = __toESM(require("fs"));
69404
+ var path39 = __toESM(require("path"));
69029
69405
  init_env();
69030
69406
  init_manifest();
69031
69407
  init_stages();
@@ -69096,16 +69472,16 @@ function substituteProjectName(content, projectName) {
69096
69472
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
69097
69473
  }
69098
69474
  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();
69475
+ if (!fs33.existsSync(boilerplateDir)) return [];
69476
+ return fs33.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
69101
69477
  }
69102
69478
  function compareHarnessToTemplate(repoPath) {
69103
- const resolved = path38.resolve(repoPath);
69479
+ const resolved = path39.resolve(repoPath);
69104
69480
  const manifest = readManifest(resolved);
69105
69481
  const profile = manifest?.profile ?? "default";
69106
69482
  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]);
69483
+ const projectName = path39.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
69484
+ const boilerplateDir = path39.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
69109
69485
  const templateFiles = listBoilerplateFiles(boilerplateDir);
69110
69486
  const missing = [];
69111
69487
  const checksumMismatch = [];
@@ -69113,17 +69489,17 @@ function compareHarnessToTemplate(repoPath) {
69113
69489
  const unchanged = [];
69114
69490
  for (const file of templateFiles) {
69115
69491
  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");
69492
+ const templatePath = path39.join(boilerplateDir, file);
69493
+ const harnessPath = path39.join(harnessDir, harnessFile);
69494
+ let templateContent = fs33.readFileSync(templatePath, "utf8");
69119
69495
  if (file === "harness.env") {
69120
69496
  templateContent = substituteProjectName(templateContent, projectName);
69121
69497
  }
69122
- if (!fs32.existsSync(harnessPath)) {
69498
+ if (!fs33.existsSync(harnessPath)) {
69123
69499
  missing.push(harnessFile);
69124
69500
  continue;
69125
69501
  }
69126
- const harnessChecksum = computeFileChecksum(fs32.readFileSync(harnessPath, "utf8"));
69502
+ const harnessChecksum = computeFileChecksum(fs33.readFileSync(harnessPath, "utf8"));
69127
69503
  const templateChecksum = computeFileChecksum(templateContent);
69128
69504
  if (harnessChecksum === templateChecksum) {
69129
69505
  unchanged.push(harnessFile);
@@ -69131,10 +69507,10 @@ function compareHarnessToTemplate(repoPath) {
69131
69507
  checksumMismatch.push(harnessFile);
69132
69508
  }
69133
69509
  }
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;
69510
+ if (fs33.existsSync(harnessDir)) {
69511
+ for (const file of fs33.readdirSync(harnessDir)) {
69512
+ const full = path39.join(harnessDir, file);
69513
+ if (!fs33.statSync(full).isFile()) continue;
69138
69514
  if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
69139
69515
  if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
69140
69516
  extra.push(file);
@@ -69165,29 +69541,29 @@ function compareHarnessToTemplate(repoPath) {
69165
69541
  }
69166
69542
 
69167
69543
  // src/harness/maintain-bundle.ts
69168
- var fs36 = __toESM(require("fs"));
69169
- var os14 = __toESM(require("os"));
69170
- var path41 = __toESM(require("path"));
69544
+ var fs37 = __toESM(require("fs"));
69545
+ var os15 = __toESM(require("os"));
69546
+ var path42 = __toESM(require("path"));
69171
69547
  init_file_ops();
69172
69548
  init_shell();
69173
69549
  init_manifest();
69174
69550
 
69175
69551
  // src/harness/plugin-drift.ts
69176
- var fs35 = __toESM(require("fs"));
69177
- var path40 = __toESM(require("path"));
69552
+ var fs36 = __toESM(require("fs"));
69553
+ var path41 = __toESM(require("path"));
69178
69554
  init_manifest();
69179
69555
 
69180
69556
  // src/harness/plugins.ts
69181
- var fs34 = __toESM(require("fs"));
69182
- var path39 = __toESM(require("path"));
69557
+ var fs35 = __toESM(require("fs"));
69558
+ var path40 = __toESM(require("path"));
69183
69559
  init_zod();
69184
69560
  init_logging();
69185
69561
 
69186
69562
  // src/harness/parser.ts
69187
- var fs33 = __toESM(require("fs"));
69563
+ var fs34 = __toESM(require("fs"));
69188
69564
  init_manifest();
69189
69565
  function harnessExists(repoPath) {
69190
- return fs33.existsSync(`${repoPath}/.har/setup-infra.sh`);
69566
+ return fs34.existsSync(`${repoPath}/.har/setup-infra.sh`);
69191
69567
  }
69192
69568
 
69193
69569
  // src/harness/plugins.ts
@@ -69212,16 +69588,16 @@ var PluginManifestSchema = external_exports.object({
69212
69588
  docsPath: external_exports.string().min(1)
69213
69589
  });
69214
69590
  function resolvePluginDir(pluginId) {
69215
- const dir = path39.join(resolveTemplatesDir(), "plugins", pluginId);
69216
- if (!fs34.existsSync(dir)) {
69591
+ const dir = path40.join(resolveTemplatesDir(), "plugins", pluginId);
69592
+ if (!fs35.existsSync(dir)) {
69217
69593
  throw new Error(`Plugin not found: ${pluginId}. Run npm run build.`);
69218
69594
  }
69219
69595
  return dir;
69220
69596
  }
69221
69597
  function readPluginManifest(pluginId) {
69222
- const manifestPath = path39.join(resolvePluginDir(pluginId), "template.manifest.json");
69598
+ const manifestPath = path40.join(resolvePluginDir(pluginId), "template.manifest.json");
69223
69599
  const parsed = PluginManifestSchema.safeParse(
69224
- JSON.parse(fs34.readFileSync(manifestPath, "utf8"))
69600
+ JSON.parse(fs35.readFileSync(manifestPath, "utf8"))
69225
69601
  );
69226
69602
  if (!parsed.success) {
69227
69603
  throw new Error(`Invalid plugin manifest for ${pluginId}: ${parsed.error.message}`);
@@ -69232,40 +69608,40 @@ function readPluginManifest(pluginId) {
69232
69608
  return parsed.data;
69233
69609
  }
69234
69610
  function ensureParentDir(filePath) {
69235
- const parent = path39.dirname(filePath);
69236
- if (!fs34.existsSync(parent)) {
69237
- fs34.mkdirSync(parent, { recursive: true });
69611
+ const parent = path40.dirname(filePath);
69612
+ if (!fs35.existsSync(parent)) {
69613
+ fs35.mkdirSync(parent, { recursive: true });
69238
69614
  }
69239
69615
  }
69240
69616
  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)) {
69617
+ const srcPath = path40.join(pluginDir, file.src);
69618
+ const destPath = path40.join(repoPath, file.dest);
69619
+ if (!fs35.existsSync(srcPath)) {
69244
69620
  throw new Error(`Plugin file missing: ${file.src}`);
69245
69621
  }
69246
- if (fs34.existsSync(destPath) && !force) {
69622
+ if (fs35.existsSync(destPath) && !force) {
69247
69623
  throw new Error(
69248
69624
  `File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
69249
69625
  );
69250
69626
  }
69251
69627
  ensureParentDir(destPath);
69252
- fs34.copyFileSync(srcPath, destPath);
69628
+ fs35.copyFileSync(srcPath, destPath);
69253
69629
  if (file.executable) {
69254
- fs34.chmodSync(destPath, 493);
69630
+ fs35.chmodSync(destPath, 493);
69255
69631
  }
69256
69632
  return { written: true, path: file.dest };
69257
69633
  }
69258
69634
  function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
69259
- const packagePath = path39.join(repoPath, "package.json");
69260
- if (!fs34.existsSync(packagePath)) {
69635
+ const packagePath = path40.join(repoPath, "package.json");
69636
+ if (!fs35.existsSync(packagePath)) {
69261
69637
  throw new Error("No package.json in repo root. Add one before applying this plugin.");
69262
69638
  }
69263
- const fragmentPath = path39.join(pluginDir, fragmentRelPath);
69264
- if (!fs34.existsSync(fragmentPath)) {
69639
+ const fragmentPath = path40.join(pluginDir, fragmentRelPath);
69640
+ if (!fs35.existsSync(fragmentPath)) {
69265
69641
  throw new Error(`Package fragment missing: ${fragmentRelPath}`);
69266
69642
  }
69267
- const pkg = JSON.parse(fs34.readFileSync(packagePath, "utf8"));
69268
- const fragment = JSON.parse(fs34.readFileSync(fragmentPath, "utf8"));
69643
+ const pkg = JSON.parse(fs35.readFileSync(packagePath, "utf8"));
69644
+ const fragment = JSON.parse(fs35.readFileSync(fragmentPath, "utf8"));
69269
69645
  for (const section of ["scripts", "devDependencies"]) {
69270
69646
  const existing = pkg[section] ?? {};
69271
69647
  const incoming = fragment[section] ?? {};
@@ -69278,7 +69654,7 @@ function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
69278
69654
  }
69279
69655
  pkg[section] = existing;
69280
69656
  }
69281
- fs34.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
69657
+ fs35.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
69282
69658
  }
69283
69659
  function patchStageRegistry(repoPath, manifest, force) {
69284
69660
  const registry2 = readStageRegistry(repoPath);
@@ -69318,8 +69694,8 @@ function assertHarnessPresent(repoPath) {
69318
69694
  }
69319
69695
  function assertStageNotPresent(repoPath, stageId, force) {
69320
69696
  if (force) return;
69321
- const scriptPath = path39.join(repoPath, ".har", "stages", `${stageId}.sh`);
69322
- if (fs34.existsSync(scriptPath)) {
69697
+ const scriptPath = path40.join(repoPath, ".har", "stages", `${stageId}.sh`);
69698
+ if (fs35.existsSync(scriptPath)) {
69323
69699
  throw new Error(
69324
69700
  `Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
69325
69701
  );
@@ -69332,7 +69708,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
69332
69708
  }
69333
69709
  }
69334
69710
  function applyPlugin(repoPath, pluginId, options = {}) {
69335
- const resolved = path39.resolve(repoPath);
69711
+ const resolved = path40.resolve(repoPath);
69336
69712
  const force = options.force ?? false;
69337
69713
  const warnings = [];
69338
69714
  const filesWritten = [];
@@ -69351,7 +69727,7 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69351
69727
  if (file.skipFlag === "skipCi" && options.skipCi) {
69352
69728
  continue;
69353
69729
  }
69354
- if (fs34.existsSync(path39.join(resolved, file.dest)) && !force) {
69730
+ if (fs35.existsSync(path40.join(resolved, file.dest)) && !force) {
69355
69731
  warnings.push(`Skipped optional file (exists): ${file.dest}`);
69356
69732
  continue;
69357
69733
  }
@@ -69386,18 +69762,18 @@ function applyPlugin(repoPath, pluginId, options = {}) {
69386
69762
  };
69387
69763
  }
69388
69764
  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));
69765
+ const root = path40.join(resolveTemplatesDir(), "plugins");
69766
+ if (!fs35.existsSync(root)) return [...PLUGIN_IDS];
69767
+ return fs35.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => PLUGIN_IDS.includes(name));
69392
69768
  }
69393
69769
 
69394
69770
  // src/harness/plugin-drift.ts
69395
69771
  init_stages();
69396
69772
  var PACKAGE_SECTIONS = ["scripts", "devDependencies"];
69397
69773
  function detectInstalledPlugins(repoPath) {
69398
- const resolved = path40.resolve(repoPath);
69399
- const registryPath = path40.join(resolved, ".har", "stages.json");
69400
- if (!fs35.existsSync(registryPath)) return [];
69774
+ const resolved = path41.resolve(repoPath);
69775
+ const registryPath = path41.join(resolved, ".har", "stages.json");
69776
+ if (!fs36.existsSync(registryPath)) return [];
69401
69777
  const registry2 = readStageRegistry(resolved);
69402
69778
  const stageIds = new Set(registry2.stages.map((stage) => stage.id));
69403
69779
  const installed = [];
@@ -69410,17 +69786,17 @@ function detectInstalledPlugins(repoPath) {
69410
69786
  return installed;
69411
69787
  }
69412
69788
  function pluginTemplatePath(pluginId, srcRel) {
69413
- return path40.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
69789
+ return path41.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
69414
69790
  }
69415
69791
  function readBundledPluginFile(pluginId, srcRel) {
69416
- return fs35.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
69792
+ return fs36.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
69417
69793
  }
69418
69794
  function listPluginDestPaths(pluginId, repoPath) {
69419
69795
  const manifest = readPluginManifest(pluginId);
69420
69796
  const paths = manifest.files.map((file) => file.dest);
69421
69797
  if (manifest.optionalFiles) {
69422
69798
  for (const file of manifest.optionalFiles) {
69423
- if (fs35.existsSync(path40.join(repoPath, file.dest))) {
69799
+ if (fs36.existsSync(path41.join(repoPath, file.dest))) {
69424
69800
  paths.push(file.dest);
69425
69801
  }
69426
69802
  }
@@ -69479,9 +69855,9 @@ function readTemplatePluginFile(pluginId, dest) {
69479
69855
  return null;
69480
69856
  }
69481
69857
  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");
69858
+ const installedPath = path41.join(path41.resolve(repoPath), dest);
69859
+ if (!fs36.existsSync(installedPath)) return null;
69860
+ const content = fs36.readFileSync(installedPath, "utf8");
69485
69861
  const manifest = readPluginManifest(pluginId);
69486
69862
  const fragmentRel = manifest.merge?.[dest];
69487
69863
  if (fragmentRel) {
@@ -69500,7 +69876,7 @@ function pluginActionHint(pluginId, dest, kind) {
69500
69876
  }
69501
69877
  function comparePluginToTemplate(repoPath, pluginId) {
69502
69878
  const manifest = readPluginManifest(pluginId);
69503
- const resolved = path40.resolve(repoPath);
69879
+ const resolved = path41.resolve(repoPath);
69504
69880
  const destPaths = listPluginDestPaths(pluginId, resolved);
69505
69881
  const missing = [];
69506
69882
  const checksumMismatch = [];
@@ -69515,8 +69891,8 @@ function comparePluginToTemplate(repoPath, pluginId) {
69515
69891
  }
69516
69892
  if (dest === "package.json") {
69517
69893
  const fragmentRel = manifest.merge?.[dest];
69518
- const installedPath = path40.join(resolved, dest);
69519
- const installedRaw = fs35.readFileSync(installedPath, "utf8");
69894
+ const installedPath = path41.join(resolved, dest);
69895
+ const installedRaw = fs36.readFileSync(installedPath, "utf8");
69520
69896
  if (fragmentRel && pluginKeysMatch(installedRaw, readBundledPluginFile(pluginId, fragmentRel))) {
69521
69897
  unchanged.push(dest);
69522
69898
  } else if (computeFileChecksum(installedContent) === computeFileChecksum(templateContent)) {
@@ -69585,14 +69961,14 @@ function substituteProjectName2(content, projectName) {
69585
69961
  return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
69586
69962
  }
69587
69963
  function projectNameFromRepo(repoPath) {
69588
- return path41.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69964
+ return path42.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
69589
69965
  }
69590
69966
  function boilerplateDirForProfile(profile) {
69591
- return path41.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
69967
+ return path42.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
69592
69968
  }
69593
69969
  function readBundledTemplateContent(repoPath, profile, file) {
69594
- const templatePath = path41.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
69595
- let content = fs36.readFileSync(templatePath, "utf8");
69970
+ const templatePath = path42.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
69971
+ let content = fs37.readFileSync(templatePath, "utf8");
69596
69972
  if (file === "harness.env") {
69597
69973
  content = substituteProjectName2(content, projectNameFromRepo(repoPath));
69598
69974
  }
@@ -69623,12 +69999,12 @@ function actionHint(file, kind) {
69623
69999
  return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
69624
70000
  }
69625
70001
  function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
69626
- const tmpDir = fs36.mkdtempSync(path41.join(os14.tmpdir(), "har-maintain-diff-"));
70002
+ const tmpDir = fs37.mkdtempSync(path42.join(os15.tmpdir(), "har-maintain-diff-"));
69627
70003
  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);
70004
+ const installedPath = path42.join(tmpDir, "installed");
70005
+ const templatePath = path42.join(tmpDir, "template");
70006
+ fs37.writeFileSync(installedPath, installedContent);
70007
+ fs37.writeFileSync(templatePath, templateContent);
69632
70008
  const result = run(`diff -u "${installedPath}" "${templatePath}"`);
69633
70009
  const body = result.stdout.trim();
69634
70010
  if (!body) return "";
@@ -69637,7 +70013,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
69637
70013
  ${body.split("\n").slice(2).join("\n")}
69638
70014
  `;
69639
70015
  } finally {
69640
- fs36.rmSync(tmpDir, { recursive: true, force: true });
70016
+ fs37.rmSync(tmpDir, { recursive: true, force: true });
69641
70017
  }
69642
70018
  }
69643
70019
  function buildActions(repoPath, profile, drift) {
@@ -69652,7 +70028,7 @@ function buildActions(repoPath, profile, drift) {
69652
70028
  });
69653
70029
  }
69654
70030
  for (const file of drift.checksumMismatch) {
69655
- const installedPath = path41.join(harnessDir, file);
70031
+ const installedPath = path42.join(harnessDir, file);
69656
70032
  const installedRel = `maintain/installed/${file}`;
69657
70033
  const templateRel = `maintain/templates/${file}`;
69658
70034
  const diffRel = `maintain/diffs/${file}.diff`;
@@ -69660,7 +70036,7 @@ function buildActions(repoPath, profile, drift) {
69660
70036
  file,
69661
70037
  kind: "drift",
69662
70038
  template: templateRel,
69663
- installed: fs36.existsSync(installedPath) ? installedRel : void 0,
70039
+ installed: fs37.existsSync(installedPath) ? installedRel : void 0,
69664
70040
  diff: diffRel,
69665
70041
  hint: actionHint(file, "drift")
69666
70042
  });
@@ -69812,28 +70188,28 @@ function buildReadme(report) {
69812
70188
  }
69813
70189
  function writeBundleArtifacts(repoPath, profile, drift, report) {
69814
70190
  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 });
70191
+ const bundleDir = path42.join(harnessDir, MAINTAIN_DIR);
70192
+ if (fs37.existsSync(bundleDir)) {
70193
+ fs37.rmSync(bundleDir, { recursive: true, force: true });
69818
70194
  }
69819
70195
  const dirs = [
69820
70196
  bundleDir,
69821
- path41.join(bundleDir, "templates"),
69822
- path41.join(bundleDir, "installed"),
69823
- path41.join(bundleDir, "diffs"),
69824
- path41.join(bundleDir, "stale")
70197
+ path42.join(bundleDir, "templates"),
70198
+ path42.join(bundleDir, "installed"),
70199
+ path42.join(bundleDir, "diffs"),
70200
+ path42.join(bundleDir, "stale")
69825
70201
  ];
69826
70202
  for (const dir of dirs) {
69827
- fs36.mkdirSync(dir, { recursive: true });
70203
+ fs37.mkdirSync(dir, { recursive: true });
69828
70204
  }
69829
70205
  const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
69830
70206
  for (const file of affectedFiles) {
69831
70207
  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);
70208
+ writeFileSafe(path42.join(bundleDir, "templates", file), templateContent);
70209
+ const harnessPath = path42.join(harnessDir, file);
70210
+ if (fs37.existsSync(harnessPath)) {
70211
+ const installedContent = fs37.readFileSync(harnessPath, "utf8");
70212
+ writeFileSafe(path42.join(bundleDir, "installed", file), installedContent);
69837
70213
  const diff = createUnifiedDiff(
69838
70214
  installedContent,
69839
70215
  templateContent,
@@ -69841,14 +70217,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
69841
70217
  `templates/${file}`
69842
70218
  );
69843
70219
  if (diff) {
69844
- writeFileSafe(path41.join(bundleDir, "diffs", `${file}.diff`), diff);
70220
+ writeFileSafe(path42.join(bundleDir, "diffs", `${file}.diff`), diff);
69845
70221
  }
69846
70222
  }
69847
70223
  }
69848
- writeFileSafe(path41.join(bundleDir, "README.md"), buildReadme(report));
69849
- writeFileSafe(path41.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
70224
+ writeFileSafe(path42.join(bundleDir, "README.md"), buildReadme(report));
70225
+ writeFileSafe(path42.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
69850
70226
  writeFileSafe(
69851
- path41.join(bundleDir, "validation.json"),
70227
+ path42.join(bundleDir, "validation.json"),
69852
70228
  JSON.stringify(report.validation, null, 2) + "\n"
69853
70229
  );
69854
70230
  if (report.stale.length > 0) {
@@ -69860,34 +70236,34 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
69860
70236
  ...report.stale.map((s) => `- **${s.file}** \u2014 ${s.hint}`),
69861
70237
  ""
69862
70238
  ];
69863
- writeFileSafe(path41.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
70239
+ writeFileSafe(path42.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
69864
70240
  }
69865
70241
  return bundleDir;
69866
70242
  }
69867
70243
  function ensureParentDir2(filePath) {
69868
- const parent = path41.dirname(filePath);
69869
- if (!fs36.existsSync(parent)) {
69870
- fs36.mkdirSync(parent, { recursive: true });
70244
+ const parent = path42.dirname(filePath);
70245
+ if (!fs37.existsSync(parent)) {
70246
+ fs37.mkdirSync(parent, { recursive: true });
69871
70247
  }
69872
70248
  }
69873
70249
  function writePluginBundleArtifacts(repoPath, bundleDir, report) {
69874
70250
  if (report.pluginActions.length === 0) return;
69875
70251
  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");
70252
+ const pluginRoot = path42.join(bundleDir, "plugins", action.pluginId);
70253
+ const templatesDir = path42.join(pluginRoot, "templates");
70254
+ const installedDir = path42.join(pluginRoot, "installed");
70255
+ const diffsDir = path42.join(pluginRoot, "diffs");
69880
70256
  for (const dir of [templatesDir, installedDir, diffsDir]) {
69881
- fs36.mkdirSync(dir, { recursive: true });
70257
+ fs37.mkdirSync(dir, { recursive: true });
69882
70258
  }
69883
70259
  const templateContent = readTemplatePluginFile(action.pluginId, action.file);
69884
70260
  if (templateContent === null) continue;
69885
- const templatePath = path41.join(templatesDir, action.file);
70261
+ const templatePath = path42.join(templatesDir, action.file);
69886
70262
  ensureParentDir2(templatePath);
69887
70263
  writeFileSafe(templatePath, templateContent);
69888
70264
  const installedContent = readInstalledPluginFile(repoPath, action.pluginId, action.file);
69889
70265
  if (installedContent !== null) {
69890
- const installedPath = path41.join(installedDir, action.file);
70266
+ const installedPath = path42.join(installedDir, action.file);
69891
70267
  ensureParentDir2(installedPath);
69892
70268
  writeFileSafe(installedPath, installedContent);
69893
70269
  const diff = createUnifiedDiff(
@@ -69897,7 +70273,7 @@ function writePluginBundleArtifacts(repoPath, bundleDir, report) {
69897
70273
  `templates/${action.file}`
69898
70274
  );
69899
70275
  if (diff) {
69900
- const diffPath = path41.join(diffsDir, `${action.file}.diff`);
70276
+ const diffPath = path42.join(diffsDir, `${action.file}.diff`);
69901
70277
  ensureParentDir2(diffPath);
69902
70278
  writeFileSafe(diffPath, diff);
69903
70279
  }
@@ -69933,9 +70309,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
69933
70309
  return { bundleDir, report };
69934
70310
  }
69935
70311
  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 });
70312
+ const bundleDir = path42.join(getHarnessDir(repoPath), MAINTAIN_DIR);
70313
+ if (fs37.existsSync(bundleDir)) {
70314
+ fs37.rmSync(bundleDir, { recursive: true, force: true });
69939
70315
  }
69940
70316
  }
69941
70317
  function formatMaintainBundlePromptSection(report) {
@@ -70024,11 +70400,11 @@ init_manifest();
70024
70400
  init_stages();
70025
70401
  function listHarnessScripts(repoPath) {
70026
70402
  const harnessDir = getHarnessDir(repoPath);
70027
- if (!fs37.existsSync(harnessDir)) return [];
70028
- return fs37.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
70403
+ if (!fs38.existsSync(harnessDir)) return [];
70404
+ return fs38.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
70029
70405
  }
70030
70406
  function describeProject(repoPath) {
70031
- const resolved = path42.resolve(repoPath);
70407
+ const resolved = path43.resolve(repoPath);
70032
70408
  const manifest = readManifest(resolved);
70033
70409
  const present = harnessExists(resolved);
70034
70410
  return {
@@ -70048,8 +70424,8 @@ function describeProject(repoPath) {
70048
70424
  };
70049
70425
  }
70050
70426
  async function initHarness(options) {
70051
- const repoPath = path42.resolve(options.repoPath);
70052
- if (!fs37.existsSync(repoPath)) {
70427
+ const repoPath = path43.resolve(options.repoPath);
70428
+ if (!fs38.existsSync(repoPath)) {
70053
70429
  throw new Error(`Path not found: ${repoPath}`);
70054
70430
  }
70055
70431
  const scaffold = scaffoldHarnessBoilerplate(repoPath, {
@@ -70069,9 +70445,9 @@ async function initHarness(options) {
70069
70445
  };
70070
70446
  }
70071
70447
  async function maintainHarness(options) {
70072
- const repoPath = path42.resolve(options.repoPath);
70448
+ const repoPath = path43.resolve(options.repoPath);
70073
70449
  const harnessDir = getHarnessDir(repoPath);
70074
- if (!fs37.existsSync(harnessDir)) {
70450
+ if (!fs38.existsSync(harnessDir)) {
70075
70451
  throw new Error('No .har/ found. Run "har env init" first.');
70076
70452
  }
70077
70453
  const drift = compareHarnessToTemplate(repoPath);
@@ -70106,13 +70482,13 @@ function addPlugin(repoPath, pluginId, options = {}) {
70106
70482
  }
70107
70483
 
70108
70484
  // src/harness/custom-stage.ts
70109
- var fs38 = __toESM(require("fs"));
70110
- var path43 = __toESM(require("path"));
70485
+ var fs39 = __toESM(require("fs"));
70486
+ var path44 = __toESM(require("path"));
70111
70487
  init_schema2();
70112
70488
  init_stages();
70113
70489
  var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
70114
70490
  function addCustomStage(repoPath, options) {
70115
- const resolved = path43.resolve(repoPath);
70491
+ const resolved = path44.resolve(repoPath);
70116
70492
  if (!harnessExists(resolved)) {
70117
70493
  throw new Error('No .har/ harness found. Run "har env init" first.');
70118
70494
  }
@@ -70149,20 +70525,20 @@ function addCustomStage(repoPath, options) {
70149
70525
  if (options.command) {
70150
70526
  stage.command = options.command;
70151
70527
  } else {
70152
- const scriptRel = path43.join("stages", `${id}.sh`);
70153
- const scriptAbs = path43.join(resolved, ".har", scriptRel);
70154
- if (fs38.existsSync(scriptAbs) && !options.force) {
70528
+ const scriptRel = path44.join("stages", `${id}.sh`);
70529
+ const scriptAbs = path44.join(resolved, ".har", scriptRel);
70530
+ if (fs39.existsSync(scriptAbs) && !options.force) {
70155
70531
  throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
70156
70532
  }
70157
- const skeletonPath = path43.join(
70533
+ const skeletonPath = path44.join(
70158
70534
  resolveTemplatesDir(),
70159
70535
  "plugins",
70160
70536
  "custom-stage-skeleton.sh"
70161
70537
  );
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);
70538
+ const skeleton = fs39.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind).replace(/__STAGE_DESCRIPTION__/g, stage.description);
70539
+ fs39.mkdirSync(path44.dirname(scriptAbs), { recursive: true });
70540
+ fs39.writeFileSync(scriptAbs, skeleton);
70541
+ fs39.chmodSync(scriptAbs, 493);
70166
70542
  filesWritten.push(`.har/${scriptRel}`);
70167
70543
  stage.script = scriptRel;
70168
70544
  stage.artifacts = [
@@ -70210,18 +70586,18 @@ function addCustomStage(repoPath, options) {
70210
70586
  }
70211
70587
 
70212
70588
  // src/harness/adaptation-prompt.ts
70213
- var fs39 = __toESM(require("fs"));
70214
- var path44 = __toESM(require("path"));
70589
+ var fs40 = __toESM(require("fs"));
70590
+ var path45 = __toESM(require("path"));
70215
70591
  var readline5 = __toESM(require("readline"));
70216
70592
 
70217
70593
  // src/utils/clipboard.ts
70218
- var import_child_process9 = require("child_process");
70594
+ var import_child_process10 = require("child_process");
70219
70595
  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"] });
70596
+ const probe = process.platform === "win32" ? (0, import_child_process10.spawnSync)("where", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }) : (0, import_child_process10.spawnSync)("which", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] });
70221
70597
  return probe.status === 0;
70222
70598
  }
70223
70599
  function tryPipeCommand(command2, args, text) {
70224
- const result = (0, import_child_process9.spawnSync)(command2, args, {
70600
+ const result = (0, import_child_process10.spawnSync)(command2, args, {
70225
70601
  input: text,
70226
70602
  encoding: "utf8",
70227
70603
  stdio: ["pipe", "ignore", "ignore"]
@@ -70241,7 +70617,7 @@ function copyViaPlatformTools(text) {
70241
70617
  }
70242
70618
  if (process.platform === "win32") {
70243
70619
  if (commandExists2("powershell")) {
70244
- const result = (0, import_child_process9.spawnSync)(
70620
+ const result = (0, import_child_process10.spawnSync)(
70245
70621
  "powershell",
70246
70622
  [
70247
70623
  "-NoProfile",
@@ -70301,7 +70677,7 @@ function loadTemplate(name) {
70301
70677
  if (!filePath) {
70302
70678
  throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
70303
70679
  }
70304
- return fs39.readFileSync(filePath, "utf8");
70680
+ return fs40.readFileSync(filePath, "utf8");
70305
70681
  }
70306
70682
  function applyProfilePlaceholders(content, profile) {
70307
70683
  return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
@@ -70323,7 +70699,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
70323
70699
  }
70324
70700
  function writeAdaptationPrompt(repoPath, content) {
70325
70701
  const harnessDir = getHarnessDir(repoPath);
70326
- const filePath = path44.join(harnessDir, ADAPTATION_PROMPT_FILE);
70702
+ const filePath = path45.join(harnessDir, ADAPTATION_PROMPT_FILE);
70327
70703
  writeFileSafe(filePath, content);
70328
70704
  return filePath;
70329
70705
  }
@@ -70378,27 +70754,27 @@ async function offerAdaptationPromptClipboard(content, options = {}) {
70378
70754
  }
70379
70755
 
70380
70756
  // src/harness/cursor-rule.ts
70381
- var fs40 = __toESM(require("fs"));
70382
- var path45 = __toESM(require("path"));
70757
+ var fs41 = __toESM(require("fs"));
70758
+ var path46 = __toESM(require("path"));
70383
70759
  var readline6 = __toESM(require("readline"));
70384
70760
  init_file_ops();
70385
70761
  init_logging();
70386
70762
  var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
70387
70763
  function getCursorRulePath(repoPath) {
70388
- return path45.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
70764
+ return path46.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
70389
70765
  }
70390
70766
  function isCursorWorkspace(repoPath) {
70391
- return fs40.existsSync(path45.join(repoPath, ".cursor"));
70767
+ return fs41.existsSync(path46.join(repoPath, ".cursor"));
70392
70768
  }
70393
70769
  function cursorRuleExists(repoPath) {
70394
- return fs40.existsSync(getCursorRulePath(repoPath));
70770
+ return fs41.existsSync(getCursorRulePath(repoPath));
70395
70771
  }
70396
70772
  function scaffoldCursorRule(repoPath) {
70397
70773
  const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
70398
70774
  if (!templatePath) {
70399
70775
  throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
70400
70776
  }
70401
- const content = fs40.readFileSync(templatePath, "utf8");
70777
+ const content = fs41.readFileSync(templatePath, "utf8");
70402
70778
  writeFileSafe(getCursorRulePath(repoPath), content);
70403
70779
  }
70404
70780
  async function handleCursorRule(options) {
@@ -70487,8 +70863,8 @@ init_validation();
70487
70863
  init_control_config();
70488
70864
 
70489
70865
  // src/core/control-lifecycle.ts
70490
- var import_child_process10 = require("child_process");
70491
- var path46 = __toESM(require("path"));
70866
+ var import_child_process11 = require("child_process");
70867
+ var path47 = __toESM(require("path"));
70492
70868
 
70493
70869
  // src/core/control-image.ts
70494
70870
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -70514,10 +70890,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
70514
70890
  var CONTROL_CONTAINER_PORT = 3847;
70515
70891
  var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
70516
70892
  function resolveControlDir() {
70517
- return path46.resolve(__dirname, "..", "control");
70893
+ return path47.resolve(__dirname, "..", "control");
70518
70894
  }
70519
70895
  function resolveControlBuildContext() {
70520
- return path46.resolve(resolveControlDir(), "..");
70896
+ return path47.resolve(resolveControlDir(), "..");
70521
70897
  }
70522
70898
  function buildDockerRunArgs(options) {
70523
70899
  const name = options.containerName ?? CONTROL_CONTAINER_NAME;
@@ -70540,7 +70916,7 @@ function buildDockerRunArgs(options) {
70540
70916
  return args;
70541
70917
  }
70542
70918
  function runDocker(args) {
70543
- const result = (0, import_child_process10.spawnSync)("docker", args, {
70919
+ const result = (0, import_child_process11.spawnSync)("docker", args, {
70544
70920
  stdio: "inherit",
70545
70921
  env: process.env
70546
70922
  });
@@ -70550,7 +70926,7 @@ function runDocker(args) {
70550
70926
  return result.status ?? 1;
70551
70927
  }
70552
70928
  function controlContainerExists(name = CONTROL_CONTAINER_NAME) {
70553
- const result = (0, import_child_process10.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
70929
+ const result = (0, import_child_process11.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
70554
70930
  encoding: "utf8",
70555
70931
  stdio: ["pipe", "pipe", "ignore"]
70556
70932
  });
@@ -70573,7 +70949,7 @@ async function startMissionControl(options) {
70573
70949
  "-t",
70574
70950
  imageRef,
70575
70951
  "-f",
70576
- path46.join(resolveControlDir(), "Dockerfile"),
70952
+ path47.join(resolveControlDir(), "Dockerfile"),
70577
70953
  resolveControlBuildContext()
70578
70954
  ]);
70579
70955
  if (buildCode !== 0) {
@@ -70720,7 +71096,7 @@ async function ensureTelemetryInfrastructure(options) {
70720
71096
  // src/core/run-service.ts
70721
71097
  init_telemetry_config();
70722
71098
  init_telemetry_env();
70723
- var path48 = __toESM(require("path"));
71099
+ var path49 = __toESM(require("path"));
70724
71100
  init_local_executor();
70725
71101
  function extractShellOutput(result) {
70726
71102
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
@@ -70819,7 +71195,8 @@ var RunService = class {
70819
71195
  }
70820
71196
  async launchEnvironment(options) {
70821
71197
  const guard = checkLaunchGuard2(options.repoPath, options.agentId, {
70822
- resume: options.resume
71198
+ resume: options.resume,
71199
+ worktree: options.worktree
70823
71200
  });
70824
71201
  if (!guard.allowed) {
70825
71202
  const slot = guard.slot;
@@ -70838,27 +71215,28 @@ var RunService = class {
70838
71215
  } : void 0
70839
71216
  };
70840
71217
  }
70841
- let telemetryBanner = "";
71218
+ let launchBanner = (guard.readiness?.warnings ?? []).map((warning) => `WARN: ${warning}
71219
+ `).join("");
70842
71220
  if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
70843
71221
  const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
70844
71222
  if (ensured.message) {
70845
- telemetryBanner += `${ensured.message}
71223
+ launchBanner += `${ensured.message}
70846
71224
  Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Worktrees. Disable: har telemetry off
70847
71225
  `;
70848
71226
  }
70849
71227
  if (ensured.warning) {
70850
- telemetryBanner += `${ensured.warning}
71228
+ launchBanner += `${ensured.warning}
70851
71229
  `;
70852
71230
  }
70853
71231
  try {
70854
71232
  const { ensureOtelHooks: ensureOtelHooks2 } = await Promise.resolve().then(() => (init_otel_hooks(), otel_hooks_exports));
70855
71233
  const hooks = ensureOtelHooks2({ setupAgents: true });
70856
- if (hooks.message) telemetryBanner += `${hooks.message}
71234
+ if (hooks.message) launchBanner += `${hooks.message}
70857
71235
  `;
70858
- if (hooks.warning) telemetryBanner += `${hooks.warning}
71236
+ if (hooks.warning) launchBanner += `${hooks.warning}
70859
71237
  `;
70860
71238
  } catch (err) {
70861
- telemetryBanner += `@osfactory/otel-hook setup skipped: ${err instanceof Error ? err.message : String(err)}
71239
+ launchBanner += `@osfactory/otel-hook setup skipped: ${err instanceof Error ? err.message : String(err)}
70862
71240
  `;
70863
71241
  }
70864
71242
  }
@@ -70923,12 +71301,12 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
70923
71301
  suffix: session.suffix,
70924
71302
  createdAt: session.createdAt
70925
71303
  });
70926
- const envFile = path48.join(session.workDir, `.env.agent.${options.agentId}`);
71304
+ const envFile = path49.join(session.workDir, `.env.agent.${options.agentId}`);
70927
71305
  try {
70928
71306
  appendTelemetryEnvToFile(envFile, {
70929
71307
  sessionKey,
70930
71308
  agentId: options.agentId,
70931
- repoPath: path48.resolve(options.repoPath),
71309
+ repoPath: path49.resolve(options.repoPath),
70932
71310
  workDir: session.workDir,
70933
71311
  branch: session.branch,
70934
71312
  suffix: session.suffix,
@@ -70951,8 +71329,8 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
70951
71329
  });
70952
71330
  }
70953
71331
  }
70954
- if (telemetryBanner) {
70955
- envResult.stderr = `${telemetryBanner}${envResult.stderr ?? ""}`;
71332
+ if (launchBanner) {
71333
+ envResult.stderr = `${launchBanner}${envResult.stderr ?? ""}`;
70956
71334
  }
70957
71335
  }
70958
71336
  return envResult;
@@ -71164,279 +71542,7 @@ init_slot_status();
71164
71542
 
71165
71543
  // src/core/commit-gate-onboarding.ts
71166
71544
  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
71545
+ init_hooks();
71440
71546
  init_stages();
71441
71547
  init_logging();
71442
71548
  function askYesNo4(question) {
@@ -71564,7 +71670,7 @@ Occupied slots always block a new launch:
71564
71670
  For a new unrelated task: checkout main (or your intended base) on the main
71565
71671
  repo checkout, free the slot (complete/teardown), then launch.`;
71566
71672
  var HAR_ENV_EPILOG = `Environment lifecycle:
71567
- preflight <id> dry-run: ports, PM2, Docker, occupied-slot gates
71673
+ preflight <id> dry-run: ports, PM2, Docker, occupied-slot gates, untracked paths
71568
71674
  launch <id> fresh session worktree from the main checkout's current HEAD
71569
71675
  recover <id> alias for launch --resume (failed/starting only)
71570
71676
  verify <id> run checks (--full before declaring done)
@@ -71760,7 +71866,7 @@ var envCommand = {
71760
71866
  handleRecover
71761
71867
  ).command(
71762
71868
  "preflight <id>",
71763
- "Check whether a slot can launch now (ports, PM2, Docker, occupied slot)",
71869
+ "Check whether a slot can launch now (ports, PM2, Docker, occupied slot, untracked worktree paths)",
71764
71870
  (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
71871
  handlePreflight
71766
71872
  ).command(
@@ -72119,11 +72225,14 @@ async function handleLaunch(argv) {
72119
72225
  const repo = path51.resolve(argv.repo);
72120
72226
  const agentId = validateAgentId(argv.id, repo);
72121
72227
  if (!argv.resume) {
72122
- const guard = checkLaunchGuard2(repo, agentId, {});
72228
+ const guard = checkLaunchGuard2(repo, agentId, { worktree: argv.worktree });
72123
72229
  if (!guard.allowed && guard.blocked) {
72124
72230
  error(guard.reason ?? `Slot ${agentId} is occupied.`);
72125
72231
  return finishCommand(2);
72126
72232
  }
72233
+ for (const warning of guard.readiness?.warnings ?? []) {
72234
+ warn(warning);
72235
+ }
72127
72236
  }
72128
72237
  const result = await launchEnvironment({
72129
72238
  repoPath: repo,
@@ -72390,13 +72499,13 @@ init_portal_credentials();
72390
72499
  // src/core/portal-login.ts
72391
72500
  var http = __toESM(require("http"));
72392
72501
  var import_crypto = require("crypto");
72393
- var import_child_process12 = require("child_process");
72502
+ var import_child_process13 = require("child_process");
72394
72503
  var CALLBACK_PATH = "/callback";
72395
72504
  var LOGIN_TIMEOUT_MS = 3 * 6e4;
72396
72505
  function openBrowser(url) {
72397
72506
  const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
72398
72507
  try {
72399
- const child = (0, import_child_process12.spawn)(cmd, args, { stdio: "ignore", detached: true });
72508
+ const child = (0, import_child_process13.spawn)(cmd, args, { stdio: "ignore", detached: true });
72400
72509
  child.on("error", () => {
72401
72510
  });
72402
72511
  child.unref();
@@ -73256,6 +73365,7 @@ async function handleLogin(argv) {
73256
73365
 
73257
73366
  // src/cli/commands/hooks.ts
73258
73367
  var path57 = __toESM(require("path"));
73368
+ init_hooks();
73259
73369
 
73260
73370
  // src/core/claude-hooks.ts
73261
73371
  var fs47 = __toESM(require("fs"));
@@ -80825,7 +80935,7 @@ var HAR_MCP_TOOLS = [
80825
80935
  },
80826
80936
  {
80827
80937
  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.",
80938
+ 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
80939
  inputSchema: objectJsonSchema(
80830
80940
  {
80831
80941
  repo: repoJsonProperty,