@osfactory/har 0.9.0 → 0.10.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/README.md +31 -0
- package/dist/index.js +1019 -563
- package/dist/templates/agent-skills/har-maintain.md +34 -0
- package/dist/templates/agent-skills/har-wt.md +47 -0
- package/dist/templates/agent-skills/setup-har.md +59 -0
- package/dist/templates/agent-skills/skills.manifest.json +48 -0
- package/dist/templates/claude-worktree-guard.sh.template +38 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -765,14 +765,14 @@ var require_url_state_machine = __commonJS({
|
|
|
765
765
|
return url.replace(/\u0009|\u000A|\u000D/g, "");
|
|
766
766
|
}
|
|
767
767
|
function shortenPath(url) {
|
|
768
|
-
const
|
|
769
|
-
if (
|
|
768
|
+
const path40 = url.path;
|
|
769
|
+
if (path40.length === 0) {
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
|
-
if (url.scheme === "file" &&
|
|
772
|
+
if (url.scheme === "file" && path40.length === 1 && isNormalizedWindowsDriveLetter(path40[0])) {
|
|
773
773
|
return;
|
|
774
774
|
}
|
|
775
|
-
|
|
775
|
+
path40.pop();
|
|
776
776
|
}
|
|
777
777
|
function includesCredentials(url) {
|
|
778
778
|
return url.username !== "" || url.password !== "";
|
|
@@ -2092,7 +2092,7 @@ var require_lib2 = __commonJS({
|
|
|
2092
2092
|
let accum = [];
|
|
2093
2093
|
let accumBytes = 0;
|
|
2094
2094
|
let abort = false;
|
|
2095
|
-
return new Body.Promise(function(
|
|
2095
|
+
return new Body.Promise(function(resolve27, reject) {
|
|
2096
2096
|
let resTimeout;
|
|
2097
2097
|
if (_this4.timeout) {
|
|
2098
2098
|
resTimeout = setTimeout(function() {
|
|
@@ -2126,7 +2126,7 @@ var require_lib2 = __commonJS({
|
|
|
2126
2126
|
}
|
|
2127
2127
|
clearTimeout(resTimeout);
|
|
2128
2128
|
try {
|
|
2129
|
-
|
|
2129
|
+
resolve27(Buffer.concat(accum, accumBytes));
|
|
2130
2130
|
} catch (err) {
|
|
2131
2131
|
reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
|
|
2132
2132
|
}
|
|
@@ -2801,7 +2801,7 @@ var require_lib2 = __commonJS({
|
|
|
2801
2801
|
throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
|
|
2802
2802
|
}
|
|
2803
2803
|
Body.Promise = fetch3.Promise;
|
|
2804
|
-
return new fetch3.Promise(function(
|
|
2804
|
+
return new fetch3.Promise(function(resolve27, reject) {
|
|
2805
2805
|
const request = new Request3(url, opts);
|
|
2806
2806
|
const options = getNodeRequestOptions(request);
|
|
2807
2807
|
const send = (options.protocol === "https:" ? https : http).request;
|
|
@@ -2934,7 +2934,7 @@ var require_lib2 = __commonJS({
|
|
|
2934
2934
|
requestOpts.body = void 0;
|
|
2935
2935
|
requestOpts.headers.delete("content-length");
|
|
2936
2936
|
}
|
|
2937
|
-
|
|
2937
|
+
resolve27(fetch3(new Request3(locationURL, requestOpts)));
|
|
2938
2938
|
finalize();
|
|
2939
2939
|
return;
|
|
2940
2940
|
}
|
|
@@ -2955,7 +2955,7 @@ var require_lib2 = __commonJS({
|
|
|
2955
2955
|
const codings = headers.get("Content-Encoding");
|
|
2956
2956
|
if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
|
|
2957
2957
|
response = new Response3(body, response_options);
|
|
2958
|
-
|
|
2958
|
+
resolve27(response);
|
|
2959
2959
|
return;
|
|
2960
2960
|
}
|
|
2961
2961
|
const zlibOptions = {
|
|
@@ -2965,7 +2965,7 @@ var require_lib2 = __commonJS({
|
|
|
2965
2965
|
if (codings == "gzip" || codings == "x-gzip") {
|
|
2966
2966
|
body = body.pipe(zlib.createGunzip(zlibOptions));
|
|
2967
2967
|
response = new Response3(body, response_options);
|
|
2968
|
-
|
|
2968
|
+
resolve27(response);
|
|
2969
2969
|
return;
|
|
2970
2970
|
}
|
|
2971
2971
|
if (codings == "deflate" || codings == "x-deflate") {
|
|
@@ -2977,12 +2977,12 @@ var require_lib2 = __commonJS({
|
|
|
2977
2977
|
body = body.pipe(zlib.createInflateRaw());
|
|
2978
2978
|
}
|
|
2979
2979
|
response = new Response3(body, response_options);
|
|
2980
|
-
|
|
2980
|
+
resolve27(response);
|
|
2981
2981
|
});
|
|
2982
2982
|
raw.on("end", function() {
|
|
2983
2983
|
if (!response) {
|
|
2984
2984
|
response = new Response3(body, response_options);
|
|
2985
|
-
|
|
2985
|
+
resolve27(response);
|
|
2986
2986
|
}
|
|
2987
2987
|
});
|
|
2988
2988
|
return;
|
|
@@ -2990,11 +2990,11 @@ var require_lib2 = __commonJS({
|
|
|
2990
2990
|
if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
|
|
2991
2991
|
body = body.pipe(zlib.createBrotliDecompress());
|
|
2992
2992
|
response = new Response3(body, response_options);
|
|
2993
|
-
|
|
2993
|
+
resolve27(response);
|
|
2994
2994
|
return;
|
|
2995
2995
|
}
|
|
2996
2996
|
response = new Response3(body, response_options);
|
|
2997
|
-
|
|
2997
|
+
resolve27(response);
|
|
2998
2998
|
});
|
|
2999
2999
|
writeToStream(req, request);
|
|
3000
3000
|
});
|
|
@@ -6351,14 +6351,14 @@ __export(fileFromPath_exports, {
|
|
|
6351
6351
|
fileFromPathSync: () => fileFromPathSync,
|
|
6352
6352
|
isFile: () => isFile
|
|
6353
6353
|
});
|
|
6354
|
-
function createFileFromPath(
|
|
6354
|
+
function createFileFromPath(path40, { mtimeMs, size }, filenameOrOptions, options = {}) {
|
|
6355
6355
|
let filename;
|
|
6356
6356
|
if (isPlainObject_default2(filenameOrOptions)) {
|
|
6357
6357
|
[options, filename] = [filenameOrOptions, void 0];
|
|
6358
6358
|
} else {
|
|
6359
6359
|
filename = filenameOrOptions;
|
|
6360
6360
|
}
|
|
6361
|
-
const file = new FileFromPath({ path:
|
|
6361
|
+
const file = new FileFromPath({ path: path40, size, lastModified: mtimeMs });
|
|
6362
6362
|
if (!filename) {
|
|
6363
6363
|
filename = file.name;
|
|
6364
6364
|
}
|
|
@@ -6367,13 +6367,13 @@ function createFileFromPath(path37, { mtimeMs, size }, filenameOrOptions, option
|
|
|
6367
6367
|
lastModified: file.lastModified
|
|
6368
6368
|
});
|
|
6369
6369
|
}
|
|
6370
|
-
function fileFromPathSync(
|
|
6371
|
-
const stats = (0, import_fs5.statSync)(
|
|
6372
|
-
return createFileFromPath(
|
|
6370
|
+
function fileFromPathSync(path40, filenameOrOptions, options = {}) {
|
|
6371
|
+
const stats = (0, import_fs5.statSync)(path40);
|
|
6372
|
+
return createFileFromPath(path40, stats, filenameOrOptions, options);
|
|
6373
6373
|
}
|
|
6374
|
-
async function fileFromPath2(
|
|
6375
|
-
const stats = await import_fs5.promises.stat(
|
|
6376
|
-
return createFileFromPath(
|
|
6374
|
+
async function fileFromPath2(path40, filenameOrOptions, options) {
|
|
6375
|
+
const stats = await import_fs5.promises.stat(path40);
|
|
6376
|
+
return createFileFromPath(path40, stats, filenameOrOptions, options);
|
|
6377
6377
|
}
|
|
6378
6378
|
var import_fs5, import_path5, import_node_domexception, __classPrivateFieldSet5, __classPrivateFieldGet6, _FileFromPath_path, _FileFromPath_start, MESSAGE, FileFromPath;
|
|
6379
6379
|
var init_fileFromPath = __esm({
|
|
@@ -9384,7 +9384,7 @@ var require_compile = __commonJS({
|
|
|
9384
9384
|
const schOrFunc = root.refs[ref];
|
|
9385
9385
|
if (schOrFunc)
|
|
9386
9386
|
return schOrFunc;
|
|
9387
|
-
let _sch =
|
|
9387
|
+
let _sch = resolve27.call(this, root, ref);
|
|
9388
9388
|
if (_sch === void 0) {
|
|
9389
9389
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
9390
9390
|
const { schemaId } = this.opts;
|
|
@@ -9411,7 +9411,7 @@ var require_compile = __commonJS({
|
|
|
9411
9411
|
function sameSchemaEnv(s1, s2) {
|
|
9412
9412
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
9413
9413
|
}
|
|
9414
|
-
function
|
|
9414
|
+
function resolve27(root, ref) {
|
|
9415
9415
|
let sch;
|
|
9416
9416
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
9417
9417
|
ref = sch;
|
|
@@ -9629,8 +9629,8 @@ var require_utils2 = __commonJS({
|
|
|
9629
9629
|
}
|
|
9630
9630
|
return ind;
|
|
9631
9631
|
}
|
|
9632
|
-
function removeDotSegments(
|
|
9633
|
-
let input =
|
|
9632
|
+
function removeDotSegments(path40) {
|
|
9633
|
+
let input = path40;
|
|
9634
9634
|
const output = [];
|
|
9635
9635
|
let nextSlash = -1;
|
|
9636
9636
|
let len = 0;
|
|
@@ -9882,8 +9882,8 @@ var require_schemes = __commonJS({
|
|
|
9882
9882
|
wsComponent.secure = void 0;
|
|
9883
9883
|
}
|
|
9884
9884
|
if (wsComponent.resourceName) {
|
|
9885
|
-
const [
|
|
9886
|
-
wsComponent.path =
|
|
9885
|
+
const [path40, query] = wsComponent.resourceName.split("?");
|
|
9886
|
+
wsComponent.path = path40 && path40 !== "/" ? path40 : void 0;
|
|
9887
9887
|
wsComponent.query = query;
|
|
9888
9888
|
wsComponent.resourceName = void 0;
|
|
9889
9889
|
}
|
|
@@ -10042,7 +10042,7 @@ var require_fast_uri = __commonJS({
|
|
|
10042
10042
|
}
|
|
10043
10043
|
return uri;
|
|
10044
10044
|
}
|
|
10045
|
-
function
|
|
10045
|
+
function resolve27(baseURI, relativeURI, options) {
|
|
10046
10046
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
10047
10047
|
const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
10048
10048
|
schemelessOptions.skipEscape = true;
|
|
@@ -10300,7 +10300,7 @@ var require_fast_uri = __commonJS({
|
|
|
10300
10300
|
var fastUri = {
|
|
10301
10301
|
SCHEMES,
|
|
10302
10302
|
normalize: normalize3,
|
|
10303
|
-
resolve:
|
|
10303
|
+
resolve: resolve27,
|
|
10304
10304
|
resolveComponent,
|
|
10305
10305
|
equal,
|
|
10306
10306
|
serialize,
|
|
@@ -13276,12 +13276,12 @@ var require_dist = __commonJS({
|
|
|
13276
13276
|
throw new Error(`Unknown format "${name}"`);
|
|
13277
13277
|
return f2;
|
|
13278
13278
|
};
|
|
13279
|
-
function addFormats(ajv, list,
|
|
13279
|
+
function addFormats(ajv, list, fs34, exportName) {
|
|
13280
13280
|
var _a3;
|
|
13281
13281
|
var _b2;
|
|
13282
13282
|
(_a3 = (_b2 = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
13283
13283
|
for (const f2 of list)
|
|
13284
|
-
ajv.addFormat(f2,
|
|
13284
|
+
ajv.addFormat(f2, fs34[f2]);
|
|
13285
13285
|
}
|
|
13286
13286
|
module2.exports = exports2 = formatsPlugin;
|
|
13287
13287
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -14565,11 +14565,11 @@ var parser = new YargsParser({
|
|
|
14565
14565
|
resolve: import_path2.resolve,
|
|
14566
14566
|
// TODO: figure out a way to combine ESM and CJS coverage, such that
|
|
14567
14567
|
// we can exercise all the lines below:
|
|
14568
|
-
require: (
|
|
14568
|
+
require: (path40) => {
|
|
14569
14569
|
if (typeof require !== "undefined") {
|
|
14570
|
-
return require(
|
|
14571
|
-
} else if (
|
|
14572
|
-
return JSON.parse((0, import_fs2.readFileSync)(
|
|
14570
|
+
return require(path40);
|
|
14571
|
+
} else if (path40.match(/\.json$/)) {
|
|
14572
|
+
return JSON.parse((0, import_fs2.readFileSync)(path40, "utf8"));
|
|
14573
14573
|
} else {
|
|
14574
14574
|
throw Error("only .json config files are supported in ESM");
|
|
14575
14575
|
}
|
|
@@ -17095,12 +17095,12 @@ var YargsInstance = class {
|
|
|
17095
17095
|
async getCompletion(args, done) {
|
|
17096
17096
|
argsert("<array> [function]", [args, done], arguments.length);
|
|
17097
17097
|
if (!done) {
|
|
17098
|
-
return new Promise((
|
|
17098
|
+
return new Promise((resolve27, reject) => {
|
|
17099
17099
|
__classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
|
|
17100
17100
|
if (err)
|
|
17101
17101
|
reject(err);
|
|
17102
17102
|
else
|
|
17103
|
-
|
|
17103
|
+
resolve27(completions);
|
|
17104
17104
|
});
|
|
17105
17105
|
});
|
|
17106
17106
|
} else {
|
|
@@ -18158,17 +18158,15 @@ function getHarPackageVersion() {
|
|
|
18158
18158
|
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
18159
18159
|
}
|
|
18160
18160
|
|
|
18161
|
-
// src/cli/commands/
|
|
18162
|
-
var path31 = __toESM(require("path"));
|
|
18163
|
-
|
|
18164
|
-
// src/core/harness.ts
|
|
18165
|
-
var fs18 = __toESM(require("fs"));
|
|
18166
|
-
var path16 = __toESM(require("path"));
|
|
18167
|
-
|
|
18168
|
-
// src/harness/generator.ts
|
|
18169
|
-
var fs6 = __toESM(require("fs"));
|
|
18161
|
+
// src/cli/commands/agents.ts
|
|
18170
18162
|
var path6 = __toESM(require("path"));
|
|
18171
18163
|
|
|
18164
|
+
// src/harness/agent-skills.ts
|
|
18165
|
+
var fs5 = __toESM(require("fs"));
|
|
18166
|
+
var os2 = __toESM(require("os"));
|
|
18167
|
+
var path5 = __toESM(require("path"));
|
|
18168
|
+
var readline = __toESM(require("readline"));
|
|
18169
|
+
|
|
18172
18170
|
// src/utils/file-ops.ts
|
|
18173
18171
|
var fs2 = __toESM(require("fs"));
|
|
18174
18172
|
var path2 = __toESM(require("path"));
|
|
@@ -18769,31 +18767,10 @@ function resolveTemplateFile(name) {
|
|
|
18769
18767
|
return fs3.existsSync(filePath) ? filePath : null;
|
|
18770
18768
|
}
|
|
18771
18769
|
|
|
18772
|
-
// src/core/gitignore.ts
|
|
18773
|
-
var fs4 = __toESM(require("fs"));
|
|
18774
|
-
var path4 = __toESM(require("path"));
|
|
18775
|
-
var HARNESS_ROOT_GITIGNORE_PATTERNS = [
|
|
18776
|
-
".env.agent.*",
|
|
18777
|
-
"ecosystem.agent.*.config.cjs"
|
|
18778
|
-
];
|
|
18779
|
-
function ensureRootGitignorePatterns(checkoutDir) {
|
|
18780
|
-
const gitignorePath = path4.join(checkoutDir, ".gitignore");
|
|
18781
|
-
const content = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
|
|
18782
|
-
const lines = content.split("\n").map((line) => line.trim());
|
|
18783
|
-
const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
18784
|
-
if (missing.length === 0) return;
|
|
18785
|
-
const header2 = lines.length === 0 || content.trim().length === 0 ? "# HAR agent slot artifacts (generated by .har/launch.sh)\n" : "";
|
|
18786
|
-
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
18787
|
-
const block = lines.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
|
|
18788
|
-
` : `${header2}${missing.join("\n")}
|
|
18789
|
-
`;
|
|
18790
|
-
fs4.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
|
|
18791
|
-
}
|
|
18792
|
-
|
|
18793
18770
|
// src/harness/manifest.ts
|
|
18794
18771
|
var crypto = __toESM(require("crypto"));
|
|
18795
|
-
var
|
|
18796
|
-
var
|
|
18772
|
+
var fs4 = __toESM(require("fs"));
|
|
18773
|
+
var path4 = __toESM(require("path"));
|
|
18797
18774
|
|
|
18798
18775
|
// node_modules/zod/v3/external.js
|
|
18799
18776
|
var external_exports = {};
|
|
@@ -19273,8 +19250,8 @@ function getErrorMap() {
|
|
|
19273
19250
|
|
|
19274
19251
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
19275
19252
|
var makeIssue = (params) => {
|
|
19276
|
-
const { data, path:
|
|
19277
|
-
const fullPath = [...
|
|
19253
|
+
const { data, path: path40, errorMaps, issueData } = params;
|
|
19254
|
+
const fullPath = [...path40, ...issueData.path || []];
|
|
19278
19255
|
const fullIssue = {
|
|
19279
19256
|
...issueData,
|
|
19280
19257
|
path: fullPath
|
|
@@ -19390,11 +19367,11 @@ var errorUtil;
|
|
|
19390
19367
|
|
|
19391
19368
|
// node_modules/zod/v3/types.js
|
|
19392
19369
|
var ParseInputLazyPath = class {
|
|
19393
|
-
constructor(parent, value,
|
|
19370
|
+
constructor(parent, value, path40, key) {
|
|
19394
19371
|
this._cachedPath = [];
|
|
19395
19372
|
this.parent = parent;
|
|
19396
19373
|
this.data = value;
|
|
19397
|
-
this._path =
|
|
19374
|
+
this._path = path40;
|
|
19398
19375
|
this._key = key;
|
|
19399
19376
|
}
|
|
19400
19377
|
get path() {
|
|
@@ -22848,6 +22825,15 @@ var HAR_STAGE_KINDS = [
|
|
|
22848
22825
|
"custom"
|
|
22849
22826
|
];
|
|
22850
22827
|
var HAR_AGENT_SLOT_MIN = 1;
|
|
22828
|
+
var AgentSkillTargetSchema = external_exports.enum(["claude", "cursor", "codex"]);
|
|
22829
|
+
var ScaffoldedAgentFileSchema = external_exports.object({
|
|
22830
|
+
/** Repo-relative path, or '~/…' when scope is 'global'. */
|
|
22831
|
+
path: external_exports.string(),
|
|
22832
|
+
agent: AgentSkillTargetSchema,
|
|
22833
|
+
skill: external_exports.string(),
|
|
22834
|
+
checksum: external_exports.string(),
|
|
22835
|
+
scope: external_exports.enum(["repo", "global"]).default("repo")
|
|
22836
|
+
});
|
|
22851
22837
|
var HarnessManifestSchema = external_exports.object({
|
|
22852
22838
|
version: external_exports.string(),
|
|
22853
22839
|
generatorVersion: external_exports.string(),
|
|
@@ -22861,7 +22847,8 @@ var HarnessManifestSchema = external_exports.object({
|
|
|
22861
22847
|
}).optional(),
|
|
22862
22848
|
adaptationSummary: external_exports.string().optional(),
|
|
22863
22849
|
profile: external_exports.enum(["default", "cli", "ios"]).optional(),
|
|
22864
|
-
fileChecksums: external_exports.record(external_exports.string()).optional()
|
|
22850
|
+
fileChecksums: external_exports.record(external_exports.string()).optional(),
|
|
22851
|
+
scaffoldedAgentFiles: external_exports.array(ScaffoldedAgentFileSchema).optional()
|
|
22865
22852
|
});
|
|
22866
22853
|
var HarnessStageKindSchema = external_exports.enum(HAR_STAGE_KINDS);
|
|
22867
22854
|
var HarnessArtifactKindSchema = external_exports.enum([
|
|
@@ -23131,15 +23118,15 @@ var CHECKSUM_SKIP = /* @__PURE__ */ new Set([
|
|
|
23131
23118
|
"ADAPT-PROMPT.md"
|
|
23132
23119
|
]);
|
|
23133
23120
|
function getHarnessDir(repoPath) {
|
|
23134
|
-
return
|
|
23121
|
+
return path4.join(repoPath, DEFAULT_HAR_DIR);
|
|
23135
23122
|
}
|
|
23136
23123
|
function getManifestPath(repoPath) {
|
|
23137
|
-
return
|
|
23124
|
+
return path4.join(getHarnessDir(repoPath), "manifest.json");
|
|
23138
23125
|
}
|
|
23139
23126
|
function readManifest(repoPath) {
|
|
23140
23127
|
const manifestPath = getManifestPath(repoPath);
|
|
23141
|
-
if (!
|
|
23142
|
-
const raw = JSON.parse(
|
|
23128
|
+
if (!fs4.existsSync(manifestPath)) return null;
|
|
23129
|
+
const raw = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
|
|
23143
23130
|
const result = HarnessManifestSchema.safeParse(raw);
|
|
23144
23131
|
return result.success ? result.data : null;
|
|
23145
23132
|
}
|
|
@@ -23152,11 +23139,11 @@ function computeFileChecksum(content) {
|
|
|
23152
23139
|
}
|
|
23153
23140
|
function computeHarnessChecksums(harnessDir) {
|
|
23154
23141
|
const checksums = {};
|
|
23155
|
-
if (!
|
|
23156
|
-
for (const entry of
|
|
23142
|
+
if (!fs4.existsSync(harnessDir)) return checksums;
|
|
23143
|
+
for (const entry of fs4.readdirSync(harnessDir, { withFileTypes: true })) {
|
|
23157
23144
|
if (!entry.isFile() || CHECKSUM_SKIP.has(entry.name)) continue;
|
|
23158
|
-
const full =
|
|
23159
|
-
checksums[entry.name] = computeFileChecksum(
|
|
23145
|
+
const full = path4.join(harnessDir, entry.name);
|
|
23146
|
+
checksums[entry.name] = computeFileChecksum(fs4.readFileSync(full, "utf8"));
|
|
23160
23147
|
}
|
|
23161
23148
|
return checksums;
|
|
23162
23149
|
}
|
|
@@ -23176,17 +23163,331 @@ function createManifest(repoPath, adaptationSummary, stack, profile) {
|
|
|
23176
23163
|
};
|
|
23177
23164
|
}
|
|
23178
23165
|
function resolveHarnessRoot(inputPath) {
|
|
23179
|
-
let current =
|
|
23180
|
-
const { root } =
|
|
23166
|
+
let current = path4.resolve(inputPath);
|
|
23167
|
+
const { root } = path4.parse(current);
|
|
23181
23168
|
for (; ; ) {
|
|
23182
|
-
const manifestPath =
|
|
23183
|
-
if (
|
|
23169
|
+
const manifestPath = path4.join(current, DEFAULT_HAR_DIR, "manifest.json");
|
|
23170
|
+
if (fs4.existsSync(manifestPath)) {
|
|
23184
23171
|
return current;
|
|
23185
23172
|
}
|
|
23186
23173
|
if (current === root) break;
|
|
23187
|
-
current =
|
|
23174
|
+
current = path4.dirname(current);
|
|
23175
|
+
}
|
|
23176
|
+
return path4.resolve(inputPath);
|
|
23177
|
+
}
|
|
23178
|
+
|
|
23179
|
+
// src/harness/agent-skills.ts
|
|
23180
|
+
var AGENT_SKILL_TARGETS = ["claude", "cursor", "codex"];
|
|
23181
|
+
var MANAGED_HEADER = "<!-- managed by har \u2014 regenerated by `har agents install` / `har env maintain`; edit at your own risk -->";
|
|
23182
|
+
function agentSkillsTemplateDir() {
|
|
23183
|
+
const dir = path5.join(resolveTemplatesDir(), "agent-skills");
|
|
23184
|
+
if (!fs5.existsSync(dir)) {
|
|
23185
|
+
throw new Error("Agent skills templates not found (templates/agent-skills). Run npm run build.");
|
|
23186
|
+
}
|
|
23187
|
+
return dir;
|
|
23188
|
+
}
|
|
23189
|
+
function readSkillsManifest() {
|
|
23190
|
+
const manifestPath = path5.join(agentSkillsTemplateDir(), "skills.manifest.json");
|
|
23191
|
+
return JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
|
|
23192
|
+
}
|
|
23193
|
+
function readSkillBody(entry) {
|
|
23194
|
+
return fs5.readFileSync(path5.join(agentSkillsTemplateDir(), entry.body), "utf8");
|
|
23195
|
+
}
|
|
23196
|
+
function renderYamlValue(value) {
|
|
23197
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
23198
|
+
return String(value);
|
|
23199
|
+
}
|
|
23200
|
+
function renderClaudeSkill(entry, body) {
|
|
23201
|
+
const frontmatter = entry.claude?.frontmatter ?? { name: entry.id, description: entry.title };
|
|
23202
|
+
const lines = Object.entries(frontmatter).map(([key, value]) => `${key}: ${renderYamlValue(value)}`);
|
|
23203
|
+
return `---
|
|
23204
|
+
${lines.join("\n")}
|
|
23205
|
+
---
|
|
23206
|
+
|
|
23207
|
+
${MANAGED_HEADER}
|
|
23208
|
+
|
|
23209
|
+
${body}`;
|
|
23210
|
+
}
|
|
23211
|
+
function renderPlainSkill(body) {
|
|
23212
|
+
return `${MANAGED_HEADER}
|
|
23213
|
+
|
|
23214
|
+
${body}`;
|
|
23215
|
+
}
|
|
23216
|
+
function renderSkillFiles(agents) {
|
|
23217
|
+
const manifest = readSkillsManifest();
|
|
23218
|
+
const files = [];
|
|
23219
|
+
for (const entry of manifest.skills) {
|
|
23220
|
+
const body = readSkillBody(entry);
|
|
23221
|
+
if (agents.includes("claude")) {
|
|
23222
|
+
files.push({
|
|
23223
|
+
agent: "claude",
|
|
23224
|
+
skill: entry.id,
|
|
23225
|
+
scope: "repo",
|
|
23226
|
+
relPath: path5.join(".claude", "skills", entry.id, "SKILL.md"),
|
|
23227
|
+
content: renderClaudeSkill(entry, body)
|
|
23228
|
+
});
|
|
23229
|
+
}
|
|
23230
|
+
if (agents.includes("cursor") && entry.cursor?.command) {
|
|
23231
|
+
files.push({
|
|
23232
|
+
agent: "cursor",
|
|
23233
|
+
skill: entry.id,
|
|
23234
|
+
scope: "repo",
|
|
23235
|
+
relPath: path5.join(".cursor", "commands", `${entry.id}.md`),
|
|
23236
|
+
content: renderPlainSkill(body)
|
|
23237
|
+
});
|
|
23238
|
+
}
|
|
23239
|
+
if (agents.includes("codex") && entry.codex?.promptName) {
|
|
23240
|
+
files.push({
|
|
23241
|
+
agent: "codex",
|
|
23242
|
+
skill: entry.id,
|
|
23243
|
+
scope: "global",
|
|
23244
|
+
relPath: path5.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
|
|
23245
|
+
content: renderPlainSkill(body)
|
|
23246
|
+
});
|
|
23247
|
+
}
|
|
23248
|
+
}
|
|
23249
|
+
return files;
|
|
23250
|
+
}
|
|
23251
|
+
function resolveScaffoldPath(repoPath, file) {
|
|
23252
|
+
if (file.scope === "global") {
|
|
23253
|
+
return path5.join(os2.homedir(), file.relPath.replace(/^~\//, ""));
|
|
23254
|
+
}
|
|
23255
|
+
return path5.join(repoPath, file.relPath);
|
|
23256
|
+
}
|
|
23257
|
+
function isHarManaged(filePath) {
|
|
23258
|
+
return fs5.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
|
|
23259
|
+
}
|
|
23260
|
+
function scaffoldAgentSkills(repoPath, agents, options = {}) {
|
|
23261
|
+
const written = [];
|
|
23262
|
+
const skipped = [];
|
|
23263
|
+
const records = [];
|
|
23264
|
+
for (const file of renderSkillFiles(agents)) {
|
|
23265
|
+
const destPath = resolveScaffoldPath(repoPath, file);
|
|
23266
|
+
if (fs5.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
|
|
23267
|
+
warn(`Skipped ${file.relPath} \u2014 exists with user modifications (use --force to overwrite)`);
|
|
23268
|
+
skipped.push(file.relPath);
|
|
23269
|
+
continue;
|
|
23270
|
+
}
|
|
23271
|
+
writeFileSafe(destPath, file.content);
|
|
23272
|
+
written.push(file.relPath);
|
|
23273
|
+
records.push({
|
|
23274
|
+
path: file.relPath,
|
|
23275
|
+
agent: file.agent,
|
|
23276
|
+
skill: file.skill,
|
|
23277
|
+
checksum: computeFileChecksum(file.content),
|
|
23278
|
+
scope: file.scope
|
|
23279
|
+
});
|
|
23280
|
+
}
|
|
23281
|
+
recordScaffoldedFiles(repoPath, agents, records);
|
|
23282
|
+
return { written, skipped };
|
|
23283
|
+
}
|
|
23284
|
+
function recordScaffoldedFiles(repoPath, agents, records) {
|
|
23285
|
+
const manifest = readManifest(repoPath);
|
|
23286
|
+
if (!manifest) return;
|
|
23287
|
+
const kept = (manifest.scaffoldedAgentFiles ?? []).filter(
|
|
23288
|
+
(entry) => !agents.includes(entry.agent)
|
|
23289
|
+
);
|
|
23290
|
+
writeManifest(repoPath, {
|
|
23291
|
+
...manifest,
|
|
23292
|
+
scaffoldedAgentFiles: [...kept, ...records],
|
|
23293
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23294
|
+
});
|
|
23295
|
+
}
|
|
23296
|
+
function removeAgentSkills(repoPath, agents) {
|
|
23297
|
+
const removed = [];
|
|
23298
|
+
for (const file of renderSkillFiles(agents)) {
|
|
23299
|
+
const destPath = resolveScaffoldPath(repoPath, file);
|
|
23300
|
+
if (!fs5.existsSync(destPath)) continue;
|
|
23301
|
+
if (!isHarManaged(destPath)) {
|
|
23302
|
+
warn(`Kept ${file.relPath} \u2014 has user modifications`);
|
|
23303
|
+
continue;
|
|
23304
|
+
}
|
|
23305
|
+
fs5.rmSync(destPath);
|
|
23306
|
+
removed.push(file.relPath);
|
|
23307
|
+
const parent = path5.dirname(destPath);
|
|
23308
|
+
if (fs5.existsSync(parent) && fs5.readdirSync(parent).length === 0) {
|
|
23309
|
+
fs5.rmdirSync(parent);
|
|
23310
|
+
}
|
|
23311
|
+
}
|
|
23312
|
+
const manifest = readManifest(repoPath);
|
|
23313
|
+
if (manifest?.scaffoldedAgentFiles) {
|
|
23314
|
+
writeManifest(repoPath, {
|
|
23315
|
+
...manifest,
|
|
23316
|
+
scaffoldedAgentFiles: manifest.scaffoldedAgentFiles.filter(
|
|
23317
|
+
(entry) => !agents.includes(entry.agent)
|
|
23318
|
+
),
|
|
23319
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23320
|
+
});
|
|
23321
|
+
}
|
|
23322
|
+
return removed;
|
|
23323
|
+
}
|
|
23324
|
+
function detectAgentTargets(repoPath) {
|
|
23325
|
+
const manifest = readManifest(repoPath);
|
|
23326
|
+
const scaffolded = new Set((manifest?.scaffoldedAgentFiles ?? []).map((entry) => entry.agent));
|
|
23327
|
+
const detected = new Set(scaffolded);
|
|
23328
|
+
if (fs5.existsSync(path5.join(repoPath, ".claude"))) detected.add("claude");
|
|
23329
|
+
if (fs5.existsSync(path5.join(repoPath, ".cursor"))) detected.add("cursor");
|
|
23330
|
+
if (fs5.existsSync(path5.join(os2.homedir(), ".codex"))) detected.add("codex");
|
|
23331
|
+
return AGENT_SKILL_TARGETS.filter((agent) => detected.has(agent));
|
|
23332
|
+
}
|
|
23333
|
+
function parseAgentTargets(raw) {
|
|
23334
|
+
const parts = raw.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
23335
|
+
const invalid = parts.filter((part) => !AGENT_SKILL_TARGETS.includes(part));
|
|
23336
|
+
if (invalid.length > 0) {
|
|
23337
|
+
throw new Error(
|
|
23338
|
+
`Unknown agent target(s): ${invalid.join(", ")}. Valid: ${AGENT_SKILL_TARGETS.join(", ")}`
|
|
23339
|
+
);
|
|
23340
|
+
}
|
|
23341
|
+
return AGENT_SKILL_TARGETS.filter((agent) => parts.includes(agent));
|
|
23342
|
+
}
|
|
23343
|
+
async function handleAgentSkills(options) {
|
|
23344
|
+
const { repoPath, mode } = options;
|
|
23345
|
+
if (options.enabled === false) return false;
|
|
23346
|
+
let targets;
|
|
23347
|
+
if (options.agents !== void 0) {
|
|
23348
|
+
targets = parseAgentTargets(options.agents);
|
|
23349
|
+
} else {
|
|
23350
|
+
targets = detectAgentTargets(repoPath);
|
|
23351
|
+
if (targets.length === 0) return false;
|
|
23352
|
+
const manifest = readManifest(repoPath);
|
|
23353
|
+
const alreadyScaffolded = (manifest?.scaffoldedAgentFiles ?? []).length > 0;
|
|
23354
|
+
const shouldPrompt = !options.autoYes && !(mode === "maintain" && alreadyScaffolded);
|
|
23355
|
+
if (shouldPrompt) {
|
|
23356
|
+
const accepted = await askYesNo(
|
|
23357
|
+
`Scaffold agent skills (/setup-har, /har-wt, /har-maintain) for: ${targets.join(", ")}? [Y/n]`
|
|
23358
|
+
);
|
|
23359
|
+
if (!accepted) {
|
|
23360
|
+
info("Skipped agent skills");
|
|
23361
|
+
return false;
|
|
23362
|
+
}
|
|
23363
|
+
}
|
|
23364
|
+
}
|
|
23365
|
+
if (targets.length === 0) return false;
|
|
23366
|
+
const result = scaffoldAgentSkills(repoPath, targets, { force: options.force });
|
|
23367
|
+
for (const file of result.written) {
|
|
23368
|
+
info(`${mode === "maintain" ? "Refreshed" : "Wrote"} ${file}`);
|
|
23369
|
+
}
|
|
23370
|
+
return result.written.length > 0;
|
|
23371
|
+
}
|
|
23372
|
+
async function askYesNo(question) {
|
|
23373
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
23374
|
+
return new Promise((resolve27) => {
|
|
23375
|
+
process.stderr.write(`${question} `);
|
|
23376
|
+
rl.once("line", (answer) => {
|
|
23377
|
+
rl.close();
|
|
23378
|
+
const trimmed = answer.trim();
|
|
23379
|
+
if (trimmed === "") {
|
|
23380
|
+
resolve27(true);
|
|
23381
|
+
return;
|
|
23382
|
+
}
|
|
23383
|
+
resolve27(/^y(es)?$/i.test(trimmed));
|
|
23384
|
+
});
|
|
23385
|
+
});
|
|
23386
|
+
}
|
|
23387
|
+
|
|
23388
|
+
// src/cli/commands/agents.ts
|
|
23389
|
+
var targetsOption = (y2) => y2.option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("claude", { type: "boolean", default: false, describe: "Target Claude Code (.claude/skills/)" }).option("cursor", { type: "boolean", default: false, describe: "Target Cursor (.cursor/commands/)" }).option("codex", { type: "boolean", default: false, describe: "Target Codex CLI (~/.codex/prompts/, global)" }).option("agents", {
|
|
23390
|
+
type: "string",
|
|
23391
|
+
describe: `Comma-separated targets (${AGENT_SKILL_TARGETS.join(",")}); alternative to the per-agent flags`
|
|
23392
|
+
});
|
|
23393
|
+
function resolveTargets(argv, repoPath) {
|
|
23394
|
+
if (argv.agents) return parseAgentTargets(argv.agents);
|
|
23395
|
+
const flagged = AGENT_SKILL_TARGETS.filter((agent) => argv[agent]);
|
|
23396
|
+
if (flagged.length > 0) return flagged;
|
|
23397
|
+
return detectAgentTargets(repoPath);
|
|
23398
|
+
}
|
|
23399
|
+
var agentsCommand = {
|
|
23400
|
+
command: "agents <subcommand>",
|
|
23401
|
+
describe: "Manage scaffolded agent skills (/setup-har, /har-wt, /har-maintain)",
|
|
23402
|
+
builder: (yargs) => yargs.command(
|
|
23403
|
+
"install",
|
|
23404
|
+
"Scaffold agent skills into this repo (Claude/Cursor) and globally (Codex)",
|
|
23405
|
+
(y2) => targetsOption(y2).option("force", {
|
|
23406
|
+
type: "boolean",
|
|
23407
|
+
default: false,
|
|
23408
|
+
describe: "Overwrite files that have user modifications"
|
|
23409
|
+
}),
|
|
23410
|
+
handleInstall
|
|
23411
|
+
).command("remove", "Remove har-managed agent skill files", targetsOption, handleRemove).demandCommand(1, "Specify a subcommand: install | remove"),
|
|
23412
|
+
handler: () => {
|
|
23413
|
+
}
|
|
23414
|
+
};
|
|
23415
|
+
function handleInstall(argv) {
|
|
23416
|
+
const repoPath = path6.resolve(argv.repo);
|
|
23417
|
+
header("har agents install");
|
|
23418
|
+
try {
|
|
23419
|
+
const targets = resolveTargets(argv, repoPath);
|
|
23420
|
+
if (targets.length === 0) {
|
|
23421
|
+
warn("No agent targets detected. Pass --claude, --cursor, --codex, or --agents=\u2026");
|
|
23422
|
+
process.exit(1);
|
|
23423
|
+
}
|
|
23424
|
+
const result = scaffoldAgentSkills(repoPath, targets, { force: argv.force });
|
|
23425
|
+
for (const file of result.written) {
|
|
23426
|
+
info(` + ${file}`);
|
|
23427
|
+
}
|
|
23428
|
+
if (result.written.length === 0 && result.skipped.length > 0) {
|
|
23429
|
+
warn("Nothing written \u2014 all target files have user modifications (use --force).");
|
|
23430
|
+
process.exit(1);
|
|
23431
|
+
}
|
|
23432
|
+
success(`Agent skills installed for: ${targets.join(", ")}`);
|
|
23433
|
+
if (targets.includes("codex")) {
|
|
23434
|
+
info("Codex prompts are global (~/.codex/prompts/) \u2014 Codex has no per-repo prompt support.");
|
|
23435
|
+
}
|
|
23436
|
+
} catch (err) {
|
|
23437
|
+
error(err.message);
|
|
23438
|
+
process.exit(1);
|
|
23439
|
+
}
|
|
23440
|
+
}
|
|
23441
|
+
function handleRemove(argv) {
|
|
23442
|
+
const repoPath = path6.resolve(argv.repo);
|
|
23443
|
+
header("har agents remove");
|
|
23444
|
+
try {
|
|
23445
|
+
const targets = resolveTargets(argv, repoPath);
|
|
23446
|
+
if (targets.length === 0) {
|
|
23447
|
+
warn("No agent targets detected or specified.");
|
|
23448
|
+
return;
|
|
23449
|
+
}
|
|
23450
|
+
const removed = removeAgentSkills(repoPath, targets);
|
|
23451
|
+
for (const file of removed) {
|
|
23452
|
+
info(` - ${file}`);
|
|
23453
|
+
}
|
|
23454
|
+
success(removed.length > 0 ? "Agent skills removed." : "Nothing to remove.");
|
|
23455
|
+
} catch (err) {
|
|
23456
|
+
error(err.message);
|
|
23457
|
+
process.exit(1);
|
|
23188
23458
|
}
|
|
23189
|
-
|
|
23459
|
+
}
|
|
23460
|
+
|
|
23461
|
+
// src/cli/commands/env.ts
|
|
23462
|
+
var path33 = __toESM(require("path"));
|
|
23463
|
+
|
|
23464
|
+
// src/core/harness.ts
|
|
23465
|
+
var fs19 = __toESM(require("fs"));
|
|
23466
|
+
var path18 = __toESM(require("path"));
|
|
23467
|
+
|
|
23468
|
+
// src/harness/generator.ts
|
|
23469
|
+
var fs7 = __toESM(require("fs"));
|
|
23470
|
+
var path8 = __toESM(require("path"));
|
|
23471
|
+
|
|
23472
|
+
// src/core/gitignore.ts
|
|
23473
|
+
var fs6 = __toESM(require("fs"));
|
|
23474
|
+
var path7 = __toESM(require("path"));
|
|
23475
|
+
var HARNESS_ROOT_GITIGNORE_PATTERNS = [
|
|
23476
|
+
".env.agent.*",
|
|
23477
|
+
"ecosystem.agent.*.config.cjs"
|
|
23478
|
+
];
|
|
23479
|
+
function ensureRootGitignorePatterns(checkoutDir) {
|
|
23480
|
+
const gitignorePath = path7.join(checkoutDir, ".gitignore");
|
|
23481
|
+
const content = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
|
|
23482
|
+
const lines = content.split("\n").map((line) => line.trim());
|
|
23483
|
+
const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
23484
|
+
if (missing.length === 0) return;
|
|
23485
|
+
const header2 = lines.length === 0 || content.trim().length === 0 ? "# HAR agent slot artifacts (generated by .har/launch.sh)\n" : "";
|
|
23486
|
+
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
23487
|
+
const block = lines.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
|
|
23488
|
+
` : `${header2}${missing.join("\n")}
|
|
23489
|
+
`;
|
|
23490
|
+
fs6.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
|
|
23190
23491
|
}
|
|
23191
23492
|
|
|
23192
23493
|
// src/harness/generator.ts
|
|
@@ -23202,46 +23503,46 @@ var CLI_PRUNE_FILES = [
|
|
|
23202
23503
|
];
|
|
23203
23504
|
function pruneCliProfile(harnessDir) {
|
|
23204
23505
|
for (const file of CLI_PRUNE_FILES) {
|
|
23205
|
-
const filePath =
|
|
23206
|
-
if (
|
|
23207
|
-
|
|
23506
|
+
const filePath = path8.join(harnessDir, file);
|
|
23507
|
+
if (fs7.existsSync(filePath)) {
|
|
23508
|
+
fs7.unlinkSync(filePath);
|
|
23208
23509
|
}
|
|
23209
23510
|
}
|
|
23210
23511
|
}
|
|
23211
23512
|
function scaffoldClaudeMd(repoPath, projectName, force) {
|
|
23212
23513
|
const templatePath = resolveTemplateFile("CLAUDE.md.template");
|
|
23213
23514
|
if (!templatePath) return;
|
|
23214
|
-
const dest =
|
|
23215
|
-
if (
|
|
23515
|
+
const dest = path8.join(repoPath, "CLAUDE.md");
|
|
23516
|
+
if (fs7.existsSync(dest) && !force) return;
|
|
23216
23517
|
const displayName = projectName.replace(/_/g, " ");
|
|
23217
|
-
const content =
|
|
23218
|
-
|
|
23518
|
+
const content = fs7.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
|
|
23519
|
+
fs7.writeFileSync(dest, content);
|
|
23219
23520
|
}
|
|
23220
23521
|
function scaffoldHarnessBoilerplate(repoPath, options = {}) {
|
|
23221
|
-
const harnessDir =
|
|
23222
|
-
const projectName =
|
|
23522
|
+
const harnessDir = path8.join(repoPath, DEFAULT_HAR_DIR);
|
|
23523
|
+
const projectName = path8.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
23223
23524
|
const profile = options.profile ?? "default";
|
|
23224
|
-
const boilerplateDir =
|
|
23225
|
-
if (
|
|
23525
|
+
const boilerplateDir = path8.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
|
|
23526
|
+
if (fs7.existsSync(harnessDir) && !options.force) {
|
|
23226
23527
|
throw new Error(
|
|
23227
23528
|
'.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
|
|
23228
23529
|
);
|
|
23229
23530
|
}
|
|
23230
|
-
if (!
|
|
23531
|
+
if (!fs7.existsSync(boilerplateDir)) {
|
|
23231
23532
|
throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
|
|
23232
23533
|
}
|
|
23233
|
-
if (options.force &&
|
|
23234
|
-
|
|
23534
|
+
if (options.force && fs7.existsSync(harnessDir)) {
|
|
23535
|
+
fs7.rmSync(harnessDir, { recursive: true, force: true });
|
|
23235
23536
|
}
|
|
23236
23537
|
copyDirRecursive(boilerplateDir, harnessDir);
|
|
23237
23538
|
if (profile === "cli") {
|
|
23238
23539
|
pruneCliProfile(harnessDir);
|
|
23239
23540
|
}
|
|
23240
|
-
const harnessEnvPath =
|
|
23241
|
-
if (
|
|
23242
|
-
let content =
|
|
23541
|
+
const harnessEnvPath = path8.join(harnessDir, "harness.env");
|
|
23542
|
+
if (fs7.existsSync(harnessEnvPath)) {
|
|
23543
|
+
let content = fs7.readFileSync(harnessEnvPath, "utf8");
|
|
23243
23544
|
content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
23244
|
-
|
|
23545
|
+
fs7.writeFileSync(harnessEnvPath, content);
|
|
23245
23546
|
}
|
|
23246
23547
|
const manifest = createManifest(
|
|
23247
23548
|
repoPath,
|
|
@@ -23264,8 +23565,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
|
|
|
23264
23565
|
}
|
|
23265
23566
|
|
|
23266
23567
|
// src/llm/authoring-agent.ts
|
|
23267
|
-
var
|
|
23268
|
-
var
|
|
23568
|
+
var fs11 = __toESM(require("fs"));
|
|
23569
|
+
var path11 = __toESM(require("path"));
|
|
23269
23570
|
|
|
23270
23571
|
// node_modules/@anthropic-ai/sdk/error.mjs
|
|
23271
23572
|
var error_exports = {};
|
|
@@ -23664,13 +23965,13 @@ var MultipartBody = class {
|
|
|
23664
23965
|
// node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
|
|
23665
23966
|
var import_web = require("node:stream/web");
|
|
23666
23967
|
var fileFromPathWarned = false;
|
|
23667
|
-
async function fileFromPath3(
|
|
23968
|
+
async function fileFromPath3(path40, ...args) {
|
|
23668
23969
|
const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
|
|
23669
23970
|
if (!fileFromPathWarned) {
|
|
23670
|
-
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(
|
|
23971
|
+
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path40)}) instead`);
|
|
23671
23972
|
fileFromPathWarned = true;
|
|
23672
23973
|
}
|
|
23673
|
-
return await _fileFromPath(
|
|
23974
|
+
return await _fileFromPath(path40, ...args);
|
|
23674
23975
|
}
|
|
23675
23976
|
var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
23676
23977
|
var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
@@ -24166,8 +24467,8 @@ async function defaultParseResponse(props) {
|
|
|
24166
24467
|
}
|
|
24167
24468
|
var APIPromise = class _APIPromise extends Promise {
|
|
24168
24469
|
constructor(responsePromise, parseResponse = defaultParseResponse) {
|
|
24169
|
-
super((
|
|
24170
|
-
|
|
24470
|
+
super((resolve27) => {
|
|
24471
|
+
resolve27(null);
|
|
24171
24472
|
});
|
|
24172
24473
|
this.responsePromise = responsePromise;
|
|
24173
24474
|
this.parseResponse = parseResponse;
|
|
@@ -24267,29 +24568,29 @@ var APIClient = class {
|
|
|
24267
24568
|
defaultIdempotencyKey() {
|
|
24268
24569
|
return `stainless-node-retry-${uuid4()}`;
|
|
24269
24570
|
}
|
|
24270
|
-
get(
|
|
24271
|
-
return this.methodRequest("get",
|
|
24571
|
+
get(path40, opts) {
|
|
24572
|
+
return this.methodRequest("get", path40, opts);
|
|
24272
24573
|
}
|
|
24273
|
-
post(
|
|
24274
|
-
return this.methodRequest("post",
|
|
24574
|
+
post(path40, opts) {
|
|
24575
|
+
return this.methodRequest("post", path40, opts);
|
|
24275
24576
|
}
|
|
24276
|
-
patch(
|
|
24277
|
-
return this.methodRequest("patch",
|
|
24577
|
+
patch(path40, opts) {
|
|
24578
|
+
return this.methodRequest("patch", path40, opts);
|
|
24278
24579
|
}
|
|
24279
|
-
put(
|
|
24280
|
-
return this.methodRequest("put",
|
|
24580
|
+
put(path40, opts) {
|
|
24581
|
+
return this.methodRequest("put", path40, opts);
|
|
24281
24582
|
}
|
|
24282
|
-
delete(
|
|
24283
|
-
return this.methodRequest("delete",
|
|
24583
|
+
delete(path40, opts) {
|
|
24584
|
+
return this.methodRequest("delete", path40, opts);
|
|
24284
24585
|
}
|
|
24285
|
-
methodRequest(method,
|
|
24586
|
+
methodRequest(method, path40, opts) {
|
|
24286
24587
|
return this.request(Promise.resolve(opts).then(async (opts2) => {
|
|
24287
24588
|
const body = opts2 && isBlobLike(opts2?.body) ? new DataView(await opts2.body.arrayBuffer()) : opts2?.body instanceof DataView ? opts2.body : opts2?.body instanceof ArrayBuffer ? new DataView(opts2.body) : opts2 && ArrayBuffer.isView(opts2?.body) ? new DataView(opts2.body.buffer) : opts2?.body;
|
|
24288
|
-
return { method, path:
|
|
24589
|
+
return { method, path: path40, ...opts2, body };
|
|
24289
24590
|
}));
|
|
24290
24591
|
}
|
|
24291
|
-
getAPIList(
|
|
24292
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
24592
|
+
getAPIList(path40, Page, opts) {
|
|
24593
|
+
return this.requestAPIList(Page, { method: "get", path: path40, ...opts });
|
|
24293
24594
|
}
|
|
24294
24595
|
calculateContentLength(body) {
|
|
24295
24596
|
if (typeof body === "string") {
|
|
@@ -24307,10 +24608,10 @@ var APIClient = class {
|
|
|
24307
24608
|
return null;
|
|
24308
24609
|
}
|
|
24309
24610
|
buildRequest(options) {
|
|
24310
|
-
const { method, path:
|
|
24611
|
+
const { method, path: path40, query, headers = {} } = options;
|
|
24311
24612
|
const body = ArrayBuffer.isView(options.body) || options.__binaryRequest && typeof options.body === "string" ? options.body : isMultipartBody(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null;
|
|
24312
24613
|
const contentLength = this.calculateContentLength(body);
|
|
24313
|
-
const url = this.buildURL(
|
|
24614
|
+
const url = this.buildURL(path40, query);
|
|
24314
24615
|
if ("timeout" in options)
|
|
24315
24616
|
validatePositiveInteger("timeout", options.timeout);
|
|
24316
24617
|
const timeout = options.timeout ?? this.timeout;
|
|
@@ -24419,8 +24720,8 @@ var APIClient = class {
|
|
|
24419
24720
|
const request = this.makeRequest(options, null);
|
|
24420
24721
|
return new PagePromise(this, request, Page);
|
|
24421
24722
|
}
|
|
24422
|
-
buildURL(
|
|
24423
|
-
const url = isAbsoluteURL(
|
|
24723
|
+
buildURL(path40, query) {
|
|
24724
|
+
const url = isAbsoluteURL(path40) ? new URL(path40) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path40.startsWith("/") ? path40.slice(1) : path40));
|
|
24424
24725
|
const defaultQuery = this.defaultQuery();
|
|
24425
24726
|
if (!isEmptyObj(defaultQuery)) {
|
|
24426
24727
|
query = { ...defaultQuery, ...query };
|
|
@@ -24705,7 +25006,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
|
|
|
24705
25006
|
var isAbsoluteURL = (url) => {
|
|
24706
25007
|
return startsWithSchemeRegexp.test(url);
|
|
24707
25008
|
};
|
|
24708
|
-
var sleep = (ms) => new Promise((
|
|
25009
|
+
var sleep = (ms) => new Promise((resolve27) => setTimeout(resolve27, ms));
|
|
24709
25010
|
var validatePositiveInteger = (name, n2) => {
|
|
24710
25011
|
if (typeof n2 !== "number" || !Number.isInteger(n2)) {
|
|
24711
25012
|
throw new AnthropicError(`${name} must be an integer`);
|
|
@@ -25196,12 +25497,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25196
25497
|
}
|
|
25197
25498
|
return this._emit("error", new AnthropicError(String(error3)));
|
|
25198
25499
|
});
|
|
25199
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((
|
|
25200
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise,
|
|
25500
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve27, reject) => {
|
|
25501
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve27, "f");
|
|
25201
25502
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
|
|
25202
25503
|
}), "f");
|
|
25203
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((
|
|
25204
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise,
|
|
25504
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve27, reject) => {
|
|
25505
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve27, "f");
|
|
25205
25506
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
|
|
25206
25507
|
}), "f");
|
|
25207
25508
|
__classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -25330,11 +25631,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25330
25631
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
25331
25632
|
*/
|
|
25332
25633
|
emitted(event) {
|
|
25333
|
-
return new Promise((
|
|
25634
|
+
return new Promise((resolve27, reject) => {
|
|
25334
25635
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
|
|
25335
25636
|
if (event !== "error")
|
|
25336
25637
|
this.once("error", reject);
|
|
25337
|
-
this.once(event,
|
|
25638
|
+
this.once(event, resolve27);
|
|
25338
25639
|
});
|
|
25339
25640
|
}
|
|
25340
25641
|
async done() {
|
|
@@ -25561,7 +25862,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25561
25862
|
if (done) {
|
|
25562
25863
|
return { value: void 0, done: true };
|
|
25563
25864
|
}
|
|
25564
|
-
return new Promise((
|
|
25865
|
+
return new Promise((resolve27, reject) => readQueue.push({ resolve: resolve27, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
25565
25866
|
}
|
|
25566
25867
|
const chunk = pushQueue.shift();
|
|
25567
25868
|
return { value: chunk, done: false };
|
|
@@ -25709,12 +26010,12 @@ var MessageStream = class _MessageStream {
|
|
|
25709
26010
|
}
|
|
25710
26011
|
return this._emit("error", new AnthropicError(String(error3)));
|
|
25711
26012
|
});
|
|
25712
|
-
__classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((
|
|
25713
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise,
|
|
26013
|
+
__classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve27, reject) => {
|
|
26014
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve27, "f");
|
|
25714
26015
|
__classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
|
|
25715
26016
|
}), "f");
|
|
25716
|
-
__classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((
|
|
25717
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise,
|
|
26017
|
+
__classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve27, reject) => {
|
|
26018
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve27, "f");
|
|
25718
26019
|
__classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
|
|
25719
26020
|
}), "f");
|
|
25720
26021
|
__classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -25843,11 +26144,11 @@ var MessageStream = class _MessageStream {
|
|
|
25843
26144
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
25844
26145
|
*/
|
|
25845
26146
|
emitted(event) {
|
|
25846
|
-
return new Promise((
|
|
26147
|
+
return new Promise((resolve27, reject) => {
|
|
25847
26148
|
__classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
|
|
25848
26149
|
if (event !== "error")
|
|
25849
26150
|
this.once("error", reject);
|
|
25850
|
-
this.once(event,
|
|
26151
|
+
this.once(event, resolve27);
|
|
25851
26152
|
});
|
|
25852
26153
|
}
|
|
25853
26154
|
async done() {
|
|
@@ -26074,7 +26375,7 @@ var MessageStream = class _MessageStream {
|
|
|
26074
26375
|
if (done) {
|
|
26075
26376
|
return { value: void 0, done: true };
|
|
26076
26377
|
}
|
|
26077
|
-
return new Promise((
|
|
26378
|
+
return new Promise((resolve27, reject) => readQueue.push({ resolve: resolve27, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
26078
26379
|
}
|
|
26079
26380
|
const chunk = pushQueue.shift();
|
|
26080
26381
|
return { value: chunk, done: false };
|
|
@@ -26243,34 +26544,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
|
|
|
26243
26544
|
var sdk_default = Anthropic;
|
|
26244
26545
|
|
|
26245
26546
|
// src/llm/tools.ts
|
|
26246
|
-
var
|
|
26247
|
-
var
|
|
26248
|
-
var
|
|
26547
|
+
var fs10 = __toESM(require("fs"));
|
|
26548
|
+
var path10 = __toESM(require("path"));
|
|
26549
|
+
var readline3 = __toESM(require("readline"));
|
|
26249
26550
|
|
|
26250
26551
|
// src/harness/agent-md.ts
|
|
26251
|
-
var
|
|
26252
|
-
var
|
|
26253
|
-
var
|
|
26552
|
+
var fs9 = __toESM(require("fs"));
|
|
26553
|
+
var path9 = __toESM(require("path"));
|
|
26554
|
+
var readline2 = __toESM(require("readline"));
|
|
26254
26555
|
var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
|
|
26255
26556
|
var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
|
|
26256
26557
|
function writeAgentMdProposal(repoPath, content, rationale) {
|
|
26257
26558
|
const harnessDir = getHarnessDir(repoPath);
|
|
26258
|
-
writeFileSafe(
|
|
26559
|
+
writeFileSafe(path9.join(harnessDir, AGENT_MD_PROPOSAL), content);
|
|
26259
26560
|
writeFileSafe(
|
|
26260
|
-
|
|
26561
|
+
path9.join(harnessDir, AGENT_MD_PROPOSAL_META),
|
|
26261
26562
|
JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
26262
26563
|
);
|
|
26263
26564
|
}
|
|
26264
26565
|
function readAgentMdProposal(repoPath) {
|
|
26265
26566
|
const harnessDir = getHarnessDir(repoPath);
|
|
26266
|
-
const proposalPath =
|
|
26267
|
-
const metaPath =
|
|
26268
|
-
if (!
|
|
26269
|
-
const content =
|
|
26567
|
+
const proposalPath = path9.join(harnessDir, AGENT_MD_PROPOSAL);
|
|
26568
|
+
const metaPath = path9.join(harnessDir, AGENT_MD_PROPOSAL_META);
|
|
26569
|
+
if (!fs9.existsSync(proposalPath)) return null;
|
|
26570
|
+
const content = fs9.readFileSync(proposalPath, "utf8");
|
|
26270
26571
|
let rationale = "";
|
|
26271
|
-
if (
|
|
26572
|
+
if (fs9.existsSync(metaPath)) {
|
|
26272
26573
|
try {
|
|
26273
|
-
rationale = JSON.parse(
|
|
26574
|
+
rationale = JSON.parse(fs9.readFileSync(metaPath, "utf8")).rationale ?? "";
|
|
26274
26575
|
} catch {
|
|
26275
26576
|
rationale = "";
|
|
26276
26577
|
}
|
|
@@ -26278,21 +26579,21 @@ function readAgentMdProposal(repoPath) {
|
|
|
26278
26579
|
return {
|
|
26279
26580
|
content,
|
|
26280
26581
|
rationale,
|
|
26281
|
-
createdAt:
|
|
26582
|
+
createdAt: fs9.existsSync(metaPath) ? JSON.parse(fs9.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
26282
26583
|
};
|
|
26283
26584
|
}
|
|
26284
26585
|
function clearAgentMdProposal(repoPath) {
|
|
26285
26586
|
const harnessDir = getHarnessDir(repoPath);
|
|
26286
26587
|
for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
|
|
26287
|
-
const p2 =
|
|
26288
|
-
if (
|
|
26588
|
+
const p2 = path9.join(harnessDir, file);
|
|
26589
|
+
if (fs9.existsSync(p2)) fs9.unlinkSync(p2);
|
|
26289
26590
|
}
|
|
26290
26591
|
}
|
|
26291
26592
|
async function promptApplyAgentMdProposal(repoPath) {
|
|
26292
26593
|
const proposal = readAgentMdProposal(repoPath);
|
|
26293
26594
|
if (!proposal) return false;
|
|
26294
|
-
const agentMdPath =
|
|
26295
|
-
const exists =
|
|
26595
|
+
const agentMdPath = path9.join(repoPath, "AGENT.md");
|
|
26596
|
+
const exists = fs9.existsSync(agentMdPath);
|
|
26296
26597
|
process.stderr.write("\n");
|
|
26297
26598
|
process.stderr.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
26298
26599
|
process.stderr.write("Proposed AGENT.md (repo root)\n");
|
|
@@ -26315,13 +26616,13 @@ async function promptApplyAgentMdProposal(repoPath) {
|
|
|
26315
26616
|
`);
|
|
26316
26617
|
if (exists) {
|
|
26317
26618
|
warn("AGENT.md already exists at repo root.");
|
|
26318
|
-
const answer = await
|
|
26619
|
+
const answer = await askYesNo2("Replace AGENT.md with this proposal? (y/n)");
|
|
26319
26620
|
if (!answer) {
|
|
26320
26621
|
info("Skipped \u2014 proposal kept at .har/AGENT.md.proposed");
|
|
26321
26622
|
return false;
|
|
26322
26623
|
}
|
|
26323
26624
|
} else {
|
|
26324
|
-
const answer = await
|
|
26625
|
+
const answer = await askYesNo2("Create AGENT.md at repo root? (y/n)");
|
|
26325
26626
|
if (!answer) {
|
|
26326
26627
|
info("Skipped \u2014 proposal kept at .har/AGENT.md.proposed");
|
|
26327
26628
|
return false;
|
|
@@ -26332,13 +26633,13 @@ async function promptApplyAgentMdProposal(repoPath) {
|
|
|
26332
26633
|
info("Wrote AGENT.md");
|
|
26333
26634
|
return true;
|
|
26334
26635
|
}
|
|
26335
|
-
async function
|
|
26336
|
-
const rl =
|
|
26337
|
-
return new Promise((
|
|
26636
|
+
async function askYesNo2(question) {
|
|
26637
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
|
|
26638
|
+
return new Promise((resolve27) => {
|
|
26338
26639
|
process.stderr.write(`${question} `);
|
|
26339
26640
|
rl.once("line", (answer) => {
|
|
26340
26641
|
rl.close();
|
|
26341
|
-
|
|
26642
|
+
resolve27(/^y(es)?$/i.test(answer.trim()));
|
|
26342
26643
|
});
|
|
26343
26644
|
});
|
|
26344
26645
|
}
|
|
@@ -26480,13 +26781,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
|
|
|
26480
26781
|
async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
|
|
26481
26782
|
switch (toolName) {
|
|
26482
26783
|
case "readRepoFile": {
|
|
26483
|
-
const filePath =
|
|
26784
|
+
const filePath = path10.join(repoPath, input.path);
|
|
26484
26785
|
const content = readFile(filePath, input.maxChars ?? 8e3);
|
|
26485
26786
|
info(`Read repo: ${input.path}`);
|
|
26486
26787
|
return content;
|
|
26487
26788
|
}
|
|
26488
26789
|
case "listRepoDir": {
|
|
26489
|
-
const dirPath =
|
|
26790
|
+
const dirPath = path10.join(repoPath, input.path ?? ".");
|
|
26490
26791
|
const entries = listDir(dirPath, input.maxFiles ?? 60);
|
|
26491
26792
|
info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
|
|
26492
26793
|
return entries.length > 0 ? entries.join("\n") : "(empty directory)";
|
|
@@ -26511,17 +26812,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26511
26812
|
const filePath = resolveSafePath(harnessDir, relPath);
|
|
26512
26813
|
writeFileSafe(filePath, input.content);
|
|
26513
26814
|
if (relPath.endsWith(".sh")) {
|
|
26514
|
-
|
|
26815
|
+
fs10.chmodSync(filePath, 493);
|
|
26515
26816
|
}
|
|
26516
26817
|
info(`Wrote harness: ${relPath}`);
|
|
26517
26818
|
return `Written ${relPath} (${input.content.length} bytes)`;
|
|
26518
26819
|
}
|
|
26519
26820
|
case "editHarnessFile": {
|
|
26520
26821
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26521
|
-
if (!
|
|
26822
|
+
if (!fs10.existsSync(filePath)) {
|
|
26522
26823
|
return `Error: file not found: ${input.path}`;
|
|
26523
26824
|
}
|
|
26524
|
-
const content =
|
|
26825
|
+
const content = fs10.readFileSync(filePath, "utf8");
|
|
26525
26826
|
const oldStr = input.old_string;
|
|
26526
26827
|
const newStr = input.new_string;
|
|
26527
26828
|
const count = content.split(oldStr).length - 1;
|
|
@@ -26538,10 +26839,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26538
26839
|
}
|
|
26539
26840
|
case "deleteHarnessFile": {
|
|
26540
26841
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26541
|
-
if (!
|
|
26842
|
+
if (!fs10.existsSync(filePath)) {
|
|
26542
26843
|
return `File not found (already deleted): ${input.path}`;
|
|
26543
26844
|
}
|
|
26544
|
-
|
|
26845
|
+
fs10.unlinkSync(filePath);
|
|
26545
26846
|
info(`Deleted harness: ${input.path}`);
|
|
26546
26847
|
return `Deleted ${input.path}`;
|
|
26547
26848
|
}
|
|
@@ -26567,8 +26868,8 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26567
26868
|
}
|
|
26568
26869
|
}
|
|
26569
26870
|
async function askUser(question, options) {
|
|
26570
|
-
const rl =
|
|
26571
|
-
return new Promise((
|
|
26871
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
26872
|
+
return new Promise((resolve27) => {
|
|
26572
26873
|
process.stderr.write("\n");
|
|
26573
26874
|
process.stderr.write(`\u2753 ${question}
|
|
26574
26875
|
`);
|
|
@@ -26579,7 +26880,7 @@ async function askUser(question, options) {
|
|
|
26579
26880
|
process.stderr.write("> ");
|
|
26580
26881
|
rl.once("line", (answer) => {
|
|
26581
26882
|
rl.close();
|
|
26582
|
-
|
|
26883
|
+
resolve27(answer.trim());
|
|
26583
26884
|
});
|
|
26584
26885
|
});
|
|
26585
26886
|
}
|
|
@@ -26592,29 +26893,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
|
|
|
26592
26893
|
const mode = options.mode ?? "init";
|
|
26593
26894
|
const systemPromptPath = resolvePromptPath("system-authoring.md");
|
|
26594
26895
|
const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
|
|
26595
|
-
let systemPrompt =
|
|
26896
|
+
let systemPrompt = fs11.existsSync(systemPromptPath) ? fs11.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
|
|
26596
26897
|
if (agentMdTemplatePath) {
|
|
26597
26898
|
systemPrompt += `
|
|
26598
26899
|
|
|
26599
26900
|
## AGENT.md template (starting point for proposeAgentMd)
|
|
26600
26901
|
|
|
26601
|
-
${
|
|
26902
|
+
${fs11.readFileSync(agentMdTemplatePath, "utf8")}`;
|
|
26602
26903
|
}
|
|
26603
26904
|
const tools = buildAuthoringTools();
|
|
26604
26905
|
const messages = [];
|
|
26605
26906
|
let rootListing;
|
|
26606
26907
|
try {
|
|
26607
|
-
rootListing =
|
|
26908
|
+
rootListing = fs11.readdirSync(repoPath).slice(0, 50).join("\n");
|
|
26608
26909
|
} catch {
|
|
26609
26910
|
throw new Error(`Cannot read repo at path: ${repoPath}`);
|
|
26610
26911
|
}
|
|
26611
26912
|
let harnessListing;
|
|
26612
26913
|
try {
|
|
26613
|
-
harnessListing =
|
|
26914
|
+
harnessListing = fs11.readdirSync(harnessDir).join("\n");
|
|
26614
26915
|
} catch {
|
|
26615
26916
|
throw new Error(`Cannot read harness dir at: ${harnessDir}`);
|
|
26616
26917
|
}
|
|
26617
|
-
const existingAgentMd =
|
|
26918
|
+
const existingAgentMd = fs11.existsSync(path11.join(repoPath, "AGENT.md")) ? "AGENT.md exists at repo root \u2014 read it before proposing changes via proposeAgentMd." : "No AGENT.md at repo root yet \u2014 propose one via proposeAgentMd.";
|
|
26618
26919
|
const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
|
|
26619
26920
|
Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
|
|
26620
26921
|
Keep README.md accurate \u2014 it is the index of the harness. ${existingAgentMd}` : `This is an INITIAL setup. A fresh boilerplate has been copied to .har/.
|
|
@@ -26694,8 +26995,8 @@ Try running with --verbose to see what the agent is doing.`
|
|
|
26694
26995
|
}
|
|
26695
26996
|
|
|
26696
26997
|
// src/harness/validator.ts
|
|
26697
|
-
var
|
|
26698
|
-
var
|
|
26998
|
+
var fs14 = __toESM(require("fs"));
|
|
26999
|
+
var path14 = __toESM(require("path"));
|
|
26699
27000
|
|
|
26700
27001
|
// src/utils/shell.ts
|
|
26701
27002
|
var import_child_process = require("child_process");
|
|
@@ -26723,7 +27024,7 @@ function runScript(scriptPath, args = [], options = {}) {
|
|
|
26723
27024
|
function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
26724
27025
|
const stream = options.stream ?? false;
|
|
26725
27026
|
const { stream: _stream, ...spawnOptions } = options;
|
|
26726
|
-
return new Promise((
|
|
27027
|
+
return new Promise((resolve27) => {
|
|
26727
27028
|
const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
|
|
26728
27029
|
...spawnOptions,
|
|
26729
27030
|
stdio: ["inherit", "pipe", "pipe"]
|
|
@@ -26738,13 +27039,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
|
26738
27039
|
stderr += d2;
|
|
26739
27040
|
if (stream) process.stderr.write(d2);
|
|
26740
27041
|
});
|
|
26741
|
-
proc.on("close", (code) =>
|
|
27042
|
+
proc.on("close", (code) => resolve27({ stdout, stderr, code: code ?? 0 }));
|
|
26742
27043
|
});
|
|
26743
27044
|
}
|
|
26744
27045
|
function runShellCommand(command2, options = {}) {
|
|
26745
27046
|
const stream = options.stream ?? false;
|
|
26746
27047
|
const { stream: _stream, ...spawnOptions } = options;
|
|
26747
|
-
return new Promise((
|
|
27048
|
+
return new Promise((resolve27) => {
|
|
26748
27049
|
const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
|
|
26749
27050
|
...spawnOptions,
|
|
26750
27051
|
stdio: ["inherit", "pipe", "pipe"]
|
|
@@ -26759,22 +27060,22 @@ function runShellCommand(command2, options = {}) {
|
|
|
26759
27060
|
stderr += d2;
|
|
26760
27061
|
if (stream) process.stderr.write(d2);
|
|
26761
27062
|
});
|
|
26762
|
-
proc.on("close", (code) =>
|
|
27063
|
+
proc.on("close", (code) => resolve27({ stdout, stderr, code: code ?? 0 }));
|
|
26763
27064
|
});
|
|
26764
27065
|
}
|
|
26765
27066
|
|
|
26766
27067
|
// src/harness/stages.ts
|
|
26767
|
-
var
|
|
26768
|
-
var
|
|
27068
|
+
var fs13 = __toESM(require("fs"));
|
|
27069
|
+
var path13 = __toESM(require("path"));
|
|
26769
27070
|
|
|
26770
27071
|
// src/harness/env.ts
|
|
26771
|
-
var
|
|
26772
|
-
var
|
|
27072
|
+
var fs12 = __toESM(require("fs"));
|
|
27073
|
+
var path12 = __toESM(require("path"));
|
|
26773
27074
|
function readHarnessEnv(repoPath) {
|
|
26774
|
-
const envPath =
|
|
26775
|
-
if (!
|
|
27075
|
+
const envPath = path12.join(getHarnessDir(repoPath), "harness.env");
|
|
27076
|
+
if (!fs12.existsSync(envPath)) return {};
|
|
26776
27077
|
const values = {};
|
|
26777
|
-
for (const line of
|
|
27078
|
+
for (const line of fs12.readFileSync(envPath, "utf8").split("\n")) {
|
|
26778
27079
|
const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
|
|
26779
27080
|
if (!match) continue;
|
|
26780
27081
|
values[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
@@ -26801,7 +27102,7 @@ var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
|
|
|
26801
27102
|
"custom"
|
|
26802
27103
|
]);
|
|
26803
27104
|
function getStageRegistryPath(repoPath) {
|
|
26804
|
-
return
|
|
27105
|
+
return path13.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
|
|
26805
27106
|
}
|
|
26806
27107
|
function synthesizeStageRegistry(repoPath) {
|
|
26807
27108
|
const harnessDir = getHarnessDir(repoPath);
|
|
@@ -26876,13 +27177,13 @@ function readAgentSlotsFromHarnessEnv(harnessEnv) {
|
|
|
26876
27177
|
}
|
|
26877
27178
|
function stageScriptExists(harnessDir, stage) {
|
|
26878
27179
|
if (stage.script) {
|
|
26879
|
-
return
|
|
27180
|
+
return fs13.existsSync(path13.join(harnessDir, stage.script));
|
|
26880
27181
|
}
|
|
26881
27182
|
if (stage.command) {
|
|
26882
27183
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
26883
|
-
return
|
|
27184
|
+
return fs13.existsSync(path13.join(harnessDir, scriptName));
|
|
26884
27185
|
}
|
|
26885
|
-
return
|
|
27186
|
+
return fs13.existsSync(path13.join(harnessDir, "stages", `${stage.id}.sh`));
|
|
26886
27187
|
}
|
|
26887
27188
|
function addStageIfRunnable(stages, harnessDir, stage) {
|
|
26888
27189
|
const parsed = HarnessStageSchema.parse(stage);
|
|
@@ -26892,10 +27193,10 @@ function addStageIfRunnable(stages, harnessDir, stage) {
|
|
|
26892
27193
|
}
|
|
26893
27194
|
function readStageRegistry(repoPath) {
|
|
26894
27195
|
const registryPath = getStageRegistryPath(repoPath);
|
|
26895
|
-
if (!
|
|
27196
|
+
if (!fs13.existsSync(registryPath)) {
|
|
26896
27197
|
return synthesizeStageRegistry(repoPath);
|
|
26897
27198
|
}
|
|
26898
|
-
const raw = JSON.parse(
|
|
27199
|
+
const raw = JSON.parse(fs13.readFileSync(registryPath, "utf8"));
|
|
26899
27200
|
const parsed = HarnessStageRegistrySchema.safeParse(raw);
|
|
26900
27201
|
if (!parsed.success) {
|
|
26901
27202
|
throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
|
|
@@ -26908,7 +27209,7 @@ function writeStageRegistry(repoPath, registry2) {
|
|
|
26908
27209
|
if (!parsed.success) {
|
|
26909
27210
|
throw new Error(`Invalid stage registry: ${parsed.error.message}`);
|
|
26910
27211
|
}
|
|
26911
|
-
|
|
27212
|
+
fs13.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
|
|
26912
27213
|
}
|
|
26913
27214
|
function listStages(repoPath) {
|
|
26914
27215
|
return readStageRegistry(repoPath).stages;
|
|
@@ -27014,22 +27315,22 @@ var SHELL_SCRIPTS = [
|
|
|
27014
27315
|
function validateHarness(repoPath) {
|
|
27015
27316
|
const harnessDir = getHarnessDir(repoPath);
|
|
27016
27317
|
const issues = [];
|
|
27017
|
-
if (!
|
|
27318
|
+
if (!fs14.existsSync(harnessDir)) {
|
|
27018
27319
|
return {
|
|
27019
27320
|
pass: false,
|
|
27020
27321
|
issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
|
|
27021
27322
|
};
|
|
27022
27323
|
}
|
|
27023
27324
|
for (const file of getRequiredFiles(repoPath)) {
|
|
27024
|
-
const filePath =
|
|
27025
|
-
if (!
|
|
27325
|
+
const filePath = path14.join(harnessDir, file);
|
|
27326
|
+
if (!fs14.existsSync(filePath)) {
|
|
27026
27327
|
issues.push({ file, message: "Required file missing", severity: "error" });
|
|
27027
27328
|
}
|
|
27028
27329
|
}
|
|
27029
27330
|
for (const script of SHELL_SCRIPTS) {
|
|
27030
|
-
const scriptPath =
|
|
27031
|
-
if (!
|
|
27032
|
-
const stat =
|
|
27331
|
+
const scriptPath = path14.join(harnessDir, script);
|
|
27332
|
+
if (!fs14.existsSync(scriptPath)) continue;
|
|
27333
|
+
const stat = fs14.statSync(scriptPath);
|
|
27033
27334
|
if (!(stat.mode & 73)) {
|
|
27034
27335
|
issues.push({ file: script, message: "Script is not executable", severity: "warning" });
|
|
27035
27336
|
}
|
|
@@ -27044,9 +27345,9 @@ function validateHarness(repoPath) {
|
|
|
27044
27345
|
}
|
|
27045
27346
|
const manifest = readManifest(repoPath);
|
|
27046
27347
|
const profile = manifest?.profile ?? "default";
|
|
27047
|
-
const ecosystemPath =
|
|
27048
|
-
if (profile !== "cli" &&
|
|
27049
|
-
const content =
|
|
27348
|
+
const ecosystemPath = path14.join(harnessDir, "ecosystem.agent.template.cjs");
|
|
27349
|
+
if (profile !== "cli" && fs14.existsSync(ecosystemPath)) {
|
|
27350
|
+
const content = fs14.readFileSync(ecosystemPath, "utf8");
|
|
27050
27351
|
if (!content.includes("module.exports")) {
|
|
27051
27352
|
issues.push({
|
|
27052
27353
|
file: "ecosystem.agent.template.cjs",
|
|
@@ -27055,9 +27356,9 @@ function validateHarness(repoPath) {
|
|
|
27055
27356
|
});
|
|
27056
27357
|
}
|
|
27057
27358
|
}
|
|
27058
|
-
const harnessEnvPath =
|
|
27059
|
-
if (
|
|
27060
|
-
const content =
|
|
27359
|
+
const harnessEnvPath = path14.join(harnessDir, "harness.env");
|
|
27360
|
+
if (fs14.existsSync(harnessEnvPath)) {
|
|
27361
|
+
const content = fs14.readFileSync(harnessEnvPath, "utf8");
|
|
27061
27362
|
if (content.includes("TODO: set migrate command")) {
|
|
27062
27363
|
issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
|
|
27063
27364
|
}
|
|
@@ -27065,17 +27366,17 @@ function validateHarness(repoPath) {
|
|
|
27065
27366
|
issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
|
|
27066
27367
|
}
|
|
27067
27368
|
}
|
|
27068
|
-
const verifyPath =
|
|
27069
|
-
if (
|
|
27070
|
-
const content =
|
|
27369
|
+
const verifyPath = path14.join(harnessDir, "verify.sh");
|
|
27370
|
+
if (fs14.existsSync(verifyPath)) {
|
|
27371
|
+
const content = fs14.readFileSync(verifyPath, "utf8");
|
|
27071
27372
|
if (content.includes("echo 'TODO:")) {
|
|
27072
27373
|
issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
|
|
27073
27374
|
}
|
|
27074
27375
|
}
|
|
27075
|
-
const stagesPath =
|
|
27076
|
-
if (
|
|
27376
|
+
const stagesPath = path14.join(harnessDir, "stages.json");
|
|
27377
|
+
if (fs14.existsSync(stagesPath)) {
|
|
27077
27378
|
try {
|
|
27078
|
-
JSON.parse(
|
|
27379
|
+
JSON.parse(fs14.readFileSync(stagesPath, "utf8"));
|
|
27079
27380
|
const registry2 = readStageRegistry(repoPath);
|
|
27080
27381
|
if (registry2.stages.length === 0) {
|
|
27081
27382
|
issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
|
|
@@ -27090,9 +27391,9 @@ function validateHarness(repoPath) {
|
|
|
27090
27391
|
} else {
|
|
27091
27392
|
issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
|
|
27092
27393
|
}
|
|
27093
|
-
const readmePath =
|
|
27094
|
-
if (
|
|
27095
|
-
const content =
|
|
27394
|
+
const readmePath = path14.join(harnessDir, "README.md");
|
|
27395
|
+
if (fs14.existsSync(readmePath)) {
|
|
27396
|
+
const content = fs14.readFileSync(readmePath, "utf8");
|
|
27096
27397
|
if (!content.includes(".har")) {
|
|
27097
27398
|
issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
|
|
27098
27399
|
}
|
|
@@ -27106,8 +27407,8 @@ function validateHarness(repoPath) {
|
|
|
27106
27407
|
async function smokeTestHarness(repoPath) {
|
|
27107
27408
|
const harnessDir = getHarnessDir(repoPath);
|
|
27108
27409
|
const issues = [];
|
|
27109
|
-
const setupScript =
|
|
27110
|
-
if (
|
|
27410
|
+
const setupScript = path14.join(harnessDir, "setup-infra.sh");
|
|
27411
|
+
if (fs14.existsSync(setupScript)) {
|
|
27111
27412
|
const result = run(`bash "${setupScript}"`, { cwd: repoPath });
|
|
27112
27413
|
if (result.code !== 0) {
|
|
27113
27414
|
issues.push({
|
|
@@ -27122,8 +27423,8 @@ async function smokeTestHarness(repoPath) {
|
|
|
27122
27423
|
}
|
|
27123
27424
|
|
|
27124
27425
|
// src/harness/drift.ts
|
|
27125
|
-
var
|
|
27126
|
-
var
|
|
27426
|
+
var fs15 = __toESM(require("fs"));
|
|
27427
|
+
var path15 = __toESM(require("path"));
|
|
27127
27428
|
var PROFILE_DIRS2 = {
|
|
27128
27429
|
default: "har-boilerplate",
|
|
27129
27430
|
cli: "har-boilerplate-cli",
|
|
@@ -27191,33 +27492,33 @@ function substituteProjectName(content, projectName) {
|
|
|
27191
27492
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27192
27493
|
}
|
|
27193
27494
|
function listBoilerplateFiles(boilerplateDir) {
|
|
27194
|
-
if (!
|
|
27195
|
-
return
|
|
27495
|
+
if (!fs15.existsSync(boilerplateDir)) return [];
|
|
27496
|
+
return fs15.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
|
|
27196
27497
|
}
|
|
27197
27498
|
function compareHarnessToTemplate(repoPath) {
|
|
27198
|
-
const resolved =
|
|
27499
|
+
const resolved = path15.resolve(repoPath);
|
|
27199
27500
|
const manifest = readManifest(resolved);
|
|
27200
27501
|
const profile = manifest?.profile ?? "default";
|
|
27201
27502
|
const harnessDir = getHarnessDir(resolved);
|
|
27202
|
-
const projectName =
|
|
27203
|
-
const boilerplateDir =
|
|
27503
|
+
const projectName = path15.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27504
|
+
const boilerplateDir = path15.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
|
|
27204
27505
|
const templateFiles = listBoilerplateFiles(boilerplateDir);
|
|
27205
27506
|
const missing = [];
|
|
27206
27507
|
const checksumMismatch = [];
|
|
27207
27508
|
const extra = [];
|
|
27208
27509
|
const unchanged = [];
|
|
27209
27510
|
for (const file of templateFiles) {
|
|
27210
|
-
const templatePath =
|
|
27211
|
-
const harnessPath =
|
|
27212
|
-
let templateContent =
|
|
27511
|
+
const templatePath = path15.join(boilerplateDir, file);
|
|
27512
|
+
const harnessPath = path15.join(harnessDir, file);
|
|
27513
|
+
let templateContent = fs15.readFileSync(templatePath, "utf8");
|
|
27213
27514
|
if (file === "harness.env") {
|
|
27214
27515
|
templateContent = substituteProjectName(templateContent, projectName);
|
|
27215
27516
|
}
|
|
27216
|
-
if (!
|
|
27517
|
+
if (!fs15.existsSync(harnessPath)) {
|
|
27217
27518
|
missing.push(file);
|
|
27218
27519
|
continue;
|
|
27219
27520
|
}
|
|
27220
|
-
const harnessChecksum = computeFileChecksum(
|
|
27521
|
+
const harnessChecksum = computeFileChecksum(fs15.readFileSync(harnessPath, "utf8"));
|
|
27221
27522
|
const templateChecksum = computeFileChecksum(templateContent);
|
|
27222
27523
|
if (harnessChecksum === templateChecksum) {
|
|
27223
27524
|
unchanged.push(file);
|
|
@@ -27225,10 +27526,10 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27225
27526
|
checksumMismatch.push(file);
|
|
27226
27527
|
}
|
|
27227
27528
|
}
|
|
27228
|
-
if (
|
|
27229
|
-
for (const file of
|
|
27230
|
-
const full =
|
|
27231
|
-
if (!
|
|
27529
|
+
if (fs15.existsSync(harnessDir)) {
|
|
27530
|
+
for (const file of fs15.readdirSync(harnessDir)) {
|
|
27531
|
+
const full = path15.join(harnessDir, file);
|
|
27532
|
+
if (!fs15.statSync(full).isFile()) continue;
|
|
27232
27533
|
if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
|
|
27233
27534
|
if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
|
|
27234
27535
|
extra.push(file);
|
|
@@ -27255,9 +27556,9 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27255
27556
|
}
|
|
27256
27557
|
|
|
27257
27558
|
// src/harness/maintain-bundle.ts
|
|
27258
|
-
var
|
|
27259
|
-
var
|
|
27260
|
-
var
|
|
27559
|
+
var fs16 = __toESM(require("fs"));
|
|
27560
|
+
var os3 = __toESM(require("os"));
|
|
27561
|
+
var path16 = __toESM(require("path"));
|
|
27261
27562
|
var MAINTAIN_DIR = "maintain";
|
|
27262
27563
|
var PROFILE_DIRS3 = {
|
|
27263
27564
|
default: "har-boilerplate",
|
|
@@ -27268,14 +27569,14 @@ function substituteProjectName2(content, projectName) {
|
|
|
27268
27569
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27269
27570
|
}
|
|
27270
27571
|
function projectNameFromRepo(repoPath) {
|
|
27271
|
-
return
|
|
27572
|
+
return path16.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27272
27573
|
}
|
|
27273
27574
|
function boilerplateDirForProfile(profile) {
|
|
27274
|
-
return
|
|
27575
|
+
return path16.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
|
|
27275
27576
|
}
|
|
27276
27577
|
function readBundledTemplateContent(repoPath, profile, file) {
|
|
27277
|
-
const templatePath =
|
|
27278
|
-
let content =
|
|
27578
|
+
const templatePath = path16.join(boilerplateDirForProfile(profile), file);
|
|
27579
|
+
let content = fs16.readFileSync(templatePath, "utf8");
|
|
27279
27580
|
if (file === "harness.env") {
|
|
27280
27581
|
content = substituteProjectName2(content, projectNameFromRepo(repoPath));
|
|
27281
27582
|
}
|
|
@@ -27306,12 +27607,12 @@ function actionHint(file, kind2) {
|
|
|
27306
27607
|
return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
|
|
27307
27608
|
}
|
|
27308
27609
|
function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
|
|
27309
|
-
const tmpDir =
|
|
27610
|
+
const tmpDir = fs16.mkdtempSync(path16.join(os3.tmpdir(), "har-maintain-diff-"));
|
|
27310
27611
|
try {
|
|
27311
|
-
const installedPath =
|
|
27312
|
-
const templatePath =
|
|
27313
|
-
|
|
27314
|
-
|
|
27612
|
+
const installedPath = path16.join(tmpDir, "installed");
|
|
27613
|
+
const templatePath = path16.join(tmpDir, "template");
|
|
27614
|
+
fs16.writeFileSync(installedPath, installedContent);
|
|
27615
|
+
fs16.writeFileSync(templatePath, templateContent);
|
|
27315
27616
|
const result = run(`diff -u "${installedPath}" "${templatePath}"`);
|
|
27316
27617
|
const body = result.stdout.trim();
|
|
27317
27618
|
if (!body) return "";
|
|
@@ -27320,7 +27621,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
|
|
|
27320
27621
|
${body.split("\n").slice(2).join("\n")}
|
|
27321
27622
|
`;
|
|
27322
27623
|
} finally {
|
|
27323
|
-
|
|
27624
|
+
fs16.rmSync(tmpDir, { recursive: true, force: true });
|
|
27324
27625
|
}
|
|
27325
27626
|
}
|
|
27326
27627
|
function buildActions(repoPath, profile, drift) {
|
|
@@ -27335,7 +27636,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
27335
27636
|
});
|
|
27336
27637
|
}
|
|
27337
27638
|
for (const file of drift.checksumMismatch) {
|
|
27338
|
-
const installedPath =
|
|
27639
|
+
const installedPath = path16.join(harnessDir, file);
|
|
27339
27640
|
const installedRel = `maintain/installed/${file}`;
|
|
27340
27641
|
const templateRel = `maintain/templates/${file}`;
|
|
27341
27642
|
const diffRel = `maintain/diffs/${file}.diff`;
|
|
@@ -27343,7 +27644,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
27343
27644
|
file,
|
|
27344
27645
|
kind: "drift",
|
|
27345
27646
|
template: templateRel,
|
|
27346
|
-
installed:
|
|
27647
|
+
installed: fs16.existsSync(installedPath) ? installedRel : void 0,
|
|
27347
27648
|
diff: diffRel,
|
|
27348
27649
|
hint: actionHint(file, "drift")
|
|
27349
27650
|
});
|
|
@@ -27414,28 +27715,28 @@ function buildReadme(report) {
|
|
|
27414
27715
|
}
|
|
27415
27716
|
function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
27416
27717
|
const harnessDir = getHarnessDir(repoPath);
|
|
27417
|
-
const bundleDir =
|
|
27418
|
-
if (
|
|
27419
|
-
|
|
27718
|
+
const bundleDir = path16.join(harnessDir, MAINTAIN_DIR);
|
|
27719
|
+
if (fs16.existsSync(bundleDir)) {
|
|
27720
|
+
fs16.rmSync(bundleDir, { recursive: true, force: true });
|
|
27420
27721
|
}
|
|
27421
27722
|
const dirs = [
|
|
27422
27723
|
bundleDir,
|
|
27423
|
-
|
|
27424
|
-
|
|
27425
|
-
|
|
27426
|
-
|
|
27724
|
+
path16.join(bundleDir, "templates"),
|
|
27725
|
+
path16.join(bundleDir, "installed"),
|
|
27726
|
+
path16.join(bundleDir, "diffs"),
|
|
27727
|
+
path16.join(bundleDir, "stale")
|
|
27427
27728
|
];
|
|
27428
27729
|
for (const dir of dirs) {
|
|
27429
|
-
|
|
27730
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
27430
27731
|
}
|
|
27431
27732
|
const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
|
|
27432
27733
|
for (const file of affectedFiles) {
|
|
27433
27734
|
const templateContent = readBundledTemplateContent(repoPath, profile, file);
|
|
27434
|
-
writeFileSafe(
|
|
27435
|
-
const harnessPath =
|
|
27436
|
-
if (
|
|
27437
|
-
const installedContent =
|
|
27438
|
-
writeFileSafe(
|
|
27735
|
+
writeFileSafe(path16.join(bundleDir, "templates", file), templateContent);
|
|
27736
|
+
const harnessPath = path16.join(harnessDir, file);
|
|
27737
|
+
if (fs16.existsSync(harnessPath)) {
|
|
27738
|
+
const installedContent = fs16.readFileSync(harnessPath, "utf8");
|
|
27739
|
+
writeFileSafe(path16.join(bundleDir, "installed", file), installedContent);
|
|
27439
27740
|
const diff = createUnifiedDiff(
|
|
27440
27741
|
installedContent,
|
|
27441
27742
|
templateContent,
|
|
@@ -27443,14 +27744,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
27443
27744
|
`templates/${file}`
|
|
27444
27745
|
);
|
|
27445
27746
|
if (diff) {
|
|
27446
|
-
writeFileSafe(
|
|
27747
|
+
writeFileSafe(path16.join(bundleDir, "diffs", `${file}.diff`), diff);
|
|
27447
27748
|
}
|
|
27448
27749
|
}
|
|
27449
27750
|
}
|
|
27450
|
-
writeFileSafe(
|
|
27451
|
-
writeFileSafe(
|
|
27751
|
+
writeFileSafe(path16.join(bundleDir, "README.md"), buildReadme(report));
|
|
27752
|
+
writeFileSafe(path16.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
|
|
27452
27753
|
writeFileSafe(
|
|
27453
|
-
|
|
27754
|
+
path16.join(bundleDir, "validation.json"),
|
|
27454
27755
|
JSON.stringify(report.validation, null, 2) + "\n"
|
|
27455
27756
|
);
|
|
27456
27757
|
if (report.stale.length > 0) {
|
|
@@ -27462,7 +27763,7 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
27462
27763
|
...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
|
|
27463
27764
|
""
|
|
27464
27765
|
];
|
|
27465
|
-
writeFileSafe(
|
|
27766
|
+
writeFileSafe(path16.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
|
|
27466
27767
|
}
|
|
27467
27768
|
return bundleDir;
|
|
27468
27769
|
}
|
|
@@ -27488,9 +27789,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
|
|
|
27488
27789
|
return { bundleDir, report };
|
|
27489
27790
|
}
|
|
27490
27791
|
function removeMaintainBundle(repoPath) {
|
|
27491
|
-
const bundleDir =
|
|
27492
|
-
if (
|
|
27493
|
-
|
|
27792
|
+
const bundleDir = path16.join(getHarnessDir(repoPath), MAINTAIN_DIR);
|
|
27793
|
+
if (fs16.existsSync(bundleDir)) {
|
|
27794
|
+
fs16.rmSync(bundleDir, { recursive: true, force: true });
|
|
27494
27795
|
}
|
|
27495
27796
|
}
|
|
27496
27797
|
function formatMaintainBundlePromptSection(report) {
|
|
@@ -27538,66 +27839,66 @@ function formatMaintainBundlePromptSection(report) {
|
|
|
27538
27839
|
}
|
|
27539
27840
|
|
|
27540
27841
|
// src/harness/stage-templates.ts
|
|
27541
|
-
var
|
|
27542
|
-
var
|
|
27842
|
+
var fs18 = __toESM(require("fs"));
|
|
27843
|
+
var path17 = __toESM(require("path"));
|
|
27543
27844
|
|
|
27544
27845
|
// src/harness/parser.ts
|
|
27545
|
-
var
|
|
27846
|
+
var fs17 = __toESM(require("fs"));
|
|
27546
27847
|
function harnessExists(repoPath) {
|
|
27547
|
-
return
|
|
27848
|
+
return fs17.existsSync(`${repoPath}/.har/setup-infra.sh`);
|
|
27548
27849
|
}
|
|
27549
27850
|
|
|
27550
27851
|
// src/harness/stage-templates.ts
|
|
27551
27852
|
function resolveTemplateDir(templateId) {
|
|
27552
|
-
const dir =
|
|
27553
|
-
if (!
|
|
27853
|
+
const dir = path17.join(resolveTemplatesDir(), "stage-templates", templateId);
|
|
27854
|
+
if (!fs18.existsSync(dir)) {
|
|
27554
27855
|
throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
|
|
27555
27856
|
}
|
|
27556
27857
|
return dir;
|
|
27557
27858
|
}
|
|
27558
27859
|
function readTemplateManifest(templateId) {
|
|
27559
|
-
const manifestPath =
|
|
27560
|
-
const raw = JSON.parse(
|
|
27860
|
+
const manifestPath = path17.join(resolveTemplateDir(templateId), "template.manifest.json");
|
|
27861
|
+
const raw = JSON.parse(fs18.readFileSync(manifestPath, "utf8"));
|
|
27561
27862
|
if (raw.id !== templateId) {
|
|
27562
27863
|
throw new Error(`Template manifest id mismatch: expected ${templateId}, got ${raw.id}`);
|
|
27563
27864
|
}
|
|
27564
27865
|
return raw;
|
|
27565
27866
|
}
|
|
27566
27867
|
function ensureParentDir(filePath) {
|
|
27567
|
-
const parent =
|
|
27568
|
-
if (!
|
|
27569
|
-
|
|
27868
|
+
const parent = path17.dirname(filePath);
|
|
27869
|
+
if (!fs18.existsSync(parent)) {
|
|
27870
|
+
fs18.mkdirSync(parent, { recursive: true });
|
|
27570
27871
|
}
|
|
27571
27872
|
}
|
|
27572
27873
|
function copyTemplateFile(templateDir, file, repoPath, force) {
|
|
27573
|
-
const srcPath =
|
|
27574
|
-
const destPath =
|
|
27575
|
-
if (!
|
|
27874
|
+
const srcPath = path17.join(templateDir, file.src);
|
|
27875
|
+
const destPath = path17.join(repoPath, file.dest);
|
|
27876
|
+
if (!fs18.existsSync(srcPath)) {
|
|
27576
27877
|
throw new Error(`Template file missing: ${file.src}`);
|
|
27577
27878
|
}
|
|
27578
|
-
if (
|
|
27879
|
+
if (fs18.existsSync(destPath) && !force) {
|
|
27579
27880
|
throw new Error(
|
|
27580
27881
|
`File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
|
|
27581
27882
|
);
|
|
27582
27883
|
}
|
|
27583
27884
|
ensureParentDir(destPath);
|
|
27584
|
-
|
|
27885
|
+
fs18.copyFileSync(srcPath, destPath);
|
|
27585
27886
|
if (file.executable) {
|
|
27586
|
-
|
|
27887
|
+
fs18.chmodSync(destPath, 493);
|
|
27587
27888
|
}
|
|
27588
27889
|
return { written: true, path: file.dest };
|
|
27589
27890
|
}
|
|
27590
27891
|
function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
27591
|
-
const packagePath =
|
|
27592
|
-
if (!
|
|
27892
|
+
const packagePath = path17.join(repoPath, "package.json");
|
|
27893
|
+
if (!fs18.existsSync(packagePath)) {
|
|
27593
27894
|
throw new Error("No package.json in repo root. Add one before applying the Playwright template.");
|
|
27594
27895
|
}
|
|
27595
|
-
const fragmentPath =
|
|
27596
|
-
if (!
|
|
27896
|
+
const fragmentPath = path17.join(templateDir, fragmentRelPath);
|
|
27897
|
+
if (!fs18.existsSync(fragmentPath)) {
|
|
27597
27898
|
throw new Error(`Package fragment missing: ${fragmentRelPath}`);
|
|
27598
27899
|
}
|
|
27599
|
-
const pkg = JSON.parse(
|
|
27600
|
-
const fragment = JSON.parse(
|
|
27900
|
+
const pkg = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
|
|
27901
|
+
const fragment = JSON.parse(fs18.readFileSync(fragmentPath, "utf8"));
|
|
27601
27902
|
for (const section of ["scripts", "devDependencies"]) {
|
|
27602
27903
|
const existing = pkg[section] ?? {};
|
|
27603
27904
|
const incoming = fragment[section] ?? {};
|
|
@@ -27610,7 +27911,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
|
27610
27911
|
}
|
|
27611
27912
|
pkg[section] = existing;
|
|
27612
27913
|
}
|
|
27613
|
-
|
|
27914
|
+
fs18.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
27614
27915
|
}
|
|
27615
27916
|
function patchStageRegistry(repoPath, manifest, force) {
|
|
27616
27917
|
const registry2 = readStageRegistry(repoPath);
|
|
@@ -27651,8 +27952,8 @@ function assertHarnessPresent(repoPath) {
|
|
|
27651
27952
|
}
|
|
27652
27953
|
function assertStageNotPresent(repoPath, stageId, force) {
|
|
27653
27954
|
if (force) return;
|
|
27654
|
-
const scriptPath =
|
|
27655
|
-
if (
|
|
27955
|
+
const scriptPath = path17.join(repoPath, ".har", "stages", `${stageId}.sh`);
|
|
27956
|
+
if (fs18.existsSync(scriptPath)) {
|
|
27656
27957
|
throw new Error(
|
|
27657
27958
|
`Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
|
|
27658
27959
|
);
|
|
@@ -27665,7 +27966,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
|
|
|
27665
27966
|
}
|
|
27666
27967
|
}
|
|
27667
27968
|
function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
27668
|
-
const resolved =
|
|
27969
|
+
const resolved = path17.resolve(repoPath);
|
|
27669
27970
|
const force = options.force ?? false;
|
|
27670
27971
|
const warnings = [];
|
|
27671
27972
|
const filesWritten = [];
|
|
@@ -27684,7 +27985,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27684
27985
|
if (file.skipFlag === "skipCi" && options.skipCi) {
|
|
27685
27986
|
continue;
|
|
27686
27987
|
}
|
|
27687
|
-
if (
|
|
27988
|
+
if (fs18.existsSync(path17.join(resolved, file.dest)) && !force) {
|
|
27688
27989
|
warnings.push(`Skipped optional file (exists): ${file.dest}`);
|
|
27689
27990
|
continue;
|
|
27690
27991
|
}
|
|
@@ -27747,11 +28048,11 @@ function validateAgentId(id, repoPath) {
|
|
|
27747
28048
|
// src/core/harness.ts
|
|
27748
28049
|
function listHarnessScripts(repoPath) {
|
|
27749
28050
|
const harnessDir = getHarnessDir(repoPath);
|
|
27750
|
-
if (!
|
|
27751
|
-
return
|
|
28051
|
+
if (!fs19.existsSync(harnessDir)) return [];
|
|
28052
|
+
return fs19.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
|
|
27752
28053
|
}
|
|
27753
28054
|
function describeProject(repoPath) {
|
|
27754
|
-
const resolved =
|
|
28055
|
+
const resolved = path18.resolve(repoPath);
|
|
27755
28056
|
const manifest = readManifest(resolved);
|
|
27756
28057
|
const present = harnessExists(resolved);
|
|
27757
28058
|
return {
|
|
@@ -27771,8 +28072,8 @@ function describeProject(repoPath) {
|
|
|
27771
28072
|
};
|
|
27772
28073
|
}
|
|
27773
28074
|
async function initHarness(options) {
|
|
27774
|
-
const repoPath =
|
|
27775
|
-
if (!
|
|
28075
|
+
const repoPath = path18.resolve(options.repoPath);
|
|
28076
|
+
if (!fs19.existsSync(repoPath)) {
|
|
27776
28077
|
throw new Error(`Path not found: ${repoPath}`);
|
|
27777
28078
|
}
|
|
27778
28079
|
const scaffold = scaffoldHarnessBoilerplate(repoPath, {
|
|
@@ -27803,9 +28104,9 @@ async function initHarness(options) {
|
|
|
27803
28104
|
};
|
|
27804
28105
|
}
|
|
27805
28106
|
async function maintainHarness(options) {
|
|
27806
|
-
const repoPath =
|
|
28107
|
+
const repoPath = path18.resolve(options.repoPath);
|
|
27807
28108
|
const harnessDir = getHarnessDir(repoPath);
|
|
27808
|
-
if (!
|
|
28109
|
+
if (!fs19.existsSync(harnessDir)) {
|
|
27809
28110
|
throw new Error('No .har/ found. Run "har env init" first.');
|
|
27810
28111
|
}
|
|
27811
28112
|
if (options.auto) {
|
|
@@ -27856,8 +28157,8 @@ function addStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27856
28157
|
}
|
|
27857
28158
|
|
|
27858
28159
|
// src/harness/adaptation-prompt.ts
|
|
27859
|
-
var
|
|
27860
|
-
var
|
|
28160
|
+
var fs20 = __toESM(require("fs"));
|
|
28161
|
+
var path19 = __toESM(require("path"));
|
|
27861
28162
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
27862
28163
|
var PROFILE_HINTS = {
|
|
27863
28164
|
default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.",
|
|
@@ -27869,7 +28170,7 @@ function loadTemplate(name) {
|
|
|
27869
28170
|
if (!filePath) {
|
|
27870
28171
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
27871
28172
|
}
|
|
27872
|
-
return
|
|
28173
|
+
return fs20.readFileSync(filePath, "utf8");
|
|
27873
28174
|
}
|
|
27874
28175
|
function applyProfilePlaceholders(content, profile) {
|
|
27875
28176
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -27891,7 +28192,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
|
27891
28192
|
}
|
|
27892
28193
|
function writeAdaptationPrompt(repoPath, content) {
|
|
27893
28194
|
const harnessDir = getHarnessDir(repoPath);
|
|
27894
|
-
const filePath =
|
|
28195
|
+
const filePath = path19.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
27895
28196
|
writeFileSafe(filePath, content);
|
|
27896
28197
|
return filePath;
|
|
27897
28198
|
}
|
|
@@ -27910,25 +28211,25 @@ function printAdaptationPrompt(content) {
|
|
|
27910
28211
|
}
|
|
27911
28212
|
|
|
27912
28213
|
// src/harness/cursor-rule.ts
|
|
27913
|
-
var
|
|
27914
|
-
var
|
|
27915
|
-
var
|
|
28214
|
+
var fs21 = __toESM(require("fs"));
|
|
28215
|
+
var path20 = __toESM(require("path"));
|
|
28216
|
+
var readline4 = __toESM(require("readline"));
|
|
27916
28217
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
27917
28218
|
function getCursorRulePath(repoPath) {
|
|
27918
|
-
return
|
|
28219
|
+
return path20.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
27919
28220
|
}
|
|
27920
28221
|
function isCursorWorkspace(repoPath) {
|
|
27921
|
-
return
|
|
28222
|
+
return fs21.existsSync(path20.join(repoPath, ".cursor"));
|
|
27922
28223
|
}
|
|
27923
28224
|
function cursorRuleExists(repoPath) {
|
|
27924
|
-
return
|
|
28225
|
+
return fs21.existsSync(getCursorRulePath(repoPath));
|
|
27925
28226
|
}
|
|
27926
28227
|
function scaffoldCursorRule(repoPath) {
|
|
27927
28228
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
27928
28229
|
if (!templatePath) {
|
|
27929
28230
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
27930
28231
|
}
|
|
27931
|
-
const content =
|
|
28232
|
+
const content = fs21.readFileSync(templatePath, "utf8");
|
|
27932
28233
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
27933
28234
|
}
|
|
27934
28235
|
async function handleCursorRule(options) {
|
|
@@ -27962,20 +28263,20 @@ async function handleCursorRule(options) {
|
|
|
27962
28263
|
}
|
|
27963
28264
|
async function promptScaffoldCursorRule(exists) {
|
|
27964
28265
|
const action = exists ? "Update" : "Create";
|
|
27965
|
-
return
|
|
28266
|
+
return askYesNo3(`${action} ${CURSOR_RULE_RELATIVE_PATH}? [Y/n]`);
|
|
27966
28267
|
}
|
|
27967
|
-
async function
|
|
27968
|
-
const rl =
|
|
27969
|
-
return new Promise((
|
|
28268
|
+
async function askYesNo3(question) {
|
|
28269
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
|
|
28270
|
+
return new Promise((resolve27) => {
|
|
27970
28271
|
process.stderr.write(`${question} `);
|
|
27971
28272
|
rl.once("line", (answer) => {
|
|
27972
28273
|
rl.close();
|
|
27973
28274
|
const trimmed = answer.trim();
|
|
27974
28275
|
if (trimmed === "") {
|
|
27975
|
-
|
|
28276
|
+
resolve27(true);
|
|
27976
28277
|
return;
|
|
27977
28278
|
}
|
|
27978
|
-
|
|
28279
|
+
resolve27(/^y(es)?$/i.test(trimmed));
|
|
27979
28280
|
});
|
|
27980
28281
|
});
|
|
27981
28282
|
}
|
|
@@ -28054,26 +28355,26 @@ function buildStageResult(input) {
|
|
|
28054
28355
|
}
|
|
28055
28356
|
|
|
28056
28357
|
// src/core/local-executor.ts
|
|
28057
|
-
var
|
|
28058
|
-
var
|
|
28358
|
+
var fs23 = __toESM(require("fs"));
|
|
28359
|
+
var path22 = __toESM(require("path"));
|
|
28059
28360
|
|
|
28060
28361
|
// src/core/slot-registry.ts
|
|
28061
|
-
var
|
|
28062
|
-
var
|
|
28362
|
+
var fs22 = __toESM(require("fs"));
|
|
28363
|
+
var path21 = __toESM(require("path"));
|
|
28063
28364
|
function getSlotRegistryDir(repoPath) {
|
|
28064
|
-
return
|
|
28365
|
+
return path21.join(getHarnessDir(repoPath), "slots");
|
|
28065
28366
|
}
|
|
28066
28367
|
function getSlotRegistryPath(repoPath, agentId) {
|
|
28067
|
-
return
|
|
28368
|
+
return path21.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
28068
28369
|
}
|
|
28069
28370
|
function isSlotResumable(session) {
|
|
28070
28371
|
return session?.status === "failed" || session?.status === "starting";
|
|
28071
28372
|
}
|
|
28072
28373
|
function readSlotRegistry(repoPath, agentId) {
|
|
28073
28374
|
const file = getSlotRegistryPath(repoPath, agentId);
|
|
28074
|
-
if (!
|
|
28375
|
+
if (!fs22.existsSync(file)) return void 0;
|
|
28075
28376
|
try {
|
|
28076
|
-
const raw = JSON.parse(
|
|
28377
|
+
const raw = JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
28077
28378
|
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
28078
28379
|
return result.success ? result.data : void 0;
|
|
28079
28380
|
} catch {
|
|
@@ -28083,7 +28384,7 @@ function readSlotRegistry(repoPath, agentId) {
|
|
|
28083
28384
|
|
|
28084
28385
|
// src/core/local-executor.ts
|
|
28085
28386
|
function resolveRepoPath(repoPath) {
|
|
28086
|
-
return
|
|
28387
|
+
return path22.resolve(repoPath);
|
|
28087
28388
|
}
|
|
28088
28389
|
function buildPreviewUrlsFromPorts(ports, env3) {
|
|
28089
28390
|
const urls = {};
|
|
@@ -28135,14 +28436,14 @@ function substituteAgentId(value, agentId) {
|
|
|
28135
28436
|
function resolveStageScriptPath(repoPath, stage) {
|
|
28136
28437
|
const harnessDir = getHarnessDir(repoPath);
|
|
28137
28438
|
if (stage.script) {
|
|
28138
|
-
return
|
|
28439
|
+
return path22.join(harnessDir, stage.script);
|
|
28139
28440
|
}
|
|
28140
28441
|
if (stage.command) {
|
|
28141
28442
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
28142
|
-
return
|
|
28443
|
+
return path22.join(harnessDir, scriptName);
|
|
28143
28444
|
}
|
|
28144
|
-
const stageScript =
|
|
28145
|
-
if (
|
|
28445
|
+
const stageScript = path22.join(harnessDir, "stages", `${stage.id}.sh`);
|
|
28446
|
+
if (fs23.existsSync(stageScript)) {
|
|
28146
28447
|
return stageScript;
|
|
28147
28448
|
}
|
|
28148
28449
|
throw new Error(
|
|
@@ -28157,7 +28458,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
28157
28458
|
...harnessEnv,
|
|
28158
28459
|
...stage.env ?? {}
|
|
28159
28460
|
};
|
|
28160
|
-
const cwd = stage.cwd ?
|
|
28461
|
+
const cwd = stage.cwd ? path22.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
|
|
28161
28462
|
const extraArgs = options.args ?? [];
|
|
28162
28463
|
const launchFlagArgs = [];
|
|
28163
28464
|
if (stage.kind === "launch" && options.launchFlags) {
|
|
@@ -28234,19 +28535,19 @@ var LocalScriptExecutor = class {
|
|
|
28234
28535
|
const repoPath = resolveRepoPath(ctx.repoPath);
|
|
28235
28536
|
const harnessDir = getHarnessDir(repoPath);
|
|
28236
28537
|
const artifactsDirName = getArtifactsDir(repoPath);
|
|
28237
|
-
const artifactsDir =
|
|
28238
|
-
if (!
|
|
28538
|
+
const artifactsDir = path22.join(harnessDir, artifactsDirName);
|
|
28539
|
+
if (!fs23.existsSync(artifactsDir)) return [];
|
|
28239
28540
|
const entries = [];
|
|
28240
28541
|
const walk = (dir, prefix) => {
|
|
28241
|
-
for (const entry of
|
|
28242
|
-
const full =
|
|
28243
|
-
const relative2 =
|
|
28542
|
+
for (const entry of fs23.readdirSync(dir, { withFileTypes: true })) {
|
|
28543
|
+
const full = path22.join(dir, entry.name);
|
|
28544
|
+
const relative2 = path22.join(prefix, entry.name);
|
|
28244
28545
|
if (entry.isDirectory()) {
|
|
28245
28546
|
walk(full, relative2);
|
|
28246
28547
|
continue;
|
|
28247
28548
|
}
|
|
28248
28549
|
if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
|
|
28249
|
-
const stat =
|
|
28550
|
+
const stat = fs23.statSync(full);
|
|
28250
28551
|
entries.push({
|
|
28251
28552
|
path: full,
|
|
28252
28553
|
relativePath: relative2,
|
|
@@ -28262,7 +28563,7 @@ var LocalScriptExecutor = class {
|
|
|
28262
28563
|
var localScriptExecutor = new LocalScriptExecutor();
|
|
28263
28564
|
|
|
28264
28565
|
// src/core/control-sync.ts
|
|
28265
|
-
var
|
|
28566
|
+
var path32 = __toESM(require("path"));
|
|
28266
28567
|
|
|
28267
28568
|
// src/core/control-config.ts
|
|
28268
28569
|
var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
|
|
@@ -28274,32 +28575,32 @@ function isControlEnabled() {
|
|
|
28274
28575
|
}
|
|
28275
28576
|
|
|
28276
28577
|
// src/core/control-registry.ts
|
|
28277
|
-
var
|
|
28278
|
-
var
|
|
28279
|
-
var
|
|
28578
|
+
var fs24 = __toESM(require("fs"));
|
|
28579
|
+
var os4 = __toESM(require("os"));
|
|
28580
|
+
var path23 = __toESM(require("path"));
|
|
28280
28581
|
function getRegistryPath() {
|
|
28281
28582
|
if (process.env.HAR_CONTROL_REGISTRY_PATH) {
|
|
28282
|
-
return
|
|
28583
|
+
return path23.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
|
|
28283
28584
|
}
|
|
28284
|
-
return
|
|
28585
|
+
return path23.join(os4.homedir(), ".har", "repos.json");
|
|
28285
28586
|
}
|
|
28286
28587
|
function readRegistry() {
|
|
28287
28588
|
const registryPath = getRegistryPath();
|
|
28288
28589
|
try {
|
|
28289
|
-
if (!
|
|
28290
|
-
return JSON.parse(
|
|
28590
|
+
if (!fs24.existsSync(registryPath)) return { repos: [] };
|
|
28591
|
+
return JSON.parse(fs24.readFileSync(registryPath, "utf8"));
|
|
28291
28592
|
} catch {
|
|
28292
28593
|
return { repos: [] };
|
|
28293
28594
|
}
|
|
28294
28595
|
}
|
|
28295
28596
|
function writeRegistry(registry2) {
|
|
28296
28597
|
const registryPath = getRegistryPath();
|
|
28297
|
-
|
|
28298
|
-
|
|
28598
|
+
fs24.mkdirSync(path23.dirname(registryPath), { recursive: true });
|
|
28599
|
+
fs24.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
|
|
28299
28600
|
}
|
|
28300
28601
|
function recordRepoForControlSync(repoPath) {
|
|
28301
28602
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
28302
|
-
const resolved =
|
|
28603
|
+
const resolved = path23.resolve(repoPath);
|
|
28303
28604
|
if (!readManifest(resolved)) return;
|
|
28304
28605
|
const registry2 = readRegistry();
|
|
28305
28606
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -28308,7 +28609,7 @@ function recordRepoForControlSync(repoPath) {
|
|
|
28308
28609
|
}
|
|
28309
28610
|
function listRegisteredRepos() {
|
|
28310
28611
|
const registry2 = readRegistry();
|
|
28311
|
-
const kept = registry2.repos.filter((repoPath) =>
|
|
28612
|
+
const kept = registry2.repos.filter((repoPath) => fs24.existsSync(repoPath) && readManifest(repoPath));
|
|
28312
28613
|
if (kept.length !== registry2.repos.length) {
|
|
28313
28614
|
writeRegistry({ repos: kept });
|
|
28314
28615
|
}
|
|
@@ -28316,17 +28617,17 @@ function listRegisteredRepos() {
|
|
|
28316
28617
|
}
|
|
28317
28618
|
|
|
28318
28619
|
// src/core/slot-status.ts
|
|
28319
|
-
var
|
|
28320
|
-
var
|
|
28321
|
-
var
|
|
28620
|
+
var fs29 = __toESM(require("fs"));
|
|
28621
|
+
var os7 = __toESM(require("os"));
|
|
28622
|
+
var path29 = __toESM(require("path"));
|
|
28322
28623
|
var import_child_process7 = require("child_process");
|
|
28323
28624
|
|
|
28324
28625
|
// src/core/runs.ts
|
|
28325
28626
|
var crypto2 = __toESM(require("crypto"));
|
|
28326
28627
|
var import_child_process2 = require("child_process");
|
|
28327
|
-
var
|
|
28328
|
-
var
|
|
28329
|
-
var
|
|
28628
|
+
var fs25 = __toESM(require("fs"));
|
|
28629
|
+
var os5 = __toESM(require("os"));
|
|
28630
|
+
var path24 = __toESM(require("path"));
|
|
28330
28631
|
var RUNS_DIR = "runs";
|
|
28331
28632
|
function gitCommonDir(cwd) {
|
|
28332
28633
|
try {
|
|
@@ -28335,7 +28636,7 @@ function gitCommonDir(cwd) {
|
|
|
28335
28636
|
encoding: "utf8",
|
|
28336
28637
|
stdio: ["pipe", "pipe", "ignore"]
|
|
28337
28638
|
}).trim();
|
|
28338
|
-
return out ?
|
|
28639
|
+
return out ? path24.resolve(cwd, out) : void 0;
|
|
28339
28640
|
} catch {
|
|
28340
28641
|
return void 0;
|
|
28341
28642
|
}
|
|
@@ -28357,7 +28658,7 @@ function gitPrefix(cwd) {
|
|
|
28357
28658
|
}
|
|
28358
28659
|
}
|
|
28359
28660
|
function getRunsDir(harnessRoot) {
|
|
28360
|
-
return
|
|
28661
|
+
return path24.join(getHarnessDir(harnessRoot), RUNS_DIR);
|
|
28361
28662
|
}
|
|
28362
28663
|
function formatLocalDate(d2) {
|
|
28363
28664
|
const y2 = d2.getFullYear();
|
|
@@ -28377,24 +28678,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
|
|
|
28377
28678
|
const timePart = formatLocalTime(started);
|
|
28378
28679
|
const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
|
|
28379
28680
|
let filename = `${timePart}_${stageId}${agentPart}.json`;
|
|
28380
|
-
const fullPath =
|
|
28381
|
-
if (
|
|
28681
|
+
const fullPath = path24.join(runsDir, dateFolder, filename);
|
|
28682
|
+
if (fs25.existsSync(fullPath)) {
|
|
28382
28683
|
filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
|
|
28383
28684
|
}
|
|
28384
|
-
return
|
|
28685
|
+
return path24.join(dateFolder, filename);
|
|
28385
28686
|
}
|
|
28386
28687
|
function resolveRunFilePath(harnessRoot, run2) {
|
|
28387
28688
|
if (run2.relativePath) {
|
|
28388
|
-
return
|
|
28689
|
+
return path24.join(getRunsDir(harnessRoot), run2.relativePath);
|
|
28389
28690
|
}
|
|
28390
|
-
return
|
|
28691
|
+
return path24.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
|
|
28391
28692
|
}
|
|
28392
28693
|
function collectRunFiles(runsDir) {
|
|
28393
|
-
if (!
|
|
28694
|
+
if (!fs25.existsSync(runsDir)) return [];
|
|
28394
28695
|
const files = [];
|
|
28395
28696
|
const walk = (dir) => {
|
|
28396
|
-
for (const entry of
|
|
28397
|
-
const full =
|
|
28697
|
+
for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
|
|
28698
|
+
const full = path24.join(dir, entry.name);
|
|
28398
28699
|
if (entry.isDirectory()) {
|
|
28399
28700
|
walk(full);
|
|
28400
28701
|
} else if (entry.name.endsWith(".json")) {
|
|
@@ -28408,30 +28709,30 @@ function collectRunFiles(runsDir) {
|
|
|
28408
28709
|
function resolveAgentWorkDir(harnessRoot, agentId) {
|
|
28409
28710
|
if (agentId === void 0) return void 0;
|
|
28410
28711
|
const entry = readSlotRegistry(harnessRoot, agentId);
|
|
28411
|
-
if (entry?.workDir &&
|
|
28712
|
+
if (entry?.workDir && fs25.existsSync(entry.workDir)) return entry.workDir;
|
|
28412
28713
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28413
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
28414
|
-
const worktreeDir =
|
|
28714
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path24.basename(harnessRoot);
|
|
28715
|
+
const worktreeDir = path24.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
28415
28716
|
const relPrefix = gitPrefix(harnessRoot);
|
|
28416
28717
|
const sessionEnvFiles = [];
|
|
28417
|
-
const worktreesRoot =
|
|
28418
|
-
if (
|
|
28718
|
+
const worktreesRoot = path24.join(os5.homedir(), "worktrees");
|
|
28719
|
+
if (fs25.existsSync(worktreesRoot)) {
|
|
28419
28720
|
const suffix = `-har-agent-${agentId}-`;
|
|
28420
|
-
for (const entry2 of
|
|
28721
|
+
for (const entry2 of fs25.readdirSync(worktreesRoot, { withFileTypes: true })) {
|
|
28421
28722
|
if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
|
|
28422
|
-
const sessionDir =
|
|
28723
|
+
const sessionDir = path24.join(worktreesRoot, entry2.name);
|
|
28423
28724
|
if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
|
|
28424
|
-
sessionEnvFiles.push(
|
|
28725
|
+
sessionEnvFiles.push(path24.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
|
|
28425
28726
|
}
|
|
28426
28727
|
}
|
|
28427
28728
|
const candidates = [
|
|
28428
|
-
|
|
28429
|
-
|
|
28729
|
+
path24.join(worktreeDir, `.env.agent.${agentId}`),
|
|
28730
|
+
path24.join(harnessRoot, `.env.agent.${agentId}`),
|
|
28430
28731
|
...sessionEnvFiles.sort()
|
|
28431
28732
|
];
|
|
28432
28733
|
for (const envFile of candidates) {
|
|
28433
|
-
if (!
|
|
28434
|
-
const content =
|
|
28734
|
+
if (!fs25.existsSync(envFile)) continue;
|
|
28735
|
+
const content = fs25.readFileSync(envFile, "utf8");
|
|
28435
28736
|
const match = content.match(/^REPO_ROOT=(.+)$/m);
|
|
28436
28737
|
if (match) return match[1].trim();
|
|
28437
28738
|
}
|
|
@@ -28443,11 +28744,11 @@ function createRun(ctx, meta) {
|
|
|
28443
28744
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28444
28745
|
const runId = crypto2.randomUUID();
|
|
28445
28746
|
const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
|
|
28446
|
-
const runFilePath =
|
|
28447
|
-
|
|
28747
|
+
const runFilePath = path24.join(runsDir, relativePath);
|
|
28748
|
+
fs25.mkdirSync(path24.dirname(runFilePath), { recursive: true });
|
|
28448
28749
|
const run2 = RunRecordSchema.parse({
|
|
28449
28750
|
runId,
|
|
28450
|
-
repoPath:
|
|
28751
|
+
repoPath: path24.resolve(ctx.repoPath),
|
|
28451
28752
|
harnessRoot,
|
|
28452
28753
|
stageId: meta.stageId,
|
|
28453
28754
|
kind: meta.kind,
|
|
@@ -28458,7 +28759,7 @@ function createRun(ctx, meta) {
|
|
|
28458
28759
|
relativePath,
|
|
28459
28760
|
trigger: ctx.trigger ?? "cli"
|
|
28460
28761
|
});
|
|
28461
|
-
|
|
28762
|
+
fs25.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
|
|
28462
28763
|
return run2;
|
|
28463
28764
|
}
|
|
28464
28765
|
function finishRun(repoPath, runId, update) {
|
|
@@ -28477,13 +28778,13 @@ function finishRun(repoPath, runId, update) {
|
|
|
28477
28778
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28478
28779
|
});
|
|
28479
28780
|
const runPath = resolveRunFilePath(harnessRoot, finished);
|
|
28480
|
-
|
|
28781
|
+
fs25.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
|
|
28481
28782
|
return finished;
|
|
28482
28783
|
}
|
|
28483
28784
|
function findRunRecord(harnessRoot, runId) {
|
|
28484
28785
|
const runsDir = getRunsDir(harnessRoot);
|
|
28485
28786
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28486
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28787
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs25.readFileSync(filePath, "utf8")));
|
|
28487
28788
|
if (parsed.success && parsed.data.runId === runId) {
|
|
28488
28789
|
return parsed.data;
|
|
28489
28790
|
}
|
|
@@ -28495,10 +28796,10 @@ function getRun(repoPath, runId) {
|
|
|
28495
28796
|
}
|
|
28496
28797
|
function listRuns(repoPath, filter = {}) {
|
|
28497
28798
|
const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
|
|
28498
|
-
if (!
|
|
28799
|
+
if (!fs25.existsSync(runsDir)) return [];
|
|
28499
28800
|
const runs = [];
|
|
28500
28801
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28501
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28802
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs25.readFileSync(filePath, "utf8")));
|
|
28502
28803
|
if (!parsed.success) continue;
|
|
28503
28804
|
if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
|
|
28504
28805
|
runs.push(parsed.data);
|
|
@@ -28511,17 +28812,17 @@ function listRuns(repoPath, filter = {}) {
|
|
|
28511
28812
|
}
|
|
28512
28813
|
|
|
28513
28814
|
// src/core/slot-preflight.ts
|
|
28514
|
-
var
|
|
28815
|
+
var path28 = __toESM(require("path"));
|
|
28515
28816
|
|
|
28516
28817
|
// src/core/control-port.ts
|
|
28517
28818
|
var import_child_process4 = require("child_process");
|
|
28518
|
-
var
|
|
28519
|
-
var
|
|
28819
|
+
var fs27 = __toESM(require("fs"));
|
|
28820
|
+
var path26 = __toESM(require("path"));
|
|
28520
28821
|
|
|
28521
28822
|
// src/core/slot-ports.ts
|
|
28522
28823
|
var import_child_process3 = require("child_process");
|
|
28523
|
-
var
|
|
28524
|
-
var
|
|
28824
|
+
var fs26 = __toESM(require("fs"));
|
|
28825
|
+
var path25 = __toESM(require("path"));
|
|
28525
28826
|
function portStep(env3) {
|
|
28526
28827
|
return Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
28527
28828
|
}
|
|
@@ -28577,7 +28878,7 @@ function allocateAppPorts(repoPath, agentId) {
|
|
|
28577
28878
|
}
|
|
28578
28879
|
function harnessUsesPm2(repoPath) {
|
|
28579
28880
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28580
|
-
return
|
|
28881
|
+
return fs26.existsSync(path25.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
|
|
28581
28882
|
}
|
|
28582
28883
|
|
|
28583
28884
|
// src/core/control-port.ts
|
|
@@ -28632,13 +28933,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
|
|
|
28632
28933
|
return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
|
|
28633
28934
|
}
|
|
28634
28935
|
function inspectControlUpReadiness(repoPath) {
|
|
28635
|
-
const resolved =
|
|
28936
|
+
const resolved = path26.resolve(repoPath);
|
|
28636
28937
|
const port = parseControlHostPort();
|
|
28637
28938
|
const containers = listDockerContainers();
|
|
28638
28939
|
const control = controlContainerOnPort(containers, port);
|
|
28639
28940
|
const warnings = [];
|
|
28640
|
-
const controlHarnessDir =
|
|
28641
|
-
const harnessSlot1Active =
|
|
28941
|
+
const controlHarnessDir = path26.join(resolved, "control", ".har");
|
|
28942
|
+
const harnessSlot1Active = fs27.existsSync(controlHarnessDir) && readSlotRegistry(path26.join(resolved, "control"), 1)?.status === "active";
|
|
28642
28943
|
if (control) {
|
|
28643
28944
|
return {
|
|
28644
28945
|
warnings,
|
|
@@ -28669,9 +28970,9 @@ function inspectControlUpReadiness(repoPath) {
|
|
|
28669
28970
|
}
|
|
28670
28971
|
|
|
28671
28972
|
// src/core/slot-launch-guard-occupied.ts
|
|
28672
|
-
var
|
|
28673
|
-
var
|
|
28674
|
-
var
|
|
28973
|
+
var fs28 = __toESM(require("fs"));
|
|
28974
|
+
var os6 = __toESM(require("os"));
|
|
28975
|
+
var path27 = __toESM(require("path"));
|
|
28675
28976
|
var import_child_process5 = require("child_process");
|
|
28676
28977
|
function runGit(cwd, args) {
|
|
28677
28978
|
try {
|
|
@@ -28690,7 +28991,7 @@ function worktreeDirty(worktreePath) {
|
|
|
28690
28991
|
}
|
|
28691
28992
|
function gitCommonDir2(cwd) {
|
|
28692
28993
|
const out = runGit(cwd, "rev-parse --git-common-dir");
|
|
28693
|
-
return out ?
|
|
28994
|
+
return out ? path27.resolve(cwd, out) : void 0;
|
|
28694
28995
|
}
|
|
28695
28996
|
function sameGitCheckout2(a2, b2) {
|
|
28696
28997
|
const left2 = gitCommonDir2(a2);
|
|
@@ -28699,20 +29000,20 @@ function sameGitCheckout2(a2, b2) {
|
|
|
28699
29000
|
}
|
|
28700
29001
|
function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
28701
29002
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28702
|
-
if (session?.worktreePath &&
|
|
29003
|
+
if (session?.worktreePath && fs28.existsSync(session.worktreePath)) {
|
|
28703
29004
|
return session.worktreePath;
|
|
28704
29005
|
}
|
|
28705
|
-
const legacy =
|
|
28706
|
-
if (
|
|
28707
|
-
const worktreesRoot =
|
|
28708
|
-
if (!
|
|
29006
|
+
const legacy = path27.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
29007
|
+
if (fs28.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
|
|
29008
|
+
const worktreesRoot = path27.join(os6.homedir(), "worktrees");
|
|
29009
|
+
if (!fs28.existsSync(worktreesRoot)) return void 0;
|
|
28709
29010
|
const suffix = `-har-agent-${agentId}-`;
|
|
28710
29011
|
const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
|
|
28711
|
-
for (const name of
|
|
29012
|
+
for (const name of fs28.readdirSync(worktreesRoot)) {
|
|
28712
29013
|
if (!name.includes(suffix)) continue;
|
|
28713
|
-
const candidate =
|
|
29014
|
+
const candidate = path27.join(worktreesRoot, name);
|
|
28714
29015
|
if (!sameGitCheckout2(harnessRoot, candidate)) continue;
|
|
28715
|
-
if (!
|
|
29016
|
+
if (!fs28.existsSync(path27.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
|
|
28716
29017
|
return candidate;
|
|
28717
29018
|
}
|
|
28718
29019
|
return void 0;
|
|
@@ -28720,13 +29021,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
|
28720
29021
|
function collectOccupiedSlot(repoPath, agentId) {
|
|
28721
29022
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28722
29023
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28723
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29024
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path27.basename(harnessRoot);
|
|
28724
29025
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28725
29026
|
const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
|
|
28726
29027
|
const workDir = session?.workDir;
|
|
28727
|
-
const envInWorkDir = workDir ?
|
|
28728
|
-
const envInRoot =
|
|
28729
|
-
const envInWorktree = worktreePath ?
|
|
29028
|
+
const envInWorkDir = workDir ? fs28.existsSync(path27.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29029
|
+
const envInRoot = fs28.existsSync(path27.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29030
|
+
const envInWorktree = worktreePath ? fs28.existsSync(path27.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
28730
29031
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
28731
29032
|
if (!active) return void 0;
|
|
28732
29033
|
const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
|
|
@@ -28879,7 +29180,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
|
28879
29180
|
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
28880
29181
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28881
29182
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28882
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29183
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path28.basename(harnessRoot);
|
|
28883
29184
|
const usesPm2 = harnessUsesPm2(repoPath);
|
|
28884
29185
|
const blockers = [];
|
|
28885
29186
|
const remediations = [];
|
|
@@ -29081,7 +29382,7 @@ function readWorktreeBranch(worktreePath) {
|
|
|
29081
29382
|
}
|
|
29082
29383
|
function gitCommonDir3(cwd) {
|
|
29083
29384
|
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
29084
|
-
return out ?
|
|
29385
|
+
return out ? path29.resolve(cwd, out) : void 0;
|
|
29085
29386
|
}
|
|
29086
29387
|
function sameGitCheckout3(a2, b2) {
|
|
29087
29388
|
const left2 = gitCommonDir3(a2);
|
|
@@ -29093,12 +29394,12 @@ function gitPrefix2(cwd) {
|
|
|
29093
29394
|
return out ?? "";
|
|
29094
29395
|
}
|
|
29095
29396
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
29096
|
-
const worktreesRoot =
|
|
29097
|
-
if (!
|
|
29397
|
+
const worktreesRoot = path29.join(os7.homedir(), "worktrees");
|
|
29398
|
+
if (!fs29.existsSync(worktreesRoot)) return void 0;
|
|
29098
29399
|
const suffix = `-har-agent-${agentId}-`;
|
|
29099
29400
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
29100
|
-
const matches =
|
|
29101
|
-
(candidate) => sameGitCheckout3(harnessRoot, candidate) &&
|
|
29401
|
+
const matches = fs29.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path29.join(worktreesRoot, entry.name)).filter(
|
|
29402
|
+
(candidate) => sameGitCheckout3(harnessRoot, candidate) && fs29.existsSync(path29.join(candidate, relPrefix, `.env.agent.${agentId}`))
|
|
29102
29403
|
);
|
|
29103
29404
|
return matches.sort()[0];
|
|
29104
29405
|
}
|
|
@@ -29173,18 +29474,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
|
|
|
29173
29474
|
}
|
|
29174
29475
|
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
29175
29476
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29176
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29477
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
|
|
29177
29478
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29178
|
-
const legacyWorktreePath =
|
|
29179
|
-
|
|
29479
|
+
const legacyWorktreePath = path29.join(
|
|
29480
|
+
os7.homedir(),
|
|
29180
29481
|
"worktrees",
|
|
29181
29482
|
`${projectName}-agent-${agentId}`
|
|
29182
29483
|
);
|
|
29183
|
-
const worktreePath = session?.worktreePath &&
|
|
29484
|
+
const worktreePath = session?.worktreePath && fs29.existsSync(session.worktreePath) ? session.worktreePath : fs29.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
29184
29485
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
29185
|
-
const envInWorkDir = workDir ?
|
|
29186
|
-
const envInRoot =
|
|
29187
|
-
const envInWorktree = worktreePath ?
|
|
29486
|
+
const envInWorkDir = workDir ? fs29.existsSync(path29.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29487
|
+
const envInRoot = fs29.existsSync(path29.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29488
|
+
const envInWorktree = worktreePath ? fs29.existsSync(path29.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
29188
29489
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29189
29490
|
const latest = latestRunForAgent(runs, agentId);
|
|
29190
29491
|
const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
|
|
@@ -29237,7 +29538,7 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29237
29538
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
29238
29539
|
const pm2Procs = listPm2Processes2();
|
|
29239
29540
|
return {
|
|
29240
|
-
repoPath:
|
|
29541
|
+
repoPath: path29.resolve(repoPath),
|
|
29241
29542
|
harnessRoot,
|
|
29242
29543
|
gitRemote: readGitRemote(harnessRoot),
|
|
29243
29544
|
profile: manifest?.profile,
|
|
@@ -29248,13 +29549,13 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29248
29549
|
|
|
29249
29550
|
// src/core/validations.ts
|
|
29250
29551
|
var crypto3 = __toESM(require("crypto"));
|
|
29251
|
-
var
|
|
29252
|
-
var
|
|
29552
|
+
var fs31 = __toESM(require("fs"));
|
|
29553
|
+
var path31 = __toESM(require("path"));
|
|
29253
29554
|
|
|
29254
29555
|
// src/core/change-batch.ts
|
|
29255
|
-
var
|
|
29256
|
-
var
|
|
29257
|
-
var
|
|
29556
|
+
var fs30 = __toESM(require("fs"));
|
|
29557
|
+
var os8 = __toESM(require("os"));
|
|
29558
|
+
var path30 = __toESM(require("path"));
|
|
29258
29559
|
function git(checkoutDir, args, env3) {
|
|
29259
29560
|
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
29260
29561
|
if (result.code !== 0) {
|
|
@@ -29270,7 +29571,7 @@ function isCheckoutRoot(checkoutDir) {
|
|
|
29270
29571
|
const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
|
|
29271
29572
|
if (!toplevel) return false;
|
|
29272
29573
|
try {
|
|
29273
|
-
return
|
|
29574
|
+
return fs30.realpathSync(toplevel) === fs30.realpathSync(checkoutDir);
|
|
29274
29575
|
} catch {
|
|
29275
29576
|
return false;
|
|
29276
29577
|
}
|
|
@@ -29292,7 +29593,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
|
|
|
29292
29593
|
if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
29293
29594
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
29294
29595
|
const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
29295
|
-
if (gitPath &&
|
|
29596
|
+
if (gitPath && fs30.existsSync(path30.resolve(checkoutDir, gitPath))) return true;
|
|
29296
29597
|
}
|
|
29297
29598
|
return false;
|
|
29298
29599
|
}
|
|
@@ -29315,8 +29616,8 @@ function parseNameStatus(output) {
|
|
|
29315
29616
|
return files;
|
|
29316
29617
|
}
|
|
29317
29618
|
function computeWorktreeSnapshot(checkoutDir) {
|
|
29318
|
-
const tmpDir =
|
|
29319
|
-
const tmpIndex =
|
|
29619
|
+
const tmpDir = fs30.mkdtempSync(path30.join(os8.tmpdir(), "har-idx-"));
|
|
29620
|
+
const tmpIndex = path30.join(tmpDir, "index");
|
|
29320
29621
|
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
29321
29622
|
try {
|
|
29322
29623
|
const headTree = getHeadTree(checkoutDir);
|
|
@@ -29339,35 +29640,35 @@ function computeWorktreeSnapshot(checkoutDir) {
|
|
|
29339
29640
|
changedFiles: parseNameStatus(diffOutput)
|
|
29340
29641
|
};
|
|
29341
29642
|
} finally {
|
|
29342
|
-
|
|
29643
|
+
fs30.rmSync(tmpDir, { recursive: true, force: true });
|
|
29343
29644
|
}
|
|
29344
29645
|
}
|
|
29345
29646
|
|
|
29346
29647
|
// src/core/validations.ts
|
|
29347
29648
|
var VALIDATIONS_DIR = "validations";
|
|
29348
29649
|
function getValidationsDir(checkoutDir) {
|
|
29349
|
-
return
|
|
29650
|
+
return path31.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
29350
29651
|
}
|
|
29351
29652
|
function validationPath(checkoutDir, treeHash) {
|
|
29352
|
-
return
|
|
29653
|
+
return path31.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
29353
29654
|
}
|
|
29354
29655
|
function writeRecord(checkoutDir, record2) {
|
|
29355
29656
|
const dir = getValidationsDir(checkoutDir);
|
|
29356
|
-
|
|
29357
|
-
|
|
29657
|
+
fs31.mkdirSync(dir, { recursive: true });
|
|
29658
|
+
fs31.writeFileSync(
|
|
29358
29659
|
validationPath(checkoutDir, record2.treeHash),
|
|
29359
29660
|
`${JSON.stringify(record2, null, 2)}
|
|
29360
29661
|
`
|
|
29361
29662
|
);
|
|
29362
29663
|
}
|
|
29363
29664
|
function ensureValidationsIgnored(checkoutDir) {
|
|
29364
|
-
const harDir =
|
|
29365
|
-
if (!
|
|
29366
|
-
const gitignorePath =
|
|
29367
|
-
const content =
|
|
29665
|
+
const harDir = path31.join(checkoutDir, ".har");
|
|
29666
|
+
if (!fs31.existsSync(harDir)) return;
|
|
29667
|
+
const gitignorePath = path31.join(harDir, ".gitignore");
|
|
29668
|
+
const content = fs31.existsSync(gitignorePath) ? fs31.readFileSync(gitignorePath, "utf8") : "";
|
|
29368
29669
|
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
29369
29670
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
29370
|
-
|
|
29671
|
+
fs31.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
29371
29672
|
`);
|
|
29372
29673
|
}
|
|
29373
29674
|
function recordValidation(input) {
|
|
@@ -29384,8 +29685,8 @@ function recordValidation(input) {
|
|
|
29384
29685
|
treeHash: snapshot.treeHash,
|
|
29385
29686
|
headSha: snapshot.headSha,
|
|
29386
29687
|
branch: snapshot.branch,
|
|
29387
|
-
workDir:
|
|
29388
|
-
harnessRoot:
|
|
29688
|
+
workDir: path31.resolve(input.checkoutDir),
|
|
29689
|
+
harnessRoot: path31.resolve(input.harnessRoot),
|
|
29389
29690
|
agentId: input.agentId,
|
|
29390
29691
|
status: input.status,
|
|
29391
29692
|
full: input.full,
|
|
@@ -29397,16 +29698,16 @@ function recordValidation(input) {
|
|
|
29397
29698
|
committedAt: existing?.committedAt
|
|
29398
29699
|
});
|
|
29399
29700
|
writeRecord(input.checkoutDir, record2);
|
|
29400
|
-
if (
|
|
29701
|
+
if (path31.resolve(input.harnessRoot) !== path31.resolve(input.checkoutDir)) {
|
|
29401
29702
|
writeRecord(input.harnessRoot, record2);
|
|
29402
29703
|
}
|
|
29403
29704
|
return record2;
|
|
29404
29705
|
}
|
|
29405
29706
|
function findValidation(checkoutDir, treeHash) {
|
|
29406
29707
|
const file = validationPath(checkoutDir, treeHash);
|
|
29407
|
-
if (!
|
|
29708
|
+
if (!fs31.existsSync(file)) return void 0;
|
|
29408
29709
|
try {
|
|
29409
|
-
return ValidationRecordSchema.parse(JSON.parse(
|
|
29710
|
+
return ValidationRecordSchema.parse(JSON.parse(fs31.readFileSync(file, "utf8")));
|
|
29410
29711
|
} catch {
|
|
29411
29712
|
return void 0;
|
|
29412
29713
|
}
|
|
@@ -29421,19 +29722,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
|
29421
29722
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29422
29723
|
};
|
|
29423
29724
|
writeRecord(checkoutDir, updated);
|
|
29424
|
-
if (record2.harnessRoot &&
|
|
29725
|
+
if (record2.harnessRoot && path31.resolve(record2.harnessRoot) !== path31.resolve(checkoutDir) && fs31.existsSync(path31.join(record2.harnessRoot, ".har"))) {
|
|
29425
29726
|
writeRecord(record2.harnessRoot, updated);
|
|
29426
29727
|
}
|
|
29427
29728
|
return updated;
|
|
29428
29729
|
}
|
|
29429
29730
|
function listValidations(harnessRoot) {
|
|
29430
29731
|
const dir = getValidationsDir(harnessRoot);
|
|
29431
|
-
if (!
|
|
29732
|
+
if (!fs31.existsSync(dir)) return [];
|
|
29432
29733
|
const records = [];
|
|
29433
|
-
for (const entry of
|
|
29734
|
+
for (const entry of fs31.readdirSync(dir)) {
|
|
29434
29735
|
if (!entry.endsWith(".json")) continue;
|
|
29435
29736
|
try {
|
|
29436
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(
|
|
29737
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs31.readFileSync(path31.join(dir, entry), "utf8"))));
|
|
29437
29738
|
} catch {
|
|
29438
29739
|
}
|
|
29439
29740
|
}
|
|
@@ -29491,7 +29792,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
|
|
|
29491
29792
|
const deadline = Date.now() + timeoutMs;
|
|
29492
29793
|
while (Date.now() < deadline) {
|
|
29493
29794
|
if (await isControlApiReachable(apiUrl)) return true;
|
|
29494
|
-
await new Promise((
|
|
29795
|
+
await new Promise((resolve27) => setTimeout(resolve27, intervalMs));
|
|
29495
29796
|
}
|
|
29496
29797
|
return false;
|
|
29497
29798
|
}
|
|
@@ -29503,7 +29804,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29503
29804
|
}
|
|
29504
29805
|
const repoPaths = new Set(listRegisteredRepos());
|
|
29505
29806
|
if (options?.cwd) {
|
|
29506
|
-
const cwd =
|
|
29807
|
+
const cwd = path32.resolve(options.cwd);
|
|
29507
29808
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
29508
29809
|
}
|
|
29509
29810
|
try {
|
|
@@ -29511,7 +29812,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29511
29812
|
if (listResponse.ok) {
|
|
29512
29813
|
const repos = await listResponse.json();
|
|
29513
29814
|
for (const repo of repos) {
|
|
29514
|
-
const resolved =
|
|
29815
|
+
const resolved = path32.resolve(repo.path);
|
|
29515
29816
|
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
29516
29817
|
}
|
|
29517
29818
|
}
|
|
@@ -29530,7 +29831,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29530
29831
|
return { synced, failed };
|
|
29531
29832
|
}
|
|
29532
29833
|
async function registerRepoWithControl(options) {
|
|
29533
|
-
const repoPath =
|
|
29834
|
+
const repoPath = path32.resolve(options.repoPath);
|
|
29534
29835
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
29535
29836
|
const manifest = readManifest(repoPath);
|
|
29536
29837
|
const stagesRegistry = readStageRegistry(repoPath);
|
|
@@ -29543,7 +29844,7 @@ async function registerRepoWithControl(options) {
|
|
|
29543
29844
|
return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
|
|
29544
29845
|
}
|
|
29545
29846
|
async function syncRepoWithControl(options) {
|
|
29546
|
-
const repoPath =
|
|
29847
|
+
const repoPath = path32.resolve(options.repoPath);
|
|
29547
29848
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
29548
29849
|
if (options.cloud) {
|
|
29549
29850
|
const remote = createRemoteExecutor();
|
|
@@ -29571,7 +29872,7 @@ async function syncRepoWithControl(options) {
|
|
|
29571
29872
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
29572
29873
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
29573
29874
|
const repos = await listResponse.json();
|
|
29574
|
-
const existing = repos.find((r2) =>
|
|
29875
|
+
const existing = repos.find((r2) => path32.resolve(r2.path) === repoPath);
|
|
29575
29876
|
if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
|
|
29576
29877
|
await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
|
|
29577
29878
|
return;
|
|
@@ -29978,6 +30279,13 @@ var envCommand = {
|
|
|
29978
30279
|
type: "boolean",
|
|
29979
30280
|
default: false,
|
|
29980
30281
|
describe: "Skip Cursor rule scaffolding"
|
|
30282
|
+
}).option("agents", {
|
|
30283
|
+
type: "string",
|
|
30284
|
+
describe: "Scaffold agent skills for these targets (comma-separated: claude,cursor,codex); auto-detected when omitted"
|
|
30285
|
+
}).option("no-agents", {
|
|
30286
|
+
type: "boolean",
|
|
30287
|
+
default: false,
|
|
30288
|
+
describe: "Skip agent skills scaffolding"
|
|
29981
30289
|
}),
|
|
29982
30290
|
handleInit
|
|
29983
30291
|
).command(
|
|
@@ -30002,6 +30310,13 @@ var envCommand = {
|
|
|
30002
30310
|
type: "boolean",
|
|
30003
30311
|
default: false,
|
|
30004
30312
|
describe: "Skip Cursor rule scaffolding"
|
|
30313
|
+
}).option("agents", {
|
|
30314
|
+
type: "string",
|
|
30315
|
+
describe: "Scaffold agent skills for these targets (comma-separated: claude,cursor,codex); auto-detected when omitted"
|
|
30316
|
+
}).option("no-agents", {
|
|
30317
|
+
type: "boolean",
|
|
30318
|
+
default: false,
|
|
30319
|
+
describe: "Skip agent skills scaffolding"
|
|
30005
30320
|
}),
|
|
30006
30321
|
handleMaintain
|
|
30007
30322
|
).command(
|
|
@@ -30112,7 +30427,7 @@ var envCommand = {
|
|
|
30112
30427
|
}
|
|
30113
30428
|
};
|
|
30114
30429
|
async function handleInit(argv) {
|
|
30115
|
-
const repoPath =
|
|
30430
|
+
const repoPath = path33.resolve(argv.repo);
|
|
30116
30431
|
header("har env init");
|
|
30117
30432
|
info(`Repository: ${repoPath}`);
|
|
30118
30433
|
try {
|
|
@@ -30156,6 +30471,14 @@ async function handleInit(argv) {
|
|
|
30156
30471
|
autoYes: argv.yes,
|
|
30157
30472
|
mode: "init"
|
|
30158
30473
|
});
|
|
30474
|
+
await handleAgentSkills({
|
|
30475
|
+
repoPath,
|
|
30476
|
+
agents: argv.agents,
|
|
30477
|
+
enabled: argv.noAgents ? false : void 0,
|
|
30478
|
+
autoYes: argv.yes,
|
|
30479
|
+
force: argv.force,
|
|
30480
|
+
mode: "init"
|
|
30481
|
+
});
|
|
30159
30482
|
printNextSteps(argv.auto);
|
|
30160
30483
|
} catch (err) {
|
|
30161
30484
|
error(err.message);
|
|
@@ -30163,7 +30486,7 @@ async function handleInit(argv) {
|
|
|
30163
30486
|
}
|
|
30164
30487
|
}
|
|
30165
30488
|
async function handleMaintain(argv) {
|
|
30166
|
-
const repoPath =
|
|
30489
|
+
const repoPath = path33.resolve(argv.repo);
|
|
30167
30490
|
header("har env maintain");
|
|
30168
30491
|
info(`Repository: ${repoPath}`);
|
|
30169
30492
|
try {
|
|
@@ -30221,6 +30544,13 @@ async function handleMaintain(argv) {
|
|
|
30221
30544
|
autoYes: argv.yes,
|
|
30222
30545
|
mode: "maintain"
|
|
30223
30546
|
});
|
|
30547
|
+
await handleAgentSkills({
|
|
30548
|
+
repoPath,
|
|
30549
|
+
agents: argv.agents,
|
|
30550
|
+
enabled: argv.noAgents ? false : void 0,
|
|
30551
|
+
autoYes: argv.yes,
|
|
30552
|
+
mode: "maintain"
|
|
30553
|
+
});
|
|
30224
30554
|
} catch (err) {
|
|
30225
30555
|
error(err.message);
|
|
30226
30556
|
process.exit(1);
|
|
@@ -30252,7 +30582,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
30252
30582
|
if (autoYes) {
|
|
30253
30583
|
const proposal = readAgentMdProposal(repoPath);
|
|
30254
30584
|
if (proposal) {
|
|
30255
|
-
writeFileSafe(
|
|
30585
|
+
writeFileSafe(path33.join(repoPath, "AGENT.md"), proposal.content);
|
|
30256
30586
|
clearAgentMdProposal(repoPath);
|
|
30257
30587
|
info("Applied AGENT.md proposal (--yes)");
|
|
30258
30588
|
}
|
|
@@ -30261,7 +30591,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
30261
30591
|
await promptApplyAgentMdProposal(repoPath);
|
|
30262
30592
|
}
|
|
30263
30593
|
async function handleAddStage(argv) {
|
|
30264
|
-
const repoPath =
|
|
30594
|
+
const repoPath = path33.resolve(argv.repo);
|
|
30265
30595
|
if (argv.template !== "playwright") {
|
|
30266
30596
|
error(`Unknown stage template: ${argv.template ?? "(missing)"}. Available: playwright`);
|
|
30267
30597
|
process.exit(1);
|
|
@@ -30290,7 +30620,7 @@ async function handleAddStage(argv) {
|
|
|
30290
30620
|
}
|
|
30291
30621
|
}
|
|
30292
30622
|
async function handlePreflight(argv) {
|
|
30293
|
-
const repo =
|
|
30623
|
+
const repo = path33.resolve(argv.repo);
|
|
30294
30624
|
const agentId = validateAgentId(argv.id, repo);
|
|
30295
30625
|
const result = await preflightEnvironment({
|
|
30296
30626
|
repoPath: repo,
|
|
@@ -30308,7 +30638,7 @@ async function handlePreflight(argv) {
|
|
|
30308
30638
|
process.exit(result.code);
|
|
30309
30639
|
}
|
|
30310
30640
|
async function handleLaunch(argv) {
|
|
30311
|
-
const repo =
|
|
30641
|
+
const repo = path33.resolve(argv.repo);
|
|
30312
30642
|
const agentId = validateAgentId(argv.id, repo);
|
|
30313
30643
|
let confirmReplace = argv.replace;
|
|
30314
30644
|
let force = argv.force;
|
|
@@ -30318,10 +30648,10 @@ async function handleLaunch(argv) {
|
|
|
30318
30648
|
if (!confirmReplace && process.stdin.isTTY && process.stdout.isTTY) {
|
|
30319
30649
|
warn("Occupied slot \u2014 review before replacing:");
|
|
30320
30650
|
console.error(guard.reason ?? "");
|
|
30321
|
-
const
|
|
30322
|
-
const rl =
|
|
30323
|
-
const answer = await new Promise((
|
|
30324
|
-
rl.question("Replace this slot? [y/N] ",
|
|
30651
|
+
const readline5 = await import("readline");
|
|
30652
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
|
|
30653
|
+
const answer = await new Promise((resolve27) => {
|
|
30654
|
+
rl.question("Replace this slot? [y/N] ", resolve27);
|
|
30325
30655
|
});
|
|
30326
30656
|
rl.close();
|
|
30327
30657
|
if (!/^[Yy]$/.test(answer.trim())) {
|
|
@@ -30335,10 +30665,10 @@ async function handleLaunch(argv) {
|
|
|
30335
30665
|
if (!guardAfterConfirm.allowed && guardAfterConfirm.blocked && guardAfterConfirm.slot?.dirty) {
|
|
30336
30666
|
if (!force && process.stdin.isTTY && process.stdout.isTTY) {
|
|
30337
30667
|
warn("Worktree has uncommitted changes.");
|
|
30338
|
-
const
|
|
30339
|
-
const rl =
|
|
30340
|
-
const answer = await new Promise((
|
|
30341
|
-
rl.question("Discard uncommitted changes? [y/N] ",
|
|
30668
|
+
const readline5 = await import("readline");
|
|
30669
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
|
|
30670
|
+
const answer = await new Promise((resolve27) => {
|
|
30671
|
+
rl.question("Discard uncommitted changes? [y/N] ", resolve27);
|
|
30342
30672
|
});
|
|
30343
30673
|
rl.close();
|
|
30344
30674
|
if (!/^[Yy]$/.test(answer.trim())) {
|
|
@@ -30385,7 +30715,7 @@ async function handleRecover(argv) {
|
|
|
30385
30715
|
});
|
|
30386
30716
|
}
|
|
30387
30717
|
async function handleVerify(argv) {
|
|
30388
|
-
const repo =
|
|
30718
|
+
const repo = path33.resolve(argv.repo);
|
|
30389
30719
|
const agentId = validateAgentId(argv.id, repo);
|
|
30390
30720
|
const result = await runVerification({
|
|
30391
30721
|
repoPath: repo,
|
|
@@ -30397,7 +30727,7 @@ async function handleVerify(argv) {
|
|
|
30397
30727
|
process.exit(result.code);
|
|
30398
30728
|
}
|
|
30399
30729
|
async function handleTeardown(argv) {
|
|
30400
|
-
const repo =
|
|
30730
|
+
const repo = path33.resolve(argv.repo);
|
|
30401
30731
|
const agentId = validateAgentId(argv.id, repo);
|
|
30402
30732
|
const result = await teardownEnvironment({
|
|
30403
30733
|
repoPath: repo,
|
|
@@ -30408,7 +30738,7 @@ async function handleTeardown(argv) {
|
|
|
30408
30738
|
process.exit(result.code);
|
|
30409
30739
|
}
|
|
30410
30740
|
async function handleComplete(argv) {
|
|
30411
|
-
const repo =
|
|
30741
|
+
const repo = path33.resolve(argv.repo);
|
|
30412
30742
|
const agentId = validateAgentId(argv.id, repo);
|
|
30413
30743
|
const result = await completeEnvironment({
|
|
30414
30744
|
repoPath: repo,
|
|
@@ -30429,7 +30759,7 @@ async function handleComplete(argv) {
|
|
|
30429
30759
|
process.exit(result.code);
|
|
30430
30760
|
}
|
|
30431
30761
|
async function handleStatus(argv) {
|
|
30432
|
-
const repoPath =
|
|
30762
|
+
const repoPath = path33.resolve(argv.repo);
|
|
30433
30763
|
if (argv.json) {
|
|
30434
30764
|
const status = collectEnvironmentStatus(repoPath);
|
|
30435
30765
|
const output = EnvironmentStatusSchema.parse(status);
|
|
@@ -30442,7 +30772,7 @@ async function handleStatus(argv) {
|
|
|
30442
30772
|
});
|
|
30443
30773
|
}
|
|
30444
30774
|
async function handleRunsList(argv) {
|
|
30445
|
-
const repoPath =
|
|
30775
|
+
const repoPath = path33.resolve(argv.repo);
|
|
30446
30776
|
const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
|
|
30447
30777
|
if (argv.json) {
|
|
30448
30778
|
process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
|
|
@@ -30459,7 +30789,7 @@ async function handleRunsList(argv) {
|
|
|
30459
30789
|
}
|
|
30460
30790
|
}
|
|
30461
30791
|
async function handleRunsGet(argv) {
|
|
30462
|
-
const run2 = getRun(
|
|
30792
|
+
const run2 = getRun(path33.resolve(argv.repo), argv.runId);
|
|
30463
30793
|
if (!run2) {
|
|
30464
30794
|
error(`Run not found: ${argv.runId}`);
|
|
30465
30795
|
process.exit(1);
|
|
@@ -30532,11 +30862,11 @@ function printNextSteps(auto2) {
|
|
|
30532
30862
|
}
|
|
30533
30863
|
|
|
30534
30864
|
// src/cli/commands/control.ts
|
|
30535
|
-
var
|
|
30865
|
+
var path35 = __toESM(require("path"));
|
|
30536
30866
|
|
|
30537
30867
|
// src/core/control-lifecycle.ts
|
|
30538
30868
|
var import_child_process8 = require("child_process");
|
|
30539
|
-
var
|
|
30869
|
+
var path34 = __toESM(require("path"));
|
|
30540
30870
|
|
|
30541
30871
|
// src/core/control-image.ts
|
|
30542
30872
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
@@ -30555,13 +30885,13 @@ function shouldBuildControlLocally() {
|
|
|
30555
30885
|
|
|
30556
30886
|
// src/core/control-lifecycle.ts
|
|
30557
30887
|
function resolveControlDir() {
|
|
30558
|
-
return
|
|
30888
|
+
return path34.resolve(__dirname, "..", "control");
|
|
30559
30889
|
}
|
|
30560
30890
|
function resolveControlComposeFiles(options) {
|
|
30561
30891
|
const controlDir = resolveControlDir();
|
|
30562
|
-
const files = [
|
|
30892
|
+
const files = [path34.join(controlDir, "docker-compose.yml")];
|
|
30563
30893
|
if (options?.build ?? shouldBuildControlLocally()) {
|
|
30564
|
-
files.push(
|
|
30894
|
+
files.push(path34.join(controlDir, "docker-compose.build.yml"));
|
|
30565
30895
|
}
|
|
30566
30896
|
return files;
|
|
30567
30897
|
}
|
|
@@ -30730,7 +31060,7 @@ async function handleDown() {
|
|
|
30730
31060
|
process.exit(code);
|
|
30731
31061
|
}
|
|
30732
31062
|
async function handleRegister(argv) {
|
|
30733
|
-
const repoPath =
|
|
31063
|
+
const repoPath = path35.resolve(argv.repo);
|
|
30734
31064
|
header("har control register");
|
|
30735
31065
|
info(`Repository: ${repoPath}`);
|
|
30736
31066
|
try {
|
|
@@ -30750,7 +31080,7 @@ async function handleRegister(argv) {
|
|
|
30750
31080
|
}
|
|
30751
31081
|
}
|
|
30752
31082
|
async function handleSync(argv) {
|
|
30753
|
-
const repoPath =
|
|
31083
|
+
const repoPath = path35.resolve(argv.repo);
|
|
30754
31084
|
try {
|
|
30755
31085
|
await syncRepoWithControl({
|
|
30756
31086
|
repoPath,
|
|
@@ -30789,7 +31119,7 @@ async function handleWatch(argv) {
|
|
|
30789
31119
|
};
|
|
30790
31120
|
const tick = async () => {
|
|
30791
31121
|
if (argv.repo) {
|
|
30792
|
-
await syncOne(
|
|
31122
|
+
await syncOne(path35.resolve(argv.repo));
|
|
30793
31123
|
return;
|
|
30794
31124
|
}
|
|
30795
31125
|
try {
|
|
@@ -30820,12 +31150,12 @@ async function handleLogin(argv) {
|
|
|
30820
31150
|
}
|
|
30821
31151
|
|
|
30822
31152
|
// src/cli/commands/hooks.ts
|
|
30823
|
-
var
|
|
31153
|
+
var path38 = __toESM(require("path"));
|
|
30824
31154
|
|
|
30825
31155
|
// src/core/hooks.ts
|
|
30826
|
-
var
|
|
30827
|
-
var
|
|
30828
|
-
var
|
|
31156
|
+
var fs32 = __toESM(require("fs"));
|
|
31157
|
+
var os9 = __toESM(require("os"));
|
|
31158
|
+
var path36 = __toESM(require("path"));
|
|
30829
31159
|
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
30830
31160
|
var MARKER_END = "# <<< har commit gate <<<";
|
|
30831
31161
|
var PRE_COMMIT_BLOCK = [
|
|
@@ -30850,7 +31180,7 @@ function resolveCheckoutRoot(cwd) {
|
|
|
30850
31180
|
function resolveHooksDir(checkoutDir) {
|
|
30851
31181
|
const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
|
|
30852
31182
|
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
30853
|
-
return
|
|
31183
|
+
return path36.resolve(checkoutDir, hooksPath);
|
|
30854
31184
|
}
|
|
30855
31185
|
function getConfiguredHooksPath(checkoutDir) {
|
|
30856
31186
|
return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
|
|
@@ -30858,9 +31188,9 @@ function getConfiguredHooksPath(checkoutDir) {
|
|
|
30858
31188
|
function defaultHarInvocation() {
|
|
30859
31189
|
const entry = process.argv[1];
|
|
30860
31190
|
if (entry && entry.endsWith(".js")) {
|
|
30861
|
-
return `"${process.execPath}" "${
|
|
31191
|
+
return `"${process.execPath}" "${path36.resolve(entry)}"`;
|
|
30862
31192
|
}
|
|
30863
|
-
if (entry) return `"${
|
|
31193
|
+
if (entry) return `"${path36.resolve(entry)}"`;
|
|
30864
31194
|
return "har";
|
|
30865
31195
|
}
|
|
30866
31196
|
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
@@ -30881,40 +31211,40 @@ exit 0
|
|
|
30881
31211
|
`;
|
|
30882
31212
|
}
|
|
30883
31213
|
function upsertMarkedBlock(filePath, block) {
|
|
30884
|
-
if (!
|
|
30885
|
-
|
|
31214
|
+
if (!fs32.existsSync(filePath)) {
|
|
31215
|
+
fs32.writeFileSync(filePath, `#!/bin/sh
|
|
30886
31216
|
${block}
|
|
30887
31217
|
`, { mode: 493 });
|
|
30888
31218
|
return "created";
|
|
30889
31219
|
}
|
|
30890
|
-
const content =
|
|
31220
|
+
const content = fs32.readFileSync(filePath, "utf8");
|
|
30891
31221
|
if (content.includes(MARKER_START)) {
|
|
30892
31222
|
const pattern = new RegExp(
|
|
30893
31223
|
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
30894
31224
|
);
|
|
30895
|
-
|
|
30896
|
-
|
|
31225
|
+
fs32.writeFileSync(filePath, content.replace(pattern, block));
|
|
31226
|
+
fs32.chmodSync(filePath, 493);
|
|
30897
31227
|
return "updated";
|
|
30898
31228
|
}
|
|
30899
31229
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
30900
|
-
|
|
31230
|
+
fs32.writeFileSync(filePath, `${content}${suffix}
|
|
30901
31231
|
${block}
|
|
30902
31232
|
`);
|
|
30903
|
-
|
|
31233
|
+
fs32.chmodSync(filePath, 493);
|
|
30904
31234
|
return "appended";
|
|
30905
31235
|
}
|
|
30906
31236
|
function removeMarkedBlock(filePath) {
|
|
30907
|
-
if (!
|
|
30908
|
-
const content =
|
|
31237
|
+
if (!fs32.existsSync(filePath)) return false;
|
|
31238
|
+
const content = fs32.readFileSync(filePath, "utf8");
|
|
30909
31239
|
if (!content.includes(MARKER_START)) return false;
|
|
30910
31240
|
const pattern = new RegExp(
|
|
30911
31241
|
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
30912
31242
|
);
|
|
30913
31243
|
const stripped = content.replace(pattern, "\n");
|
|
30914
31244
|
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
30915
|
-
|
|
31245
|
+
fs32.rmSync(filePath);
|
|
30916
31246
|
} else {
|
|
30917
|
-
|
|
31247
|
+
fs32.writeFileSync(filePath, stripped);
|
|
30918
31248
|
}
|
|
30919
31249
|
return true;
|
|
30920
31250
|
}
|
|
@@ -30936,20 +31266,20 @@ Or re-run with --force to write into that directory anyway.`
|
|
|
30936
31266
|
);
|
|
30937
31267
|
}
|
|
30938
31268
|
const hooksDir = resolveHooksDir(checkout);
|
|
30939
|
-
|
|
31269
|
+
fs32.mkdirSync(hooksDir, { recursive: true });
|
|
30940
31270
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
30941
|
-
|
|
30942
|
-
|
|
31271
|
+
fs32.writeFileSync(
|
|
31272
|
+
path36.join(hooksDir, "har-pre-commit"),
|
|
30943
31273
|
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
30944
31274
|
{ mode: 493 }
|
|
30945
31275
|
);
|
|
30946
|
-
|
|
30947
|
-
|
|
31276
|
+
fs32.writeFileSync(
|
|
31277
|
+
path36.join(hooksDir, "har-post-commit"),
|
|
30948
31278
|
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
30949
31279
|
{ mode: 493 }
|
|
30950
31280
|
);
|
|
30951
|
-
const preCommit = upsertMarkedBlock(
|
|
30952
|
-
const postCommit = upsertMarkedBlock(
|
|
31281
|
+
const preCommit = upsertMarkedBlock(path36.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
31282
|
+
const postCommit = upsertMarkedBlock(path36.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
30953
31283
|
ensureValidationsIgnored(checkout);
|
|
30954
31284
|
return { hooksDir, preCommit, postCommit };
|
|
30955
31285
|
}
|
|
@@ -30958,12 +31288,12 @@ function uninstallHooks(repoPath) {
|
|
|
30958
31288
|
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
30959
31289
|
const hooksDir = resolveHooksDir(checkout);
|
|
30960
31290
|
let removed = false;
|
|
30961
|
-
removed = removeMarkedBlock(
|
|
30962
|
-
removed = removeMarkedBlock(
|
|
31291
|
+
removed = removeMarkedBlock(path36.join(hooksDir, "pre-commit")) || removed;
|
|
31292
|
+
removed = removeMarkedBlock(path36.join(hooksDir, "post-commit")) || removed;
|
|
30963
31293
|
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
30964
|
-
const full =
|
|
30965
|
-
if (
|
|
30966
|
-
|
|
31294
|
+
const full = path36.join(hooksDir, file);
|
|
31295
|
+
if (fs32.existsSync(full)) {
|
|
31296
|
+
fs32.rmSync(full);
|
|
30967
31297
|
removed = true;
|
|
30968
31298
|
}
|
|
30969
31299
|
}
|
|
@@ -30980,9 +31310,9 @@ function getCommitGateConfig(checkoutDir) {
|
|
|
30980
31310
|
function isAgentWorktree(checkoutDir) {
|
|
30981
31311
|
const branch = getCurrentBranch(checkoutDir);
|
|
30982
31312
|
if (branch && /^har-agent-\d+$/.test(branch)) return true;
|
|
30983
|
-
const worktreesRoot =
|
|
30984
|
-
const resolved =
|
|
30985
|
-
return resolved.startsWith(`${worktreesRoot}${
|
|
31313
|
+
const worktreesRoot = path36.join(os9.homedir(), "worktrees");
|
|
31314
|
+
const resolved = path36.resolve(checkoutDir);
|
|
31315
|
+
return resolved.startsWith(`${worktreesRoot}${path36.sep}`) && /-agent-\d+$/.test(path36.basename(resolved));
|
|
30986
31316
|
}
|
|
30987
31317
|
function resolveEffectiveMode(checkoutDir, gate) {
|
|
30988
31318
|
if (!gate.enabled) return "off";
|
|
@@ -30995,15 +31325,15 @@ function getHooksStatus(repoPath) {
|
|
|
30995
31325
|
const hooksDir = resolveHooksDir(checkout);
|
|
30996
31326
|
const gate = getCommitGateConfig(checkout);
|
|
30997
31327
|
const hasBlock = (name) => {
|
|
30998
|
-
const file =
|
|
30999
|
-
return
|
|
31328
|
+
const file = path36.join(hooksDir, name);
|
|
31329
|
+
return fs32.existsSync(file) && fs32.readFileSync(file, "utf8").includes(MARKER_START);
|
|
31000
31330
|
};
|
|
31001
31331
|
return {
|
|
31002
31332
|
checkout,
|
|
31003
31333
|
hooksDir,
|
|
31004
31334
|
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
31005
|
-
preCommitInstalled: hasBlock("pre-commit") &&
|
|
31006
|
-
postCommitInstalled: hasBlock("post-commit") &&
|
|
31335
|
+
preCommitInstalled: hasBlock("pre-commit") && fs32.existsSync(path36.join(hooksDir, "har-pre-commit")),
|
|
31336
|
+
postCommitInstalled: hasBlock("post-commit") && fs32.existsSync(path36.join(hooksDir, "har-post-commit")),
|
|
31007
31337
|
gate,
|
|
31008
31338
|
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
31009
31339
|
};
|
|
@@ -31014,7 +31344,7 @@ function checkCommitGate(cwd) {
|
|
|
31014
31344
|
}
|
|
31015
31345
|
const checkout = resolveCheckoutRoot(cwd);
|
|
31016
31346
|
if (!checkout) return { exitCode: 0, messages: [] };
|
|
31017
|
-
if (!
|
|
31347
|
+
if (!fs32.existsSync(path36.join(checkout, ".har", "stages.json"))) {
|
|
31018
31348
|
return { exitCode: 0, messages: [] };
|
|
31019
31349
|
}
|
|
31020
31350
|
const gate = getCommitGateConfig(checkout);
|
|
@@ -31084,13 +31414,114 @@ async function recordCommitAssociation(cwd) {
|
|
|
31084
31414
|
}
|
|
31085
31415
|
}
|
|
31086
31416
|
|
|
31417
|
+
// src/core/claude-hooks.ts
|
|
31418
|
+
var fs33 = __toESM(require("fs"));
|
|
31419
|
+
var path37 = __toESM(require("path"));
|
|
31420
|
+
var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
|
|
31421
|
+
var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
31422
|
+
var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
|
|
31423
|
+
var GUARD_COMMAND = `"$CLAUDE_PROJECT_DIR"/${CLAUDE_GUARD_RELATIVE_PATH}`;
|
|
31424
|
+
function isHarGuardEntry(entry) {
|
|
31425
|
+
return (entry.hooks ?? []).some(
|
|
31426
|
+
(hook) => typeof hook.command === "string" && hook.command.includes("claude-worktree-guard.sh")
|
|
31427
|
+
);
|
|
31428
|
+
}
|
|
31429
|
+
function readSettings(settingsPath) {
|
|
31430
|
+
if (!fs33.existsSync(settingsPath)) return {};
|
|
31431
|
+
const raw = fs33.readFileSync(settingsPath, "utf8").trim();
|
|
31432
|
+
if (raw === "") return {};
|
|
31433
|
+
const parsed = JSON.parse(raw);
|
|
31434
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
31435
|
+
throw new Error(`${settingsPath} is not a JSON object \u2014 fix it before installing the guard.`);
|
|
31436
|
+
}
|
|
31437
|
+
return parsed;
|
|
31438
|
+
}
|
|
31439
|
+
function writeSettings(settingsPath, settings) {
|
|
31440
|
+
fs33.mkdirSync(path37.dirname(settingsPath), { recursive: true });
|
|
31441
|
+
fs33.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
31442
|
+
}
|
|
31443
|
+
function installClaudeGuard(repoPath) {
|
|
31444
|
+
const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
|
|
31445
|
+
if (!templatePath) {
|
|
31446
|
+
throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
|
|
31447
|
+
}
|
|
31448
|
+
const guardScript = path37.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
31449
|
+
fs33.mkdirSync(path37.dirname(guardScript), { recursive: true });
|
|
31450
|
+
fs33.copyFileSync(templatePath, guardScript);
|
|
31451
|
+
fs33.chmodSync(guardScript, 493);
|
|
31452
|
+
const settingsPath = path37.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31453
|
+
const settings = readSettings(settingsPath);
|
|
31454
|
+
const hooks = settings.hooks ?? {};
|
|
31455
|
+
const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
|
|
31456
|
+
preToolUse.push({
|
|
31457
|
+
matcher: GUARD_MATCHER,
|
|
31458
|
+
hooks: [{ type: "command", command: GUARD_COMMAND }]
|
|
31459
|
+
});
|
|
31460
|
+
settings.hooks = { ...hooks, PreToolUse: preToolUse };
|
|
31461
|
+
writeSettings(settingsPath, settings);
|
|
31462
|
+
return { guardScript, settingsPath };
|
|
31463
|
+
}
|
|
31464
|
+
function uninstallClaudeGuard(repoPath) {
|
|
31465
|
+
let removed = false;
|
|
31466
|
+
const guardScript = path37.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
31467
|
+
if (fs33.existsSync(guardScript)) {
|
|
31468
|
+
fs33.rmSync(guardScript);
|
|
31469
|
+
removed = true;
|
|
31470
|
+
const parent = path37.dirname(guardScript);
|
|
31471
|
+
if (fs33.existsSync(parent) && fs33.readdirSync(parent).length === 0) fs33.rmdirSync(parent);
|
|
31472
|
+
}
|
|
31473
|
+
const settingsPath = path37.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31474
|
+
if (fs33.existsSync(settingsPath)) {
|
|
31475
|
+
const settings = readSettings(settingsPath);
|
|
31476
|
+
const preToolUse = settings.hooks?.PreToolUse;
|
|
31477
|
+
if (preToolUse) {
|
|
31478
|
+
const filtered = preToolUse.filter((entry) => !isHarGuardEntry(entry));
|
|
31479
|
+
if (filtered.length !== preToolUse.length) {
|
|
31480
|
+
removed = true;
|
|
31481
|
+
if (filtered.length > 0) {
|
|
31482
|
+
settings.hooks = { ...settings.hooks, PreToolUse: filtered };
|
|
31483
|
+
} else {
|
|
31484
|
+
const rest = { ...settings.hooks };
|
|
31485
|
+
delete rest.PreToolUse;
|
|
31486
|
+
if (Object.keys(rest).length > 0) {
|
|
31487
|
+
settings.hooks = rest;
|
|
31488
|
+
} else {
|
|
31489
|
+
delete settings.hooks;
|
|
31490
|
+
}
|
|
31491
|
+
}
|
|
31492
|
+
writeSettings(settingsPath, settings);
|
|
31493
|
+
}
|
|
31494
|
+
}
|
|
31495
|
+
}
|
|
31496
|
+
return { removed };
|
|
31497
|
+
}
|
|
31498
|
+
function claudeGuardInstalled(repoPath) {
|
|
31499
|
+
const settingsPath = path37.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31500
|
+
if (!fs33.existsSync(settingsPath)) return false;
|
|
31501
|
+
try {
|
|
31502
|
+
const settings = readSettings(settingsPath);
|
|
31503
|
+
return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
|
|
31504
|
+
} catch {
|
|
31505
|
+
return false;
|
|
31506
|
+
}
|
|
31507
|
+
}
|
|
31508
|
+
|
|
31087
31509
|
// src/cli/commands/hooks.ts
|
|
31088
31510
|
function repoOption(y2) {
|
|
31089
31511
|
return y2.option("repo", { type: "string", default: ".", describe: "Path to the repository" });
|
|
31090
31512
|
}
|
|
31091
|
-
function
|
|
31513
|
+
function handleInstall2(argv) {
|
|
31092
31514
|
try {
|
|
31093
|
-
|
|
31515
|
+
if (argv.claude) {
|
|
31516
|
+
const result2 = installClaudeGuard(path38.resolve(argv.repo));
|
|
31517
|
+
success("Claude Code worktree guard installed.");
|
|
31518
|
+
info(`Guard script: ${result2.guardScript}`);
|
|
31519
|
+
info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
|
|
31520
|
+
info("File edits in the MAIN checkout are now blocked with a pointer to /har-wt.");
|
|
31521
|
+
info("Commit both files so the guard travels with the repo.");
|
|
31522
|
+
return;
|
|
31523
|
+
}
|
|
31524
|
+
const result = installHooks({ repoPath: path38.resolve(argv.repo), force: argv.force });
|
|
31094
31525
|
success(`Commit gate installed in ${result.hooksDir}`);
|
|
31095
31526
|
info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
|
|
31096
31527
|
info("Commits of unverified change batches will be blocked in agent worktrees.");
|
|
@@ -31102,7 +31533,16 @@ function handleInstall(argv) {
|
|
|
31102
31533
|
}
|
|
31103
31534
|
function handleUninstall(argv) {
|
|
31104
31535
|
try {
|
|
31105
|
-
|
|
31536
|
+
if (argv.claude) {
|
|
31537
|
+
const result2 = uninstallClaudeGuard(path38.resolve(argv.repo));
|
|
31538
|
+
if (result2.removed) {
|
|
31539
|
+
success("Claude Code worktree guard removed.");
|
|
31540
|
+
} else {
|
|
31541
|
+
info("No Claude Code worktree guard was installed.");
|
|
31542
|
+
}
|
|
31543
|
+
return;
|
|
31544
|
+
}
|
|
31545
|
+
const result = uninstallHooks(path38.resolve(argv.repo));
|
|
31106
31546
|
if (result.removed) {
|
|
31107
31547
|
success(`Commit gate removed from ${result.hooksDir}`);
|
|
31108
31548
|
} else {
|
|
@@ -31115,7 +31555,7 @@ function handleUninstall(argv) {
|
|
|
31115
31555
|
}
|
|
31116
31556
|
function handleStatus2(argv) {
|
|
31117
31557
|
try {
|
|
31118
|
-
const status = getHooksStatus(
|
|
31558
|
+
const status = getHooksStatus(path38.resolve(argv.repo));
|
|
31119
31559
|
if (argv.json) {
|
|
31120
31560
|
process.stdout.write(`${JSON.stringify(status, null, 2)}
|
|
31121
31561
|
`);
|
|
@@ -31132,6 +31572,9 @@ function handleStatus2(argv) {
|
|
|
31132
31572
|
`Gate: enabled=${status.gate.enabled} mode=${status.gate.mode} scope=${status.gate.scope}`
|
|
31133
31573
|
);
|
|
31134
31574
|
info(`Effective: ${status.effectiveMode} (in this checkout)`);
|
|
31575
|
+
info(
|
|
31576
|
+
`Claude guard: ${claudeGuardInstalled(path38.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
|
|
31577
|
+
);
|
|
31135
31578
|
} catch (err) {
|
|
31136
31579
|
error(err instanceof Error ? err.message : String(err));
|
|
31137
31580
|
process.exitCode = 1;
|
|
@@ -31158,9 +31601,22 @@ var hooksCommand = {
|
|
|
31158
31601
|
type: "boolean",
|
|
31159
31602
|
default: false,
|
|
31160
31603
|
describe: "Write hooks even when core.hooksPath points at a managed directory"
|
|
31604
|
+
}).option("claude", {
|
|
31605
|
+
type: "boolean",
|
|
31606
|
+
default: false,
|
|
31607
|
+
describe: "Install the Claude Code worktree guard instead (blocks Edit/Write in the main checkout, points to /har-wt)"
|
|
31161
31608
|
}),
|
|
31162
|
-
|
|
31163
|
-
).command(
|
|
31609
|
+
handleInstall2
|
|
31610
|
+
).command(
|
|
31611
|
+
"uninstall",
|
|
31612
|
+
"Remove the har commit gate hooks",
|
|
31613
|
+
(y2) => repoOption(y2).option("claude", {
|
|
31614
|
+
type: "boolean",
|
|
31615
|
+
default: false,
|
|
31616
|
+
describe: "Remove the Claude Code worktree guard instead"
|
|
31617
|
+
}),
|
|
31618
|
+
handleUninstall
|
|
31619
|
+
).command(
|
|
31164
31620
|
"status",
|
|
31165
31621
|
"Show hook installation and gate configuration",
|
|
31166
31622
|
(y2) => repoOption(y2).option("json", { type: "boolean", default: false }),
|
|
@@ -31378,10 +31834,10 @@ function assignProp(target, prop, value) {
|
|
|
31378
31834
|
configurable: true
|
|
31379
31835
|
});
|
|
31380
31836
|
}
|
|
31381
|
-
function getElementAtPath(obj,
|
|
31382
|
-
if (!
|
|
31837
|
+
function getElementAtPath(obj, path40) {
|
|
31838
|
+
if (!path40)
|
|
31383
31839
|
return obj;
|
|
31384
|
-
return
|
|
31840
|
+
return path40.reduce((acc, key) => acc?.[key], obj);
|
|
31385
31841
|
}
|
|
31386
31842
|
function promiseAllObject(promisesObj) {
|
|
31387
31843
|
const keys = Object.keys(promisesObj);
|
|
@@ -31701,11 +32157,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
31701
32157
|
}
|
|
31702
32158
|
return false;
|
|
31703
32159
|
}
|
|
31704
|
-
function prefixIssues(
|
|
32160
|
+
function prefixIssues(path40, issues) {
|
|
31705
32161
|
return issues.map((iss) => {
|
|
31706
32162
|
var _a3;
|
|
31707
32163
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
31708
|
-
iss.path.unshift(
|
|
32164
|
+
iss.path.unshift(path40);
|
|
31709
32165
|
return iss;
|
|
31710
32166
|
});
|
|
31711
32167
|
}
|
|
@@ -37028,7 +37484,7 @@ var Protocol = class {
|
|
|
37028
37484
|
return;
|
|
37029
37485
|
}
|
|
37030
37486
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
37031
|
-
await new Promise((
|
|
37487
|
+
await new Promise((resolve27) => setTimeout(resolve27, pollInterval));
|
|
37032
37488
|
options?.signal?.throwIfAborted();
|
|
37033
37489
|
}
|
|
37034
37490
|
} catch (error3) {
|
|
@@ -37045,7 +37501,7 @@ var Protocol = class {
|
|
|
37045
37501
|
*/
|
|
37046
37502
|
request(request, resultSchema, options) {
|
|
37047
37503
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
37048
|
-
return new Promise((
|
|
37504
|
+
return new Promise((resolve27, reject) => {
|
|
37049
37505
|
const earlyReject = (error3) => {
|
|
37050
37506
|
reject(error3);
|
|
37051
37507
|
};
|
|
@@ -37123,7 +37579,7 @@ var Protocol = class {
|
|
|
37123
37579
|
if (!parseResult.success) {
|
|
37124
37580
|
reject(parseResult.error);
|
|
37125
37581
|
} else {
|
|
37126
|
-
|
|
37582
|
+
resolve27(parseResult.data);
|
|
37127
37583
|
}
|
|
37128
37584
|
} catch (error3) {
|
|
37129
37585
|
reject(error3);
|
|
@@ -37384,12 +37840,12 @@ var Protocol = class {
|
|
|
37384
37840
|
}
|
|
37385
37841
|
} catch {
|
|
37386
37842
|
}
|
|
37387
|
-
return new Promise((
|
|
37843
|
+
return new Promise((resolve27, reject) => {
|
|
37388
37844
|
if (signal.aborted) {
|
|
37389
37845
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
37390
37846
|
return;
|
|
37391
37847
|
}
|
|
37392
|
-
const timeoutId = setTimeout(
|
|
37848
|
+
const timeoutId = setTimeout(resolve27, interval);
|
|
37393
37849
|
signal.addEventListener("abort", () => {
|
|
37394
37850
|
clearTimeout(timeoutId);
|
|
37395
37851
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -38179,7 +38635,7 @@ var Server = class extends Protocol {
|
|
|
38179
38635
|
};
|
|
38180
38636
|
|
|
38181
38637
|
// src/mcp/server.ts
|
|
38182
|
-
var
|
|
38638
|
+
var path39 = __toESM(require("path"));
|
|
38183
38639
|
|
|
38184
38640
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
38185
38641
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
@@ -38262,12 +38718,12 @@ var StdioServerTransport = class {
|
|
|
38262
38718
|
this.onclose?.();
|
|
38263
38719
|
}
|
|
38264
38720
|
send(message) {
|
|
38265
|
-
return new Promise((
|
|
38721
|
+
return new Promise((resolve27) => {
|
|
38266
38722
|
const json = serializeMessage(message);
|
|
38267
38723
|
if (this._stdout.write(json)) {
|
|
38268
|
-
|
|
38724
|
+
resolve27();
|
|
38269
38725
|
} else {
|
|
38270
|
-
this._stdout.once("drain",
|
|
38726
|
+
this._stdout.once("drain", resolve27);
|
|
38271
38727
|
}
|
|
38272
38728
|
});
|
|
38273
38729
|
}
|
|
@@ -38811,7 +39267,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
38811
39267
|
const input = ControlUpInputSchema.parse({ ...args, repo });
|
|
38812
39268
|
const result = await startControlAndSync({
|
|
38813
39269
|
detach: input.detach,
|
|
38814
|
-
cwd:
|
|
39270
|
+
cwd: path39.resolve(input.repo)
|
|
38815
39271
|
});
|
|
38816
39272
|
if (result.code !== 0) {
|
|
38817
39273
|
return {
|
|
@@ -38875,7 +39331,7 @@ var mcpCommand = {
|
|
|
38875
39331
|
|
|
38876
39332
|
// src/cli/index.ts
|
|
38877
39333
|
async function runCli() {
|
|
38878
|
-
await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
|
|
39334
|
+
await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(agentsCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
|
|
38879
39335
|
}
|
|
38880
39336
|
|
|
38881
39337
|
// src/index.ts
|