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