@osfactory/har 0.9.0 → 0.11.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 +43 -2
- package/dist/index.js +1240 -586
- package/dist/templates/agent-skills/har-maintain.md +36 -0
- package/dist/templates/agent-skills/har-wt.md +47 -0
- package/dist/templates/agent-skills/setup-har.md +70 -0
- package/dist/templates/agent-skills/skills.manifest.json +48 -0
- package/dist/templates/claude-worktree-guard.sh.template +38 -0
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +4 -2
- package/dist/templates/har-boilerplate/STAGES.md +91 -0
- package/dist/templates/har-boilerplate/agent-slot.sh +35 -6
- package/dist/templates/har-boilerplate/stages/README.sh +7 -5
- package/dist/templates/har-boilerplate/verify.sh +9 -2
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +3 -1
- package/dist/templates/har-boilerplate-cli/STAGES.md +91 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +35 -6
- package/dist/templates/har-boilerplate-cli/stages/README.sh +7 -5
- package/dist/templates/har-boilerplate-cli/verify.sh +9 -2
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +2 -0
- package/dist/templates/har-boilerplate-ios/STAGES.md +91 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +35 -6
- package/dist/templates/har-boilerplate-ios/stages/README.sh +6 -7
- package/dist/templates/har-boilerplate-ios/verify.sh +9 -3
- package/dist/templates/stage-templates/custom-stage-skeleton.sh +65 -0
- package/dist/templates/stage-templates/playwright/template.manifest.json +9 -1
- package/dist/templates/stage-templates/rocketsim/template.manifest.json +2 -1
- 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 path41 = url.path;
|
|
769
|
+
if (path41.length === 0) {
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
|
-
if (url.scheme === "file" &&
|
|
772
|
+
if (url.scheme === "file" && path41.length === 1 && isNormalizedWindowsDriveLetter(path41[0])) {
|
|
773
773
|
return;
|
|
774
774
|
}
|
|
775
|
-
|
|
775
|
+
path41.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(resolve28, 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
|
+
resolve28(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(resolve28, 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
|
+
resolve28(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
|
+
resolve28(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
|
+
resolve28(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
|
+
resolve28(response);
|
|
2981
2981
|
});
|
|
2982
2982
|
raw.on("end", function() {
|
|
2983
2983
|
if (!response) {
|
|
2984
2984
|
response = new Response3(body, response_options);
|
|
2985
|
-
|
|
2985
|
+
resolve28(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
|
+
resolve28(response);
|
|
2994
2994
|
return;
|
|
2995
2995
|
}
|
|
2996
2996
|
response = new Response3(body, response_options);
|
|
2997
|
-
|
|
2997
|
+
resolve28(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(path41, { 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: path41, 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(path41, filenameOrOptions, options = {}) {
|
|
6371
|
+
const stats = (0, import_fs5.statSync)(path41);
|
|
6372
|
+
return createFileFromPath(path41, stats, filenameOrOptions, options);
|
|
6373
6373
|
}
|
|
6374
|
-
async function fileFromPath2(
|
|
6375
|
-
const stats = await import_fs5.promises.stat(
|
|
6376
|
-
return createFileFromPath(
|
|
6374
|
+
async function fileFromPath2(path41, filenameOrOptions, options) {
|
|
6375
|
+
const stats = await import_fs5.promises.stat(path41);
|
|
6376
|
+
return createFileFromPath(path41, 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 = resolve28.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 resolve28(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(path41) {
|
|
9633
|
+
let input = path41;
|
|
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 [path41, query] = wsComponent.resourceName.split("?");
|
|
9886
|
+
wsComponent.path = path41 && path41 !== "/" ? path41 : 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 resolve28(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: resolve28,
|
|
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, fs35, 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, fs35[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: (path41) => {
|
|
14569
14569
|
if (typeof require !== "undefined") {
|
|
14570
|
-
return require(
|
|
14571
|
-
} else if (
|
|
14572
|
-
return JSON.parse((0, import_fs2.readFileSync)(
|
|
14570
|
+
return require(path41);
|
|
14571
|
+
} else if (path41.match(/\.json$/)) {
|
|
14572
|
+
return JSON.parse((0, import_fs2.readFileSync)(path41, "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((resolve28, 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
|
+
resolve28(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: path41, errorMaps, issueData } = params;
|
|
19254
|
+
const fullPath = [...path41, ...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, path41, key) {
|
|
19394
19371
|
this._cachedPath = [];
|
|
19395
19372
|
this.parent = parent;
|
|
19396
19373
|
this.data = value;
|
|
19397
|
-
this._path =
|
|
19374
|
+
this._path = path41;
|
|
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.");
|
|
23188
23186
|
}
|
|
23189
|
-
return
|
|
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((resolve28) => {
|
|
23375
|
+
process.stderr.write(`${question} `);
|
|
23376
|
+
rl.once("line", (answer) => {
|
|
23377
|
+
rl.close();
|
|
23378
|
+
const trimmed = answer.trim();
|
|
23379
|
+
if (trimmed === "") {
|
|
23380
|
+
resolve28(true);
|
|
23381
|
+
return;
|
|
23382
|
+
}
|
|
23383
|
+
resolve28(/^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);
|
|
23458
|
+
}
|
|
23459
|
+
}
|
|
23460
|
+
|
|
23461
|
+
// src/cli/commands/env.ts
|
|
23462
|
+
var path34 = __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(path41, ...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(path41)}) instead`);
|
|
23671
23972
|
fileFromPathWarned = true;
|
|
23672
23973
|
}
|
|
23673
|
-
return await _fileFromPath(
|
|
23974
|
+
return await _fileFromPath(path41, ...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((resolve28) => {
|
|
24471
|
+
resolve28(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(path41, opts) {
|
|
24572
|
+
return this.methodRequest("get", path41, opts);
|
|
24272
24573
|
}
|
|
24273
|
-
post(
|
|
24274
|
-
return this.methodRequest("post",
|
|
24574
|
+
post(path41, opts) {
|
|
24575
|
+
return this.methodRequest("post", path41, opts);
|
|
24275
24576
|
}
|
|
24276
|
-
patch(
|
|
24277
|
-
return this.methodRequest("patch",
|
|
24577
|
+
patch(path41, opts) {
|
|
24578
|
+
return this.methodRequest("patch", path41, opts);
|
|
24278
24579
|
}
|
|
24279
|
-
put(
|
|
24280
|
-
return this.methodRequest("put",
|
|
24580
|
+
put(path41, opts) {
|
|
24581
|
+
return this.methodRequest("put", path41, opts);
|
|
24281
24582
|
}
|
|
24282
|
-
delete(
|
|
24283
|
-
return this.methodRequest("delete",
|
|
24583
|
+
delete(path41, opts) {
|
|
24584
|
+
return this.methodRequest("delete", path41, opts);
|
|
24284
24585
|
}
|
|
24285
|
-
methodRequest(method,
|
|
24586
|
+
methodRequest(method, path41, 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: path41, ...opts2, body };
|
|
24289
24590
|
}));
|
|
24290
24591
|
}
|
|
24291
|
-
getAPIList(
|
|
24292
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
24592
|
+
getAPIList(path41, Page, opts) {
|
|
24593
|
+
return this.requestAPIList(Page, { method: "get", path: path41, ...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: path41, 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(path41, 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(path41, query) {
|
|
24724
|
+
const url = isAbsoluteURL(path41) ? new URL(path41) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path41.startsWith("/") ? path41.slice(1) : path41));
|
|
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((resolve28) => setTimeout(resolve28, 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((resolve28, reject) => {
|
|
25501
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve28, "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((resolve28, reject) => {
|
|
25505
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve28, "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((resolve28, 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, resolve28);
|
|
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((resolve28, reject) => readQueue.push({ resolve: resolve28, 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((resolve28, reject) => {
|
|
26014
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve28, "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((resolve28, reject) => {
|
|
26018
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve28, "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((resolve28, 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, resolve28);
|
|
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((resolve28, reject) => readQueue.push({ resolve: resolve28, 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((resolve28) => {
|
|
26338
26639
|
process.stderr.write(`${question} `);
|
|
26339
26640
|
rl.once("line", (answer) => {
|
|
26340
26641
|
rl.close();
|
|
26341
|
-
|
|
26642
|
+
resolve28(/^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((resolve28) => {
|
|
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
|
+
resolve28(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((resolve28) => {
|
|
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) => resolve28({ 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((resolve28) => {
|
|
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) => resolve28({ 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,89 @@ 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
|
|
27852
|
+
var STAGE_TEMPLATE_IDS = ["playwright", "rocketsim"];
|
|
27853
|
+
var TemplateManifestFileSchema = external_exports.object({
|
|
27854
|
+
src: external_exports.string().min(1),
|
|
27855
|
+
dest: external_exports.string().min(1),
|
|
27856
|
+
executable: external_exports.boolean().optional(),
|
|
27857
|
+
skipFlag: external_exports.string().optional()
|
|
27858
|
+
});
|
|
27859
|
+
var StageTemplateManifestSchema = external_exports.object({
|
|
27860
|
+
id: external_exports.enum(STAGE_TEMPLATE_IDS),
|
|
27861
|
+
stageId: external_exports.string().min(1),
|
|
27862
|
+
verificationStages: external_exports.array(external_exports.string().min(1)).min(1),
|
|
27863
|
+
stage: external_exports.record(external_exports.unknown()),
|
|
27864
|
+
files: external_exports.array(TemplateManifestFileSchema).min(1),
|
|
27865
|
+
optionalFiles: external_exports.array(TemplateManifestFileSchema).optional(),
|
|
27866
|
+
merge: external_exports.record(external_exports.string()).optional(),
|
|
27867
|
+
nextSteps: external_exports.array(external_exports.string().min(1)).min(1),
|
|
27868
|
+
docsPath: external_exports.string().min(1)
|
|
27869
|
+
});
|
|
27551
27870
|
function resolveTemplateDir(templateId) {
|
|
27552
|
-
const dir =
|
|
27553
|
-
if (!
|
|
27871
|
+
const dir = path17.join(resolveTemplatesDir(), "stage-templates", templateId);
|
|
27872
|
+
if (!fs18.existsSync(dir)) {
|
|
27554
27873
|
throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
|
|
27555
27874
|
}
|
|
27556
27875
|
return dir;
|
|
27557
27876
|
}
|
|
27558
27877
|
function readTemplateManifest(templateId) {
|
|
27559
|
-
const manifestPath =
|
|
27560
|
-
const
|
|
27561
|
-
|
|
27562
|
-
|
|
27878
|
+
const manifestPath = path17.join(resolveTemplateDir(templateId), "template.manifest.json");
|
|
27879
|
+
const parsed = StageTemplateManifestSchema.safeParse(
|
|
27880
|
+
JSON.parse(fs18.readFileSync(manifestPath, "utf8"))
|
|
27881
|
+
);
|
|
27882
|
+
if (!parsed.success) {
|
|
27883
|
+
throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
|
|
27884
|
+
}
|
|
27885
|
+
if (parsed.data.id !== templateId) {
|
|
27886
|
+
throw new Error(`Template manifest id mismatch: expected ${templateId}, got ${parsed.data.id}`);
|
|
27563
27887
|
}
|
|
27564
|
-
return
|
|
27888
|
+
return parsed.data;
|
|
27565
27889
|
}
|
|
27566
27890
|
function ensureParentDir(filePath) {
|
|
27567
|
-
const parent =
|
|
27568
|
-
if (!
|
|
27569
|
-
|
|
27891
|
+
const parent = path17.dirname(filePath);
|
|
27892
|
+
if (!fs18.existsSync(parent)) {
|
|
27893
|
+
fs18.mkdirSync(parent, { recursive: true });
|
|
27570
27894
|
}
|
|
27571
27895
|
}
|
|
27572
27896
|
function copyTemplateFile(templateDir, file, repoPath, force) {
|
|
27573
|
-
const srcPath =
|
|
27574
|
-
const destPath =
|
|
27575
|
-
if (!
|
|
27897
|
+
const srcPath = path17.join(templateDir, file.src);
|
|
27898
|
+
const destPath = path17.join(repoPath, file.dest);
|
|
27899
|
+
if (!fs18.existsSync(srcPath)) {
|
|
27576
27900
|
throw new Error(`Template file missing: ${file.src}`);
|
|
27577
27901
|
}
|
|
27578
|
-
if (
|
|
27902
|
+
if (fs18.existsSync(destPath) && !force) {
|
|
27579
27903
|
throw new Error(
|
|
27580
27904
|
`File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
|
|
27581
27905
|
);
|
|
27582
27906
|
}
|
|
27583
27907
|
ensureParentDir(destPath);
|
|
27584
|
-
|
|
27908
|
+
fs18.copyFileSync(srcPath, destPath);
|
|
27585
27909
|
if (file.executable) {
|
|
27586
|
-
|
|
27910
|
+
fs18.chmodSync(destPath, 493);
|
|
27587
27911
|
}
|
|
27588
27912
|
return { written: true, path: file.dest };
|
|
27589
27913
|
}
|
|
27590
27914
|
function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
27591
|
-
const packagePath =
|
|
27592
|
-
if (!
|
|
27593
|
-
throw new Error("No package.json in repo root. Add one before applying
|
|
27915
|
+
const packagePath = path17.join(repoPath, "package.json");
|
|
27916
|
+
if (!fs18.existsSync(packagePath)) {
|
|
27917
|
+
throw new Error("No package.json in repo root. Add one before applying this stage template.");
|
|
27594
27918
|
}
|
|
27595
|
-
const fragmentPath =
|
|
27596
|
-
if (!
|
|
27919
|
+
const fragmentPath = path17.join(templateDir, fragmentRelPath);
|
|
27920
|
+
if (!fs18.existsSync(fragmentPath)) {
|
|
27597
27921
|
throw new Error(`Package fragment missing: ${fragmentRelPath}`);
|
|
27598
27922
|
}
|
|
27599
|
-
const pkg = JSON.parse(
|
|
27600
|
-
const fragment = JSON.parse(
|
|
27923
|
+
const pkg = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
|
|
27924
|
+
const fragment = JSON.parse(fs18.readFileSync(fragmentPath, "utf8"));
|
|
27601
27925
|
for (const section of ["scripts", "devDependencies"]) {
|
|
27602
27926
|
const existing = pkg[section] ?? {};
|
|
27603
27927
|
const incoming = fragment[section] ?? {};
|
|
@@ -27610,7 +27934,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
|
27610
27934
|
}
|
|
27611
27935
|
pkg[section] = existing;
|
|
27612
27936
|
}
|
|
27613
|
-
|
|
27937
|
+
fs18.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
27614
27938
|
}
|
|
27615
27939
|
function patchStageRegistry(repoPath, manifest, force) {
|
|
27616
27940
|
const registry2 = readStageRegistry(repoPath);
|
|
@@ -27623,8 +27947,7 @@ function patchStageRegistry(repoPath, manifest, force) {
|
|
|
27623
27947
|
}
|
|
27624
27948
|
const stages = existing ? registry2.stages.map((s2) => s2.id === stage.id ? stage : s2) : [...registry2.stages, stage];
|
|
27625
27949
|
const verificationStages = [...registry2.verificationStages ?? []];
|
|
27626
|
-
const
|
|
27627
|
-
for (const id of toAdd) {
|
|
27950
|
+
for (const id of manifest.verificationStages) {
|
|
27628
27951
|
if (!verificationStages.includes(id)) {
|
|
27629
27952
|
verificationStages.push(id);
|
|
27630
27953
|
}
|
|
@@ -27633,7 +27956,7 @@ function patchStageRegistry(repoPath, manifest, force) {
|
|
|
27633
27956
|
if (verifyIdx >= 0) {
|
|
27634
27957
|
stages[verifyIdx] = {
|
|
27635
27958
|
...stages[verifyIdx],
|
|
27636
|
-
description:
|
|
27959
|
+
description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`,
|
|
27637
27960
|
acceptsArgs: ["--full"]
|
|
27638
27961
|
};
|
|
27639
27962
|
}
|
|
@@ -27651,8 +27974,8 @@ function assertHarnessPresent(repoPath) {
|
|
|
27651
27974
|
}
|
|
27652
27975
|
function assertStageNotPresent(repoPath, stageId, force) {
|
|
27653
27976
|
if (force) return;
|
|
27654
|
-
const scriptPath =
|
|
27655
|
-
if (
|
|
27977
|
+
const scriptPath = path17.join(repoPath, ".har", "stages", `${stageId}.sh`);
|
|
27978
|
+
if (fs18.existsSync(scriptPath)) {
|
|
27656
27979
|
throw new Error(
|
|
27657
27980
|
`Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
|
|
27658
27981
|
);
|
|
@@ -27665,7 +27988,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
|
|
|
27665
27988
|
}
|
|
27666
27989
|
}
|
|
27667
27990
|
function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
27668
|
-
const resolved =
|
|
27991
|
+
const resolved = path17.resolve(repoPath);
|
|
27669
27992
|
const force = options.force ?? false;
|
|
27670
27993
|
const warnings = [];
|
|
27671
27994
|
const filesWritten = [];
|
|
@@ -27684,7 +28007,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27684
28007
|
if (file.skipFlag === "skipCi" && options.skipCi) {
|
|
27685
28008
|
continue;
|
|
27686
28009
|
}
|
|
27687
|
-
if (
|
|
28010
|
+
if (fs18.existsSync(path17.join(resolved, file.dest)) && !force) {
|
|
27688
28011
|
warnings.push(`Skipped optional file (exists): ${file.dest}`);
|
|
27689
28012
|
continue;
|
|
27690
28013
|
}
|
|
@@ -27709,21 +28032,22 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27709
28032
|
for (const warning of warnings) {
|
|
27710
28033
|
warn(` \u26A0 ${warning}`);
|
|
27711
28034
|
}
|
|
27712
|
-
const nextSteps = manifest.nextSteps ?? [
|
|
27713
|
-
"npm install",
|
|
27714
|
-
"npx playwright install",
|
|
27715
|
-
"./.har/launch.sh 1",
|
|
27716
|
-
`./.har/stages/${manifest.stageId}.sh 1`,
|
|
27717
|
-
"npx playwright show-report .har/artifacts/browser-e2e/playwright-report"
|
|
27718
|
-
];
|
|
27719
28035
|
return {
|
|
27720
28036
|
templateId,
|
|
27721
28037
|
stageId: manifest.stageId,
|
|
27722
28038
|
filesWritten,
|
|
27723
28039
|
warnings,
|
|
27724
|
-
nextSteps
|
|
28040
|
+
nextSteps: manifest.nextSteps,
|
|
28041
|
+
docsPath: manifest.docsPath
|
|
27725
28042
|
};
|
|
27726
28043
|
}
|
|
28044
|
+
function listStageTemplateIds() {
|
|
28045
|
+
const root = path17.join(resolveTemplatesDir(), "stage-templates");
|
|
28046
|
+
if (!fs18.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
|
|
28047
|
+
return fs18.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
|
|
28048
|
+
(name) => STAGE_TEMPLATE_IDS.includes(name)
|
|
28049
|
+
);
|
|
28050
|
+
}
|
|
27727
28051
|
|
|
27728
28052
|
// src/utils/validation.ts
|
|
27729
28053
|
function requireApiKey() {
|
|
@@ -27747,11 +28071,11 @@ function validateAgentId(id, repoPath) {
|
|
|
27747
28071
|
// src/core/harness.ts
|
|
27748
28072
|
function listHarnessScripts(repoPath) {
|
|
27749
28073
|
const harnessDir = getHarnessDir(repoPath);
|
|
27750
|
-
if (!
|
|
27751
|
-
return
|
|
28074
|
+
if (!fs19.existsSync(harnessDir)) return [];
|
|
28075
|
+
return fs19.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
|
|
27752
28076
|
}
|
|
27753
28077
|
function describeProject(repoPath) {
|
|
27754
|
-
const resolved =
|
|
28078
|
+
const resolved = path18.resolve(repoPath);
|
|
27755
28079
|
const manifest = readManifest(resolved);
|
|
27756
28080
|
const present = harnessExists(resolved);
|
|
27757
28081
|
return {
|
|
@@ -27771,8 +28095,8 @@ function describeProject(repoPath) {
|
|
|
27771
28095
|
};
|
|
27772
28096
|
}
|
|
27773
28097
|
async function initHarness(options) {
|
|
27774
|
-
const repoPath =
|
|
27775
|
-
if (!
|
|
28098
|
+
const repoPath = path18.resolve(options.repoPath);
|
|
28099
|
+
if (!fs19.existsSync(repoPath)) {
|
|
27776
28100
|
throw new Error(`Path not found: ${repoPath}`);
|
|
27777
28101
|
}
|
|
27778
28102
|
const scaffold = scaffoldHarnessBoilerplate(repoPath, {
|
|
@@ -27803,9 +28127,9 @@ async function initHarness(options) {
|
|
|
27803
28127
|
};
|
|
27804
28128
|
}
|
|
27805
28129
|
async function maintainHarness(options) {
|
|
27806
|
-
const repoPath =
|
|
28130
|
+
const repoPath = path18.resolve(options.repoPath);
|
|
27807
28131
|
const harnessDir = getHarnessDir(repoPath);
|
|
27808
|
-
if (!
|
|
28132
|
+
if (!fs19.existsSync(harnessDir)) {
|
|
27809
28133
|
throw new Error('No .har/ found. Run "har env init" first.');
|
|
27810
28134
|
}
|
|
27811
28135
|
if (options.auto) {
|
|
@@ -27855,9 +28179,111 @@ function addStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27855
28179
|
return applyStageTemplate(repoPath, templateId, options);
|
|
27856
28180
|
}
|
|
27857
28181
|
|
|
28182
|
+
// src/harness/custom-stage.ts
|
|
28183
|
+
var fs20 = __toESM(require("fs"));
|
|
28184
|
+
var path19 = __toESM(require("path"));
|
|
28185
|
+
var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
28186
|
+
function addCustomStage(repoPath, options) {
|
|
28187
|
+
const resolved = path19.resolve(repoPath);
|
|
28188
|
+
if (!harnessExists(resolved)) {
|
|
28189
|
+
throw new Error('No .har/ harness found. Run "har env init" first.');
|
|
28190
|
+
}
|
|
28191
|
+
const id = options.id.trim();
|
|
28192
|
+
if (!STAGE_ID_PATTERN.test(id)) {
|
|
28193
|
+
throw new Error(
|
|
28194
|
+
`Invalid stage id "${id}". Use lowercase letters, digits, dots, dashes (e.g. "unit-tests").`
|
|
28195
|
+
);
|
|
28196
|
+
}
|
|
28197
|
+
const kind2 = options.kind ?? "custom";
|
|
28198
|
+
if (!HarnessStageKindSchema.options.includes(kind2)) {
|
|
28199
|
+
throw new Error(
|
|
28200
|
+
`Invalid stage kind "${kind2}". Available: ${HarnessStageKindSchema.options.join(", ")}`
|
|
28201
|
+
);
|
|
28202
|
+
}
|
|
28203
|
+
if (Boolean(options.command) === Boolean(options.script)) {
|
|
28204
|
+
throw new Error(
|
|
28205
|
+
'Choose exactly one execution mode: --command "<shell command>" for a one-liner, or --script to scaffold .har/stages/<id>.sh.'
|
|
28206
|
+
);
|
|
28207
|
+
}
|
|
28208
|
+
const registry2 = readStageRegistry(resolved);
|
|
28209
|
+
const existing = registry2.stages.find((s2) => s2.id === id);
|
|
28210
|
+
if (existing && !options.force) {
|
|
28211
|
+
throw new Error(`Stage "${id}" already registered in .har/stages.json. Use --force to replace.`);
|
|
28212
|
+
}
|
|
28213
|
+
const filesWritten = [];
|
|
28214
|
+
const stage = {
|
|
28215
|
+
id,
|
|
28216
|
+
kind: kind2,
|
|
28217
|
+
description: options.description ?? `Custom ${kind2} stage`,
|
|
28218
|
+
requiresAgentId: true,
|
|
28219
|
+
artifacts: []
|
|
28220
|
+
};
|
|
28221
|
+
if (options.command) {
|
|
28222
|
+
stage.command = options.command;
|
|
28223
|
+
} else {
|
|
28224
|
+
const scriptRel = path19.join("stages", `${id}.sh`);
|
|
28225
|
+
const scriptAbs = path19.join(resolved, ".har", scriptRel);
|
|
28226
|
+
if (fs20.existsSync(scriptAbs) && !options.force) {
|
|
28227
|
+
throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
|
|
28228
|
+
}
|
|
28229
|
+
const skeletonPath = path19.join(
|
|
28230
|
+
resolveTemplatesDir(),
|
|
28231
|
+
"stage-templates",
|
|
28232
|
+
"custom-stage-skeleton.sh"
|
|
28233
|
+
);
|
|
28234
|
+
const skeleton = fs20.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
|
|
28235
|
+
fs20.mkdirSync(path19.dirname(scriptAbs), { recursive: true });
|
|
28236
|
+
fs20.writeFileSync(scriptAbs, skeleton);
|
|
28237
|
+
fs20.chmodSync(scriptAbs, 493);
|
|
28238
|
+
filesWritten.push(`.har/${scriptRel}`);
|
|
28239
|
+
stage.script = scriptRel;
|
|
28240
|
+
stage.artifacts = [
|
|
28241
|
+
{
|
|
28242
|
+
path: `.har/artifacts/${id}`,
|
|
28243
|
+
kind: "directory",
|
|
28244
|
+
description: `Artifacts for the ${id} stage`
|
|
28245
|
+
}
|
|
28246
|
+
];
|
|
28247
|
+
}
|
|
28248
|
+
const parsedStage = HarnessStageSchema.parse(stage);
|
|
28249
|
+
const stages = existing ? registry2.stages.map((s2) => s2.id === id ? parsedStage : s2) : [...registry2.stages, parsedStage];
|
|
28250
|
+
const verification = options.verification ?? false;
|
|
28251
|
+
const verificationStages = [...registry2.verificationStages ?? []];
|
|
28252
|
+
if (verification && !verificationStages.includes(id)) {
|
|
28253
|
+
verificationStages.push(id);
|
|
28254
|
+
const verifyIdx = stages.findIndex((s2) => s2.id === "verify");
|
|
28255
|
+
if (verifyIdx >= 0) {
|
|
28256
|
+
stages[verifyIdx] = {
|
|
28257
|
+
...stages[verifyIdx],
|
|
28258
|
+
description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`
|
|
28259
|
+
};
|
|
28260
|
+
}
|
|
28261
|
+
}
|
|
28262
|
+
writeStageRegistry(resolved, { ...registry2, stages, verificationStages });
|
|
28263
|
+
filesWritten.push(".har/stages.json");
|
|
28264
|
+
const nextSteps = options.command ? [
|
|
28265
|
+
`Try it: ${options.command.replace(/\{agentId\}/g, "1")}`,
|
|
28266
|
+
`Agents run it via the MCP tool har_run_stage (stageId: "${id}")`,
|
|
28267
|
+
...verification ? ["./.har/verify.sh 1 --full # runs it as part of full verification"] : []
|
|
28268
|
+
] : [
|
|
28269
|
+
`Edit .har/stages/${id}.sh \u2014 replace the TODO block with the real check`,
|
|
28270
|
+
"./.har/launch.sh 1",
|
|
28271
|
+
`./.har/stages/${id}.sh 1`,
|
|
28272
|
+
...verification ? ["./.har/verify.sh 1 --full # runs it as part of full verification"] : []
|
|
28273
|
+
];
|
|
28274
|
+
return {
|
|
28275
|
+
stageId: id,
|
|
28276
|
+
kind: kind2,
|
|
28277
|
+
mode: options.command ? "command" : "script",
|
|
28278
|
+
filesWritten,
|
|
28279
|
+
verification,
|
|
28280
|
+
nextSteps
|
|
28281
|
+
};
|
|
28282
|
+
}
|
|
28283
|
+
|
|
27858
28284
|
// src/harness/adaptation-prompt.ts
|
|
27859
|
-
var
|
|
27860
|
-
var
|
|
28285
|
+
var fs21 = __toESM(require("fs"));
|
|
28286
|
+
var path20 = __toESM(require("path"));
|
|
27861
28287
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
27862
28288
|
var PROFILE_HINTS = {
|
|
27863
28289
|
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 +28295,7 @@ function loadTemplate(name) {
|
|
|
27869
28295
|
if (!filePath) {
|
|
27870
28296
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
27871
28297
|
}
|
|
27872
|
-
return
|
|
28298
|
+
return fs21.readFileSync(filePath, "utf8");
|
|
27873
28299
|
}
|
|
27874
28300
|
function applyProfilePlaceholders(content, profile) {
|
|
27875
28301
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -27891,7 +28317,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
|
27891
28317
|
}
|
|
27892
28318
|
function writeAdaptationPrompt(repoPath, content) {
|
|
27893
28319
|
const harnessDir = getHarnessDir(repoPath);
|
|
27894
|
-
const filePath =
|
|
28320
|
+
const filePath = path20.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
27895
28321
|
writeFileSafe(filePath, content);
|
|
27896
28322
|
return filePath;
|
|
27897
28323
|
}
|
|
@@ -27910,25 +28336,25 @@ function printAdaptationPrompt(content) {
|
|
|
27910
28336
|
}
|
|
27911
28337
|
|
|
27912
28338
|
// src/harness/cursor-rule.ts
|
|
27913
|
-
var
|
|
27914
|
-
var
|
|
27915
|
-
var
|
|
28339
|
+
var fs22 = __toESM(require("fs"));
|
|
28340
|
+
var path21 = __toESM(require("path"));
|
|
28341
|
+
var readline4 = __toESM(require("readline"));
|
|
27916
28342
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
27917
28343
|
function getCursorRulePath(repoPath) {
|
|
27918
|
-
return
|
|
28344
|
+
return path21.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
27919
28345
|
}
|
|
27920
28346
|
function isCursorWorkspace(repoPath) {
|
|
27921
|
-
return
|
|
28347
|
+
return fs22.existsSync(path21.join(repoPath, ".cursor"));
|
|
27922
28348
|
}
|
|
27923
28349
|
function cursorRuleExists(repoPath) {
|
|
27924
|
-
return
|
|
28350
|
+
return fs22.existsSync(getCursorRulePath(repoPath));
|
|
27925
28351
|
}
|
|
27926
28352
|
function scaffoldCursorRule(repoPath) {
|
|
27927
28353
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
27928
28354
|
if (!templatePath) {
|
|
27929
28355
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
27930
28356
|
}
|
|
27931
|
-
const content =
|
|
28357
|
+
const content = fs22.readFileSync(templatePath, "utf8");
|
|
27932
28358
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
27933
28359
|
}
|
|
27934
28360
|
async function handleCursorRule(options) {
|
|
@@ -27962,20 +28388,20 @@ async function handleCursorRule(options) {
|
|
|
27962
28388
|
}
|
|
27963
28389
|
async function promptScaffoldCursorRule(exists) {
|
|
27964
28390
|
const action = exists ? "Update" : "Create";
|
|
27965
|
-
return
|
|
28391
|
+
return askYesNo3(`${action} ${CURSOR_RULE_RELATIVE_PATH}? [Y/n]`);
|
|
27966
28392
|
}
|
|
27967
|
-
async function
|
|
27968
|
-
const rl =
|
|
27969
|
-
return new Promise((
|
|
28393
|
+
async function askYesNo3(question) {
|
|
28394
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
|
|
28395
|
+
return new Promise((resolve28) => {
|
|
27970
28396
|
process.stderr.write(`${question} `);
|
|
27971
28397
|
rl.once("line", (answer) => {
|
|
27972
28398
|
rl.close();
|
|
27973
28399
|
const trimmed = answer.trim();
|
|
27974
28400
|
if (trimmed === "") {
|
|
27975
|
-
|
|
28401
|
+
resolve28(true);
|
|
27976
28402
|
return;
|
|
27977
28403
|
}
|
|
27978
|
-
|
|
28404
|
+
resolve28(/^y(es)?$/i.test(trimmed));
|
|
27979
28405
|
});
|
|
27980
28406
|
});
|
|
27981
28407
|
}
|
|
@@ -28054,26 +28480,26 @@ function buildStageResult(input) {
|
|
|
28054
28480
|
}
|
|
28055
28481
|
|
|
28056
28482
|
// src/core/local-executor.ts
|
|
28057
|
-
var
|
|
28058
|
-
var
|
|
28483
|
+
var fs24 = __toESM(require("fs"));
|
|
28484
|
+
var path23 = __toESM(require("path"));
|
|
28059
28485
|
|
|
28060
28486
|
// src/core/slot-registry.ts
|
|
28061
|
-
var
|
|
28062
|
-
var
|
|
28487
|
+
var fs23 = __toESM(require("fs"));
|
|
28488
|
+
var path22 = __toESM(require("path"));
|
|
28063
28489
|
function getSlotRegistryDir(repoPath) {
|
|
28064
|
-
return
|
|
28490
|
+
return path22.join(getHarnessDir(repoPath), "slots");
|
|
28065
28491
|
}
|
|
28066
28492
|
function getSlotRegistryPath(repoPath, agentId) {
|
|
28067
|
-
return
|
|
28493
|
+
return path22.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
28068
28494
|
}
|
|
28069
28495
|
function isSlotResumable(session) {
|
|
28070
28496
|
return session?.status === "failed" || session?.status === "starting";
|
|
28071
28497
|
}
|
|
28072
28498
|
function readSlotRegistry(repoPath, agentId) {
|
|
28073
28499
|
const file = getSlotRegistryPath(repoPath, agentId);
|
|
28074
|
-
if (!
|
|
28500
|
+
if (!fs23.existsSync(file)) return void 0;
|
|
28075
28501
|
try {
|
|
28076
|
-
const raw = JSON.parse(
|
|
28502
|
+
const raw = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
28077
28503
|
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
28078
28504
|
return result.success ? result.data : void 0;
|
|
28079
28505
|
} catch {
|
|
@@ -28083,7 +28509,7 @@ function readSlotRegistry(repoPath, agentId) {
|
|
|
28083
28509
|
|
|
28084
28510
|
// src/core/local-executor.ts
|
|
28085
28511
|
function resolveRepoPath(repoPath) {
|
|
28086
|
-
return
|
|
28512
|
+
return path23.resolve(repoPath);
|
|
28087
28513
|
}
|
|
28088
28514
|
function buildPreviewUrlsFromPorts(ports, env3) {
|
|
28089
28515
|
const urls = {};
|
|
@@ -28135,14 +28561,14 @@ function substituteAgentId(value, agentId) {
|
|
|
28135
28561
|
function resolveStageScriptPath(repoPath, stage) {
|
|
28136
28562
|
const harnessDir = getHarnessDir(repoPath);
|
|
28137
28563
|
if (stage.script) {
|
|
28138
|
-
return
|
|
28564
|
+
return path23.join(harnessDir, stage.script);
|
|
28139
28565
|
}
|
|
28140
28566
|
if (stage.command) {
|
|
28141
28567
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
28142
|
-
return
|
|
28568
|
+
return path23.join(harnessDir, scriptName);
|
|
28143
28569
|
}
|
|
28144
|
-
const stageScript =
|
|
28145
|
-
if (
|
|
28570
|
+
const stageScript = path23.join(harnessDir, "stages", `${stage.id}.sh`);
|
|
28571
|
+
if (fs24.existsSync(stageScript)) {
|
|
28146
28572
|
return stageScript;
|
|
28147
28573
|
}
|
|
28148
28574
|
throw new Error(
|
|
@@ -28157,7 +28583,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
28157
28583
|
...harnessEnv,
|
|
28158
28584
|
...stage.env ?? {}
|
|
28159
28585
|
};
|
|
28160
|
-
const cwd = stage.cwd ?
|
|
28586
|
+
const cwd = stage.cwd ? path23.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
|
|
28161
28587
|
const extraArgs = options.args ?? [];
|
|
28162
28588
|
const launchFlagArgs = [];
|
|
28163
28589
|
if (stage.kind === "launch" && options.launchFlags) {
|
|
@@ -28234,19 +28660,19 @@ var LocalScriptExecutor = class {
|
|
|
28234
28660
|
const repoPath = resolveRepoPath(ctx.repoPath);
|
|
28235
28661
|
const harnessDir = getHarnessDir(repoPath);
|
|
28236
28662
|
const artifactsDirName = getArtifactsDir(repoPath);
|
|
28237
|
-
const artifactsDir =
|
|
28238
|
-
if (!
|
|
28663
|
+
const artifactsDir = path23.join(harnessDir, artifactsDirName);
|
|
28664
|
+
if (!fs24.existsSync(artifactsDir)) return [];
|
|
28239
28665
|
const entries = [];
|
|
28240
28666
|
const walk = (dir, prefix) => {
|
|
28241
|
-
for (const entry of
|
|
28242
|
-
const full =
|
|
28243
|
-
const relative2 =
|
|
28667
|
+
for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
|
|
28668
|
+
const full = path23.join(dir, entry.name);
|
|
28669
|
+
const relative2 = path23.join(prefix, entry.name);
|
|
28244
28670
|
if (entry.isDirectory()) {
|
|
28245
28671
|
walk(full, relative2);
|
|
28246
28672
|
continue;
|
|
28247
28673
|
}
|
|
28248
28674
|
if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
|
|
28249
|
-
const stat =
|
|
28675
|
+
const stat = fs24.statSync(full);
|
|
28250
28676
|
entries.push({
|
|
28251
28677
|
path: full,
|
|
28252
28678
|
relativePath: relative2,
|
|
@@ -28262,7 +28688,7 @@ var LocalScriptExecutor = class {
|
|
|
28262
28688
|
var localScriptExecutor = new LocalScriptExecutor();
|
|
28263
28689
|
|
|
28264
28690
|
// src/core/control-sync.ts
|
|
28265
|
-
var
|
|
28691
|
+
var path33 = __toESM(require("path"));
|
|
28266
28692
|
|
|
28267
28693
|
// src/core/control-config.ts
|
|
28268
28694
|
var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
|
|
@@ -28274,32 +28700,32 @@ function isControlEnabled() {
|
|
|
28274
28700
|
}
|
|
28275
28701
|
|
|
28276
28702
|
// src/core/control-registry.ts
|
|
28277
|
-
var
|
|
28278
|
-
var
|
|
28279
|
-
var
|
|
28703
|
+
var fs25 = __toESM(require("fs"));
|
|
28704
|
+
var os4 = __toESM(require("os"));
|
|
28705
|
+
var path24 = __toESM(require("path"));
|
|
28280
28706
|
function getRegistryPath() {
|
|
28281
28707
|
if (process.env.HAR_CONTROL_REGISTRY_PATH) {
|
|
28282
|
-
return
|
|
28708
|
+
return path24.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
|
|
28283
28709
|
}
|
|
28284
|
-
return
|
|
28710
|
+
return path24.join(os4.homedir(), ".har", "repos.json");
|
|
28285
28711
|
}
|
|
28286
28712
|
function readRegistry() {
|
|
28287
28713
|
const registryPath = getRegistryPath();
|
|
28288
28714
|
try {
|
|
28289
|
-
if (!
|
|
28290
|
-
return JSON.parse(
|
|
28715
|
+
if (!fs25.existsSync(registryPath)) return { repos: [] };
|
|
28716
|
+
return JSON.parse(fs25.readFileSync(registryPath, "utf8"));
|
|
28291
28717
|
} catch {
|
|
28292
28718
|
return { repos: [] };
|
|
28293
28719
|
}
|
|
28294
28720
|
}
|
|
28295
28721
|
function writeRegistry(registry2) {
|
|
28296
28722
|
const registryPath = getRegistryPath();
|
|
28297
|
-
|
|
28298
|
-
|
|
28723
|
+
fs25.mkdirSync(path24.dirname(registryPath), { recursive: true });
|
|
28724
|
+
fs25.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
|
|
28299
28725
|
}
|
|
28300
28726
|
function recordRepoForControlSync(repoPath) {
|
|
28301
28727
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
28302
|
-
const resolved =
|
|
28728
|
+
const resolved = path24.resolve(repoPath);
|
|
28303
28729
|
if (!readManifest(resolved)) return;
|
|
28304
28730
|
const registry2 = readRegistry();
|
|
28305
28731
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -28308,7 +28734,7 @@ function recordRepoForControlSync(repoPath) {
|
|
|
28308
28734
|
}
|
|
28309
28735
|
function listRegisteredRepos() {
|
|
28310
28736
|
const registry2 = readRegistry();
|
|
28311
|
-
const kept = registry2.repos.filter((repoPath) =>
|
|
28737
|
+
const kept = registry2.repos.filter((repoPath) => fs25.existsSync(repoPath) && readManifest(repoPath));
|
|
28312
28738
|
if (kept.length !== registry2.repos.length) {
|
|
28313
28739
|
writeRegistry({ repos: kept });
|
|
28314
28740
|
}
|
|
@@ -28316,17 +28742,17 @@ function listRegisteredRepos() {
|
|
|
28316
28742
|
}
|
|
28317
28743
|
|
|
28318
28744
|
// src/core/slot-status.ts
|
|
28319
|
-
var
|
|
28320
|
-
var
|
|
28321
|
-
var
|
|
28745
|
+
var fs30 = __toESM(require("fs"));
|
|
28746
|
+
var os7 = __toESM(require("os"));
|
|
28747
|
+
var path30 = __toESM(require("path"));
|
|
28322
28748
|
var import_child_process7 = require("child_process");
|
|
28323
28749
|
|
|
28324
28750
|
// src/core/runs.ts
|
|
28325
28751
|
var crypto2 = __toESM(require("crypto"));
|
|
28326
28752
|
var import_child_process2 = require("child_process");
|
|
28327
|
-
var
|
|
28328
|
-
var
|
|
28329
|
-
var
|
|
28753
|
+
var fs26 = __toESM(require("fs"));
|
|
28754
|
+
var os5 = __toESM(require("os"));
|
|
28755
|
+
var path25 = __toESM(require("path"));
|
|
28330
28756
|
var RUNS_DIR = "runs";
|
|
28331
28757
|
function gitCommonDir(cwd) {
|
|
28332
28758
|
try {
|
|
@@ -28335,7 +28761,7 @@ function gitCommonDir(cwd) {
|
|
|
28335
28761
|
encoding: "utf8",
|
|
28336
28762
|
stdio: ["pipe", "pipe", "ignore"]
|
|
28337
28763
|
}).trim();
|
|
28338
|
-
return out ?
|
|
28764
|
+
return out ? path25.resolve(cwd, out) : void 0;
|
|
28339
28765
|
} catch {
|
|
28340
28766
|
return void 0;
|
|
28341
28767
|
}
|
|
@@ -28357,7 +28783,7 @@ function gitPrefix(cwd) {
|
|
|
28357
28783
|
}
|
|
28358
28784
|
}
|
|
28359
28785
|
function getRunsDir(harnessRoot) {
|
|
28360
|
-
return
|
|
28786
|
+
return path25.join(getHarnessDir(harnessRoot), RUNS_DIR);
|
|
28361
28787
|
}
|
|
28362
28788
|
function formatLocalDate(d2) {
|
|
28363
28789
|
const y2 = d2.getFullYear();
|
|
@@ -28377,24 +28803,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
|
|
|
28377
28803
|
const timePart = formatLocalTime(started);
|
|
28378
28804
|
const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
|
|
28379
28805
|
let filename = `${timePart}_${stageId}${agentPart}.json`;
|
|
28380
|
-
const fullPath =
|
|
28381
|
-
if (
|
|
28806
|
+
const fullPath = path25.join(runsDir, dateFolder, filename);
|
|
28807
|
+
if (fs26.existsSync(fullPath)) {
|
|
28382
28808
|
filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
|
|
28383
28809
|
}
|
|
28384
|
-
return
|
|
28810
|
+
return path25.join(dateFolder, filename);
|
|
28385
28811
|
}
|
|
28386
28812
|
function resolveRunFilePath(harnessRoot, run2) {
|
|
28387
28813
|
if (run2.relativePath) {
|
|
28388
|
-
return
|
|
28814
|
+
return path25.join(getRunsDir(harnessRoot), run2.relativePath);
|
|
28389
28815
|
}
|
|
28390
|
-
return
|
|
28816
|
+
return path25.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
|
|
28391
28817
|
}
|
|
28392
28818
|
function collectRunFiles(runsDir) {
|
|
28393
|
-
if (!
|
|
28819
|
+
if (!fs26.existsSync(runsDir)) return [];
|
|
28394
28820
|
const files = [];
|
|
28395
28821
|
const walk = (dir) => {
|
|
28396
|
-
for (const entry of
|
|
28397
|
-
const full =
|
|
28822
|
+
for (const entry of fs26.readdirSync(dir, { withFileTypes: true })) {
|
|
28823
|
+
const full = path25.join(dir, entry.name);
|
|
28398
28824
|
if (entry.isDirectory()) {
|
|
28399
28825
|
walk(full);
|
|
28400
28826
|
} else if (entry.name.endsWith(".json")) {
|
|
@@ -28408,30 +28834,30 @@ function collectRunFiles(runsDir) {
|
|
|
28408
28834
|
function resolveAgentWorkDir(harnessRoot, agentId) {
|
|
28409
28835
|
if (agentId === void 0) return void 0;
|
|
28410
28836
|
const entry = readSlotRegistry(harnessRoot, agentId);
|
|
28411
|
-
if (entry?.workDir &&
|
|
28837
|
+
if (entry?.workDir && fs26.existsSync(entry.workDir)) return entry.workDir;
|
|
28412
28838
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28413
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
28414
|
-
const worktreeDir =
|
|
28839
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
|
|
28840
|
+
const worktreeDir = path25.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
28415
28841
|
const relPrefix = gitPrefix(harnessRoot);
|
|
28416
28842
|
const sessionEnvFiles = [];
|
|
28417
|
-
const worktreesRoot =
|
|
28418
|
-
if (
|
|
28843
|
+
const worktreesRoot = path25.join(os5.homedir(), "worktrees");
|
|
28844
|
+
if (fs26.existsSync(worktreesRoot)) {
|
|
28419
28845
|
const suffix = `-har-agent-${agentId}-`;
|
|
28420
|
-
for (const entry2 of
|
|
28846
|
+
for (const entry2 of fs26.readdirSync(worktreesRoot, { withFileTypes: true })) {
|
|
28421
28847
|
if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
|
|
28422
|
-
const sessionDir =
|
|
28848
|
+
const sessionDir = path25.join(worktreesRoot, entry2.name);
|
|
28423
28849
|
if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
|
|
28424
|
-
sessionEnvFiles.push(
|
|
28850
|
+
sessionEnvFiles.push(path25.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
|
|
28425
28851
|
}
|
|
28426
28852
|
}
|
|
28427
28853
|
const candidates = [
|
|
28428
|
-
|
|
28429
|
-
|
|
28854
|
+
path25.join(worktreeDir, `.env.agent.${agentId}`),
|
|
28855
|
+
path25.join(harnessRoot, `.env.agent.${agentId}`),
|
|
28430
28856
|
...sessionEnvFiles.sort()
|
|
28431
28857
|
];
|
|
28432
28858
|
for (const envFile of candidates) {
|
|
28433
|
-
if (!
|
|
28434
|
-
const content =
|
|
28859
|
+
if (!fs26.existsSync(envFile)) continue;
|
|
28860
|
+
const content = fs26.readFileSync(envFile, "utf8");
|
|
28435
28861
|
const match = content.match(/^REPO_ROOT=(.+)$/m);
|
|
28436
28862
|
if (match) return match[1].trim();
|
|
28437
28863
|
}
|
|
@@ -28443,11 +28869,11 @@ function createRun(ctx, meta) {
|
|
|
28443
28869
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28444
28870
|
const runId = crypto2.randomUUID();
|
|
28445
28871
|
const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
|
|
28446
|
-
const runFilePath =
|
|
28447
|
-
|
|
28872
|
+
const runFilePath = path25.join(runsDir, relativePath);
|
|
28873
|
+
fs26.mkdirSync(path25.dirname(runFilePath), { recursive: true });
|
|
28448
28874
|
const run2 = RunRecordSchema.parse({
|
|
28449
28875
|
runId,
|
|
28450
|
-
repoPath:
|
|
28876
|
+
repoPath: path25.resolve(ctx.repoPath),
|
|
28451
28877
|
harnessRoot,
|
|
28452
28878
|
stageId: meta.stageId,
|
|
28453
28879
|
kind: meta.kind,
|
|
@@ -28458,7 +28884,7 @@ function createRun(ctx, meta) {
|
|
|
28458
28884
|
relativePath,
|
|
28459
28885
|
trigger: ctx.trigger ?? "cli"
|
|
28460
28886
|
});
|
|
28461
|
-
|
|
28887
|
+
fs26.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
|
|
28462
28888
|
return run2;
|
|
28463
28889
|
}
|
|
28464
28890
|
function finishRun(repoPath, runId, update) {
|
|
@@ -28477,13 +28903,13 @@ function finishRun(repoPath, runId, update) {
|
|
|
28477
28903
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28478
28904
|
});
|
|
28479
28905
|
const runPath = resolveRunFilePath(harnessRoot, finished);
|
|
28480
|
-
|
|
28906
|
+
fs26.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
|
|
28481
28907
|
return finished;
|
|
28482
28908
|
}
|
|
28483
28909
|
function findRunRecord(harnessRoot, runId) {
|
|
28484
28910
|
const runsDir = getRunsDir(harnessRoot);
|
|
28485
28911
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28486
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28912
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
|
|
28487
28913
|
if (parsed.success && parsed.data.runId === runId) {
|
|
28488
28914
|
return parsed.data;
|
|
28489
28915
|
}
|
|
@@ -28495,10 +28921,10 @@ function getRun(repoPath, runId) {
|
|
|
28495
28921
|
}
|
|
28496
28922
|
function listRuns(repoPath, filter = {}) {
|
|
28497
28923
|
const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
|
|
28498
|
-
if (!
|
|
28924
|
+
if (!fs26.existsSync(runsDir)) return [];
|
|
28499
28925
|
const runs = [];
|
|
28500
28926
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28501
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28927
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
|
|
28502
28928
|
if (!parsed.success) continue;
|
|
28503
28929
|
if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
|
|
28504
28930
|
runs.push(parsed.data);
|
|
@@ -28511,17 +28937,17 @@ function listRuns(repoPath, filter = {}) {
|
|
|
28511
28937
|
}
|
|
28512
28938
|
|
|
28513
28939
|
// src/core/slot-preflight.ts
|
|
28514
|
-
var
|
|
28940
|
+
var path29 = __toESM(require("path"));
|
|
28515
28941
|
|
|
28516
28942
|
// src/core/control-port.ts
|
|
28517
28943
|
var import_child_process4 = require("child_process");
|
|
28518
|
-
var
|
|
28519
|
-
var
|
|
28944
|
+
var fs28 = __toESM(require("fs"));
|
|
28945
|
+
var path27 = __toESM(require("path"));
|
|
28520
28946
|
|
|
28521
28947
|
// src/core/slot-ports.ts
|
|
28522
28948
|
var import_child_process3 = require("child_process");
|
|
28523
|
-
var
|
|
28524
|
-
var
|
|
28949
|
+
var fs27 = __toESM(require("fs"));
|
|
28950
|
+
var path26 = __toESM(require("path"));
|
|
28525
28951
|
function portStep(env3) {
|
|
28526
28952
|
return Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
28527
28953
|
}
|
|
@@ -28577,7 +29003,7 @@ function allocateAppPorts(repoPath, agentId) {
|
|
|
28577
29003
|
}
|
|
28578
29004
|
function harnessUsesPm2(repoPath) {
|
|
28579
29005
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28580
|
-
return
|
|
29006
|
+
return fs27.existsSync(path26.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
|
|
28581
29007
|
}
|
|
28582
29008
|
|
|
28583
29009
|
// src/core/control-port.ts
|
|
@@ -28632,13 +29058,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
|
|
|
28632
29058
|
return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
|
|
28633
29059
|
}
|
|
28634
29060
|
function inspectControlUpReadiness(repoPath) {
|
|
28635
|
-
const resolved =
|
|
29061
|
+
const resolved = path27.resolve(repoPath);
|
|
28636
29062
|
const port = parseControlHostPort();
|
|
28637
29063
|
const containers = listDockerContainers();
|
|
28638
29064
|
const control = controlContainerOnPort(containers, port);
|
|
28639
29065
|
const warnings = [];
|
|
28640
|
-
const controlHarnessDir =
|
|
28641
|
-
const harnessSlot1Active =
|
|
29066
|
+
const controlHarnessDir = path27.join(resolved, "control", ".har");
|
|
29067
|
+
const harnessSlot1Active = fs28.existsSync(controlHarnessDir) && readSlotRegistry(path27.join(resolved, "control"), 1)?.status === "active";
|
|
28642
29068
|
if (control) {
|
|
28643
29069
|
return {
|
|
28644
29070
|
warnings,
|
|
@@ -28669,9 +29095,9 @@ function inspectControlUpReadiness(repoPath) {
|
|
|
28669
29095
|
}
|
|
28670
29096
|
|
|
28671
29097
|
// src/core/slot-launch-guard-occupied.ts
|
|
28672
|
-
var
|
|
28673
|
-
var
|
|
28674
|
-
var
|
|
29098
|
+
var fs29 = __toESM(require("fs"));
|
|
29099
|
+
var os6 = __toESM(require("os"));
|
|
29100
|
+
var path28 = __toESM(require("path"));
|
|
28675
29101
|
var import_child_process5 = require("child_process");
|
|
28676
29102
|
function runGit(cwd, args) {
|
|
28677
29103
|
try {
|
|
@@ -28690,7 +29116,7 @@ function worktreeDirty(worktreePath) {
|
|
|
28690
29116
|
}
|
|
28691
29117
|
function gitCommonDir2(cwd) {
|
|
28692
29118
|
const out = runGit(cwd, "rev-parse --git-common-dir");
|
|
28693
|
-
return out ?
|
|
29119
|
+
return out ? path28.resolve(cwd, out) : void 0;
|
|
28694
29120
|
}
|
|
28695
29121
|
function sameGitCheckout2(a2, b2) {
|
|
28696
29122
|
const left2 = gitCommonDir2(a2);
|
|
@@ -28699,20 +29125,20 @@ function sameGitCheckout2(a2, b2) {
|
|
|
28699
29125
|
}
|
|
28700
29126
|
function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
28701
29127
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28702
|
-
if (session?.worktreePath &&
|
|
29128
|
+
if (session?.worktreePath && fs29.existsSync(session.worktreePath)) {
|
|
28703
29129
|
return session.worktreePath;
|
|
28704
29130
|
}
|
|
28705
|
-
const legacy =
|
|
28706
|
-
if (
|
|
28707
|
-
const worktreesRoot =
|
|
28708
|
-
if (!
|
|
29131
|
+
const legacy = path28.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
29132
|
+
if (fs29.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
|
|
29133
|
+
const worktreesRoot = path28.join(os6.homedir(), "worktrees");
|
|
29134
|
+
if (!fs29.existsSync(worktreesRoot)) return void 0;
|
|
28709
29135
|
const suffix = `-har-agent-${agentId}-`;
|
|
28710
29136
|
const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
|
|
28711
|
-
for (const name of
|
|
29137
|
+
for (const name of fs29.readdirSync(worktreesRoot)) {
|
|
28712
29138
|
if (!name.includes(suffix)) continue;
|
|
28713
|
-
const candidate =
|
|
29139
|
+
const candidate = path28.join(worktreesRoot, name);
|
|
28714
29140
|
if (!sameGitCheckout2(harnessRoot, candidate)) continue;
|
|
28715
|
-
if (!
|
|
29141
|
+
if (!fs29.existsSync(path28.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
|
|
28716
29142
|
return candidate;
|
|
28717
29143
|
}
|
|
28718
29144
|
return void 0;
|
|
@@ -28720,13 +29146,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
|
28720
29146
|
function collectOccupiedSlot(repoPath, agentId) {
|
|
28721
29147
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28722
29148
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28723
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29149
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path28.basename(harnessRoot);
|
|
28724
29150
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28725
29151
|
const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
|
|
28726
29152
|
const workDir = session?.workDir;
|
|
28727
|
-
const envInWorkDir = workDir ?
|
|
28728
|
-
const envInRoot =
|
|
28729
|
-
const envInWorktree = worktreePath ?
|
|
29153
|
+
const envInWorkDir = workDir ? fs29.existsSync(path28.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29154
|
+
const envInRoot = fs29.existsSync(path28.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29155
|
+
const envInWorktree = worktreePath ? fs29.existsSync(path28.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
28730
29156
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
28731
29157
|
if (!active) return void 0;
|
|
28732
29158
|
const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
|
|
@@ -28879,7 +29305,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
|
28879
29305
|
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
28880
29306
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28881
29307
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28882
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29308
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
|
|
28883
29309
|
const usesPm2 = harnessUsesPm2(repoPath);
|
|
28884
29310
|
const blockers = [];
|
|
28885
29311
|
const remediations = [];
|
|
@@ -29081,7 +29507,7 @@ function readWorktreeBranch(worktreePath) {
|
|
|
29081
29507
|
}
|
|
29082
29508
|
function gitCommonDir3(cwd) {
|
|
29083
29509
|
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
29084
|
-
return out ?
|
|
29510
|
+
return out ? path30.resolve(cwd, out) : void 0;
|
|
29085
29511
|
}
|
|
29086
29512
|
function sameGitCheckout3(a2, b2) {
|
|
29087
29513
|
const left2 = gitCommonDir3(a2);
|
|
@@ -29093,12 +29519,12 @@ function gitPrefix2(cwd) {
|
|
|
29093
29519
|
return out ?? "";
|
|
29094
29520
|
}
|
|
29095
29521
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
29096
|
-
const worktreesRoot =
|
|
29097
|
-
if (!
|
|
29522
|
+
const worktreesRoot = path30.join(os7.homedir(), "worktrees");
|
|
29523
|
+
if (!fs30.existsSync(worktreesRoot)) return void 0;
|
|
29098
29524
|
const suffix = `-har-agent-${agentId}-`;
|
|
29099
29525
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
29100
|
-
const matches =
|
|
29101
|
-
(candidate) => sameGitCheckout3(harnessRoot, candidate) &&
|
|
29526
|
+
const matches = fs30.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path30.join(worktreesRoot, entry.name)).filter(
|
|
29527
|
+
(candidate) => sameGitCheckout3(harnessRoot, candidate) && fs30.existsSync(path30.join(candidate, relPrefix, `.env.agent.${agentId}`))
|
|
29102
29528
|
);
|
|
29103
29529
|
return matches.sort()[0];
|
|
29104
29530
|
}
|
|
@@ -29173,18 +29599,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
|
|
|
29173
29599
|
}
|
|
29174
29600
|
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
29175
29601
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29176
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29602
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
|
|
29177
29603
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29178
|
-
const legacyWorktreePath =
|
|
29179
|
-
|
|
29604
|
+
const legacyWorktreePath = path30.join(
|
|
29605
|
+
os7.homedir(),
|
|
29180
29606
|
"worktrees",
|
|
29181
29607
|
`${projectName}-agent-${agentId}`
|
|
29182
29608
|
);
|
|
29183
|
-
const worktreePath = session?.worktreePath &&
|
|
29609
|
+
const worktreePath = session?.worktreePath && fs30.existsSync(session.worktreePath) ? session.worktreePath : fs30.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
29184
29610
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
29185
|
-
const envInWorkDir = workDir ?
|
|
29186
|
-
const envInRoot =
|
|
29187
|
-
const envInWorktree = worktreePath ?
|
|
29611
|
+
const envInWorkDir = workDir ? fs30.existsSync(path30.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29612
|
+
const envInRoot = fs30.existsSync(path30.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29613
|
+
const envInWorktree = worktreePath ? fs30.existsSync(path30.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
29188
29614
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29189
29615
|
const latest = latestRunForAgent(runs, agentId);
|
|
29190
29616
|
const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
|
|
@@ -29237,7 +29663,7 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29237
29663
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
29238
29664
|
const pm2Procs = listPm2Processes2();
|
|
29239
29665
|
return {
|
|
29240
|
-
repoPath:
|
|
29666
|
+
repoPath: path30.resolve(repoPath),
|
|
29241
29667
|
harnessRoot,
|
|
29242
29668
|
gitRemote: readGitRemote(harnessRoot),
|
|
29243
29669
|
profile: manifest?.profile,
|
|
@@ -29248,13 +29674,13 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29248
29674
|
|
|
29249
29675
|
// src/core/validations.ts
|
|
29250
29676
|
var crypto3 = __toESM(require("crypto"));
|
|
29251
|
-
var
|
|
29252
|
-
var
|
|
29677
|
+
var fs32 = __toESM(require("fs"));
|
|
29678
|
+
var path32 = __toESM(require("path"));
|
|
29253
29679
|
|
|
29254
29680
|
// src/core/change-batch.ts
|
|
29255
|
-
var
|
|
29256
|
-
var
|
|
29257
|
-
var
|
|
29681
|
+
var fs31 = __toESM(require("fs"));
|
|
29682
|
+
var os8 = __toESM(require("os"));
|
|
29683
|
+
var path31 = __toESM(require("path"));
|
|
29258
29684
|
function git(checkoutDir, args, env3) {
|
|
29259
29685
|
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
29260
29686
|
if (result.code !== 0) {
|
|
@@ -29270,7 +29696,7 @@ function isCheckoutRoot(checkoutDir) {
|
|
|
29270
29696
|
const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
|
|
29271
29697
|
if (!toplevel) return false;
|
|
29272
29698
|
try {
|
|
29273
|
-
return
|
|
29699
|
+
return fs31.realpathSync(toplevel) === fs31.realpathSync(checkoutDir);
|
|
29274
29700
|
} catch {
|
|
29275
29701
|
return false;
|
|
29276
29702
|
}
|
|
@@ -29292,7 +29718,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
|
|
|
29292
29718
|
if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
29293
29719
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
29294
29720
|
const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
29295
|
-
if (gitPath &&
|
|
29721
|
+
if (gitPath && fs31.existsSync(path31.resolve(checkoutDir, gitPath))) return true;
|
|
29296
29722
|
}
|
|
29297
29723
|
return false;
|
|
29298
29724
|
}
|
|
@@ -29315,8 +29741,8 @@ function parseNameStatus(output) {
|
|
|
29315
29741
|
return files;
|
|
29316
29742
|
}
|
|
29317
29743
|
function computeWorktreeSnapshot(checkoutDir) {
|
|
29318
|
-
const tmpDir =
|
|
29319
|
-
const tmpIndex =
|
|
29744
|
+
const tmpDir = fs31.mkdtempSync(path31.join(os8.tmpdir(), "har-idx-"));
|
|
29745
|
+
const tmpIndex = path31.join(tmpDir, "index");
|
|
29320
29746
|
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
29321
29747
|
try {
|
|
29322
29748
|
const headTree = getHeadTree(checkoutDir);
|
|
@@ -29339,35 +29765,35 @@ function computeWorktreeSnapshot(checkoutDir) {
|
|
|
29339
29765
|
changedFiles: parseNameStatus(diffOutput)
|
|
29340
29766
|
};
|
|
29341
29767
|
} finally {
|
|
29342
|
-
|
|
29768
|
+
fs31.rmSync(tmpDir, { recursive: true, force: true });
|
|
29343
29769
|
}
|
|
29344
29770
|
}
|
|
29345
29771
|
|
|
29346
29772
|
// src/core/validations.ts
|
|
29347
29773
|
var VALIDATIONS_DIR = "validations";
|
|
29348
29774
|
function getValidationsDir(checkoutDir) {
|
|
29349
|
-
return
|
|
29775
|
+
return path32.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
29350
29776
|
}
|
|
29351
29777
|
function validationPath(checkoutDir, treeHash) {
|
|
29352
|
-
return
|
|
29778
|
+
return path32.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
29353
29779
|
}
|
|
29354
29780
|
function writeRecord(checkoutDir, record2) {
|
|
29355
29781
|
const dir = getValidationsDir(checkoutDir);
|
|
29356
|
-
|
|
29357
|
-
|
|
29782
|
+
fs32.mkdirSync(dir, { recursive: true });
|
|
29783
|
+
fs32.writeFileSync(
|
|
29358
29784
|
validationPath(checkoutDir, record2.treeHash),
|
|
29359
29785
|
`${JSON.stringify(record2, null, 2)}
|
|
29360
29786
|
`
|
|
29361
29787
|
);
|
|
29362
29788
|
}
|
|
29363
29789
|
function ensureValidationsIgnored(checkoutDir) {
|
|
29364
|
-
const harDir =
|
|
29365
|
-
if (!
|
|
29366
|
-
const gitignorePath =
|
|
29367
|
-
const content =
|
|
29790
|
+
const harDir = path32.join(checkoutDir, ".har");
|
|
29791
|
+
if (!fs32.existsSync(harDir)) return;
|
|
29792
|
+
const gitignorePath = path32.join(harDir, ".gitignore");
|
|
29793
|
+
const content = fs32.existsSync(gitignorePath) ? fs32.readFileSync(gitignorePath, "utf8") : "";
|
|
29368
29794
|
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
29369
29795
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
29370
|
-
|
|
29796
|
+
fs32.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
29371
29797
|
`);
|
|
29372
29798
|
}
|
|
29373
29799
|
function recordValidation(input) {
|
|
@@ -29384,8 +29810,8 @@ function recordValidation(input) {
|
|
|
29384
29810
|
treeHash: snapshot.treeHash,
|
|
29385
29811
|
headSha: snapshot.headSha,
|
|
29386
29812
|
branch: snapshot.branch,
|
|
29387
|
-
workDir:
|
|
29388
|
-
harnessRoot:
|
|
29813
|
+
workDir: path32.resolve(input.checkoutDir),
|
|
29814
|
+
harnessRoot: path32.resolve(input.harnessRoot),
|
|
29389
29815
|
agentId: input.agentId,
|
|
29390
29816
|
status: input.status,
|
|
29391
29817
|
full: input.full,
|
|
@@ -29397,16 +29823,16 @@ function recordValidation(input) {
|
|
|
29397
29823
|
committedAt: existing?.committedAt
|
|
29398
29824
|
});
|
|
29399
29825
|
writeRecord(input.checkoutDir, record2);
|
|
29400
|
-
if (
|
|
29826
|
+
if (path32.resolve(input.harnessRoot) !== path32.resolve(input.checkoutDir)) {
|
|
29401
29827
|
writeRecord(input.harnessRoot, record2);
|
|
29402
29828
|
}
|
|
29403
29829
|
return record2;
|
|
29404
29830
|
}
|
|
29405
29831
|
function findValidation(checkoutDir, treeHash) {
|
|
29406
29832
|
const file = validationPath(checkoutDir, treeHash);
|
|
29407
|
-
if (!
|
|
29833
|
+
if (!fs32.existsSync(file)) return void 0;
|
|
29408
29834
|
try {
|
|
29409
|
-
return ValidationRecordSchema.parse(JSON.parse(
|
|
29835
|
+
return ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(file, "utf8")));
|
|
29410
29836
|
} catch {
|
|
29411
29837
|
return void 0;
|
|
29412
29838
|
}
|
|
@@ -29421,19 +29847,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
|
29421
29847
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29422
29848
|
};
|
|
29423
29849
|
writeRecord(checkoutDir, updated);
|
|
29424
|
-
if (record2.harnessRoot &&
|
|
29850
|
+
if (record2.harnessRoot && path32.resolve(record2.harnessRoot) !== path32.resolve(checkoutDir) && fs32.existsSync(path32.join(record2.harnessRoot, ".har"))) {
|
|
29425
29851
|
writeRecord(record2.harnessRoot, updated);
|
|
29426
29852
|
}
|
|
29427
29853
|
return updated;
|
|
29428
29854
|
}
|
|
29429
29855
|
function listValidations(harnessRoot) {
|
|
29430
29856
|
const dir = getValidationsDir(harnessRoot);
|
|
29431
|
-
if (!
|
|
29857
|
+
if (!fs32.existsSync(dir)) return [];
|
|
29432
29858
|
const records = [];
|
|
29433
|
-
for (const entry of
|
|
29859
|
+
for (const entry of fs32.readdirSync(dir)) {
|
|
29434
29860
|
if (!entry.endsWith(".json")) continue;
|
|
29435
29861
|
try {
|
|
29436
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(
|
|
29862
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(path32.join(dir, entry), "utf8"))));
|
|
29437
29863
|
} catch {
|
|
29438
29864
|
}
|
|
29439
29865
|
}
|
|
@@ -29491,7 +29917,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
|
|
|
29491
29917
|
const deadline = Date.now() + timeoutMs;
|
|
29492
29918
|
while (Date.now() < deadline) {
|
|
29493
29919
|
if (await isControlApiReachable(apiUrl)) return true;
|
|
29494
|
-
await new Promise((
|
|
29920
|
+
await new Promise((resolve28) => setTimeout(resolve28, intervalMs));
|
|
29495
29921
|
}
|
|
29496
29922
|
return false;
|
|
29497
29923
|
}
|
|
@@ -29503,7 +29929,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29503
29929
|
}
|
|
29504
29930
|
const repoPaths = new Set(listRegisteredRepos());
|
|
29505
29931
|
if (options?.cwd) {
|
|
29506
|
-
const cwd =
|
|
29932
|
+
const cwd = path33.resolve(options.cwd);
|
|
29507
29933
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
29508
29934
|
}
|
|
29509
29935
|
try {
|
|
@@ -29511,7 +29937,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29511
29937
|
if (listResponse.ok) {
|
|
29512
29938
|
const repos = await listResponse.json();
|
|
29513
29939
|
for (const repo of repos) {
|
|
29514
|
-
const resolved =
|
|
29940
|
+
const resolved = path33.resolve(repo.path);
|
|
29515
29941
|
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
29516
29942
|
}
|
|
29517
29943
|
}
|
|
@@ -29530,7 +29956,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29530
29956
|
return { synced, failed };
|
|
29531
29957
|
}
|
|
29532
29958
|
async function registerRepoWithControl(options) {
|
|
29533
|
-
const repoPath =
|
|
29959
|
+
const repoPath = path33.resolve(options.repoPath);
|
|
29534
29960
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
29535
29961
|
const manifest = readManifest(repoPath);
|
|
29536
29962
|
const stagesRegistry = readStageRegistry(repoPath);
|
|
@@ -29543,7 +29969,7 @@ async function registerRepoWithControl(options) {
|
|
|
29543
29969
|
return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
|
|
29544
29970
|
}
|
|
29545
29971
|
async function syncRepoWithControl(options) {
|
|
29546
|
-
const repoPath =
|
|
29972
|
+
const repoPath = path33.resolve(options.repoPath);
|
|
29547
29973
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
29548
29974
|
if (options.cloud) {
|
|
29549
29975
|
const remote = createRemoteExecutor();
|
|
@@ -29571,7 +29997,7 @@ async function syncRepoWithControl(options) {
|
|
|
29571
29997
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
29572
29998
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
29573
29999
|
const repos = await listResponse.json();
|
|
29574
|
-
const existing = repos.find((r2) =>
|
|
30000
|
+
const existing = repos.find((r2) => path33.resolve(r2.path) === repoPath);
|
|
29575
30001
|
if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
|
|
29576
30002
|
await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
|
|
29577
30003
|
return;
|
|
@@ -29978,6 +30404,13 @@ var envCommand = {
|
|
|
29978
30404
|
type: "boolean",
|
|
29979
30405
|
default: false,
|
|
29980
30406
|
describe: "Skip Cursor rule scaffolding"
|
|
30407
|
+
}).option("agents", {
|
|
30408
|
+
type: "string",
|
|
30409
|
+
describe: "Scaffold agent skills for these targets (comma-separated: claude,cursor,codex); auto-detected when omitted"
|
|
30410
|
+
}).option("no-agents", {
|
|
30411
|
+
type: "boolean",
|
|
30412
|
+
default: false,
|
|
30413
|
+
describe: "Skip agent skills scaffolding"
|
|
29981
30414
|
}),
|
|
29982
30415
|
handleInit
|
|
29983
30416
|
).command(
|
|
@@ -30002,14 +30435,46 @@ var envCommand = {
|
|
|
30002
30435
|
type: "boolean",
|
|
30003
30436
|
default: false,
|
|
30004
30437
|
describe: "Skip Cursor rule scaffolding"
|
|
30438
|
+
}).option("agents", {
|
|
30439
|
+
type: "string",
|
|
30440
|
+
describe: "Scaffold agent skills for these targets (comma-separated: claude,cursor,codex); auto-detected when omitted"
|
|
30441
|
+
}).option("no-agents", {
|
|
30442
|
+
type: "boolean",
|
|
30443
|
+
default: false,
|
|
30444
|
+
describe: "Skip agent skills scaffolding"
|
|
30005
30445
|
}),
|
|
30006
30446
|
handleMaintain
|
|
30007
30447
|
).command(
|
|
30008
|
-
"add-stage
|
|
30009
|
-
|
|
30448
|
+
"add-stage [template]",
|
|
30449
|
+
`Add a stage template (${STAGE_TEMPLATE_IDS.join(", ")}) or a custom stage (--custom)`,
|
|
30010
30450
|
(y2) => y2.positional("template", {
|
|
30011
30451
|
type: "string",
|
|
30012
|
-
describe:
|
|
30452
|
+
describe: `Stage template id (${STAGE_TEMPLATE_IDS.join(", ")}), or the new stage id with --custom`
|
|
30453
|
+
}).option("list", {
|
|
30454
|
+
type: "boolean",
|
|
30455
|
+
default: false,
|
|
30456
|
+
describe: "List available stage templates and exit"
|
|
30457
|
+
}).option("custom", {
|
|
30458
|
+
type: "boolean",
|
|
30459
|
+
default: false,
|
|
30460
|
+
describe: "Register a custom stage instead of a shipped template"
|
|
30461
|
+
}).option("kind", {
|
|
30462
|
+
type: "string",
|
|
30463
|
+
describe: "Custom stage kind (setup, launch, verify, test, inspect, reset, teardown, custom)"
|
|
30464
|
+
}).option("command", {
|
|
30465
|
+
type: "string",
|
|
30466
|
+
describe: 'Custom stage shell command ({agentId} is substituted), e.g. "npm test"'
|
|
30467
|
+
}).option("script", {
|
|
30468
|
+
type: "boolean",
|
|
30469
|
+
default: false,
|
|
30470
|
+
describe: "Scaffold .har/stages/<id>.sh from the contract skeleton (see .har/STAGES.md)"
|
|
30471
|
+
}).option("description", {
|
|
30472
|
+
type: "string",
|
|
30473
|
+
describe: "Custom stage description shown in the registry and Mission Control"
|
|
30474
|
+
}).option("verification", {
|
|
30475
|
+
type: "boolean",
|
|
30476
|
+
default: false,
|
|
30477
|
+
describe: "Include the custom stage in verify --full (stages.json verificationStages)"
|
|
30013
30478
|
}).option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("force", {
|
|
30014
30479
|
type: "boolean",
|
|
30015
30480
|
default: false,
|
|
@@ -30017,7 +30482,7 @@ var envCommand = {
|
|
|
30017
30482
|
}).option("skip-ci", {
|
|
30018
30483
|
type: "boolean",
|
|
30019
30484
|
default: false,
|
|
30020
|
-
describe: "Do not copy .github/workflows/playwright.yml"
|
|
30485
|
+
describe: "Do not copy optional CI workflow files (e.g. .github/workflows/playwright.yml)"
|
|
30021
30486
|
}),
|
|
30022
30487
|
handleAddStage
|
|
30023
30488
|
).command(
|
|
@@ -30112,7 +30577,7 @@ var envCommand = {
|
|
|
30112
30577
|
}
|
|
30113
30578
|
};
|
|
30114
30579
|
async function handleInit(argv) {
|
|
30115
|
-
const repoPath =
|
|
30580
|
+
const repoPath = path34.resolve(argv.repo);
|
|
30116
30581
|
header("har env init");
|
|
30117
30582
|
info(`Repository: ${repoPath}`);
|
|
30118
30583
|
try {
|
|
@@ -30156,6 +30621,14 @@ async function handleInit(argv) {
|
|
|
30156
30621
|
autoYes: argv.yes,
|
|
30157
30622
|
mode: "init"
|
|
30158
30623
|
});
|
|
30624
|
+
await handleAgentSkills({
|
|
30625
|
+
repoPath,
|
|
30626
|
+
agents: argv.agents,
|
|
30627
|
+
enabled: argv.noAgents ? false : void 0,
|
|
30628
|
+
autoYes: argv.yes,
|
|
30629
|
+
force: argv.force,
|
|
30630
|
+
mode: "init"
|
|
30631
|
+
});
|
|
30159
30632
|
printNextSteps(argv.auto);
|
|
30160
30633
|
} catch (err) {
|
|
30161
30634
|
error(err.message);
|
|
@@ -30163,7 +30636,7 @@ async function handleInit(argv) {
|
|
|
30163
30636
|
}
|
|
30164
30637
|
}
|
|
30165
30638
|
async function handleMaintain(argv) {
|
|
30166
|
-
const repoPath =
|
|
30639
|
+
const repoPath = path34.resolve(argv.repo);
|
|
30167
30640
|
header("har env maintain");
|
|
30168
30641
|
info(`Repository: ${repoPath}`);
|
|
30169
30642
|
try {
|
|
@@ -30221,6 +30694,13 @@ async function handleMaintain(argv) {
|
|
|
30221
30694
|
autoYes: argv.yes,
|
|
30222
30695
|
mode: "maintain"
|
|
30223
30696
|
});
|
|
30697
|
+
await handleAgentSkills({
|
|
30698
|
+
repoPath,
|
|
30699
|
+
agents: argv.agents,
|
|
30700
|
+
enabled: argv.noAgents ? false : void 0,
|
|
30701
|
+
autoYes: argv.yes,
|
|
30702
|
+
mode: "maintain"
|
|
30703
|
+
});
|
|
30224
30704
|
} catch (err) {
|
|
30225
30705
|
error(err.message);
|
|
30226
30706
|
process.exit(1);
|
|
@@ -30252,7 +30732,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
30252
30732
|
if (autoYes) {
|
|
30253
30733
|
const proposal = readAgentMdProposal(repoPath);
|
|
30254
30734
|
if (proposal) {
|
|
30255
|
-
writeFileSafe(
|
|
30735
|
+
writeFileSafe(path34.join(repoPath, "AGENT.md"), proposal.content);
|
|
30256
30736
|
clearAgentMdProposal(repoPath);
|
|
30257
30737
|
info("Applied AGENT.md proposal (--yes)");
|
|
30258
30738
|
}
|
|
@@ -30261,16 +30741,64 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
30261
30741
|
await promptApplyAgentMdProposal(repoPath);
|
|
30262
30742
|
}
|
|
30263
30743
|
async function handleAddStage(argv) {
|
|
30264
|
-
const repoPath =
|
|
30265
|
-
|
|
30266
|
-
|
|
30744
|
+
const repoPath = path34.resolve(argv.repo);
|
|
30745
|
+
const available = listStageTemplateIds();
|
|
30746
|
+
if (argv.list) {
|
|
30747
|
+
for (const id of available) {
|
|
30748
|
+
console.log(id);
|
|
30749
|
+
}
|
|
30750
|
+
return;
|
|
30751
|
+
}
|
|
30752
|
+
if (argv.custom) {
|
|
30753
|
+
if (!argv.template) {
|
|
30754
|
+
error(
|
|
30755
|
+
'Missing stage id. Usage: har env add-stage <id> --custom (--command "npm test" | --script) [--kind test] [--verification]'
|
|
30756
|
+
);
|
|
30757
|
+
process.exit(1);
|
|
30758
|
+
}
|
|
30759
|
+
header("har env add-stage --custom");
|
|
30760
|
+
info(`Repository: ${repoPath}`);
|
|
30761
|
+
info(`Stage: ${argv.template}`);
|
|
30762
|
+
try {
|
|
30763
|
+
const result = addCustomStage(repoPath, {
|
|
30764
|
+
id: argv.template,
|
|
30765
|
+
kind: argv.kind,
|
|
30766
|
+
command: argv.command,
|
|
30767
|
+
script: argv.script,
|
|
30768
|
+
description: argv.description,
|
|
30769
|
+
verification: argv.verification,
|
|
30770
|
+
force: argv.force
|
|
30771
|
+
});
|
|
30772
|
+
divider();
|
|
30773
|
+
success(`Custom stage registered: ${result.stageId} (kind: ${result.kind}, ${result.mode})`);
|
|
30774
|
+
for (const file of result.filesWritten) {
|
|
30775
|
+
info(` + ${file}`);
|
|
30776
|
+
}
|
|
30777
|
+
console.error("");
|
|
30778
|
+
console.error(" Next steps:");
|
|
30779
|
+
for (const step of result.nextSteps) {
|
|
30780
|
+
console.error(` ${step}`);
|
|
30781
|
+
}
|
|
30782
|
+
console.error("");
|
|
30783
|
+
console.error(" Docs: .har/STAGES.md");
|
|
30784
|
+
console.error("");
|
|
30785
|
+
} catch (err) {
|
|
30786
|
+
error(err.message);
|
|
30787
|
+
process.exit(1);
|
|
30788
|
+
}
|
|
30789
|
+
return;
|
|
30790
|
+
}
|
|
30791
|
+
if (!argv.template || !available.includes(argv.template)) {
|
|
30792
|
+
error(
|
|
30793
|
+
`Unknown stage template: ${argv.template ?? "(missing)"}. Available: ${available.join(", ")}. For a project-specific stage, use: har env add-stage <id> --custom`
|
|
30794
|
+
);
|
|
30267
30795
|
process.exit(1);
|
|
30268
30796
|
}
|
|
30269
30797
|
header("har env add-stage");
|
|
30270
30798
|
info(`Repository: ${repoPath}`);
|
|
30271
30799
|
info(`Template: ${argv.template}`);
|
|
30272
30800
|
try {
|
|
30273
|
-
const result = addStageTemplate(repoPath,
|
|
30801
|
+
const result = addStageTemplate(repoPath, argv.template, {
|
|
30274
30802
|
force: argv.force,
|
|
30275
30803
|
skipCi: argv.skipCi
|
|
30276
30804
|
});
|
|
@@ -30282,7 +30810,7 @@ async function handleAddStage(argv) {
|
|
|
30282
30810
|
console.error(` ${step}`);
|
|
30283
30811
|
}
|
|
30284
30812
|
console.error("");
|
|
30285
|
-
console.error(
|
|
30813
|
+
console.error(` Docs: ${result.docsPath}`);
|
|
30286
30814
|
console.error("");
|
|
30287
30815
|
} catch (err) {
|
|
30288
30816
|
error(err.message);
|
|
@@ -30290,7 +30818,7 @@ async function handleAddStage(argv) {
|
|
|
30290
30818
|
}
|
|
30291
30819
|
}
|
|
30292
30820
|
async function handlePreflight(argv) {
|
|
30293
|
-
const repo =
|
|
30821
|
+
const repo = path34.resolve(argv.repo);
|
|
30294
30822
|
const agentId = validateAgentId(argv.id, repo);
|
|
30295
30823
|
const result = await preflightEnvironment({
|
|
30296
30824
|
repoPath: repo,
|
|
@@ -30308,7 +30836,7 @@ async function handlePreflight(argv) {
|
|
|
30308
30836
|
process.exit(result.code);
|
|
30309
30837
|
}
|
|
30310
30838
|
async function handleLaunch(argv) {
|
|
30311
|
-
const repo =
|
|
30839
|
+
const repo = path34.resolve(argv.repo);
|
|
30312
30840
|
const agentId = validateAgentId(argv.id, repo);
|
|
30313
30841
|
let confirmReplace = argv.replace;
|
|
30314
30842
|
let force = argv.force;
|
|
@@ -30318,10 +30846,10 @@ async function handleLaunch(argv) {
|
|
|
30318
30846
|
if (!confirmReplace && process.stdin.isTTY && process.stdout.isTTY) {
|
|
30319
30847
|
warn("Occupied slot \u2014 review before replacing:");
|
|
30320
30848
|
console.error(guard.reason ?? "");
|
|
30321
|
-
const
|
|
30322
|
-
const rl =
|
|
30323
|
-
const answer = await new Promise((
|
|
30324
|
-
rl.question("Replace this slot? [y/N] ",
|
|
30849
|
+
const readline5 = await import("readline");
|
|
30850
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
|
|
30851
|
+
const answer = await new Promise((resolve28) => {
|
|
30852
|
+
rl.question("Replace this slot? [y/N] ", resolve28);
|
|
30325
30853
|
});
|
|
30326
30854
|
rl.close();
|
|
30327
30855
|
if (!/^[Yy]$/.test(answer.trim())) {
|
|
@@ -30335,10 +30863,10 @@ async function handleLaunch(argv) {
|
|
|
30335
30863
|
if (!guardAfterConfirm.allowed && guardAfterConfirm.blocked && guardAfterConfirm.slot?.dirty) {
|
|
30336
30864
|
if (!force && process.stdin.isTTY && process.stdout.isTTY) {
|
|
30337
30865
|
warn("Worktree has uncommitted changes.");
|
|
30338
|
-
const
|
|
30339
|
-
const rl =
|
|
30340
|
-
const answer = await new Promise((
|
|
30341
|
-
rl.question("Discard uncommitted changes? [y/N] ",
|
|
30866
|
+
const readline5 = await import("readline");
|
|
30867
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
|
|
30868
|
+
const answer = await new Promise((resolve28) => {
|
|
30869
|
+
rl.question("Discard uncommitted changes? [y/N] ", resolve28);
|
|
30342
30870
|
});
|
|
30343
30871
|
rl.close();
|
|
30344
30872
|
if (!/^[Yy]$/.test(answer.trim())) {
|
|
@@ -30385,7 +30913,7 @@ async function handleRecover(argv) {
|
|
|
30385
30913
|
});
|
|
30386
30914
|
}
|
|
30387
30915
|
async function handleVerify(argv) {
|
|
30388
|
-
const repo =
|
|
30916
|
+
const repo = path34.resolve(argv.repo);
|
|
30389
30917
|
const agentId = validateAgentId(argv.id, repo);
|
|
30390
30918
|
const result = await runVerification({
|
|
30391
30919
|
repoPath: repo,
|
|
@@ -30397,7 +30925,7 @@ async function handleVerify(argv) {
|
|
|
30397
30925
|
process.exit(result.code);
|
|
30398
30926
|
}
|
|
30399
30927
|
async function handleTeardown(argv) {
|
|
30400
|
-
const repo =
|
|
30928
|
+
const repo = path34.resolve(argv.repo);
|
|
30401
30929
|
const agentId = validateAgentId(argv.id, repo);
|
|
30402
30930
|
const result = await teardownEnvironment({
|
|
30403
30931
|
repoPath: repo,
|
|
@@ -30408,7 +30936,7 @@ async function handleTeardown(argv) {
|
|
|
30408
30936
|
process.exit(result.code);
|
|
30409
30937
|
}
|
|
30410
30938
|
async function handleComplete(argv) {
|
|
30411
|
-
const repo =
|
|
30939
|
+
const repo = path34.resolve(argv.repo);
|
|
30412
30940
|
const agentId = validateAgentId(argv.id, repo);
|
|
30413
30941
|
const result = await completeEnvironment({
|
|
30414
30942
|
repoPath: repo,
|
|
@@ -30429,7 +30957,7 @@ async function handleComplete(argv) {
|
|
|
30429
30957
|
process.exit(result.code);
|
|
30430
30958
|
}
|
|
30431
30959
|
async function handleStatus(argv) {
|
|
30432
|
-
const repoPath =
|
|
30960
|
+
const repoPath = path34.resolve(argv.repo);
|
|
30433
30961
|
if (argv.json) {
|
|
30434
30962
|
const status = collectEnvironmentStatus(repoPath);
|
|
30435
30963
|
const output = EnvironmentStatusSchema.parse(status);
|
|
@@ -30442,7 +30970,7 @@ async function handleStatus(argv) {
|
|
|
30442
30970
|
});
|
|
30443
30971
|
}
|
|
30444
30972
|
async function handleRunsList(argv) {
|
|
30445
|
-
const repoPath =
|
|
30973
|
+
const repoPath = path34.resolve(argv.repo);
|
|
30446
30974
|
const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
|
|
30447
30975
|
if (argv.json) {
|
|
30448
30976
|
process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
|
|
@@ -30459,7 +30987,7 @@ async function handleRunsList(argv) {
|
|
|
30459
30987
|
}
|
|
30460
30988
|
}
|
|
30461
30989
|
async function handleRunsGet(argv) {
|
|
30462
|
-
const run2 = getRun(
|
|
30990
|
+
const run2 = getRun(path34.resolve(argv.repo), argv.runId);
|
|
30463
30991
|
if (!run2) {
|
|
30464
30992
|
error(`Run not found: ${argv.runId}`);
|
|
30465
30993
|
process.exit(1);
|
|
@@ -30532,11 +31060,11 @@ function printNextSteps(auto2) {
|
|
|
30532
31060
|
}
|
|
30533
31061
|
|
|
30534
31062
|
// src/cli/commands/control.ts
|
|
30535
|
-
var
|
|
31063
|
+
var path36 = __toESM(require("path"));
|
|
30536
31064
|
|
|
30537
31065
|
// src/core/control-lifecycle.ts
|
|
30538
31066
|
var import_child_process8 = require("child_process");
|
|
30539
|
-
var
|
|
31067
|
+
var path35 = __toESM(require("path"));
|
|
30540
31068
|
|
|
30541
31069
|
// src/core/control-image.ts
|
|
30542
31070
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
@@ -30555,13 +31083,13 @@ function shouldBuildControlLocally() {
|
|
|
30555
31083
|
|
|
30556
31084
|
// src/core/control-lifecycle.ts
|
|
30557
31085
|
function resolveControlDir() {
|
|
30558
|
-
return
|
|
31086
|
+
return path35.resolve(__dirname, "..", "control");
|
|
30559
31087
|
}
|
|
30560
31088
|
function resolveControlComposeFiles(options) {
|
|
30561
31089
|
const controlDir = resolveControlDir();
|
|
30562
|
-
const files = [
|
|
31090
|
+
const files = [path35.join(controlDir, "docker-compose.yml")];
|
|
30563
31091
|
if (options?.build ?? shouldBuildControlLocally()) {
|
|
30564
|
-
files.push(
|
|
31092
|
+
files.push(path35.join(controlDir, "docker-compose.build.yml"));
|
|
30565
31093
|
}
|
|
30566
31094
|
return files;
|
|
30567
31095
|
}
|
|
@@ -30730,7 +31258,7 @@ async function handleDown() {
|
|
|
30730
31258
|
process.exit(code);
|
|
30731
31259
|
}
|
|
30732
31260
|
async function handleRegister(argv) {
|
|
30733
|
-
const repoPath =
|
|
31261
|
+
const repoPath = path36.resolve(argv.repo);
|
|
30734
31262
|
header("har control register");
|
|
30735
31263
|
info(`Repository: ${repoPath}`);
|
|
30736
31264
|
try {
|
|
@@ -30750,7 +31278,7 @@ async function handleRegister(argv) {
|
|
|
30750
31278
|
}
|
|
30751
31279
|
}
|
|
30752
31280
|
async function handleSync(argv) {
|
|
30753
|
-
const repoPath =
|
|
31281
|
+
const repoPath = path36.resolve(argv.repo);
|
|
30754
31282
|
try {
|
|
30755
31283
|
await syncRepoWithControl({
|
|
30756
31284
|
repoPath,
|
|
@@ -30789,7 +31317,7 @@ async function handleWatch(argv) {
|
|
|
30789
31317
|
};
|
|
30790
31318
|
const tick = async () => {
|
|
30791
31319
|
if (argv.repo) {
|
|
30792
|
-
await syncOne(
|
|
31320
|
+
await syncOne(path36.resolve(argv.repo));
|
|
30793
31321
|
return;
|
|
30794
31322
|
}
|
|
30795
31323
|
try {
|
|
@@ -30820,12 +31348,12 @@ async function handleLogin(argv) {
|
|
|
30820
31348
|
}
|
|
30821
31349
|
|
|
30822
31350
|
// src/cli/commands/hooks.ts
|
|
30823
|
-
var
|
|
31351
|
+
var path39 = __toESM(require("path"));
|
|
30824
31352
|
|
|
30825
31353
|
// src/core/hooks.ts
|
|
30826
|
-
var
|
|
30827
|
-
var
|
|
30828
|
-
var
|
|
31354
|
+
var fs33 = __toESM(require("fs"));
|
|
31355
|
+
var os9 = __toESM(require("os"));
|
|
31356
|
+
var path37 = __toESM(require("path"));
|
|
30829
31357
|
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
30830
31358
|
var MARKER_END = "# <<< har commit gate <<<";
|
|
30831
31359
|
var PRE_COMMIT_BLOCK = [
|
|
@@ -30850,7 +31378,7 @@ function resolveCheckoutRoot(cwd) {
|
|
|
30850
31378
|
function resolveHooksDir(checkoutDir) {
|
|
30851
31379
|
const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
|
|
30852
31380
|
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
30853
|
-
return
|
|
31381
|
+
return path37.resolve(checkoutDir, hooksPath);
|
|
30854
31382
|
}
|
|
30855
31383
|
function getConfiguredHooksPath(checkoutDir) {
|
|
30856
31384
|
return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
|
|
@@ -30858,9 +31386,9 @@ function getConfiguredHooksPath(checkoutDir) {
|
|
|
30858
31386
|
function defaultHarInvocation() {
|
|
30859
31387
|
const entry = process.argv[1];
|
|
30860
31388
|
if (entry && entry.endsWith(".js")) {
|
|
30861
|
-
return `"${process.execPath}" "${
|
|
31389
|
+
return `"${process.execPath}" "${path37.resolve(entry)}"`;
|
|
30862
31390
|
}
|
|
30863
|
-
if (entry) return `"${
|
|
31391
|
+
if (entry) return `"${path37.resolve(entry)}"`;
|
|
30864
31392
|
return "har";
|
|
30865
31393
|
}
|
|
30866
31394
|
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
@@ -30881,40 +31409,40 @@ exit 0
|
|
|
30881
31409
|
`;
|
|
30882
31410
|
}
|
|
30883
31411
|
function upsertMarkedBlock(filePath, block) {
|
|
30884
|
-
if (!
|
|
30885
|
-
|
|
31412
|
+
if (!fs33.existsSync(filePath)) {
|
|
31413
|
+
fs33.writeFileSync(filePath, `#!/bin/sh
|
|
30886
31414
|
${block}
|
|
30887
31415
|
`, { mode: 493 });
|
|
30888
31416
|
return "created";
|
|
30889
31417
|
}
|
|
30890
|
-
const content =
|
|
31418
|
+
const content = fs33.readFileSync(filePath, "utf8");
|
|
30891
31419
|
if (content.includes(MARKER_START)) {
|
|
30892
31420
|
const pattern = new RegExp(
|
|
30893
31421
|
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
30894
31422
|
);
|
|
30895
|
-
|
|
30896
|
-
|
|
31423
|
+
fs33.writeFileSync(filePath, content.replace(pattern, block));
|
|
31424
|
+
fs33.chmodSync(filePath, 493);
|
|
30897
31425
|
return "updated";
|
|
30898
31426
|
}
|
|
30899
31427
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
30900
|
-
|
|
31428
|
+
fs33.writeFileSync(filePath, `${content}${suffix}
|
|
30901
31429
|
${block}
|
|
30902
31430
|
`);
|
|
30903
|
-
|
|
31431
|
+
fs33.chmodSync(filePath, 493);
|
|
30904
31432
|
return "appended";
|
|
30905
31433
|
}
|
|
30906
31434
|
function removeMarkedBlock(filePath) {
|
|
30907
|
-
if (!
|
|
30908
|
-
const content =
|
|
31435
|
+
if (!fs33.existsSync(filePath)) return false;
|
|
31436
|
+
const content = fs33.readFileSync(filePath, "utf8");
|
|
30909
31437
|
if (!content.includes(MARKER_START)) return false;
|
|
30910
31438
|
const pattern = new RegExp(
|
|
30911
31439
|
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
30912
31440
|
);
|
|
30913
31441
|
const stripped = content.replace(pattern, "\n");
|
|
30914
31442
|
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
30915
|
-
|
|
31443
|
+
fs33.rmSync(filePath);
|
|
30916
31444
|
} else {
|
|
30917
|
-
|
|
31445
|
+
fs33.writeFileSync(filePath, stripped);
|
|
30918
31446
|
}
|
|
30919
31447
|
return true;
|
|
30920
31448
|
}
|
|
@@ -30936,20 +31464,20 @@ Or re-run with --force to write into that directory anyway.`
|
|
|
30936
31464
|
);
|
|
30937
31465
|
}
|
|
30938
31466
|
const hooksDir = resolveHooksDir(checkout);
|
|
30939
|
-
|
|
31467
|
+
fs33.mkdirSync(hooksDir, { recursive: true });
|
|
30940
31468
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
30941
|
-
|
|
30942
|
-
|
|
31469
|
+
fs33.writeFileSync(
|
|
31470
|
+
path37.join(hooksDir, "har-pre-commit"),
|
|
30943
31471
|
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
30944
31472
|
{ mode: 493 }
|
|
30945
31473
|
);
|
|
30946
|
-
|
|
30947
|
-
|
|
31474
|
+
fs33.writeFileSync(
|
|
31475
|
+
path37.join(hooksDir, "har-post-commit"),
|
|
30948
31476
|
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
30949
31477
|
{ mode: 493 }
|
|
30950
31478
|
);
|
|
30951
|
-
const preCommit = upsertMarkedBlock(
|
|
30952
|
-
const postCommit = upsertMarkedBlock(
|
|
31479
|
+
const preCommit = upsertMarkedBlock(path37.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
31480
|
+
const postCommit = upsertMarkedBlock(path37.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
30953
31481
|
ensureValidationsIgnored(checkout);
|
|
30954
31482
|
return { hooksDir, preCommit, postCommit };
|
|
30955
31483
|
}
|
|
@@ -30958,12 +31486,12 @@ function uninstallHooks(repoPath) {
|
|
|
30958
31486
|
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
30959
31487
|
const hooksDir = resolveHooksDir(checkout);
|
|
30960
31488
|
let removed = false;
|
|
30961
|
-
removed = removeMarkedBlock(
|
|
30962
|
-
removed = removeMarkedBlock(
|
|
31489
|
+
removed = removeMarkedBlock(path37.join(hooksDir, "pre-commit")) || removed;
|
|
31490
|
+
removed = removeMarkedBlock(path37.join(hooksDir, "post-commit")) || removed;
|
|
30963
31491
|
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
30964
|
-
const full =
|
|
30965
|
-
if (
|
|
30966
|
-
|
|
31492
|
+
const full = path37.join(hooksDir, file);
|
|
31493
|
+
if (fs33.existsSync(full)) {
|
|
31494
|
+
fs33.rmSync(full);
|
|
30967
31495
|
removed = true;
|
|
30968
31496
|
}
|
|
30969
31497
|
}
|
|
@@ -30980,9 +31508,9 @@ function getCommitGateConfig(checkoutDir) {
|
|
|
30980
31508
|
function isAgentWorktree(checkoutDir) {
|
|
30981
31509
|
const branch = getCurrentBranch(checkoutDir);
|
|
30982
31510
|
if (branch && /^har-agent-\d+$/.test(branch)) return true;
|
|
30983
|
-
const worktreesRoot =
|
|
30984
|
-
const resolved =
|
|
30985
|
-
return resolved.startsWith(`${worktreesRoot}${
|
|
31511
|
+
const worktreesRoot = path37.join(os9.homedir(), "worktrees");
|
|
31512
|
+
const resolved = path37.resolve(checkoutDir);
|
|
31513
|
+
return resolved.startsWith(`${worktreesRoot}${path37.sep}`) && /-agent-\d+$/.test(path37.basename(resolved));
|
|
30986
31514
|
}
|
|
30987
31515
|
function resolveEffectiveMode(checkoutDir, gate) {
|
|
30988
31516
|
if (!gate.enabled) return "off";
|
|
@@ -30995,15 +31523,15 @@ function getHooksStatus(repoPath) {
|
|
|
30995
31523
|
const hooksDir = resolveHooksDir(checkout);
|
|
30996
31524
|
const gate = getCommitGateConfig(checkout);
|
|
30997
31525
|
const hasBlock = (name) => {
|
|
30998
|
-
const file =
|
|
30999
|
-
return
|
|
31526
|
+
const file = path37.join(hooksDir, name);
|
|
31527
|
+
return fs33.existsSync(file) && fs33.readFileSync(file, "utf8").includes(MARKER_START);
|
|
31000
31528
|
};
|
|
31001
31529
|
return {
|
|
31002
31530
|
checkout,
|
|
31003
31531
|
hooksDir,
|
|
31004
31532
|
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
31005
|
-
preCommitInstalled: hasBlock("pre-commit") &&
|
|
31006
|
-
postCommitInstalled: hasBlock("post-commit") &&
|
|
31533
|
+
preCommitInstalled: hasBlock("pre-commit") && fs33.existsSync(path37.join(hooksDir, "har-pre-commit")),
|
|
31534
|
+
postCommitInstalled: hasBlock("post-commit") && fs33.existsSync(path37.join(hooksDir, "har-post-commit")),
|
|
31007
31535
|
gate,
|
|
31008
31536
|
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
31009
31537
|
};
|
|
@@ -31014,7 +31542,7 @@ function checkCommitGate(cwd) {
|
|
|
31014
31542
|
}
|
|
31015
31543
|
const checkout = resolveCheckoutRoot(cwd);
|
|
31016
31544
|
if (!checkout) return { exitCode: 0, messages: [] };
|
|
31017
|
-
if (!
|
|
31545
|
+
if (!fs33.existsSync(path37.join(checkout, ".har", "stages.json"))) {
|
|
31018
31546
|
return { exitCode: 0, messages: [] };
|
|
31019
31547
|
}
|
|
31020
31548
|
const gate = getCommitGateConfig(checkout);
|
|
@@ -31084,13 +31612,114 @@ async function recordCommitAssociation(cwd) {
|
|
|
31084
31612
|
}
|
|
31085
31613
|
}
|
|
31086
31614
|
|
|
31615
|
+
// src/core/claude-hooks.ts
|
|
31616
|
+
var fs34 = __toESM(require("fs"));
|
|
31617
|
+
var path38 = __toESM(require("path"));
|
|
31618
|
+
var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
|
|
31619
|
+
var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
31620
|
+
var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
|
|
31621
|
+
var GUARD_COMMAND = `"$CLAUDE_PROJECT_DIR"/${CLAUDE_GUARD_RELATIVE_PATH}`;
|
|
31622
|
+
function isHarGuardEntry(entry) {
|
|
31623
|
+
return (entry.hooks ?? []).some(
|
|
31624
|
+
(hook) => typeof hook.command === "string" && hook.command.includes("claude-worktree-guard.sh")
|
|
31625
|
+
);
|
|
31626
|
+
}
|
|
31627
|
+
function readSettings(settingsPath) {
|
|
31628
|
+
if (!fs34.existsSync(settingsPath)) return {};
|
|
31629
|
+
const raw = fs34.readFileSync(settingsPath, "utf8").trim();
|
|
31630
|
+
if (raw === "") return {};
|
|
31631
|
+
const parsed = JSON.parse(raw);
|
|
31632
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
31633
|
+
throw new Error(`${settingsPath} is not a JSON object \u2014 fix it before installing the guard.`);
|
|
31634
|
+
}
|
|
31635
|
+
return parsed;
|
|
31636
|
+
}
|
|
31637
|
+
function writeSettings(settingsPath, settings) {
|
|
31638
|
+
fs34.mkdirSync(path38.dirname(settingsPath), { recursive: true });
|
|
31639
|
+
fs34.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
31640
|
+
}
|
|
31641
|
+
function installClaudeGuard(repoPath) {
|
|
31642
|
+
const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
|
|
31643
|
+
if (!templatePath) {
|
|
31644
|
+
throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
|
|
31645
|
+
}
|
|
31646
|
+
const guardScript = path38.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
31647
|
+
fs34.mkdirSync(path38.dirname(guardScript), { recursive: true });
|
|
31648
|
+
fs34.copyFileSync(templatePath, guardScript);
|
|
31649
|
+
fs34.chmodSync(guardScript, 493);
|
|
31650
|
+
const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31651
|
+
const settings = readSettings(settingsPath);
|
|
31652
|
+
const hooks = settings.hooks ?? {};
|
|
31653
|
+
const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
|
|
31654
|
+
preToolUse.push({
|
|
31655
|
+
matcher: GUARD_MATCHER,
|
|
31656
|
+
hooks: [{ type: "command", command: GUARD_COMMAND }]
|
|
31657
|
+
});
|
|
31658
|
+
settings.hooks = { ...hooks, PreToolUse: preToolUse };
|
|
31659
|
+
writeSettings(settingsPath, settings);
|
|
31660
|
+
return { guardScript, settingsPath };
|
|
31661
|
+
}
|
|
31662
|
+
function uninstallClaudeGuard(repoPath) {
|
|
31663
|
+
let removed = false;
|
|
31664
|
+
const guardScript = path38.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
31665
|
+
if (fs34.existsSync(guardScript)) {
|
|
31666
|
+
fs34.rmSync(guardScript);
|
|
31667
|
+
removed = true;
|
|
31668
|
+
const parent = path38.dirname(guardScript);
|
|
31669
|
+
if (fs34.existsSync(parent) && fs34.readdirSync(parent).length === 0) fs34.rmdirSync(parent);
|
|
31670
|
+
}
|
|
31671
|
+
const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31672
|
+
if (fs34.existsSync(settingsPath)) {
|
|
31673
|
+
const settings = readSettings(settingsPath);
|
|
31674
|
+
const preToolUse = settings.hooks?.PreToolUse;
|
|
31675
|
+
if (preToolUse) {
|
|
31676
|
+
const filtered = preToolUse.filter((entry) => !isHarGuardEntry(entry));
|
|
31677
|
+
if (filtered.length !== preToolUse.length) {
|
|
31678
|
+
removed = true;
|
|
31679
|
+
if (filtered.length > 0) {
|
|
31680
|
+
settings.hooks = { ...settings.hooks, PreToolUse: filtered };
|
|
31681
|
+
} else {
|
|
31682
|
+
const rest = { ...settings.hooks };
|
|
31683
|
+
delete rest.PreToolUse;
|
|
31684
|
+
if (Object.keys(rest).length > 0) {
|
|
31685
|
+
settings.hooks = rest;
|
|
31686
|
+
} else {
|
|
31687
|
+
delete settings.hooks;
|
|
31688
|
+
}
|
|
31689
|
+
}
|
|
31690
|
+
writeSettings(settingsPath, settings);
|
|
31691
|
+
}
|
|
31692
|
+
}
|
|
31693
|
+
}
|
|
31694
|
+
return { removed };
|
|
31695
|
+
}
|
|
31696
|
+
function claudeGuardInstalled(repoPath) {
|
|
31697
|
+
const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31698
|
+
if (!fs34.existsSync(settingsPath)) return false;
|
|
31699
|
+
try {
|
|
31700
|
+
const settings = readSettings(settingsPath);
|
|
31701
|
+
return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
|
|
31702
|
+
} catch {
|
|
31703
|
+
return false;
|
|
31704
|
+
}
|
|
31705
|
+
}
|
|
31706
|
+
|
|
31087
31707
|
// src/cli/commands/hooks.ts
|
|
31088
31708
|
function repoOption(y2) {
|
|
31089
31709
|
return y2.option("repo", { type: "string", default: ".", describe: "Path to the repository" });
|
|
31090
31710
|
}
|
|
31091
|
-
function
|
|
31711
|
+
function handleInstall2(argv) {
|
|
31092
31712
|
try {
|
|
31093
|
-
|
|
31713
|
+
if (argv.claude) {
|
|
31714
|
+
const result2 = installClaudeGuard(path39.resolve(argv.repo));
|
|
31715
|
+
success("Claude Code worktree guard installed.");
|
|
31716
|
+
info(`Guard script: ${result2.guardScript}`);
|
|
31717
|
+
info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
|
|
31718
|
+
info("File edits in the MAIN checkout are now blocked with a pointer to /har-wt.");
|
|
31719
|
+
info("Commit both files so the guard travels with the repo.");
|
|
31720
|
+
return;
|
|
31721
|
+
}
|
|
31722
|
+
const result = installHooks({ repoPath: path39.resolve(argv.repo), force: argv.force });
|
|
31094
31723
|
success(`Commit gate installed in ${result.hooksDir}`);
|
|
31095
31724
|
info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
|
|
31096
31725
|
info("Commits of unverified change batches will be blocked in agent worktrees.");
|
|
@@ -31102,7 +31731,16 @@ function handleInstall(argv) {
|
|
|
31102
31731
|
}
|
|
31103
31732
|
function handleUninstall(argv) {
|
|
31104
31733
|
try {
|
|
31105
|
-
|
|
31734
|
+
if (argv.claude) {
|
|
31735
|
+
const result2 = uninstallClaudeGuard(path39.resolve(argv.repo));
|
|
31736
|
+
if (result2.removed) {
|
|
31737
|
+
success("Claude Code worktree guard removed.");
|
|
31738
|
+
} else {
|
|
31739
|
+
info("No Claude Code worktree guard was installed.");
|
|
31740
|
+
}
|
|
31741
|
+
return;
|
|
31742
|
+
}
|
|
31743
|
+
const result = uninstallHooks(path39.resolve(argv.repo));
|
|
31106
31744
|
if (result.removed) {
|
|
31107
31745
|
success(`Commit gate removed from ${result.hooksDir}`);
|
|
31108
31746
|
} else {
|
|
@@ -31115,7 +31753,7 @@ function handleUninstall(argv) {
|
|
|
31115
31753
|
}
|
|
31116
31754
|
function handleStatus2(argv) {
|
|
31117
31755
|
try {
|
|
31118
|
-
const status = getHooksStatus(
|
|
31756
|
+
const status = getHooksStatus(path39.resolve(argv.repo));
|
|
31119
31757
|
if (argv.json) {
|
|
31120
31758
|
process.stdout.write(`${JSON.stringify(status, null, 2)}
|
|
31121
31759
|
`);
|
|
@@ -31132,6 +31770,9 @@ function handleStatus2(argv) {
|
|
|
31132
31770
|
`Gate: enabled=${status.gate.enabled} mode=${status.gate.mode} scope=${status.gate.scope}`
|
|
31133
31771
|
);
|
|
31134
31772
|
info(`Effective: ${status.effectiveMode} (in this checkout)`);
|
|
31773
|
+
info(
|
|
31774
|
+
`Claude guard: ${claudeGuardInstalled(path39.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
|
|
31775
|
+
);
|
|
31135
31776
|
} catch (err) {
|
|
31136
31777
|
error(err instanceof Error ? err.message : String(err));
|
|
31137
31778
|
process.exitCode = 1;
|
|
@@ -31158,9 +31799,22 @@ var hooksCommand = {
|
|
|
31158
31799
|
type: "boolean",
|
|
31159
31800
|
default: false,
|
|
31160
31801
|
describe: "Write hooks even when core.hooksPath points at a managed directory"
|
|
31802
|
+
}).option("claude", {
|
|
31803
|
+
type: "boolean",
|
|
31804
|
+
default: false,
|
|
31805
|
+
describe: "Install the Claude Code worktree guard instead (blocks Edit/Write in the main checkout, points to /har-wt)"
|
|
31161
31806
|
}),
|
|
31162
|
-
|
|
31163
|
-
).command(
|
|
31807
|
+
handleInstall2
|
|
31808
|
+
).command(
|
|
31809
|
+
"uninstall",
|
|
31810
|
+
"Remove the har commit gate hooks",
|
|
31811
|
+
(y2) => repoOption(y2).option("claude", {
|
|
31812
|
+
type: "boolean",
|
|
31813
|
+
default: false,
|
|
31814
|
+
describe: "Remove the Claude Code worktree guard instead"
|
|
31815
|
+
}),
|
|
31816
|
+
handleUninstall
|
|
31817
|
+
).command(
|
|
31164
31818
|
"status",
|
|
31165
31819
|
"Show hook installation and gate configuration",
|
|
31166
31820
|
(y2) => repoOption(y2).option("json", { type: "boolean", default: false }),
|
|
@@ -31378,10 +32032,10 @@ function assignProp(target, prop, value) {
|
|
|
31378
32032
|
configurable: true
|
|
31379
32033
|
});
|
|
31380
32034
|
}
|
|
31381
|
-
function getElementAtPath(obj,
|
|
31382
|
-
if (!
|
|
32035
|
+
function getElementAtPath(obj, path41) {
|
|
32036
|
+
if (!path41)
|
|
31383
32037
|
return obj;
|
|
31384
|
-
return
|
|
32038
|
+
return path41.reduce((acc, key) => acc?.[key], obj);
|
|
31385
32039
|
}
|
|
31386
32040
|
function promiseAllObject(promisesObj) {
|
|
31387
32041
|
const keys = Object.keys(promisesObj);
|
|
@@ -31701,11 +32355,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
31701
32355
|
}
|
|
31702
32356
|
return false;
|
|
31703
32357
|
}
|
|
31704
|
-
function prefixIssues(
|
|
32358
|
+
function prefixIssues(path41, issues) {
|
|
31705
32359
|
return issues.map((iss) => {
|
|
31706
32360
|
var _a3;
|
|
31707
32361
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
31708
|
-
iss.path.unshift(
|
|
32362
|
+
iss.path.unshift(path41);
|
|
31709
32363
|
return iss;
|
|
31710
32364
|
});
|
|
31711
32365
|
}
|
|
@@ -37028,7 +37682,7 @@ var Protocol = class {
|
|
|
37028
37682
|
return;
|
|
37029
37683
|
}
|
|
37030
37684
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
37031
|
-
await new Promise((
|
|
37685
|
+
await new Promise((resolve28) => setTimeout(resolve28, pollInterval));
|
|
37032
37686
|
options?.signal?.throwIfAborted();
|
|
37033
37687
|
}
|
|
37034
37688
|
} catch (error3) {
|
|
@@ -37045,7 +37699,7 @@ var Protocol = class {
|
|
|
37045
37699
|
*/
|
|
37046
37700
|
request(request, resultSchema, options) {
|
|
37047
37701
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
37048
|
-
return new Promise((
|
|
37702
|
+
return new Promise((resolve28, reject) => {
|
|
37049
37703
|
const earlyReject = (error3) => {
|
|
37050
37704
|
reject(error3);
|
|
37051
37705
|
};
|
|
@@ -37123,7 +37777,7 @@ var Protocol = class {
|
|
|
37123
37777
|
if (!parseResult.success) {
|
|
37124
37778
|
reject(parseResult.error);
|
|
37125
37779
|
} else {
|
|
37126
|
-
|
|
37780
|
+
resolve28(parseResult.data);
|
|
37127
37781
|
}
|
|
37128
37782
|
} catch (error3) {
|
|
37129
37783
|
reject(error3);
|
|
@@ -37384,12 +38038,12 @@ var Protocol = class {
|
|
|
37384
38038
|
}
|
|
37385
38039
|
} catch {
|
|
37386
38040
|
}
|
|
37387
|
-
return new Promise((
|
|
38041
|
+
return new Promise((resolve28, reject) => {
|
|
37388
38042
|
if (signal.aborted) {
|
|
37389
38043
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
37390
38044
|
return;
|
|
37391
38045
|
}
|
|
37392
|
-
const timeoutId = setTimeout(
|
|
38046
|
+
const timeoutId = setTimeout(resolve28, interval);
|
|
37393
38047
|
signal.addEventListener("abort", () => {
|
|
37394
38048
|
clearTimeout(timeoutId);
|
|
37395
38049
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -38179,7 +38833,7 @@ var Server = class extends Protocol {
|
|
|
38179
38833
|
};
|
|
38180
38834
|
|
|
38181
38835
|
// src/mcp/server.ts
|
|
38182
|
-
var
|
|
38836
|
+
var path40 = __toESM(require("path"));
|
|
38183
38837
|
|
|
38184
38838
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
38185
38839
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
@@ -38262,12 +38916,12 @@ var StdioServerTransport = class {
|
|
|
38262
38916
|
this.onclose?.();
|
|
38263
38917
|
}
|
|
38264
38918
|
send(message) {
|
|
38265
|
-
return new Promise((
|
|
38919
|
+
return new Promise((resolve28) => {
|
|
38266
38920
|
const json = serializeMessage(message);
|
|
38267
38921
|
if (this._stdout.write(json)) {
|
|
38268
|
-
|
|
38922
|
+
resolve28();
|
|
38269
38923
|
} else {
|
|
38270
|
-
this._stdout.once("drain",
|
|
38924
|
+
this._stdout.once("drain", resolve28);
|
|
38271
38925
|
}
|
|
38272
38926
|
});
|
|
38273
38927
|
}
|
|
@@ -38811,7 +39465,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
38811
39465
|
const input = ControlUpInputSchema.parse({ ...args, repo });
|
|
38812
39466
|
const result = await startControlAndSync({
|
|
38813
39467
|
detach: input.detach,
|
|
38814
|
-
cwd:
|
|
39468
|
+
cwd: path40.resolve(input.repo)
|
|
38815
39469
|
});
|
|
38816
39470
|
if (result.code !== 0) {
|
|
38817
39471
|
return {
|
|
@@ -38875,7 +39529,7 @@ var mcpCommand = {
|
|
|
38875
39529
|
|
|
38876
39530
|
// src/cli/index.ts
|
|
38877
39531
|
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();
|
|
39532
|
+
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
39533
|
}
|
|
38880
39534
|
|
|
38881
39535
|
// src/index.ts
|