@osfactory/har 0.6.0 → 0.7.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 +2 -0
- package/dist/index.js +1811 -648
- package/dist/prompts/system-authoring.md +27 -2
- package/dist/templates/adaptation-prompt-init.md +32 -2
- package/dist/templates/adaptation-prompt-maintain.md +5 -2
- package/dist/templates/har-boilerplate/README.md +39 -3
- package/dist/templates/har-boilerplate/agent-cli.sh +72 -26
- package/dist/templates/har-boilerplate/agent-slot.sh +351 -0
- package/dist/templates/har-boilerplate/attach.sh +7 -1
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +1 -1
- package/dist/templates/har-boilerplate/env.template +1 -1
- package/dist/templates/har-boilerplate/harness.env +27 -0
- package/dist/templates/har-boilerplate/launch.sh +167 -114
- package/dist/templates/har-boilerplate/preflight.sh +41 -0
- package/dist/templates/har-boilerplate/setup-infra.sh +87 -11
- package/dist/templates/har-boilerplate/teardown.sh +2 -1
- package/dist/templates/har-boilerplate-cli/README.md +28 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +331 -0
- package/dist/templates/har-boilerplate-cli/harness.env +7 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +145 -95
- package/dist/templates/har-boilerplate-cli/preflight.sh +37 -0
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +87 -11
- package/dist/templates/har-boilerplate-ios/README.md +26 -0
- package/dist/templates/har-boilerplate-ios/harness.env +2 -1
- package/package.json +1 -1
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -57
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 path37 = url.path;
|
|
769
|
+
if (path37.length === 0) {
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
|
-
if (url.scheme === "file" &&
|
|
772
|
+
if (url.scheme === "file" && path37.length === 1 && isNormalizedWindowsDriveLetter(path37[0])) {
|
|
773
773
|
return;
|
|
774
774
|
}
|
|
775
|
-
|
|
775
|
+
path37.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(resolve26, 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
|
+
resolve26(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(resolve26, 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
|
+
resolve26(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
|
+
resolve26(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
|
+
resolve26(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
|
+
resolve26(response);
|
|
2981
2981
|
});
|
|
2982
2982
|
raw.on("end", function() {
|
|
2983
2983
|
if (!response) {
|
|
2984
2984
|
response = new Response3(body, response_options);
|
|
2985
|
-
|
|
2985
|
+
resolve26(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
|
+
resolve26(response);
|
|
2994
2994
|
return;
|
|
2995
2995
|
}
|
|
2996
2996
|
response = new Response3(body, response_options);
|
|
2997
|
-
|
|
2997
|
+
resolve26(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(path37, { 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: path37, size, lastModified: mtimeMs });
|
|
6362
6362
|
if (!filename) {
|
|
6363
6363
|
filename = file.name;
|
|
6364
6364
|
}
|
|
@@ -6367,13 +6367,13 @@ function createFileFromPath(path32, { 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(path37, filenameOrOptions, options = {}) {
|
|
6371
|
+
const stats = (0, import_fs5.statSync)(path37);
|
|
6372
|
+
return createFileFromPath(path37, stats, filenameOrOptions, options);
|
|
6373
6373
|
}
|
|
6374
|
-
async function fileFromPath2(
|
|
6375
|
-
const stats = await import_fs5.promises.stat(
|
|
6376
|
-
return createFileFromPath(
|
|
6374
|
+
async function fileFromPath2(path37, filenameOrOptions, options) {
|
|
6375
|
+
const stats = await import_fs5.promises.stat(path37);
|
|
6376
|
+
return createFileFromPath(path37, 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 = resolve26.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 resolve26(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(path37) {
|
|
9633
|
+
let input = path37;
|
|
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 [path37, query] = wsComponent.resourceName.split("?");
|
|
9886
|
+
wsComponent.path = path37 && path37 !== "/" ? path37 : 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 resolve26(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: resolve26,
|
|
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, fs32, 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, fs32[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: (path37) => {
|
|
14569
14569
|
if (typeof require !== "undefined") {
|
|
14570
|
-
return require(
|
|
14571
|
-
} else if (
|
|
14572
|
-
return JSON.parse((0, import_fs2.readFileSync)(
|
|
14570
|
+
return require(path37);
|
|
14571
|
+
} else if (path37.match(/\.json$/)) {
|
|
14572
|
+
return JSON.parse((0, import_fs2.readFileSync)(path37, "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((resolve26, 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
|
+
resolve26(completions);
|
|
17104
17104
|
});
|
|
17105
17105
|
});
|
|
17106
17106
|
} else {
|
|
@@ -18139,26 +18139,45 @@ function isYargsInstance(y2) {
|
|
|
18139
18139
|
var Yargs = YargsFactory(esm_default);
|
|
18140
18140
|
var yargs_default = Yargs;
|
|
18141
18141
|
|
|
18142
|
+
// src/core/package-version.ts
|
|
18143
|
+
var fs = __toESM(require("fs"));
|
|
18144
|
+
var path = __toESM(require("path"));
|
|
18145
|
+
function getHarPackageVersion() {
|
|
18146
|
+
if (process.env.HAR_PACKAGE_VERSION) {
|
|
18147
|
+
return process.env.HAR_PACKAGE_VERSION;
|
|
18148
|
+
}
|
|
18149
|
+
const candidates = [
|
|
18150
|
+
path.resolve(__dirname, "..", "package.json"),
|
|
18151
|
+
path.resolve(__dirname, "..", "..", "package.json")
|
|
18152
|
+
];
|
|
18153
|
+
for (const packageJsonPath of candidates) {
|
|
18154
|
+
if (!fs.existsSync(packageJsonPath)) continue;
|
|
18155
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
18156
|
+
return pkg.version;
|
|
18157
|
+
}
|
|
18158
|
+
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
18159
|
+
}
|
|
18160
|
+
|
|
18142
18161
|
// src/cli/commands/env.ts
|
|
18143
|
-
var
|
|
18162
|
+
var path31 = __toESM(require("path"));
|
|
18144
18163
|
|
|
18145
18164
|
// src/core/harness.ts
|
|
18146
|
-
var
|
|
18147
|
-
var
|
|
18165
|
+
var fs18 = __toESM(require("fs"));
|
|
18166
|
+
var path16 = __toESM(require("path"));
|
|
18148
18167
|
|
|
18149
18168
|
// src/harness/generator.ts
|
|
18150
|
-
var
|
|
18151
|
-
var
|
|
18169
|
+
var fs6 = __toESM(require("fs"));
|
|
18170
|
+
var path6 = __toESM(require("path"));
|
|
18152
18171
|
|
|
18153
18172
|
// src/utils/file-ops.ts
|
|
18154
|
-
var
|
|
18155
|
-
var
|
|
18173
|
+
var fs2 = __toESM(require("fs"));
|
|
18174
|
+
var path2 = __toESM(require("path"));
|
|
18156
18175
|
function readFile(filePath, maxChars = 8e3) {
|
|
18157
|
-
if (!
|
|
18158
|
-
if (
|
|
18176
|
+
if (!fs2.existsSync(filePath)) return `[file not found: ${filePath}]`;
|
|
18177
|
+
if (fs2.statSync(filePath).isDirectory()) {
|
|
18159
18178
|
return `[path is a directory, not a file: ${filePath}. Use listDir instead.]`;
|
|
18160
18179
|
}
|
|
18161
|
-
const content =
|
|
18180
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
18162
18181
|
if (content.length > maxChars) {
|
|
18163
18182
|
return content.slice(0, maxChars) + `
|
|
18164
18183
|
... [truncated at ${maxChars} chars]`;
|
|
@@ -18166,8 +18185,8 @@ function readFile(filePath, maxChars = 8e3) {
|
|
|
18166
18185
|
return content;
|
|
18167
18186
|
}
|
|
18168
18187
|
function listDir(dirPath, maxFiles = 60) {
|
|
18169
|
-
if (!
|
|
18170
|
-
const entries =
|
|
18188
|
+
if (!fs2.existsSync(dirPath)) return [];
|
|
18189
|
+
const entries = fs2.readdirSync(dirPath, { withFileTypes: true });
|
|
18171
18190
|
const result = [];
|
|
18172
18191
|
for (const entry of entries.slice(0, maxFiles)) {
|
|
18173
18192
|
const name = entry.isDirectory() ? entry.name + "/" : entry.name;
|
|
@@ -18176,30 +18195,30 @@ function listDir(dirPath, maxFiles = 60) {
|
|
|
18176
18195
|
return result;
|
|
18177
18196
|
}
|
|
18178
18197
|
function writeFileSafe(filePath, content) {
|
|
18179
|
-
|
|
18180
|
-
|
|
18198
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
18199
|
+
fs2.writeFileSync(filePath, content);
|
|
18181
18200
|
}
|
|
18182
18201
|
function copyDirRecursive(srcDir, destDir) {
|
|
18183
|
-
|
|
18184
|
-
for (const entry of
|
|
18185
|
-
const srcPath =
|
|
18186
|
-
const destPath =
|
|
18202
|
+
fs2.mkdirSync(destDir, { recursive: true });
|
|
18203
|
+
for (const entry of fs2.readdirSync(srcDir, { withFileTypes: true })) {
|
|
18204
|
+
const srcPath = path2.join(srcDir, entry.name);
|
|
18205
|
+
const destPath = path2.join(destDir, entry.name);
|
|
18187
18206
|
if (entry.isDirectory()) {
|
|
18188
18207
|
copyDirRecursive(srcPath, destPath);
|
|
18189
18208
|
} else {
|
|
18190
|
-
|
|
18191
|
-
|
|
18209
|
+
fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
|
|
18210
|
+
fs2.copyFileSync(srcPath, destPath);
|
|
18192
18211
|
if (entry.name.endsWith(".sh")) {
|
|
18193
|
-
|
|
18212
|
+
fs2.chmodSync(destPath, 493);
|
|
18194
18213
|
}
|
|
18195
18214
|
}
|
|
18196
18215
|
}
|
|
18197
18216
|
}
|
|
18198
18217
|
function resolveSafePath(baseDir, relativePath) {
|
|
18199
|
-
const normalized =
|
|
18200
|
-
const resolved =
|
|
18201
|
-
const base =
|
|
18202
|
-
if (!resolved.startsWith(base +
|
|
18218
|
+
const normalized = path2.normalize(relativePath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
18219
|
+
const resolved = path2.resolve(baseDir, normalized);
|
|
18220
|
+
const base = path2.resolve(baseDir);
|
|
18221
|
+
if (!resolved.startsWith(base + path2.sep) && resolved !== base) {
|
|
18203
18222
|
throw new Error(`Path escapes harness directory: ${relativePath}`);
|
|
18204
18223
|
}
|
|
18205
18224
|
return resolved;
|
|
@@ -18721,45 +18740,45 @@ function divider() {
|
|
|
18721
18740
|
}
|
|
18722
18741
|
|
|
18723
18742
|
// src/utils/paths.ts
|
|
18724
|
-
var
|
|
18725
|
-
var
|
|
18743
|
+
var fs3 = __toESM(require("fs"));
|
|
18744
|
+
var path3 = __toESM(require("path"));
|
|
18726
18745
|
function resolveTemplatesDir() {
|
|
18727
18746
|
const candidates = [
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
|
|
18747
|
+
path3.join(__dirname, "templates"),
|
|
18748
|
+
path3.join(__dirname, "..", "templates"),
|
|
18749
|
+
path3.join(__dirname, "..", "harness", "templates")
|
|
18731
18750
|
];
|
|
18732
18751
|
for (const dir of candidates) {
|
|
18733
|
-
if (
|
|
18752
|
+
if (fs3.existsSync(path3.join(dir, "har-boilerplate"))) return dir;
|
|
18734
18753
|
}
|
|
18735
18754
|
throw new Error("Templates directory not found. Run npm run build.");
|
|
18736
18755
|
}
|
|
18737
18756
|
function resolvePromptPath(name) {
|
|
18738
18757
|
const candidates = [
|
|
18739
|
-
|
|
18740
|
-
|
|
18758
|
+
path3.join(__dirname, "prompts", name),
|
|
18759
|
+
path3.join(__dirname, "..", "llm", "prompts", name)
|
|
18741
18760
|
];
|
|
18742
18761
|
for (const p2 of candidates) {
|
|
18743
|
-
if (
|
|
18762
|
+
if (fs3.existsSync(p2)) return p2;
|
|
18744
18763
|
}
|
|
18745
18764
|
return candidates[0];
|
|
18746
18765
|
}
|
|
18747
18766
|
function resolveTemplateFile(name) {
|
|
18748
18767
|
const dir = resolveTemplatesDir();
|
|
18749
|
-
const filePath =
|
|
18750
|
-
return
|
|
18768
|
+
const filePath = path3.join(dir, name);
|
|
18769
|
+
return fs3.existsSync(filePath) ? filePath : null;
|
|
18751
18770
|
}
|
|
18752
18771
|
|
|
18753
18772
|
// src/core/gitignore.ts
|
|
18754
|
-
var
|
|
18755
|
-
var
|
|
18773
|
+
var fs4 = __toESM(require("fs"));
|
|
18774
|
+
var path4 = __toESM(require("path"));
|
|
18756
18775
|
var HARNESS_ROOT_GITIGNORE_PATTERNS = [
|
|
18757
18776
|
".env.agent.*",
|
|
18758
18777
|
"ecosystem.agent.*.config.cjs"
|
|
18759
18778
|
];
|
|
18760
18779
|
function ensureRootGitignorePatterns(checkoutDir) {
|
|
18761
|
-
const gitignorePath =
|
|
18762
|
-
const content =
|
|
18780
|
+
const gitignorePath = path4.join(checkoutDir, ".gitignore");
|
|
18781
|
+
const content = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
|
|
18763
18782
|
const lines = content.split("\n").map((line) => line.trim());
|
|
18764
18783
|
const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
18765
18784
|
if (missing.length === 0) return;
|
|
@@ -18768,13 +18787,13 @@ function ensureRootGitignorePatterns(checkoutDir) {
|
|
|
18768
18787
|
const block = lines.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
|
|
18769
18788
|
` : `${header2}${missing.join("\n")}
|
|
18770
18789
|
`;
|
|
18771
|
-
|
|
18790
|
+
fs4.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
|
|
18772
18791
|
}
|
|
18773
18792
|
|
|
18774
18793
|
// src/harness/manifest.ts
|
|
18775
18794
|
var crypto = __toESM(require("crypto"));
|
|
18776
|
-
var
|
|
18777
|
-
var
|
|
18795
|
+
var fs5 = __toESM(require("fs"));
|
|
18796
|
+
var path5 = __toESM(require("path"));
|
|
18778
18797
|
|
|
18779
18798
|
// node_modules/zod/v3/external.js
|
|
18780
18799
|
var external_exports = {};
|
|
@@ -19254,8 +19273,8 @@ function getErrorMap() {
|
|
|
19254
19273
|
|
|
19255
19274
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
19256
19275
|
var makeIssue = (params) => {
|
|
19257
|
-
const { data, path:
|
|
19258
|
-
const fullPath = [...
|
|
19276
|
+
const { data, path: path37, errorMaps, issueData } = params;
|
|
19277
|
+
const fullPath = [...path37, ...issueData.path || []];
|
|
19259
19278
|
const fullIssue = {
|
|
19260
19279
|
...issueData,
|
|
19261
19280
|
path: fullPath
|
|
@@ -19371,11 +19390,11 @@ var errorUtil;
|
|
|
19371
19390
|
|
|
19372
19391
|
// node_modules/zod/v3/types.js
|
|
19373
19392
|
var ParseInputLazyPath = class {
|
|
19374
|
-
constructor(parent, value,
|
|
19393
|
+
constructor(parent, value, path37, key) {
|
|
19375
19394
|
this._cachedPath = [];
|
|
19376
19395
|
this.parent = parent;
|
|
19377
19396
|
this.data = value;
|
|
19378
|
-
this._path =
|
|
19397
|
+
this._path = path37;
|
|
19379
19398
|
this._key = key;
|
|
19380
19399
|
}
|
|
19381
19400
|
get path() {
|
|
@@ -22999,6 +23018,22 @@ var AgentSlotHarnessUsageSchema = external_exports.enum([
|
|
|
22999
23018
|
"none",
|
|
23000
23019
|
"bypass_warning"
|
|
23001
23020
|
]);
|
|
23021
|
+
var PreflightBlockerSchema = external_exports.object({
|
|
23022
|
+
code: external_exports.string(),
|
|
23023
|
+
message: external_exports.string(),
|
|
23024
|
+
remediation: external_exports.string().optional(),
|
|
23025
|
+
details: external_exports.record(external_exports.unknown()).optional()
|
|
23026
|
+
});
|
|
23027
|
+
var SlotReadinessSchema = external_exports.object({
|
|
23028
|
+
canLaunch: external_exports.boolean(),
|
|
23029
|
+
verdict: external_exports.enum(["ready", "blocked"]),
|
|
23030
|
+
blockers: external_exports.array(PreflightBlockerSchema),
|
|
23031
|
+
remediations: external_exports.array(external_exports.string()),
|
|
23032
|
+
ports: external_exports.record(external_exports.number()).optional(),
|
|
23033
|
+
allocatedPorts: external_exports.boolean().optional(),
|
|
23034
|
+
/** Non-blocking notices (e.g. har control up holds the default port but an alternate was picked). */
|
|
23035
|
+
warnings: external_exports.array(external_exports.string()).optional()
|
|
23036
|
+
});
|
|
23002
23037
|
var AgentSlotStatusSchema = external_exports.object({
|
|
23003
23038
|
agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
|
|
23004
23039
|
active: external_exports.boolean(),
|
|
@@ -23019,6 +23054,8 @@ var AgentSlotStatusSchema = external_exports.object({
|
|
|
23019
23054
|
purpose: external_exports.string().optional(),
|
|
23020
23055
|
sessionStatus: external_exports.enum(["starting", "active", "failed", "completed"]).optional(),
|
|
23021
23056
|
lastError: external_exports.string().optional(),
|
|
23057
|
+
/** Set when a failed/starting session can be resumed without --replace. */
|
|
23058
|
+
resumeHint: external_exports.string().optional(),
|
|
23022
23059
|
/** Worktree checked out to no branch (legacy failure mode; should not happen with sessions). */
|
|
23023
23060
|
detachedHead: external_exports.boolean().optional(),
|
|
23024
23061
|
/** Worktree has uncommitted changes. */
|
|
@@ -23027,7 +23064,13 @@ var AgentSlotStatusSchema = external_exports.object({
|
|
|
23027
23064
|
ahead: external_exports.number().int().optional(),
|
|
23028
23065
|
/** Main checkout has commits the session base doesn't (worktree serves stale code). */
|
|
23029
23066
|
behind: external_exports.number().int().optional(),
|
|
23030
|
-
stale: external_exports.boolean().optional()
|
|
23067
|
+
stale: external_exports.boolean().optional(),
|
|
23068
|
+
/** Persisted host ports from the slot registry (when dynamically allocated). */
|
|
23069
|
+
ports: external_exports.record(external_exports.number()).optional(),
|
|
23070
|
+
/** PM2 namespace mismatch — foreign or legacy processes for this slot id. */
|
|
23071
|
+
pm2Issue: external_exports.enum(["foreign_pm2", "registry_missing", "project_mismatch"]).optional(),
|
|
23072
|
+
/** Launch readiness slice — same core as `har env preflight`. */
|
|
23073
|
+
readiness: SlotReadinessSchema.optional()
|
|
23031
23074
|
});
|
|
23032
23075
|
var EnvironmentStatusSchema = external_exports.object({
|
|
23033
23076
|
repoPath: external_exports.string(),
|
|
@@ -23081,17 +23124,22 @@ var SyncValidationsInputSchema = external_exports.object({
|
|
|
23081
23124
|
var GENERATOR_VERSION = "0.4.0";
|
|
23082
23125
|
var MANIFEST_VERSION = "1";
|
|
23083
23126
|
var DEFAULT_HAR_DIR = ".har";
|
|
23084
|
-
var CHECKSUM_SKIP = /* @__PURE__ */ new Set([
|
|
23127
|
+
var CHECKSUM_SKIP = /* @__PURE__ */ new Set([
|
|
23128
|
+
"manifest.json",
|
|
23129
|
+
"AGENT.md.proposed",
|
|
23130
|
+
"AGENT.md.proposed.meta.json",
|
|
23131
|
+
"ADAPT-PROMPT.md"
|
|
23132
|
+
]);
|
|
23085
23133
|
function getHarnessDir(repoPath) {
|
|
23086
|
-
return
|
|
23134
|
+
return path5.join(repoPath, DEFAULT_HAR_DIR);
|
|
23087
23135
|
}
|
|
23088
23136
|
function getManifestPath(repoPath) {
|
|
23089
|
-
return
|
|
23137
|
+
return path5.join(getHarnessDir(repoPath), "manifest.json");
|
|
23090
23138
|
}
|
|
23091
23139
|
function readManifest(repoPath) {
|
|
23092
23140
|
const manifestPath = getManifestPath(repoPath);
|
|
23093
|
-
if (!
|
|
23094
|
-
const raw = JSON.parse(
|
|
23141
|
+
if (!fs5.existsSync(manifestPath)) return null;
|
|
23142
|
+
const raw = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
|
|
23095
23143
|
const result = HarnessManifestSchema.safeParse(raw);
|
|
23096
23144
|
return result.success ? result.data : null;
|
|
23097
23145
|
}
|
|
@@ -23104,11 +23152,11 @@ function computeFileChecksum(content) {
|
|
|
23104
23152
|
}
|
|
23105
23153
|
function computeHarnessChecksums(harnessDir) {
|
|
23106
23154
|
const checksums = {};
|
|
23107
|
-
if (!
|
|
23108
|
-
for (const entry of
|
|
23155
|
+
if (!fs5.existsSync(harnessDir)) return checksums;
|
|
23156
|
+
for (const entry of fs5.readdirSync(harnessDir, { withFileTypes: true })) {
|
|
23109
23157
|
if (!entry.isFile() || CHECKSUM_SKIP.has(entry.name)) continue;
|
|
23110
|
-
const full =
|
|
23111
|
-
checksums[entry.name] = computeFileChecksum(
|
|
23158
|
+
const full = path5.join(harnessDir, entry.name);
|
|
23159
|
+
checksums[entry.name] = computeFileChecksum(fs5.readFileSync(full, "utf8"));
|
|
23112
23160
|
}
|
|
23113
23161
|
return checksums;
|
|
23114
23162
|
}
|
|
@@ -23128,17 +23176,17 @@ function createManifest(repoPath, adaptationSummary, stack, profile) {
|
|
|
23128
23176
|
};
|
|
23129
23177
|
}
|
|
23130
23178
|
function resolveHarnessRoot(inputPath) {
|
|
23131
|
-
let current =
|
|
23132
|
-
const { root } =
|
|
23179
|
+
let current = path5.resolve(inputPath);
|
|
23180
|
+
const { root } = path5.parse(current);
|
|
23133
23181
|
for (; ; ) {
|
|
23134
|
-
const manifestPath =
|
|
23135
|
-
if (
|
|
23182
|
+
const manifestPath = path5.join(current, DEFAULT_HAR_DIR, "manifest.json");
|
|
23183
|
+
if (fs5.existsSync(manifestPath)) {
|
|
23136
23184
|
return current;
|
|
23137
23185
|
}
|
|
23138
23186
|
if (current === root) break;
|
|
23139
|
-
current =
|
|
23187
|
+
current = path5.dirname(current);
|
|
23140
23188
|
}
|
|
23141
|
-
return
|
|
23189
|
+
return path5.resolve(inputPath);
|
|
23142
23190
|
}
|
|
23143
23191
|
|
|
23144
23192
|
// src/harness/generator.ts
|
|
@@ -23154,46 +23202,46 @@ var CLI_PRUNE_FILES = [
|
|
|
23154
23202
|
];
|
|
23155
23203
|
function pruneCliProfile(harnessDir) {
|
|
23156
23204
|
for (const file of CLI_PRUNE_FILES) {
|
|
23157
|
-
const filePath =
|
|
23158
|
-
if (
|
|
23159
|
-
|
|
23205
|
+
const filePath = path6.join(harnessDir, file);
|
|
23206
|
+
if (fs6.existsSync(filePath)) {
|
|
23207
|
+
fs6.unlinkSync(filePath);
|
|
23160
23208
|
}
|
|
23161
23209
|
}
|
|
23162
23210
|
}
|
|
23163
23211
|
function scaffoldClaudeMd(repoPath, projectName, force) {
|
|
23164
23212
|
const templatePath = resolveTemplateFile("CLAUDE.md.template");
|
|
23165
23213
|
if (!templatePath) return;
|
|
23166
|
-
const dest =
|
|
23167
|
-
if (
|
|
23214
|
+
const dest = path6.join(repoPath, "CLAUDE.md");
|
|
23215
|
+
if (fs6.existsSync(dest) && !force) return;
|
|
23168
23216
|
const displayName = projectName.replace(/_/g, " ");
|
|
23169
|
-
const content =
|
|
23170
|
-
|
|
23217
|
+
const content = fs6.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
|
|
23218
|
+
fs6.writeFileSync(dest, content);
|
|
23171
23219
|
}
|
|
23172
23220
|
function scaffoldHarnessBoilerplate(repoPath, options = {}) {
|
|
23173
|
-
const harnessDir =
|
|
23174
|
-
const projectName =
|
|
23221
|
+
const harnessDir = path6.join(repoPath, DEFAULT_HAR_DIR);
|
|
23222
|
+
const projectName = path6.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
23175
23223
|
const profile = options.profile ?? "default";
|
|
23176
|
-
const boilerplateDir =
|
|
23177
|
-
if (
|
|
23224
|
+
const boilerplateDir = path6.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
|
|
23225
|
+
if (fs6.existsSync(harnessDir) && !options.force) {
|
|
23178
23226
|
throw new Error(
|
|
23179
23227
|
'.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
|
|
23180
23228
|
);
|
|
23181
23229
|
}
|
|
23182
|
-
if (!
|
|
23230
|
+
if (!fs6.existsSync(boilerplateDir)) {
|
|
23183
23231
|
throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
|
|
23184
23232
|
}
|
|
23185
|
-
if (options.force &&
|
|
23186
|
-
|
|
23233
|
+
if (options.force && fs6.existsSync(harnessDir)) {
|
|
23234
|
+
fs6.rmSync(harnessDir, { recursive: true, force: true });
|
|
23187
23235
|
}
|
|
23188
23236
|
copyDirRecursive(boilerplateDir, harnessDir);
|
|
23189
23237
|
if (profile === "cli") {
|
|
23190
23238
|
pruneCliProfile(harnessDir);
|
|
23191
23239
|
}
|
|
23192
|
-
const harnessEnvPath =
|
|
23193
|
-
if (
|
|
23194
|
-
let content =
|
|
23240
|
+
const harnessEnvPath = path6.join(harnessDir, "harness.env");
|
|
23241
|
+
if (fs6.existsSync(harnessEnvPath)) {
|
|
23242
|
+
let content = fs6.readFileSync(harnessEnvPath, "utf8");
|
|
23195
23243
|
content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
23196
|
-
|
|
23244
|
+
fs6.writeFileSync(harnessEnvPath, content);
|
|
23197
23245
|
}
|
|
23198
23246
|
const manifest = createManifest(
|
|
23199
23247
|
repoPath,
|
|
@@ -23216,8 +23264,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
|
|
|
23216
23264
|
}
|
|
23217
23265
|
|
|
23218
23266
|
// src/llm/authoring-agent.ts
|
|
23219
|
-
var
|
|
23220
|
-
var
|
|
23267
|
+
var fs10 = __toESM(require("fs"));
|
|
23268
|
+
var path9 = __toESM(require("path"));
|
|
23221
23269
|
|
|
23222
23270
|
// node_modules/@anthropic-ai/sdk/error.mjs
|
|
23223
23271
|
var error_exports = {};
|
|
@@ -23616,13 +23664,13 @@ var MultipartBody = class {
|
|
|
23616
23664
|
// node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
|
|
23617
23665
|
var import_web = require("node:stream/web");
|
|
23618
23666
|
var fileFromPathWarned = false;
|
|
23619
|
-
async function fileFromPath3(
|
|
23667
|
+
async function fileFromPath3(path37, ...args) {
|
|
23620
23668
|
const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
|
|
23621
23669
|
if (!fileFromPathWarned) {
|
|
23622
|
-
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(
|
|
23670
|
+
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path37)}) instead`);
|
|
23623
23671
|
fileFromPathWarned = true;
|
|
23624
23672
|
}
|
|
23625
|
-
return await _fileFromPath(
|
|
23673
|
+
return await _fileFromPath(path37, ...args);
|
|
23626
23674
|
}
|
|
23627
23675
|
var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
23628
23676
|
var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
@@ -24118,8 +24166,8 @@ async function defaultParseResponse(props) {
|
|
|
24118
24166
|
}
|
|
24119
24167
|
var APIPromise = class _APIPromise extends Promise {
|
|
24120
24168
|
constructor(responsePromise, parseResponse = defaultParseResponse) {
|
|
24121
|
-
super((
|
|
24122
|
-
|
|
24169
|
+
super((resolve26) => {
|
|
24170
|
+
resolve26(null);
|
|
24123
24171
|
});
|
|
24124
24172
|
this.responsePromise = responsePromise;
|
|
24125
24173
|
this.parseResponse = parseResponse;
|
|
@@ -24219,29 +24267,29 @@ var APIClient = class {
|
|
|
24219
24267
|
defaultIdempotencyKey() {
|
|
24220
24268
|
return `stainless-node-retry-${uuid4()}`;
|
|
24221
24269
|
}
|
|
24222
|
-
get(
|
|
24223
|
-
return this.methodRequest("get",
|
|
24270
|
+
get(path37, opts) {
|
|
24271
|
+
return this.methodRequest("get", path37, opts);
|
|
24224
24272
|
}
|
|
24225
|
-
post(
|
|
24226
|
-
return this.methodRequest("post",
|
|
24273
|
+
post(path37, opts) {
|
|
24274
|
+
return this.methodRequest("post", path37, opts);
|
|
24227
24275
|
}
|
|
24228
|
-
patch(
|
|
24229
|
-
return this.methodRequest("patch",
|
|
24276
|
+
patch(path37, opts) {
|
|
24277
|
+
return this.methodRequest("patch", path37, opts);
|
|
24230
24278
|
}
|
|
24231
|
-
put(
|
|
24232
|
-
return this.methodRequest("put",
|
|
24279
|
+
put(path37, opts) {
|
|
24280
|
+
return this.methodRequest("put", path37, opts);
|
|
24233
24281
|
}
|
|
24234
|
-
delete(
|
|
24235
|
-
return this.methodRequest("delete",
|
|
24282
|
+
delete(path37, opts) {
|
|
24283
|
+
return this.methodRequest("delete", path37, opts);
|
|
24236
24284
|
}
|
|
24237
|
-
methodRequest(method,
|
|
24285
|
+
methodRequest(method, path37, opts) {
|
|
24238
24286
|
return this.request(Promise.resolve(opts).then(async (opts2) => {
|
|
24239
24287
|
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;
|
|
24240
|
-
return { method, path:
|
|
24288
|
+
return { method, path: path37, ...opts2, body };
|
|
24241
24289
|
}));
|
|
24242
24290
|
}
|
|
24243
|
-
getAPIList(
|
|
24244
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
24291
|
+
getAPIList(path37, Page, opts) {
|
|
24292
|
+
return this.requestAPIList(Page, { method: "get", path: path37, ...opts });
|
|
24245
24293
|
}
|
|
24246
24294
|
calculateContentLength(body) {
|
|
24247
24295
|
if (typeof body === "string") {
|
|
@@ -24259,10 +24307,10 @@ var APIClient = class {
|
|
|
24259
24307
|
return null;
|
|
24260
24308
|
}
|
|
24261
24309
|
buildRequest(options) {
|
|
24262
|
-
const { method, path:
|
|
24310
|
+
const { method, path: path37, query, headers = {} } = options;
|
|
24263
24311
|
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;
|
|
24264
24312
|
const contentLength = this.calculateContentLength(body);
|
|
24265
|
-
const url = this.buildURL(
|
|
24313
|
+
const url = this.buildURL(path37, query);
|
|
24266
24314
|
if ("timeout" in options)
|
|
24267
24315
|
validatePositiveInteger("timeout", options.timeout);
|
|
24268
24316
|
const timeout = options.timeout ?? this.timeout;
|
|
@@ -24371,8 +24419,8 @@ var APIClient = class {
|
|
|
24371
24419
|
const request = this.makeRequest(options, null);
|
|
24372
24420
|
return new PagePromise(this, request, Page);
|
|
24373
24421
|
}
|
|
24374
|
-
buildURL(
|
|
24375
|
-
const url = isAbsoluteURL(
|
|
24422
|
+
buildURL(path37, query) {
|
|
24423
|
+
const url = isAbsoluteURL(path37) ? new URL(path37) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path37.startsWith("/") ? path37.slice(1) : path37));
|
|
24376
24424
|
const defaultQuery = this.defaultQuery();
|
|
24377
24425
|
if (!isEmptyObj(defaultQuery)) {
|
|
24378
24426
|
query = { ...defaultQuery, ...query };
|
|
@@ -24657,7 +24705,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
|
|
|
24657
24705
|
var isAbsoluteURL = (url) => {
|
|
24658
24706
|
return startsWithSchemeRegexp.test(url);
|
|
24659
24707
|
};
|
|
24660
|
-
var sleep = (ms) => new Promise((
|
|
24708
|
+
var sleep = (ms) => new Promise((resolve26) => setTimeout(resolve26, ms));
|
|
24661
24709
|
var validatePositiveInteger = (name, n2) => {
|
|
24662
24710
|
if (typeof n2 !== "number" || !Number.isInteger(n2)) {
|
|
24663
24711
|
throw new AnthropicError(`${name} must be an integer`);
|
|
@@ -25148,12 +25196,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25148
25196
|
}
|
|
25149
25197
|
return this._emit("error", new AnthropicError(String(error3)));
|
|
25150
25198
|
});
|
|
25151
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((
|
|
25152
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise,
|
|
25199
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve26, reject) => {
|
|
25200
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve26, "f");
|
|
25153
25201
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
|
|
25154
25202
|
}), "f");
|
|
25155
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((
|
|
25156
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise,
|
|
25203
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve26, reject) => {
|
|
25204
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve26, "f");
|
|
25157
25205
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
|
|
25158
25206
|
}), "f");
|
|
25159
25207
|
__classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -25282,11 +25330,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25282
25330
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
25283
25331
|
*/
|
|
25284
25332
|
emitted(event) {
|
|
25285
|
-
return new Promise((
|
|
25333
|
+
return new Promise((resolve26, reject) => {
|
|
25286
25334
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
|
|
25287
25335
|
if (event !== "error")
|
|
25288
25336
|
this.once("error", reject);
|
|
25289
|
-
this.once(event,
|
|
25337
|
+
this.once(event, resolve26);
|
|
25290
25338
|
});
|
|
25291
25339
|
}
|
|
25292
25340
|
async done() {
|
|
@@ -25513,7 +25561,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25513
25561
|
if (done) {
|
|
25514
25562
|
return { value: void 0, done: true };
|
|
25515
25563
|
}
|
|
25516
|
-
return new Promise((
|
|
25564
|
+
return new Promise((resolve26, reject) => readQueue.push({ resolve: resolve26, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
25517
25565
|
}
|
|
25518
25566
|
const chunk = pushQueue.shift();
|
|
25519
25567
|
return { value: chunk, done: false };
|
|
@@ -25661,12 +25709,12 @@ var MessageStream = class _MessageStream {
|
|
|
25661
25709
|
}
|
|
25662
25710
|
return this._emit("error", new AnthropicError(String(error3)));
|
|
25663
25711
|
});
|
|
25664
|
-
__classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((
|
|
25665
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise,
|
|
25712
|
+
__classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve26, reject) => {
|
|
25713
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve26, "f");
|
|
25666
25714
|
__classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
|
|
25667
25715
|
}), "f");
|
|
25668
|
-
__classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((
|
|
25669
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise,
|
|
25716
|
+
__classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve26, reject) => {
|
|
25717
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve26, "f");
|
|
25670
25718
|
__classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
|
|
25671
25719
|
}), "f");
|
|
25672
25720
|
__classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -25795,11 +25843,11 @@ var MessageStream = class _MessageStream {
|
|
|
25795
25843
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
25796
25844
|
*/
|
|
25797
25845
|
emitted(event) {
|
|
25798
|
-
return new Promise((
|
|
25846
|
+
return new Promise((resolve26, reject) => {
|
|
25799
25847
|
__classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
|
|
25800
25848
|
if (event !== "error")
|
|
25801
25849
|
this.once("error", reject);
|
|
25802
|
-
this.once(event,
|
|
25850
|
+
this.once(event, resolve26);
|
|
25803
25851
|
});
|
|
25804
25852
|
}
|
|
25805
25853
|
async done() {
|
|
@@ -26026,7 +26074,7 @@ var MessageStream = class _MessageStream {
|
|
|
26026
26074
|
if (done) {
|
|
26027
26075
|
return { value: void 0, done: true };
|
|
26028
26076
|
}
|
|
26029
|
-
return new Promise((
|
|
26077
|
+
return new Promise((resolve26, reject) => readQueue.push({ resolve: resolve26, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
26030
26078
|
}
|
|
26031
26079
|
const chunk = pushQueue.shift();
|
|
26032
26080
|
return { value: chunk, done: false };
|
|
@@ -26195,34 +26243,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
|
|
|
26195
26243
|
var sdk_default = Anthropic;
|
|
26196
26244
|
|
|
26197
26245
|
// src/llm/tools.ts
|
|
26198
|
-
var
|
|
26199
|
-
var
|
|
26246
|
+
var fs9 = __toESM(require("fs"));
|
|
26247
|
+
var path8 = __toESM(require("path"));
|
|
26200
26248
|
var readline2 = __toESM(require("readline"));
|
|
26201
26249
|
|
|
26202
26250
|
// src/harness/agent-md.ts
|
|
26203
|
-
var
|
|
26204
|
-
var
|
|
26251
|
+
var fs8 = __toESM(require("fs"));
|
|
26252
|
+
var path7 = __toESM(require("path"));
|
|
26205
26253
|
var readline = __toESM(require("readline"));
|
|
26206
26254
|
var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
|
|
26207
26255
|
var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
|
|
26208
26256
|
function writeAgentMdProposal(repoPath, content, rationale) {
|
|
26209
26257
|
const harnessDir = getHarnessDir(repoPath);
|
|
26210
|
-
writeFileSafe(
|
|
26258
|
+
writeFileSafe(path7.join(harnessDir, AGENT_MD_PROPOSAL), content);
|
|
26211
26259
|
writeFileSafe(
|
|
26212
|
-
|
|
26260
|
+
path7.join(harnessDir, AGENT_MD_PROPOSAL_META),
|
|
26213
26261
|
JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
26214
26262
|
);
|
|
26215
26263
|
}
|
|
26216
26264
|
function readAgentMdProposal(repoPath) {
|
|
26217
26265
|
const harnessDir = getHarnessDir(repoPath);
|
|
26218
|
-
const proposalPath =
|
|
26219
|
-
const metaPath =
|
|
26220
|
-
if (!
|
|
26221
|
-
const content =
|
|
26266
|
+
const proposalPath = path7.join(harnessDir, AGENT_MD_PROPOSAL);
|
|
26267
|
+
const metaPath = path7.join(harnessDir, AGENT_MD_PROPOSAL_META);
|
|
26268
|
+
if (!fs8.existsSync(proposalPath)) return null;
|
|
26269
|
+
const content = fs8.readFileSync(proposalPath, "utf8");
|
|
26222
26270
|
let rationale = "";
|
|
26223
|
-
if (
|
|
26271
|
+
if (fs8.existsSync(metaPath)) {
|
|
26224
26272
|
try {
|
|
26225
|
-
rationale = JSON.parse(
|
|
26273
|
+
rationale = JSON.parse(fs8.readFileSync(metaPath, "utf8")).rationale ?? "";
|
|
26226
26274
|
} catch {
|
|
26227
26275
|
rationale = "";
|
|
26228
26276
|
}
|
|
@@ -26230,21 +26278,21 @@ function readAgentMdProposal(repoPath) {
|
|
|
26230
26278
|
return {
|
|
26231
26279
|
content,
|
|
26232
26280
|
rationale,
|
|
26233
|
-
createdAt:
|
|
26281
|
+
createdAt: fs8.existsSync(metaPath) ? JSON.parse(fs8.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
26234
26282
|
};
|
|
26235
26283
|
}
|
|
26236
26284
|
function clearAgentMdProposal(repoPath) {
|
|
26237
26285
|
const harnessDir = getHarnessDir(repoPath);
|
|
26238
26286
|
for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
|
|
26239
|
-
const p2 =
|
|
26240
|
-
if (
|
|
26287
|
+
const p2 = path7.join(harnessDir, file);
|
|
26288
|
+
if (fs8.existsSync(p2)) fs8.unlinkSync(p2);
|
|
26241
26289
|
}
|
|
26242
26290
|
}
|
|
26243
26291
|
async function promptApplyAgentMdProposal(repoPath) {
|
|
26244
26292
|
const proposal = readAgentMdProposal(repoPath);
|
|
26245
26293
|
if (!proposal) return false;
|
|
26246
|
-
const agentMdPath =
|
|
26247
|
-
const exists =
|
|
26294
|
+
const agentMdPath = path7.join(repoPath, "AGENT.md");
|
|
26295
|
+
const exists = fs8.existsSync(agentMdPath);
|
|
26248
26296
|
process.stderr.write("\n");
|
|
26249
26297
|
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");
|
|
26250
26298
|
process.stderr.write("Proposed AGENT.md (repo root)\n");
|
|
@@ -26286,11 +26334,11 @@ async function promptApplyAgentMdProposal(repoPath) {
|
|
|
26286
26334
|
}
|
|
26287
26335
|
async function askYesNo(question) {
|
|
26288
26336
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
26289
|
-
return new Promise((
|
|
26337
|
+
return new Promise((resolve26) => {
|
|
26290
26338
|
process.stderr.write(`${question} `);
|
|
26291
26339
|
rl.once("line", (answer) => {
|
|
26292
26340
|
rl.close();
|
|
26293
|
-
|
|
26341
|
+
resolve26(/^y(es)?$/i.test(answer.trim()));
|
|
26294
26342
|
});
|
|
26295
26343
|
});
|
|
26296
26344
|
}
|
|
@@ -26432,13 +26480,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
|
|
|
26432
26480
|
async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
|
|
26433
26481
|
switch (toolName) {
|
|
26434
26482
|
case "readRepoFile": {
|
|
26435
|
-
const filePath =
|
|
26483
|
+
const filePath = path8.join(repoPath, input.path);
|
|
26436
26484
|
const content = readFile(filePath, input.maxChars ?? 8e3);
|
|
26437
26485
|
info(`Read repo: ${input.path}`);
|
|
26438
26486
|
return content;
|
|
26439
26487
|
}
|
|
26440
26488
|
case "listRepoDir": {
|
|
26441
|
-
const dirPath =
|
|
26489
|
+
const dirPath = path8.join(repoPath, input.path ?? ".");
|
|
26442
26490
|
const entries = listDir(dirPath, input.maxFiles ?? 60);
|
|
26443
26491
|
info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
|
|
26444
26492
|
return entries.length > 0 ? entries.join("\n") : "(empty directory)";
|
|
@@ -26463,17 +26511,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26463
26511
|
const filePath = resolveSafePath(harnessDir, relPath);
|
|
26464
26512
|
writeFileSafe(filePath, input.content);
|
|
26465
26513
|
if (relPath.endsWith(".sh")) {
|
|
26466
|
-
|
|
26514
|
+
fs9.chmodSync(filePath, 493);
|
|
26467
26515
|
}
|
|
26468
26516
|
info(`Wrote harness: ${relPath}`);
|
|
26469
26517
|
return `Written ${relPath} (${input.content.length} bytes)`;
|
|
26470
26518
|
}
|
|
26471
26519
|
case "editHarnessFile": {
|
|
26472
26520
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26473
|
-
if (!
|
|
26521
|
+
if (!fs9.existsSync(filePath)) {
|
|
26474
26522
|
return `Error: file not found: ${input.path}`;
|
|
26475
26523
|
}
|
|
26476
|
-
const content =
|
|
26524
|
+
const content = fs9.readFileSync(filePath, "utf8");
|
|
26477
26525
|
const oldStr = input.old_string;
|
|
26478
26526
|
const newStr = input.new_string;
|
|
26479
26527
|
const count = content.split(oldStr).length - 1;
|
|
@@ -26490,10 +26538,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26490
26538
|
}
|
|
26491
26539
|
case "deleteHarnessFile": {
|
|
26492
26540
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26493
|
-
if (!
|
|
26541
|
+
if (!fs9.existsSync(filePath)) {
|
|
26494
26542
|
return `File not found (already deleted): ${input.path}`;
|
|
26495
26543
|
}
|
|
26496
|
-
|
|
26544
|
+
fs9.unlinkSync(filePath);
|
|
26497
26545
|
info(`Deleted harness: ${input.path}`);
|
|
26498
26546
|
return `Deleted ${input.path}`;
|
|
26499
26547
|
}
|
|
@@ -26520,7 +26568,7 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26520
26568
|
}
|
|
26521
26569
|
async function askUser(question, options) {
|
|
26522
26570
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
|
|
26523
|
-
return new Promise((
|
|
26571
|
+
return new Promise((resolve26) => {
|
|
26524
26572
|
process.stderr.write("\n");
|
|
26525
26573
|
process.stderr.write(`\u2753 ${question}
|
|
26526
26574
|
`);
|
|
@@ -26531,7 +26579,7 @@ async function askUser(question, options) {
|
|
|
26531
26579
|
process.stderr.write("> ");
|
|
26532
26580
|
rl.once("line", (answer) => {
|
|
26533
26581
|
rl.close();
|
|
26534
|
-
|
|
26582
|
+
resolve26(answer.trim());
|
|
26535
26583
|
});
|
|
26536
26584
|
});
|
|
26537
26585
|
}
|
|
@@ -26544,29 +26592,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
|
|
|
26544
26592
|
const mode = options.mode ?? "init";
|
|
26545
26593
|
const systemPromptPath = resolvePromptPath("system-authoring.md");
|
|
26546
26594
|
const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
|
|
26547
|
-
let systemPrompt =
|
|
26595
|
+
let systemPrompt = fs10.existsSync(systemPromptPath) ? fs10.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
|
|
26548
26596
|
if (agentMdTemplatePath) {
|
|
26549
26597
|
systemPrompt += `
|
|
26550
26598
|
|
|
26551
26599
|
## AGENT.md template (starting point for proposeAgentMd)
|
|
26552
26600
|
|
|
26553
|
-
${
|
|
26601
|
+
${fs10.readFileSync(agentMdTemplatePath, "utf8")}`;
|
|
26554
26602
|
}
|
|
26555
26603
|
const tools = buildAuthoringTools();
|
|
26556
26604
|
const messages = [];
|
|
26557
26605
|
let rootListing;
|
|
26558
26606
|
try {
|
|
26559
|
-
rootListing =
|
|
26607
|
+
rootListing = fs10.readdirSync(repoPath).slice(0, 50).join("\n");
|
|
26560
26608
|
} catch {
|
|
26561
26609
|
throw new Error(`Cannot read repo at path: ${repoPath}`);
|
|
26562
26610
|
}
|
|
26563
26611
|
let harnessListing;
|
|
26564
26612
|
try {
|
|
26565
|
-
harnessListing =
|
|
26613
|
+
harnessListing = fs10.readdirSync(harnessDir).join("\n");
|
|
26566
26614
|
} catch {
|
|
26567
26615
|
throw new Error(`Cannot read harness dir at: ${harnessDir}`);
|
|
26568
26616
|
}
|
|
26569
|
-
const existingAgentMd =
|
|
26617
|
+
const existingAgentMd = fs10.existsSync(path9.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.";
|
|
26570
26618
|
const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
|
|
26571
26619
|
Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
|
|
26572
26620
|
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/.
|
|
@@ -26646,8 +26694,8 @@ Try running with --verbose to see what the agent is doing.`
|
|
|
26646
26694
|
}
|
|
26647
26695
|
|
|
26648
26696
|
// src/harness/validator.ts
|
|
26649
|
-
var
|
|
26650
|
-
var
|
|
26697
|
+
var fs13 = __toESM(require("fs"));
|
|
26698
|
+
var path12 = __toESM(require("path"));
|
|
26651
26699
|
|
|
26652
26700
|
// src/utils/shell.ts
|
|
26653
26701
|
var import_child_process = require("child_process");
|
|
@@ -26675,7 +26723,7 @@ function runScript(scriptPath, args = [], options = {}) {
|
|
|
26675
26723
|
function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
26676
26724
|
const stream = options.stream ?? false;
|
|
26677
26725
|
const { stream: _stream, ...spawnOptions } = options;
|
|
26678
|
-
return new Promise((
|
|
26726
|
+
return new Promise((resolve26) => {
|
|
26679
26727
|
const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
|
|
26680
26728
|
...spawnOptions,
|
|
26681
26729
|
stdio: ["inherit", "pipe", "pipe"]
|
|
@@ -26690,13 +26738,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
|
26690
26738
|
stderr += d2;
|
|
26691
26739
|
if (stream) process.stderr.write(d2);
|
|
26692
26740
|
});
|
|
26693
|
-
proc.on("close", (code) =>
|
|
26741
|
+
proc.on("close", (code) => resolve26({ stdout, stderr, code: code ?? 0 }));
|
|
26694
26742
|
});
|
|
26695
26743
|
}
|
|
26696
26744
|
function runShellCommand(command2, options = {}) {
|
|
26697
26745
|
const stream = options.stream ?? false;
|
|
26698
26746
|
const { stream: _stream, ...spawnOptions } = options;
|
|
26699
|
-
return new Promise((
|
|
26747
|
+
return new Promise((resolve26) => {
|
|
26700
26748
|
const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
|
|
26701
26749
|
...spawnOptions,
|
|
26702
26750
|
stdio: ["inherit", "pipe", "pipe"]
|
|
@@ -26711,22 +26759,22 @@ function runShellCommand(command2, options = {}) {
|
|
|
26711
26759
|
stderr += d2;
|
|
26712
26760
|
if (stream) process.stderr.write(d2);
|
|
26713
26761
|
});
|
|
26714
|
-
proc.on("close", (code) =>
|
|
26762
|
+
proc.on("close", (code) => resolve26({ stdout, stderr, code: code ?? 0 }));
|
|
26715
26763
|
});
|
|
26716
26764
|
}
|
|
26717
26765
|
|
|
26718
26766
|
// src/harness/stages.ts
|
|
26719
|
-
var
|
|
26720
|
-
var
|
|
26767
|
+
var fs12 = __toESM(require("fs"));
|
|
26768
|
+
var path11 = __toESM(require("path"));
|
|
26721
26769
|
|
|
26722
26770
|
// src/harness/env.ts
|
|
26723
|
-
var
|
|
26724
|
-
var
|
|
26771
|
+
var fs11 = __toESM(require("fs"));
|
|
26772
|
+
var path10 = __toESM(require("path"));
|
|
26725
26773
|
function readHarnessEnv(repoPath) {
|
|
26726
|
-
const envPath =
|
|
26727
|
-
if (!
|
|
26774
|
+
const envPath = path10.join(getHarnessDir(repoPath), "harness.env");
|
|
26775
|
+
if (!fs11.existsSync(envPath)) return {};
|
|
26728
26776
|
const values = {};
|
|
26729
|
-
for (const line of
|
|
26777
|
+
for (const line of fs11.readFileSync(envPath, "utf8").split("\n")) {
|
|
26730
26778
|
const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
|
|
26731
26779
|
if (!match) continue;
|
|
26732
26780
|
values[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
@@ -26753,7 +26801,7 @@ var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
|
|
|
26753
26801
|
"custom"
|
|
26754
26802
|
]);
|
|
26755
26803
|
function getStageRegistryPath(repoPath) {
|
|
26756
|
-
return
|
|
26804
|
+
return path11.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
|
|
26757
26805
|
}
|
|
26758
26806
|
function synthesizeStageRegistry(repoPath) {
|
|
26759
26807
|
const harnessDir = getHarnessDir(repoPath);
|
|
@@ -26828,13 +26876,13 @@ function readAgentSlotsFromHarnessEnv(harnessEnv) {
|
|
|
26828
26876
|
}
|
|
26829
26877
|
function stageScriptExists(harnessDir, stage) {
|
|
26830
26878
|
if (stage.script) {
|
|
26831
|
-
return
|
|
26879
|
+
return fs12.existsSync(path11.join(harnessDir, stage.script));
|
|
26832
26880
|
}
|
|
26833
26881
|
if (stage.command) {
|
|
26834
26882
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
26835
|
-
return
|
|
26883
|
+
return fs12.existsSync(path11.join(harnessDir, scriptName));
|
|
26836
26884
|
}
|
|
26837
|
-
return
|
|
26885
|
+
return fs12.existsSync(path11.join(harnessDir, "stages", `${stage.id}.sh`));
|
|
26838
26886
|
}
|
|
26839
26887
|
function addStageIfRunnable(stages, harnessDir, stage) {
|
|
26840
26888
|
const parsed = HarnessStageSchema.parse(stage);
|
|
@@ -26844,10 +26892,10 @@ function addStageIfRunnable(stages, harnessDir, stage) {
|
|
|
26844
26892
|
}
|
|
26845
26893
|
function readStageRegistry(repoPath) {
|
|
26846
26894
|
const registryPath = getStageRegistryPath(repoPath);
|
|
26847
|
-
if (!
|
|
26895
|
+
if (!fs12.existsSync(registryPath)) {
|
|
26848
26896
|
return synthesizeStageRegistry(repoPath);
|
|
26849
26897
|
}
|
|
26850
|
-
const raw = JSON.parse(
|
|
26898
|
+
const raw = JSON.parse(fs12.readFileSync(registryPath, "utf8"));
|
|
26851
26899
|
const parsed = HarnessStageRegistrySchema.safeParse(raw);
|
|
26852
26900
|
if (!parsed.success) {
|
|
26853
26901
|
throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
|
|
@@ -26860,7 +26908,7 @@ function writeStageRegistry(repoPath, registry2) {
|
|
|
26860
26908
|
if (!parsed.success) {
|
|
26861
26909
|
throw new Error(`Invalid stage registry: ${parsed.error.message}`);
|
|
26862
26910
|
}
|
|
26863
|
-
|
|
26911
|
+
fs12.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
|
|
26864
26912
|
}
|
|
26865
26913
|
function listStages(repoPath) {
|
|
26866
26914
|
return readStageRegistry(repoPath).stages;
|
|
@@ -26966,22 +27014,22 @@ var SHELL_SCRIPTS = [
|
|
|
26966
27014
|
function validateHarness(repoPath) {
|
|
26967
27015
|
const harnessDir = getHarnessDir(repoPath);
|
|
26968
27016
|
const issues = [];
|
|
26969
|
-
if (!
|
|
27017
|
+
if (!fs13.existsSync(harnessDir)) {
|
|
26970
27018
|
return {
|
|
26971
27019
|
pass: false,
|
|
26972
27020
|
issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
|
|
26973
27021
|
};
|
|
26974
27022
|
}
|
|
26975
27023
|
for (const file of getRequiredFiles(repoPath)) {
|
|
26976
|
-
const filePath =
|
|
26977
|
-
if (!
|
|
27024
|
+
const filePath = path12.join(harnessDir, file);
|
|
27025
|
+
if (!fs13.existsSync(filePath)) {
|
|
26978
27026
|
issues.push({ file, message: "Required file missing", severity: "error" });
|
|
26979
27027
|
}
|
|
26980
27028
|
}
|
|
26981
27029
|
for (const script of SHELL_SCRIPTS) {
|
|
26982
|
-
const scriptPath =
|
|
26983
|
-
if (!
|
|
26984
|
-
const stat =
|
|
27030
|
+
const scriptPath = path12.join(harnessDir, script);
|
|
27031
|
+
if (!fs13.existsSync(scriptPath)) continue;
|
|
27032
|
+
const stat = fs13.statSync(scriptPath);
|
|
26985
27033
|
if (!(stat.mode & 73)) {
|
|
26986
27034
|
issues.push({ file: script, message: "Script is not executable", severity: "warning" });
|
|
26987
27035
|
}
|
|
@@ -26996,9 +27044,9 @@ function validateHarness(repoPath) {
|
|
|
26996
27044
|
}
|
|
26997
27045
|
const manifest = readManifest(repoPath);
|
|
26998
27046
|
const profile = manifest?.profile ?? "default";
|
|
26999
|
-
const ecosystemPath =
|
|
27000
|
-
if (profile !== "cli" &&
|
|
27001
|
-
const content =
|
|
27047
|
+
const ecosystemPath = path12.join(harnessDir, "ecosystem.agent.template.cjs");
|
|
27048
|
+
if (profile !== "cli" && fs13.existsSync(ecosystemPath)) {
|
|
27049
|
+
const content = fs13.readFileSync(ecosystemPath, "utf8");
|
|
27002
27050
|
if (!content.includes("module.exports")) {
|
|
27003
27051
|
issues.push({
|
|
27004
27052
|
file: "ecosystem.agent.template.cjs",
|
|
@@ -27007,9 +27055,9 @@ function validateHarness(repoPath) {
|
|
|
27007
27055
|
});
|
|
27008
27056
|
}
|
|
27009
27057
|
}
|
|
27010
|
-
const harnessEnvPath =
|
|
27011
|
-
if (
|
|
27012
|
-
const content =
|
|
27058
|
+
const harnessEnvPath = path12.join(harnessDir, "harness.env");
|
|
27059
|
+
if (fs13.existsSync(harnessEnvPath)) {
|
|
27060
|
+
const content = fs13.readFileSync(harnessEnvPath, "utf8");
|
|
27013
27061
|
if (content.includes("TODO: set migrate command")) {
|
|
27014
27062
|
issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
|
|
27015
27063
|
}
|
|
@@ -27017,17 +27065,17 @@ function validateHarness(repoPath) {
|
|
|
27017
27065
|
issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
|
|
27018
27066
|
}
|
|
27019
27067
|
}
|
|
27020
|
-
const verifyPath =
|
|
27021
|
-
if (
|
|
27022
|
-
const content =
|
|
27068
|
+
const verifyPath = path12.join(harnessDir, "verify.sh");
|
|
27069
|
+
if (fs13.existsSync(verifyPath)) {
|
|
27070
|
+
const content = fs13.readFileSync(verifyPath, "utf8");
|
|
27023
27071
|
if (content.includes("echo 'TODO:")) {
|
|
27024
27072
|
issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
|
|
27025
27073
|
}
|
|
27026
27074
|
}
|
|
27027
|
-
const stagesPath =
|
|
27028
|
-
if (
|
|
27075
|
+
const stagesPath = path12.join(harnessDir, "stages.json");
|
|
27076
|
+
if (fs13.existsSync(stagesPath)) {
|
|
27029
27077
|
try {
|
|
27030
|
-
JSON.parse(
|
|
27078
|
+
JSON.parse(fs13.readFileSync(stagesPath, "utf8"));
|
|
27031
27079
|
const registry2 = readStageRegistry(repoPath);
|
|
27032
27080
|
if (registry2.stages.length === 0) {
|
|
27033
27081
|
issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
|
|
@@ -27042,9 +27090,9 @@ function validateHarness(repoPath) {
|
|
|
27042
27090
|
} else {
|
|
27043
27091
|
issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
|
|
27044
27092
|
}
|
|
27045
|
-
const readmePath =
|
|
27046
|
-
if (
|
|
27047
|
-
const content =
|
|
27093
|
+
const readmePath = path12.join(harnessDir, "README.md");
|
|
27094
|
+
if (fs13.existsSync(readmePath)) {
|
|
27095
|
+
const content = fs13.readFileSync(readmePath, "utf8");
|
|
27048
27096
|
if (!content.includes(".har")) {
|
|
27049
27097
|
issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
|
|
27050
27098
|
}
|
|
@@ -27058,8 +27106,8 @@ function validateHarness(repoPath) {
|
|
|
27058
27106
|
async function smokeTestHarness(repoPath) {
|
|
27059
27107
|
const harnessDir = getHarnessDir(repoPath);
|
|
27060
27108
|
const issues = [];
|
|
27061
|
-
const setupScript =
|
|
27062
|
-
if (
|
|
27109
|
+
const setupScript = path12.join(harnessDir, "setup-infra.sh");
|
|
27110
|
+
if (fs13.existsSync(setupScript)) {
|
|
27063
27111
|
const result = run(`bash "${setupScript}"`, { cwd: repoPath });
|
|
27064
27112
|
if (result.code !== 0) {
|
|
27065
27113
|
issues.push({
|
|
@@ -27074,8 +27122,8 @@ async function smokeTestHarness(repoPath) {
|
|
|
27074
27122
|
}
|
|
27075
27123
|
|
|
27076
27124
|
// src/harness/drift.ts
|
|
27077
|
-
var
|
|
27078
|
-
var
|
|
27125
|
+
var fs14 = __toESM(require("fs"));
|
|
27126
|
+
var path13 = __toESM(require("path"));
|
|
27079
27127
|
var PROFILE_DIRS2 = {
|
|
27080
27128
|
default: "har-boilerplate",
|
|
27081
27129
|
cli: "har-boilerplate-cli",
|
|
@@ -27086,37 +27134,90 @@ var CLI_EXPECTED_ABSENT = /* @__PURE__ */ new Set([
|
|
|
27086
27134
|
"env.template",
|
|
27087
27135
|
"attach.sh"
|
|
27088
27136
|
]);
|
|
27137
|
+
var APP_PORT_VARS = [
|
|
27138
|
+
"HARNESS_FE_BASE_PORT",
|
|
27139
|
+
"HARNESS_API_BASE_PORT",
|
|
27140
|
+
"HARNESS_PORT_STEP"
|
|
27141
|
+
];
|
|
27142
|
+
var INFRA_PORT_VARS_BY_SERVICE = {
|
|
27143
|
+
db: [
|
|
27144
|
+
"HARNESS_DB_PORT_DEFAULT",
|
|
27145
|
+
"HARNESS_DB_PORT_SCAN_START",
|
|
27146
|
+
"HARNESS_DB_PORT_SCAN_END"
|
|
27147
|
+
],
|
|
27148
|
+
minio: [
|
|
27149
|
+
"HARNESS_MINIO_PORT_DEFAULT",
|
|
27150
|
+
"HARNESS_MINIO_PORT_SCAN_START",
|
|
27151
|
+
"HARNESS_MINIO_PORT_SCAN_END",
|
|
27152
|
+
"HARNESS_MINIO_CONSOLE_PORT_DEFAULT",
|
|
27153
|
+
"HARNESS_MINIO_CONSOLE_PORT_SCAN_START",
|
|
27154
|
+
"HARNESS_MINIO_CONSOLE_PORT_SCAN_END"
|
|
27155
|
+
],
|
|
27156
|
+
mailpit: [
|
|
27157
|
+
"HARNESS_MAILPIT_WEB_PORT_DEFAULT",
|
|
27158
|
+
"HARNESS_MAILPIT_WEB_PORT_SCAN_START",
|
|
27159
|
+
"HARNESS_MAILPIT_WEB_PORT_SCAN_END",
|
|
27160
|
+
"HARNESS_MAILPIT_SMTP_PORT_DEFAULT",
|
|
27161
|
+
"HARNESS_MAILPIT_SMTP_PORT_SCAN_START",
|
|
27162
|
+
"HARNESS_MAILPIT_SMTP_PORT_SCAN_END"
|
|
27163
|
+
],
|
|
27164
|
+
"headless-browser": [
|
|
27165
|
+
"HARNESS_BROWSER_PORT_DEFAULT",
|
|
27166
|
+
"HARNESS_BROWSER_PORT_SCAN_START",
|
|
27167
|
+
"HARNESS_BROWSER_PORT_SCAN_END"
|
|
27168
|
+
]
|
|
27169
|
+
};
|
|
27170
|
+
function missingPortDocumentationVars(profile, env3) {
|
|
27171
|
+
const missing = [];
|
|
27172
|
+
if (profile === "default") {
|
|
27173
|
+
for (const key of APP_PORT_VARS) {
|
|
27174
|
+
if (!(key in env3)) missing.push(key);
|
|
27175
|
+
}
|
|
27176
|
+
}
|
|
27177
|
+
if (profile === "cli" && !("HARNESS_PORT_STEP" in env3)) {
|
|
27178
|
+
missing.push("HARNESS_PORT_STEP");
|
|
27179
|
+
}
|
|
27180
|
+
const services = (env3.HARNESS_INFRA_SERVICES ?? "").trim().split(/\s+/).filter(Boolean);
|
|
27181
|
+
for (const service of services) {
|
|
27182
|
+
const vars = INFRA_PORT_VARS_BY_SERVICE[service];
|
|
27183
|
+
if (!vars) continue;
|
|
27184
|
+
for (const key of vars) {
|
|
27185
|
+
if (!(key in env3)) missing.push(key);
|
|
27186
|
+
}
|
|
27187
|
+
}
|
|
27188
|
+
return [...new Set(missing)];
|
|
27189
|
+
}
|
|
27089
27190
|
function substituteProjectName(content, projectName) {
|
|
27090
27191
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27091
27192
|
}
|
|
27092
27193
|
function listBoilerplateFiles(boilerplateDir) {
|
|
27093
|
-
if (!
|
|
27094
|
-
return
|
|
27194
|
+
if (!fs14.existsSync(boilerplateDir)) return [];
|
|
27195
|
+
return fs14.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
|
|
27095
27196
|
}
|
|
27096
27197
|
function compareHarnessToTemplate(repoPath) {
|
|
27097
|
-
const resolved =
|
|
27198
|
+
const resolved = path13.resolve(repoPath);
|
|
27098
27199
|
const manifest = readManifest(resolved);
|
|
27099
27200
|
const profile = manifest?.profile ?? "default";
|
|
27100
27201
|
const harnessDir = getHarnessDir(resolved);
|
|
27101
|
-
const projectName =
|
|
27102
|
-
const boilerplateDir =
|
|
27202
|
+
const projectName = path13.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27203
|
+
const boilerplateDir = path13.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
|
|
27103
27204
|
const templateFiles = listBoilerplateFiles(boilerplateDir);
|
|
27104
27205
|
const missing = [];
|
|
27105
27206
|
const checksumMismatch = [];
|
|
27106
27207
|
const extra = [];
|
|
27107
27208
|
const unchanged = [];
|
|
27108
27209
|
for (const file of templateFiles) {
|
|
27109
|
-
const templatePath =
|
|
27110
|
-
const harnessPath =
|
|
27111
|
-
let templateContent =
|
|
27210
|
+
const templatePath = path13.join(boilerplateDir, file);
|
|
27211
|
+
const harnessPath = path13.join(harnessDir, file);
|
|
27212
|
+
let templateContent = fs14.readFileSync(templatePath, "utf8");
|
|
27112
27213
|
if (file === "harness.env") {
|
|
27113
27214
|
templateContent = substituteProjectName(templateContent, projectName);
|
|
27114
27215
|
}
|
|
27115
|
-
if (!
|
|
27216
|
+
if (!fs14.existsSync(harnessPath)) {
|
|
27116
27217
|
missing.push(file);
|
|
27117
27218
|
continue;
|
|
27118
27219
|
}
|
|
27119
|
-
const harnessChecksum = computeFileChecksum(
|
|
27220
|
+
const harnessChecksum = computeFileChecksum(fs14.readFileSync(harnessPath, "utf8"));
|
|
27120
27221
|
const templateChecksum = computeFileChecksum(templateContent);
|
|
27121
27222
|
if (harnessChecksum === templateChecksum) {
|
|
27122
27223
|
unchanged.push(file);
|
|
@@ -27124,12 +27225,11 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27124
27225
|
checksumMismatch.push(file);
|
|
27125
27226
|
}
|
|
27126
27227
|
}
|
|
27127
|
-
if (
|
|
27128
|
-
for (const file of
|
|
27129
|
-
const full =
|
|
27130
|
-
if (!
|
|
27228
|
+
if (fs14.existsSync(harnessDir)) {
|
|
27229
|
+
for (const file of fs14.readdirSync(harnessDir)) {
|
|
27230
|
+
const full = path13.join(harnessDir, file);
|
|
27231
|
+
if (!fs14.statSync(full).isFile()) continue;
|
|
27131
27232
|
if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
|
|
27132
|
-
if (templateFiles.includes(file)) continue;
|
|
27133
27233
|
if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
|
|
27134
27234
|
extra.push(file);
|
|
27135
27235
|
} else if (!templateFiles.includes(file)) {
|
|
@@ -27138,6 +27238,8 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27138
27238
|
}
|
|
27139
27239
|
}
|
|
27140
27240
|
const installed = manifest?.generatorVersion;
|
|
27241
|
+
const harnessEnv = readHarnessEnv(resolved);
|
|
27242
|
+
const missingPortVars = missingPortDocumentationVars(profile, harnessEnv);
|
|
27141
27243
|
return {
|
|
27142
27244
|
generatorVersion: {
|
|
27143
27245
|
installed,
|
|
@@ -27147,71 +27249,355 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27147
27249
|
missing,
|
|
27148
27250
|
checksumMismatch,
|
|
27149
27251
|
extra,
|
|
27150
|
-
unchanged
|
|
27252
|
+
unchanged,
|
|
27253
|
+
missingPortVars
|
|
27151
27254
|
};
|
|
27152
27255
|
}
|
|
27153
27256
|
|
|
27154
|
-
// src/harness/
|
|
27257
|
+
// src/harness/maintain-bundle.ts
|
|
27155
27258
|
var fs15 = __toESM(require("fs"));
|
|
27156
|
-
var
|
|
27259
|
+
var os2 = __toESM(require("os"));
|
|
27260
|
+
var path14 = __toESM(require("path"));
|
|
27261
|
+
var MAINTAIN_DIR = "maintain";
|
|
27262
|
+
var PROFILE_DIRS3 = {
|
|
27263
|
+
default: "har-boilerplate",
|
|
27264
|
+
cli: "har-boilerplate-cli",
|
|
27265
|
+
ios: "har-boilerplate-ios"
|
|
27266
|
+
};
|
|
27267
|
+
function substituteProjectName2(content, projectName) {
|
|
27268
|
+
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27269
|
+
}
|
|
27270
|
+
function projectNameFromRepo(repoPath) {
|
|
27271
|
+
return path14.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27272
|
+
}
|
|
27273
|
+
function boilerplateDirForProfile(profile) {
|
|
27274
|
+
return path14.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
|
|
27275
|
+
}
|
|
27276
|
+
function readBundledTemplateContent(repoPath, profile, file) {
|
|
27277
|
+
const templatePath = path14.join(boilerplateDirForProfile(profile), file);
|
|
27278
|
+
let content = fs15.readFileSync(templatePath, "utf8");
|
|
27279
|
+
if (file === "harness.env") {
|
|
27280
|
+
content = substituteProjectName2(content, projectNameFromRepo(repoPath));
|
|
27281
|
+
}
|
|
27282
|
+
return content;
|
|
27283
|
+
}
|
|
27284
|
+
function staleFileHint(file) {
|
|
27285
|
+
if (file.endsWith(".sh")) {
|
|
27286
|
+
return "Legacy harness script \u2014 merge behavior into current templates, then delete.";
|
|
27287
|
+
}
|
|
27288
|
+
if (file.endsWith(".local") || file.includes(".env")) {
|
|
27289
|
+
return "Local override \u2014 move needed values into harness.env or delete if obsolete.";
|
|
27290
|
+
}
|
|
27291
|
+
return "Not in the current bundled template \u2014 review, merge, or delete.";
|
|
27292
|
+
}
|
|
27293
|
+
function actionHint(file, kind2) {
|
|
27294
|
+
if (kind2 === "missing") {
|
|
27295
|
+
if (file === "provision-toolchain.sh") {
|
|
27296
|
+
return "Add this file and ensure launch.sh calls it to provision the toolchain.";
|
|
27297
|
+
}
|
|
27298
|
+
return `Copy/adapt from maintain/templates/${file} into .har/${file}.`;
|
|
27299
|
+
}
|
|
27300
|
+
if (file === "verify.sh") {
|
|
27301
|
+
return "Merge template upgrades into .har/verify.sh \u2014 do not blind-overwrite repo-specific checks.";
|
|
27302
|
+
}
|
|
27303
|
+
if (file === "harness.env") {
|
|
27304
|
+
return "Merge template changes; keep repo-specific commands and add any missing port vars.";
|
|
27305
|
+
}
|
|
27306
|
+
return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
|
|
27307
|
+
}
|
|
27308
|
+
function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
|
|
27309
|
+
const tmpDir = fs15.mkdtempSync(path14.join(os2.tmpdir(), "har-maintain-diff-"));
|
|
27310
|
+
try {
|
|
27311
|
+
const installedPath = path14.join(tmpDir, "installed");
|
|
27312
|
+
const templatePath = path14.join(tmpDir, "template");
|
|
27313
|
+
fs15.writeFileSync(installedPath, installedContent);
|
|
27314
|
+
fs15.writeFileSync(templatePath, templateContent);
|
|
27315
|
+
const result = run(`diff -u "${installedPath}" "${templatePath}"`);
|
|
27316
|
+
const body = result.stdout.trim();
|
|
27317
|
+
if (!body) return "";
|
|
27318
|
+
return `--- ${installedLabel}
|
|
27319
|
+
+++ ${templateLabel}
|
|
27320
|
+
${body.split("\n").slice(2).join("\n")}
|
|
27321
|
+
`;
|
|
27322
|
+
} finally {
|
|
27323
|
+
fs15.rmSync(tmpDir, { recursive: true, force: true });
|
|
27324
|
+
}
|
|
27325
|
+
}
|
|
27326
|
+
function buildActions(repoPath, profile, drift) {
|
|
27327
|
+
const harnessDir = getHarnessDir(repoPath);
|
|
27328
|
+
const actions = [];
|
|
27329
|
+
for (const file of drift.missing) {
|
|
27330
|
+
actions.push({
|
|
27331
|
+
file,
|
|
27332
|
+
kind: "missing",
|
|
27333
|
+
template: `maintain/templates/${file}`,
|
|
27334
|
+
hint: actionHint(file, "missing")
|
|
27335
|
+
});
|
|
27336
|
+
}
|
|
27337
|
+
for (const file of drift.checksumMismatch) {
|
|
27338
|
+
const installedPath = path14.join(harnessDir, file);
|
|
27339
|
+
const installedRel = `maintain/installed/${file}`;
|
|
27340
|
+
const templateRel = `maintain/templates/${file}`;
|
|
27341
|
+
const diffRel = `maintain/diffs/${file}.diff`;
|
|
27342
|
+
actions.push({
|
|
27343
|
+
file,
|
|
27344
|
+
kind: "drift",
|
|
27345
|
+
template: templateRel,
|
|
27346
|
+
installed: fs15.existsSync(installedPath) ? installedRel : void 0,
|
|
27347
|
+
diff: diffRel,
|
|
27348
|
+
hint: actionHint(file, "drift")
|
|
27349
|
+
});
|
|
27350
|
+
}
|
|
27351
|
+
return actions.sort((a2, b2) => a2.file.localeCompare(b2.file));
|
|
27352
|
+
}
|
|
27353
|
+
function buildStale(drift) {
|
|
27354
|
+
return drift.extra.map((file) => ({ file, hint: staleFileHint(file) })).sort((a2, b2) => a2.file.localeCompare(b2.file));
|
|
27355
|
+
}
|
|
27356
|
+
function buildReadme(report) {
|
|
27357
|
+
const lines = [
|
|
27358
|
+
"# Harness maintenance bundle",
|
|
27359
|
+
"",
|
|
27360
|
+
`Generated by \`har env maintain\` on ${report.generatedAt}.`,
|
|
27361
|
+
`Bundled generator: ${report.generatorVersion.bundled} | Installed: ${report.generatorVersion.installed ?? "(unknown)"} | Profile: ${report.profile}`,
|
|
27362
|
+
"",
|
|
27363
|
+
"Reference templates and diffs live in this directory. Edit live files in `.har/` \u2014 not `maintain/templates/` directly.",
|
|
27364
|
+
"",
|
|
27365
|
+
"## Validation (fix before `--finalize`)",
|
|
27366
|
+
""
|
|
27367
|
+
];
|
|
27368
|
+
if (report.validation.errors.length === 0 && report.validation.warnings.length === 0) {
|
|
27369
|
+
lines.push("- All structural checks passed.");
|
|
27370
|
+
} else {
|
|
27371
|
+
for (const issue2 of report.validation.errors) {
|
|
27372
|
+
lines.push(`- \u2717 **${issue2.file}**: ${issue2.message}`);
|
|
27373
|
+
}
|
|
27374
|
+
for (const issue2 of report.validation.warnings) {
|
|
27375
|
+
lines.push(`- \u26A0 **${issue2.file}**: ${issue2.message}`);
|
|
27376
|
+
}
|
|
27377
|
+
}
|
|
27378
|
+
lines.push("", "## Drift actions", "");
|
|
27379
|
+
if (report.actions.length === 0) {
|
|
27380
|
+
lines.push("No missing or drifted template files.");
|
|
27381
|
+
} else {
|
|
27382
|
+
lines.push("| File | Status | Reference |");
|
|
27383
|
+
lines.push("|------|--------|-----------|");
|
|
27384
|
+
for (const action of report.actions) {
|
|
27385
|
+
const ref = action.kind === "missing" ? `templates/${action.file}` : `diffs/${action.file}.diff`;
|
|
27386
|
+
lines.push(`| ${action.file} | ${action.kind} | ${ref} |`);
|
|
27387
|
+
}
|
|
27388
|
+
}
|
|
27389
|
+
if (report.missingPortVars.length > 0) {
|
|
27390
|
+
lines.push("", "## Missing port vars in harness.env", "");
|
|
27391
|
+
for (const v2 of report.missingPortVars) {
|
|
27392
|
+
lines.push(`- \`${v2}\` \u2014 copy from \`maintain/templates/harness.env\``);
|
|
27393
|
+
}
|
|
27394
|
+
}
|
|
27395
|
+
lines.push("", "## Stale files (review)", "");
|
|
27396
|
+
if (report.stale.length === 0) {
|
|
27397
|
+
lines.push("None detected.");
|
|
27398
|
+
} else {
|
|
27399
|
+
for (const stale of report.stale) {
|
|
27400
|
+
lines.push(`- **${stale.file}** \u2014 ${stale.hint}`);
|
|
27401
|
+
}
|
|
27402
|
+
}
|
|
27403
|
+
lines.push(
|
|
27404
|
+
"",
|
|
27405
|
+
"## Next steps",
|
|
27406
|
+
"",
|
|
27407
|
+
"1. Resolve each drift action (merge diffs; keep repo-specific customizations).",
|
|
27408
|
+
"2. Review stale files \u2014 delete or merge superseded scripts.",
|
|
27409
|
+
"3. Re-run `har env maintain` until validation passes and drift is clear.",
|
|
27410
|
+
'4. `har env maintain --finalize --summary "<what changed>"`',
|
|
27411
|
+
""
|
|
27412
|
+
);
|
|
27413
|
+
return lines.join("\n");
|
|
27414
|
+
}
|
|
27415
|
+
function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
27416
|
+
const harnessDir = getHarnessDir(repoPath);
|
|
27417
|
+
const bundleDir = path14.join(harnessDir, MAINTAIN_DIR);
|
|
27418
|
+
if (fs15.existsSync(bundleDir)) {
|
|
27419
|
+
fs15.rmSync(bundleDir, { recursive: true, force: true });
|
|
27420
|
+
}
|
|
27421
|
+
const dirs = [
|
|
27422
|
+
bundleDir,
|
|
27423
|
+
path14.join(bundleDir, "templates"),
|
|
27424
|
+
path14.join(bundleDir, "installed"),
|
|
27425
|
+
path14.join(bundleDir, "diffs"),
|
|
27426
|
+
path14.join(bundleDir, "stale")
|
|
27427
|
+
];
|
|
27428
|
+
for (const dir of dirs) {
|
|
27429
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
27430
|
+
}
|
|
27431
|
+
const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
|
|
27432
|
+
for (const file of affectedFiles) {
|
|
27433
|
+
const templateContent = readBundledTemplateContent(repoPath, profile, file);
|
|
27434
|
+
writeFileSafe(path14.join(bundleDir, "templates", file), templateContent);
|
|
27435
|
+
const harnessPath = path14.join(harnessDir, file);
|
|
27436
|
+
if (fs15.existsSync(harnessPath)) {
|
|
27437
|
+
const installedContent = fs15.readFileSync(harnessPath, "utf8");
|
|
27438
|
+
writeFileSafe(path14.join(bundleDir, "installed", file), installedContent);
|
|
27439
|
+
const diff = createUnifiedDiff(
|
|
27440
|
+
installedContent,
|
|
27441
|
+
templateContent,
|
|
27442
|
+
`installed/${file}`,
|
|
27443
|
+
`templates/${file}`
|
|
27444
|
+
);
|
|
27445
|
+
if (diff) {
|
|
27446
|
+
writeFileSafe(path14.join(bundleDir, "diffs", `${file}.diff`), diff);
|
|
27447
|
+
}
|
|
27448
|
+
}
|
|
27449
|
+
}
|
|
27450
|
+
writeFileSafe(path14.join(bundleDir, "README.md"), buildReadme(report));
|
|
27451
|
+
writeFileSafe(path14.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
|
|
27452
|
+
writeFileSafe(
|
|
27453
|
+
path14.join(bundleDir, "validation.json"),
|
|
27454
|
+
JSON.stringify(report.validation, null, 2) + "\n"
|
|
27455
|
+
);
|
|
27456
|
+
if (report.stale.length > 0) {
|
|
27457
|
+
const staleLines = [
|
|
27458
|
+
"# Stale harness files",
|
|
27459
|
+
"",
|
|
27460
|
+
"These files exist in `.har/` but are not part of the current bundled template.",
|
|
27461
|
+
"",
|
|
27462
|
+
...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
|
|
27463
|
+
""
|
|
27464
|
+
];
|
|
27465
|
+
writeFileSafe(path14.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
|
|
27466
|
+
}
|
|
27467
|
+
return bundleDir;
|
|
27468
|
+
}
|
|
27469
|
+
function buildMaintainBundle(repoPath, validation2, drift) {
|
|
27470
|
+
const manifest = readManifest(repoPath);
|
|
27471
|
+
const profile = manifest?.profile ?? "default";
|
|
27472
|
+
const errors = validation2.issues.filter((i2) => i2.severity === "error");
|
|
27473
|
+
const warnings = validation2.issues.filter((i2) => i2.severity === "warning");
|
|
27474
|
+
const report = {
|
|
27475
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27476
|
+
generatorVersion: drift.generatorVersion,
|
|
27477
|
+
profile,
|
|
27478
|
+
actions: buildActions(repoPath, profile, drift),
|
|
27479
|
+
stale: buildStale(drift),
|
|
27480
|
+
missingPortVars: drift.missingPortVars,
|
|
27481
|
+
validation: {
|
|
27482
|
+
pass: validation2.pass,
|
|
27483
|
+
errors,
|
|
27484
|
+
warnings
|
|
27485
|
+
}
|
|
27486
|
+
};
|
|
27487
|
+
const bundleDir = writeBundleArtifacts(repoPath, profile, drift, report);
|
|
27488
|
+
return { bundleDir, report };
|
|
27489
|
+
}
|
|
27490
|
+
function removeMaintainBundle(repoPath) {
|
|
27491
|
+
const bundleDir = path14.join(getHarnessDir(repoPath), MAINTAIN_DIR);
|
|
27492
|
+
if (fs15.existsSync(bundleDir)) {
|
|
27493
|
+
fs15.rmSync(bundleDir, { recursive: true, force: true });
|
|
27494
|
+
}
|
|
27495
|
+
}
|
|
27496
|
+
function formatMaintainBundlePromptSection(report) {
|
|
27497
|
+
const lines = [
|
|
27498
|
+
"## Step 0 \u2014 Read the maintenance bundle",
|
|
27499
|
+
"",
|
|
27500
|
+
"Open `.har/maintain/README.md` and `.har/maintain/drift-report.json`.",
|
|
27501
|
+
"All reference templates are under `.har/maintain/templates/`.",
|
|
27502
|
+
"Do **not** read files from the globally installed har package.",
|
|
27503
|
+
""
|
|
27504
|
+
];
|
|
27505
|
+
if (report.validation.errors.length > 0) {
|
|
27506
|
+
lines.push("### Validation blockers (fix first)", "");
|
|
27507
|
+
for (const issue2 of report.validation.errors) {
|
|
27508
|
+
lines.push(`- \u2717 **${issue2.file}**: ${issue2.message}`);
|
|
27509
|
+
}
|
|
27510
|
+
lines.push("");
|
|
27511
|
+
}
|
|
27512
|
+
if (report.actions.length > 0) {
|
|
27513
|
+
lines.push("### Drift actions", "", "| File | Status | Reference |", "|------|--------|-----------|");
|
|
27514
|
+
for (const action of report.actions) {
|
|
27515
|
+
const ref = action.kind === "missing" ? `maintain/templates/${action.file}` : `maintain/diffs/${action.file}.diff`;
|
|
27516
|
+
lines.push(`| ${action.file} | ${action.kind} | ${ref} |`);
|
|
27517
|
+
}
|
|
27518
|
+
lines.push("");
|
|
27519
|
+
}
|
|
27520
|
+
if (report.stale.length > 0) {
|
|
27521
|
+
lines.push("### Stale files", "");
|
|
27522
|
+
for (const stale of report.stale) {
|
|
27523
|
+
lines.push(`- **${stale.file}** \u2014 ${stale.hint}`);
|
|
27524
|
+
}
|
|
27525
|
+
lines.push("");
|
|
27526
|
+
}
|
|
27527
|
+
if (report.missingPortVars.length > 0) {
|
|
27528
|
+
lines.push("### Missing port vars in harness.env", "");
|
|
27529
|
+
for (const v2 of report.missingPortVars) {
|
|
27530
|
+
lines.push(`- \`${v2}\` \u2014 see \`maintain/templates/harness.env\``);
|
|
27531
|
+
}
|
|
27532
|
+
lines.push("");
|
|
27533
|
+
}
|
|
27534
|
+
if (report.actions.length === 0 && report.stale.length === 0 && report.missingPortVars.length === 0 && report.validation.errors.length === 0) {
|
|
27535
|
+
lines.push("No template drift detected. Review repo stack changes manually if needed.", "");
|
|
27536
|
+
}
|
|
27537
|
+
return lines.join("\n");
|
|
27538
|
+
}
|
|
27539
|
+
|
|
27540
|
+
// src/harness/stage-templates.ts
|
|
27541
|
+
var fs17 = __toESM(require("fs"));
|
|
27542
|
+
var path15 = __toESM(require("path"));
|
|
27157
27543
|
|
|
27158
27544
|
// src/harness/parser.ts
|
|
27159
|
-
var
|
|
27545
|
+
var fs16 = __toESM(require("fs"));
|
|
27160
27546
|
function harnessExists(repoPath) {
|
|
27161
|
-
return
|
|
27547
|
+
return fs16.existsSync(`${repoPath}/.har/setup-infra.sh`);
|
|
27162
27548
|
}
|
|
27163
27549
|
|
|
27164
27550
|
// src/harness/stage-templates.ts
|
|
27165
27551
|
function resolveTemplateDir(templateId) {
|
|
27166
|
-
const dir =
|
|
27167
|
-
if (!
|
|
27552
|
+
const dir = path15.join(resolveTemplatesDir(), "stage-templates", templateId);
|
|
27553
|
+
if (!fs17.existsSync(dir)) {
|
|
27168
27554
|
throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
|
|
27169
27555
|
}
|
|
27170
27556
|
return dir;
|
|
27171
27557
|
}
|
|
27172
27558
|
function readTemplateManifest(templateId) {
|
|
27173
|
-
const manifestPath =
|
|
27174
|
-
const raw = JSON.parse(
|
|
27559
|
+
const manifestPath = path15.join(resolveTemplateDir(templateId), "template.manifest.json");
|
|
27560
|
+
const raw = JSON.parse(fs17.readFileSync(manifestPath, "utf8"));
|
|
27175
27561
|
if (raw.id !== templateId) {
|
|
27176
27562
|
throw new Error(`Template manifest id mismatch: expected ${templateId}, got ${raw.id}`);
|
|
27177
27563
|
}
|
|
27178
27564
|
return raw;
|
|
27179
27565
|
}
|
|
27180
27566
|
function ensureParentDir(filePath) {
|
|
27181
|
-
const parent =
|
|
27182
|
-
if (!
|
|
27183
|
-
|
|
27567
|
+
const parent = path15.dirname(filePath);
|
|
27568
|
+
if (!fs17.existsSync(parent)) {
|
|
27569
|
+
fs17.mkdirSync(parent, { recursive: true });
|
|
27184
27570
|
}
|
|
27185
27571
|
}
|
|
27186
27572
|
function copyTemplateFile(templateDir, file, repoPath, force) {
|
|
27187
|
-
const srcPath =
|
|
27188
|
-
const destPath =
|
|
27189
|
-
if (!
|
|
27573
|
+
const srcPath = path15.join(templateDir, file.src);
|
|
27574
|
+
const destPath = path15.join(repoPath, file.dest);
|
|
27575
|
+
if (!fs17.existsSync(srcPath)) {
|
|
27190
27576
|
throw new Error(`Template file missing: ${file.src}`);
|
|
27191
27577
|
}
|
|
27192
|
-
if (
|
|
27578
|
+
if (fs17.existsSync(destPath) && !force) {
|
|
27193
27579
|
throw new Error(
|
|
27194
27580
|
`File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
|
|
27195
27581
|
);
|
|
27196
27582
|
}
|
|
27197
27583
|
ensureParentDir(destPath);
|
|
27198
|
-
|
|
27584
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
27199
27585
|
if (file.executable) {
|
|
27200
|
-
|
|
27586
|
+
fs17.chmodSync(destPath, 493);
|
|
27201
27587
|
}
|
|
27202
27588
|
return { written: true, path: file.dest };
|
|
27203
27589
|
}
|
|
27204
27590
|
function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
27205
|
-
const packagePath =
|
|
27206
|
-
if (!
|
|
27591
|
+
const packagePath = path15.join(repoPath, "package.json");
|
|
27592
|
+
if (!fs17.existsSync(packagePath)) {
|
|
27207
27593
|
throw new Error("No package.json in repo root. Add one before applying the Playwright template.");
|
|
27208
27594
|
}
|
|
27209
|
-
const fragmentPath =
|
|
27210
|
-
if (!
|
|
27595
|
+
const fragmentPath = path15.join(templateDir, fragmentRelPath);
|
|
27596
|
+
if (!fs17.existsSync(fragmentPath)) {
|
|
27211
27597
|
throw new Error(`Package fragment missing: ${fragmentRelPath}`);
|
|
27212
27598
|
}
|
|
27213
|
-
const pkg = JSON.parse(
|
|
27214
|
-
const fragment = JSON.parse(
|
|
27599
|
+
const pkg = JSON.parse(fs17.readFileSync(packagePath, "utf8"));
|
|
27600
|
+
const fragment = JSON.parse(fs17.readFileSync(fragmentPath, "utf8"));
|
|
27215
27601
|
for (const section of ["scripts", "devDependencies"]) {
|
|
27216
27602
|
const existing = pkg[section] ?? {};
|
|
27217
27603
|
const incoming = fragment[section] ?? {};
|
|
@@ -27224,7 +27610,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
|
27224
27610
|
}
|
|
27225
27611
|
pkg[section] = existing;
|
|
27226
27612
|
}
|
|
27227
|
-
|
|
27613
|
+
fs17.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
27228
27614
|
}
|
|
27229
27615
|
function patchStageRegistry(repoPath, manifest, force) {
|
|
27230
27616
|
const registry2 = readStageRegistry(repoPath);
|
|
@@ -27265,8 +27651,8 @@ function assertHarnessPresent(repoPath) {
|
|
|
27265
27651
|
}
|
|
27266
27652
|
function assertStageNotPresent(repoPath, stageId, force) {
|
|
27267
27653
|
if (force) return;
|
|
27268
|
-
const scriptPath =
|
|
27269
|
-
if (
|
|
27654
|
+
const scriptPath = path15.join(repoPath, ".har", "stages", `${stageId}.sh`);
|
|
27655
|
+
if (fs17.existsSync(scriptPath)) {
|
|
27270
27656
|
throw new Error(
|
|
27271
27657
|
`Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
|
|
27272
27658
|
);
|
|
@@ -27279,7 +27665,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
|
|
|
27279
27665
|
}
|
|
27280
27666
|
}
|
|
27281
27667
|
function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
27282
|
-
const resolved =
|
|
27668
|
+
const resolved = path15.resolve(repoPath);
|
|
27283
27669
|
const force = options.force ?? false;
|
|
27284
27670
|
const warnings = [];
|
|
27285
27671
|
const filesWritten = [];
|
|
@@ -27298,7 +27684,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27298
27684
|
if (file.skipFlag === "skipCi" && options.skipCi) {
|
|
27299
27685
|
continue;
|
|
27300
27686
|
}
|
|
27301
|
-
if (
|
|
27687
|
+
if (fs17.existsSync(path15.join(resolved, file.dest)) && !force) {
|
|
27302
27688
|
warnings.push(`Skipped optional file (exists): ${file.dest}`);
|
|
27303
27689
|
continue;
|
|
27304
27690
|
}
|
|
@@ -27361,11 +27747,11 @@ function validateAgentId(id, repoPath) {
|
|
|
27361
27747
|
// src/core/harness.ts
|
|
27362
27748
|
function listHarnessScripts(repoPath) {
|
|
27363
27749
|
const harnessDir = getHarnessDir(repoPath);
|
|
27364
|
-
if (!
|
|
27365
|
-
return
|
|
27750
|
+
if (!fs18.existsSync(harnessDir)) return [];
|
|
27751
|
+
return fs18.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
|
|
27366
27752
|
}
|
|
27367
27753
|
function describeProject(repoPath) {
|
|
27368
|
-
const resolved =
|
|
27754
|
+
const resolved = path16.resolve(repoPath);
|
|
27369
27755
|
const manifest = readManifest(resolved);
|
|
27370
27756
|
const present = harnessExists(resolved);
|
|
27371
27757
|
return {
|
|
@@ -27385,8 +27771,8 @@ function describeProject(repoPath) {
|
|
|
27385
27771
|
};
|
|
27386
27772
|
}
|
|
27387
27773
|
async function initHarness(options) {
|
|
27388
|
-
const repoPath =
|
|
27389
|
-
if (!
|
|
27774
|
+
const repoPath = path16.resolve(options.repoPath);
|
|
27775
|
+
if (!fs18.existsSync(repoPath)) {
|
|
27390
27776
|
throw new Error(`Path not found: ${repoPath}`);
|
|
27391
27777
|
}
|
|
27392
27778
|
const scaffold = scaffoldHarnessBoilerplate(repoPath, {
|
|
@@ -27417,9 +27803,9 @@ async function initHarness(options) {
|
|
|
27417
27803
|
};
|
|
27418
27804
|
}
|
|
27419
27805
|
async function maintainHarness(options) {
|
|
27420
|
-
const repoPath =
|
|
27806
|
+
const repoPath = path16.resolve(options.repoPath);
|
|
27421
27807
|
const harnessDir = getHarnessDir(repoPath);
|
|
27422
|
-
if (!
|
|
27808
|
+
if (!fs18.existsSync(harnessDir)) {
|
|
27423
27809
|
throw new Error('No .har/ found. Run "har env init" first.');
|
|
27424
27810
|
}
|
|
27425
27811
|
if (options.auto) {
|
|
@@ -27430,6 +27816,7 @@ async function maintainHarness(options) {
|
|
|
27430
27816
|
mode: "maintain"
|
|
27431
27817
|
});
|
|
27432
27818
|
finalizeHarness(repoPath, authoringResult.summary, authoringResult.stack);
|
|
27819
|
+
removeMaintainBundle(repoPath);
|
|
27433
27820
|
const validation3 = validateHarness(repoPath);
|
|
27434
27821
|
return {
|
|
27435
27822
|
validation: validation3,
|
|
@@ -27437,6 +27824,7 @@ async function maintainHarness(options) {
|
|
|
27437
27824
|
drift: compareHarnessToTemplate(repoPath)
|
|
27438
27825
|
};
|
|
27439
27826
|
}
|
|
27827
|
+
const drift = compareHarnessToTemplate(repoPath);
|
|
27440
27828
|
const validation2 = validateHarness(repoPath);
|
|
27441
27829
|
if (options.finalize) {
|
|
27442
27830
|
if (!validation2.pass) {
|
|
@@ -27448,16 +27836,19 @@ async function maintainHarness(options) {
|
|
|
27448
27836
|
options.summary ?? "Manual adaptation finalized via har env maintain --finalize",
|
|
27449
27837
|
existing?.stack
|
|
27450
27838
|
);
|
|
27839
|
+
removeMaintainBundle(repoPath);
|
|
27451
27840
|
return {
|
|
27452
27841
|
validation: validation2,
|
|
27453
27842
|
adaptationSummary: options.summary,
|
|
27454
27843
|
drift: compareHarnessToTemplate(repoPath)
|
|
27455
27844
|
};
|
|
27456
27845
|
}
|
|
27846
|
+
const bundle = buildMaintainBundle(repoPath, validation2, drift);
|
|
27457
27847
|
return {
|
|
27458
27848
|
validation: validation2,
|
|
27459
27849
|
adaptationSummary: "Manual maintenance \u2014 use coding agent prompt in .har/ADAPT-PROMPT.md",
|
|
27460
|
-
drift
|
|
27850
|
+
drift,
|
|
27851
|
+
bundle
|
|
27461
27852
|
};
|
|
27462
27853
|
}
|
|
27463
27854
|
function addStageTemplate(repoPath, templateId, options = {}) {
|
|
@@ -27465,8 +27856,8 @@ function addStageTemplate(repoPath, templateId, options = {}) {
|
|
|
27465
27856
|
}
|
|
27466
27857
|
|
|
27467
27858
|
// src/harness/adaptation-prompt.ts
|
|
27468
|
-
var
|
|
27469
|
-
var
|
|
27859
|
+
var fs19 = __toESM(require("fs"));
|
|
27860
|
+
var path17 = __toESM(require("path"));
|
|
27470
27861
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
27471
27862
|
var PROFILE_HINTS = {
|
|
27472
27863
|
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.",
|
|
@@ -27478,7 +27869,7 @@ function loadTemplate(name) {
|
|
|
27478
27869
|
if (!filePath) {
|
|
27479
27870
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
27480
27871
|
}
|
|
27481
|
-
return
|
|
27872
|
+
return fs19.readFileSync(filePath, "utf8");
|
|
27482
27873
|
}
|
|
27483
27874
|
function applyProfilePlaceholders(content, profile) {
|
|
27484
27875
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -27486,12 +27877,21 @@ function applyProfilePlaceholders(content, profile) {
|
|
|
27486
27877
|
function buildInitAdaptationPrompt(_repoPath, profile) {
|
|
27487
27878
|
return applyProfilePlaceholders(loadTemplate("adaptation-prompt-init.md"), profile);
|
|
27488
27879
|
}
|
|
27489
|
-
function buildMaintainAdaptationPrompt(_repoPath) {
|
|
27490
|
-
|
|
27880
|
+
function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
27881
|
+
const template = loadTemplate("adaptation-prompt-maintain.md");
|
|
27882
|
+
const section = bundleReport ? formatMaintainBundlePromptSection(bundleReport) : [
|
|
27883
|
+
"## Step 0 \u2014 Read the maintenance bundle",
|
|
27884
|
+
"",
|
|
27885
|
+
"Open `.har/maintain/README.md` and `.har/maintain/drift-report.json`.",
|
|
27886
|
+
"All reference templates are under `.har/maintain/templates/`.",
|
|
27887
|
+
"Do **not** read files from the globally installed har package.",
|
|
27888
|
+
""
|
|
27889
|
+
].join("\n");
|
|
27890
|
+
return template.replace("{{MAINTAIN_BUNDLE_SECTION}}", section);
|
|
27491
27891
|
}
|
|
27492
27892
|
function writeAdaptationPrompt(repoPath, content) {
|
|
27493
27893
|
const harnessDir = getHarnessDir(repoPath);
|
|
27494
|
-
const filePath =
|
|
27894
|
+
const filePath = path17.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
27495
27895
|
writeFileSafe(filePath, content);
|
|
27496
27896
|
return filePath;
|
|
27497
27897
|
}
|
|
@@ -27510,25 +27910,25 @@ function printAdaptationPrompt(content) {
|
|
|
27510
27910
|
}
|
|
27511
27911
|
|
|
27512
27912
|
// src/harness/cursor-rule.ts
|
|
27513
|
-
var
|
|
27514
|
-
var
|
|
27913
|
+
var fs20 = __toESM(require("fs"));
|
|
27914
|
+
var path18 = __toESM(require("path"));
|
|
27515
27915
|
var readline3 = __toESM(require("readline"));
|
|
27516
27916
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
27517
27917
|
function getCursorRulePath(repoPath) {
|
|
27518
|
-
return
|
|
27918
|
+
return path18.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
27519
27919
|
}
|
|
27520
27920
|
function isCursorWorkspace(repoPath) {
|
|
27521
|
-
return
|
|
27921
|
+
return fs20.existsSync(path18.join(repoPath, ".cursor"));
|
|
27522
27922
|
}
|
|
27523
27923
|
function cursorRuleExists(repoPath) {
|
|
27524
|
-
return
|
|
27924
|
+
return fs20.existsSync(getCursorRulePath(repoPath));
|
|
27525
27925
|
}
|
|
27526
27926
|
function scaffoldCursorRule(repoPath) {
|
|
27527
27927
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
27528
27928
|
if (!templatePath) {
|
|
27529
27929
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
27530
27930
|
}
|
|
27531
|
-
const content =
|
|
27931
|
+
const content = fs20.readFileSync(templatePath, "utf8");
|
|
27532
27932
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
27533
27933
|
}
|
|
27534
27934
|
async function handleCursorRule(options) {
|
|
@@ -27566,16 +27966,16 @@ async function promptScaffoldCursorRule(exists) {
|
|
|
27566
27966
|
}
|
|
27567
27967
|
async function askYesNo2(question) {
|
|
27568
27968
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
27569
|
-
return new Promise((
|
|
27969
|
+
return new Promise((resolve26) => {
|
|
27570
27970
|
process.stderr.write(`${question} `);
|
|
27571
27971
|
rl.once("line", (answer) => {
|
|
27572
27972
|
rl.close();
|
|
27573
27973
|
const trimmed = answer.trim();
|
|
27574
27974
|
if (trimmed === "") {
|
|
27575
|
-
|
|
27975
|
+
resolve26(true);
|
|
27576
27976
|
return;
|
|
27577
27977
|
}
|
|
27578
|
-
|
|
27978
|
+
resolve26(/^y(es)?$/i.test(trimmed));
|
|
27579
27979
|
});
|
|
27580
27980
|
});
|
|
27581
27981
|
}
|
|
@@ -27654,17 +28054,65 @@ function buildStageResult(input) {
|
|
|
27654
28054
|
}
|
|
27655
28055
|
|
|
27656
28056
|
// src/core/local-executor.ts
|
|
27657
|
-
var
|
|
27658
|
-
var
|
|
28057
|
+
var fs22 = __toESM(require("fs"));
|
|
28058
|
+
var path20 = __toESM(require("path"));
|
|
28059
|
+
|
|
28060
|
+
// src/core/slot-registry.ts
|
|
28061
|
+
var fs21 = __toESM(require("fs"));
|
|
28062
|
+
var path19 = __toESM(require("path"));
|
|
28063
|
+
function getSlotRegistryDir(repoPath) {
|
|
28064
|
+
return path19.join(getHarnessDir(repoPath), "slots");
|
|
28065
|
+
}
|
|
28066
|
+
function getSlotRegistryPath(repoPath, agentId) {
|
|
28067
|
+
return path19.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
28068
|
+
}
|
|
28069
|
+
function isSlotResumable(session) {
|
|
28070
|
+
return session?.status === "failed" || session?.status === "starting";
|
|
28071
|
+
}
|
|
28072
|
+
function readSlotRegistry(repoPath, agentId) {
|
|
28073
|
+
const file = getSlotRegistryPath(repoPath, agentId);
|
|
28074
|
+
if (!fs21.existsSync(file)) return void 0;
|
|
28075
|
+
try {
|
|
28076
|
+
const raw = JSON.parse(fs21.readFileSync(file, "utf8"));
|
|
28077
|
+
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
28078
|
+
return result.success ? result.data : void 0;
|
|
28079
|
+
} catch {
|
|
28080
|
+
return void 0;
|
|
28081
|
+
}
|
|
28082
|
+
}
|
|
28083
|
+
|
|
28084
|
+
// src/core/local-executor.ts
|
|
27659
28085
|
function resolveRepoPath(repoPath) {
|
|
27660
|
-
return
|
|
28086
|
+
return path20.resolve(repoPath);
|
|
28087
|
+
}
|
|
28088
|
+
function buildPreviewUrlsFromPorts(ports, env3) {
|
|
28089
|
+
const urls = {};
|
|
28090
|
+
if (ports.frontend) urls.frontend = `http://localhost:${ports.frontend}`;
|
|
28091
|
+
if (ports.api) {
|
|
28092
|
+
urls.api = `http://localhost:${ports.api}`;
|
|
28093
|
+
if (env3.HARNESS_HEALTH_CHECK_PATH) {
|
|
28094
|
+
urls.health = `http://localhost:${ports.api}${env3.HARNESS_HEALTH_CHECK_PATH}`;
|
|
28095
|
+
}
|
|
28096
|
+
}
|
|
28097
|
+
if (ports.browser) urls.browser = `http://localhost:${ports.browser}`;
|
|
28098
|
+
if (ports.mailpit) urls.mailpit = `http://localhost:${ports.mailpit}`;
|
|
28099
|
+
return urls;
|
|
27661
28100
|
}
|
|
27662
28101
|
function computePreviewUrls(repoPath, agentId) {
|
|
27663
|
-
const
|
|
28102
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28103
|
+
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28104
|
+
if (session?.previewUrls && Object.keys(session.previewUrls).length > 0) {
|
|
28105
|
+
return session.previewUrls;
|
|
28106
|
+
}
|
|
28107
|
+
const env3 = readHarnessEnv(harnessRoot);
|
|
28108
|
+
if (session?.ports && Object.keys(session.ports).length > 0) {
|
|
28109
|
+
return buildPreviewUrlsFromPorts(session.ports, env3);
|
|
28110
|
+
}
|
|
28111
|
+
const step = Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
27664
28112
|
const feBase = Number(env3.HARNESS_FE_BASE_PORT ?? 3e3);
|
|
27665
28113
|
const apiBase = Number(env3.HARNESS_API_BASE_PORT ?? 8e3);
|
|
27666
|
-
const fePort = feBase + agentId *
|
|
27667
|
-
const apiPort = apiBase + agentId *
|
|
28114
|
+
const fePort = feBase + agentId * step;
|
|
28115
|
+
const apiPort = apiBase + agentId * step;
|
|
27668
28116
|
const urls = {
|
|
27669
28117
|
frontend: `http://localhost:${fePort}`,
|
|
27670
28118
|
api: `http://localhost:${apiPort}`
|
|
@@ -27673,10 +28121,10 @@ function computePreviewUrls(repoPath, agentId) {
|
|
|
27673
28121
|
urls.health = `http://localhost:${apiPort}${env3.HARNESS_HEALTH_CHECK_PATH}`;
|
|
27674
28122
|
}
|
|
27675
28123
|
if (env3.HARNESS_INFRA_BROWSER === "true") {
|
|
27676
|
-
urls.browser =
|
|
28124
|
+
urls.browser = `http://localhost:${env3.HARNESS_BROWSER_PORT_DEFAULT ?? 13001}`;
|
|
27677
28125
|
}
|
|
27678
28126
|
if (env3.HARNESS_INFRA_MAILPIT === "true") {
|
|
27679
|
-
urls.mailpit =
|
|
28127
|
+
urls.mailpit = `http://localhost:${env3.HARNESS_MAILPIT_WEB_PORT_DEFAULT ?? 18025}`;
|
|
27680
28128
|
}
|
|
27681
28129
|
return urls;
|
|
27682
28130
|
}
|
|
@@ -27687,14 +28135,14 @@ function substituteAgentId(value, agentId) {
|
|
|
27687
28135
|
function resolveStageScriptPath(repoPath, stage) {
|
|
27688
28136
|
const harnessDir = getHarnessDir(repoPath);
|
|
27689
28137
|
if (stage.script) {
|
|
27690
|
-
return
|
|
28138
|
+
return path20.join(harnessDir, stage.script);
|
|
27691
28139
|
}
|
|
27692
28140
|
if (stage.command) {
|
|
27693
28141
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
27694
|
-
return
|
|
28142
|
+
return path20.join(harnessDir, scriptName);
|
|
27695
28143
|
}
|
|
27696
|
-
const stageScript =
|
|
27697
|
-
if (
|
|
28144
|
+
const stageScript = path20.join(harnessDir, "stages", `${stage.id}.sh`);
|
|
28145
|
+
if (fs22.existsSync(stageScript)) {
|
|
27698
28146
|
return stageScript;
|
|
27699
28147
|
}
|
|
27700
28148
|
throw new Error(
|
|
@@ -27709,7 +28157,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
27709
28157
|
...harnessEnv,
|
|
27710
28158
|
...stage.env ?? {}
|
|
27711
28159
|
};
|
|
27712
|
-
const cwd = stage.cwd ?
|
|
28160
|
+
const cwd = stage.cwd ? path20.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
|
|
27713
28161
|
const extraArgs = options.args ?? [];
|
|
27714
28162
|
const launchFlagArgs = [];
|
|
27715
28163
|
if (stage.kind === "launch" && options.launchFlags) {
|
|
@@ -27717,6 +28165,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
27717
28165
|
if (options.launchFlags.claude) launchFlagArgs.push("--claude");
|
|
27718
28166
|
if (options.launchFlags.confirmReplace) launchFlagArgs.push("--replace");
|
|
27719
28167
|
if (options.launchFlags.force) launchFlagArgs.push("--force");
|
|
28168
|
+
if (options.launchFlags.resume) launchFlagArgs.push("--resume");
|
|
27720
28169
|
}
|
|
27721
28170
|
if (stage.command) {
|
|
27722
28171
|
let shellCommand = substituteAgentId(stage.command, options.agentId);
|
|
@@ -27785,19 +28234,19 @@ var LocalScriptExecutor = class {
|
|
|
27785
28234
|
const repoPath = resolveRepoPath(ctx.repoPath);
|
|
27786
28235
|
const harnessDir = getHarnessDir(repoPath);
|
|
27787
28236
|
const artifactsDirName = getArtifactsDir(repoPath);
|
|
27788
|
-
const artifactsDir =
|
|
27789
|
-
if (!
|
|
28237
|
+
const artifactsDir = path20.join(harnessDir, artifactsDirName);
|
|
28238
|
+
if (!fs22.existsSync(artifactsDir)) return [];
|
|
27790
28239
|
const entries = [];
|
|
27791
28240
|
const walk = (dir, prefix) => {
|
|
27792
|
-
for (const entry of
|
|
27793
|
-
const full =
|
|
27794
|
-
const relative2 =
|
|
28241
|
+
for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
|
|
28242
|
+
const full = path20.join(dir, entry.name);
|
|
28243
|
+
const relative2 = path20.join(prefix, entry.name);
|
|
27795
28244
|
if (entry.isDirectory()) {
|
|
27796
28245
|
walk(full, relative2);
|
|
27797
28246
|
continue;
|
|
27798
28247
|
}
|
|
27799
28248
|
if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
|
|
27800
|
-
const stat =
|
|
28249
|
+
const stat = fs22.statSync(full);
|
|
27801
28250
|
entries.push({
|
|
27802
28251
|
path: full,
|
|
27803
28252
|
relativePath: relative2,
|
|
@@ -27813,7 +28262,7 @@ var LocalScriptExecutor = class {
|
|
|
27813
28262
|
var localScriptExecutor = new LocalScriptExecutor();
|
|
27814
28263
|
|
|
27815
28264
|
// src/core/control-sync.ts
|
|
27816
|
-
var
|
|
28265
|
+
var path30 = __toESM(require("path"));
|
|
27817
28266
|
|
|
27818
28267
|
// src/core/control-config.ts
|
|
27819
28268
|
var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
|
|
@@ -27825,32 +28274,32 @@ function isControlEnabled() {
|
|
|
27825
28274
|
}
|
|
27826
28275
|
|
|
27827
28276
|
// src/core/control-registry.ts
|
|
27828
|
-
var
|
|
27829
|
-
var
|
|
27830
|
-
var
|
|
28277
|
+
var fs23 = __toESM(require("fs"));
|
|
28278
|
+
var os3 = __toESM(require("os"));
|
|
28279
|
+
var path21 = __toESM(require("path"));
|
|
27831
28280
|
function getRegistryPath() {
|
|
27832
28281
|
if (process.env.HAR_CONTROL_REGISTRY_PATH) {
|
|
27833
|
-
return
|
|
28282
|
+
return path21.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
|
|
27834
28283
|
}
|
|
27835
|
-
return
|
|
28284
|
+
return path21.join(os3.homedir(), ".har", "repos.json");
|
|
27836
28285
|
}
|
|
27837
28286
|
function readRegistry() {
|
|
27838
28287
|
const registryPath = getRegistryPath();
|
|
27839
28288
|
try {
|
|
27840
|
-
if (!
|
|
27841
|
-
return JSON.parse(
|
|
28289
|
+
if (!fs23.existsSync(registryPath)) return { repos: [] };
|
|
28290
|
+
return JSON.parse(fs23.readFileSync(registryPath, "utf8"));
|
|
27842
28291
|
} catch {
|
|
27843
28292
|
return { repos: [] };
|
|
27844
28293
|
}
|
|
27845
28294
|
}
|
|
27846
28295
|
function writeRegistry(registry2) {
|
|
27847
28296
|
const registryPath = getRegistryPath();
|
|
27848
|
-
|
|
27849
|
-
|
|
28297
|
+
fs23.mkdirSync(path21.dirname(registryPath), { recursive: true });
|
|
28298
|
+
fs23.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
|
|
27850
28299
|
}
|
|
27851
28300
|
function recordRepoForControlSync(repoPath) {
|
|
27852
28301
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
27853
|
-
const resolved =
|
|
28302
|
+
const resolved = path21.resolve(repoPath);
|
|
27854
28303
|
if (!readManifest(resolved)) return;
|
|
27855
28304
|
const registry2 = readRegistry();
|
|
27856
28305
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -27859,7 +28308,7 @@ function recordRepoForControlSync(repoPath) {
|
|
|
27859
28308
|
}
|
|
27860
28309
|
function listRegisteredRepos() {
|
|
27861
28310
|
const registry2 = readRegistry();
|
|
27862
|
-
const kept = registry2.repos.filter((repoPath) =>
|
|
28311
|
+
const kept = registry2.repos.filter((repoPath) => fs23.existsSync(repoPath) && readManifest(repoPath));
|
|
27863
28312
|
if (kept.length !== registry2.repos.length) {
|
|
27864
28313
|
writeRegistry({ repos: kept });
|
|
27865
28314
|
}
|
|
@@ -27867,40 +28316,17 @@ function listRegisteredRepos() {
|
|
|
27867
28316
|
}
|
|
27868
28317
|
|
|
27869
28318
|
// src/core/slot-status.ts
|
|
27870
|
-
var
|
|
27871
|
-
var
|
|
27872
|
-
var
|
|
27873
|
-
var
|
|
28319
|
+
var fs28 = __toESM(require("fs"));
|
|
28320
|
+
var os6 = __toESM(require("os"));
|
|
28321
|
+
var path27 = __toESM(require("path"));
|
|
28322
|
+
var import_child_process7 = require("child_process");
|
|
27874
28323
|
|
|
27875
28324
|
// src/core/runs.ts
|
|
27876
28325
|
var crypto2 = __toESM(require("crypto"));
|
|
27877
28326
|
var import_child_process2 = require("child_process");
|
|
27878
|
-
var
|
|
27879
|
-
var
|
|
27880
|
-
var
|
|
27881
|
-
|
|
27882
|
-
// src/core/slot-registry.ts
|
|
27883
|
-
var fs21 = __toESM(require("fs"));
|
|
27884
|
-
var path19 = __toESM(require("path"));
|
|
27885
|
-
function getSlotRegistryDir(repoPath) {
|
|
27886
|
-
return path19.join(getHarnessDir(repoPath), "slots");
|
|
27887
|
-
}
|
|
27888
|
-
function getSlotRegistryPath(repoPath, agentId) {
|
|
27889
|
-
return path19.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
27890
|
-
}
|
|
27891
|
-
function readSlotRegistry(repoPath, agentId) {
|
|
27892
|
-
const file = getSlotRegistryPath(repoPath, agentId);
|
|
27893
|
-
if (!fs21.existsSync(file)) return void 0;
|
|
27894
|
-
try {
|
|
27895
|
-
const raw = JSON.parse(fs21.readFileSync(file, "utf8"));
|
|
27896
|
-
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
27897
|
-
return result.success ? result.data : void 0;
|
|
27898
|
-
} catch {
|
|
27899
|
-
return void 0;
|
|
27900
|
-
}
|
|
27901
|
-
}
|
|
27902
|
-
|
|
27903
|
-
// src/core/runs.ts
|
|
28327
|
+
var fs24 = __toESM(require("fs"));
|
|
28328
|
+
var os4 = __toESM(require("os"));
|
|
28329
|
+
var path22 = __toESM(require("path"));
|
|
27904
28330
|
var RUNS_DIR = "runs";
|
|
27905
28331
|
function gitCommonDir(cwd) {
|
|
27906
28332
|
try {
|
|
@@ -27909,7 +28335,7 @@ function gitCommonDir(cwd) {
|
|
|
27909
28335
|
encoding: "utf8",
|
|
27910
28336
|
stdio: ["pipe", "pipe", "ignore"]
|
|
27911
28337
|
}).trim();
|
|
27912
|
-
return out ?
|
|
28338
|
+
return out ? path22.resolve(cwd, out) : void 0;
|
|
27913
28339
|
} catch {
|
|
27914
28340
|
return void 0;
|
|
27915
28341
|
}
|
|
@@ -27931,7 +28357,7 @@ function gitPrefix(cwd) {
|
|
|
27931
28357
|
}
|
|
27932
28358
|
}
|
|
27933
28359
|
function getRunsDir(harnessRoot) {
|
|
27934
|
-
return
|
|
28360
|
+
return path22.join(getHarnessDir(harnessRoot), RUNS_DIR);
|
|
27935
28361
|
}
|
|
27936
28362
|
function formatLocalDate(d2) {
|
|
27937
28363
|
const y2 = d2.getFullYear();
|
|
@@ -27951,24 +28377,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
|
|
|
27951
28377
|
const timePart = formatLocalTime(started);
|
|
27952
28378
|
const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
|
|
27953
28379
|
let filename = `${timePart}_${stageId}${agentPart}.json`;
|
|
27954
|
-
const fullPath =
|
|
27955
|
-
if (
|
|
28380
|
+
const fullPath = path22.join(runsDir, dateFolder, filename);
|
|
28381
|
+
if (fs24.existsSync(fullPath)) {
|
|
27956
28382
|
filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
|
|
27957
28383
|
}
|
|
27958
|
-
return
|
|
28384
|
+
return path22.join(dateFolder, filename);
|
|
27959
28385
|
}
|
|
27960
28386
|
function resolveRunFilePath(harnessRoot, run2) {
|
|
27961
28387
|
if (run2.relativePath) {
|
|
27962
|
-
return
|
|
28388
|
+
return path22.join(getRunsDir(harnessRoot), run2.relativePath);
|
|
27963
28389
|
}
|
|
27964
|
-
return
|
|
28390
|
+
return path22.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
|
|
27965
28391
|
}
|
|
27966
28392
|
function collectRunFiles(runsDir) {
|
|
27967
|
-
if (!
|
|
28393
|
+
if (!fs24.existsSync(runsDir)) return [];
|
|
27968
28394
|
const files = [];
|
|
27969
28395
|
const walk = (dir) => {
|
|
27970
|
-
for (const entry of
|
|
27971
|
-
const full =
|
|
28396
|
+
for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
|
|
28397
|
+
const full = path22.join(dir, entry.name);
|
|
27972
28398
|
if (entry.isDirectory()) {
|
|
27973
28399
|
walk(full);
|
|
27974
28400
|
} else if (entry.name.endsWith(".json")) {
|
|
@@ -27982,30 +28408,30 @@ function collectRunFiles(runsDir) {
|
|
|
27982
28408
|
function resolveAgentWorkDir(harnessRoot, agentId) {
|
|
27983
28409
|
if (agentId === void 0) return void 0;
|
|
27984
28410
|
const entry = readSlotRegistry(harnessRoot, agentId);
|
|
27985
|
-
if (entry?.workDir &&
|
|
28411
|
+
if (entry?.workDir && fs24.existsSync(entry.workDir)) return entry.workDir;
|
|
27986
28412
|
const env3 = readHarnessEnv(harnessRoot);
|
|
27987
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
27988
|
-
const worktreeDir =
|
|
28413
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path22.basename(harnessRoot);
|
|
28414
|
+
const worktreeDir = path22.join(os4.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
27989
28415
|
const relPrefix = gitPrefix(harnessRoot);
|
|
27990
28416
|
const sessionEnvFiles = [];
|
|
27991
|
-
const worktreesRoot =
|
|
27992
|
-
if (
|
|
28417
|
+
const worktreesRoot = path22.join(os4.homedir(), "worktrees");
|
|
28418
|
+
if (fs24.existsSync(worktreesRoot)) {
|
|
27993
28419
|
const suffix = `-har-agent-${agentId}-`;
|
|
27994
|
-
for (const entry2 of
|
|
28420
|
+
for (const entry2 of fs24.readdirSync(worktreesRoot, { withFileTypes: true })) {
|
|
27995
28421
|
if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
|
|
27996
|
-
const sessionDir =
|
|
28422
|
+
const sessionDir = path22.join(worktreesRoot, entry2.name);
|
|
27997
28423
|
if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
|
|
27998
|
-
sessionEnvFiles.push(
|
|
28424
|
+
sessionEnvFiles.push(path22.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
|
|
27999
28425
|
}
|
|
28000
28426
|
}
|
|
28001
28427
|
const candidates = [
|
|
28002
|
-
|
|
28003
|
-
|
|
28428
|
+
path22.join(worktreeDir, `.env.agent.${agentId}`),
|
|
28429
|
+
path22.join(harnessRoot, `.env.agent.${agentId}`),
|
|
28004
28430
|
...sessionEnvFiles.sort()
|
|
28005
28431
|
];
|
|
28006
28432
|
for (const envFile of candidates) {
|
|
28007
|
-
if (!
|
|
28008
|
-
const content =
|
|
28433
|
+
if (!fs24.existsSync(envFile)) continue;
|
|
28434
|
+
const content = fs24.readFileSync(envFile, "utf8");
|
|
28009
28435
|
const match = content.match(/^REPO_ROOT=(.+)$/m);
|
|
28010
28436
|
if (match) return match[1].trim();
|
|
28011
28437
|
}
|
|
@@ -28017,11 +28443,11 @@ function createRun(ctx, meta) {
|
|
|
28017
28443
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28018
28444
|
const runId = crypto2.randomUUID();
|
|
28019
28445
|
const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
|
|
28020
|
-
const runFilePath =
|
|
28021
|
-
|
|
28446
|
+
const runFilePath = path22.join(runsDir, relativePath);
|
|
28447
|
+
fs24.mkdirSync(path22.dirname(runFilePath), { recursive: true });
|
|
28022
28448
|
const run2 = RunRecordSchema.parse({
|
|
28023
28449
|
runId,
|
|
28024
|
-
repoPath:
|
|
28450
|
+
repoPath: path22.resolve(ctx.repoPath),
|
|
28025
28451
|
harnessRoot,
|
|
28026
28452
|
stageId: meta.stageId,
|
|
28027
28453
|
kind: meta.kind,
|
|
@@ -28032,7 +28458,7 @@ function createRun(ctx, meta) {
|
|
|
28032
28458
|
relativePath,
|
|
28033
28459
|
trigger: ctx.trigger ?? "cli"
|
|
28034
28460
|
});
|
|
28035
|
-
|
|
28461
|
+
fs24.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
|
|
28036
28462
|
return run2;
|
|
28037
28463
|
}
|
|
28038
28464
|
function finishRun(repoPath, runId, update) {
|
|
@@ -28051,13 +28477,13 @@ function finishRun(repoPath, runId, update) {
|
|
|
28051
28477
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28052
28478
|
});
|
|
28053
28479
|
const runPath = resolveRunFilePath(harnessRoot, finished);
|
|
28054
|
-
|
|
28480
|
+
fs24.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
|
|
28055
28481
|
return finished;
|
|
28056
28482
|
}
|
|
28057
28483
|
function findRunRecord(harnessRoot, runId) {
|
|
28058
28484
|
const runsDir = getRunsDir(harnessRoot);
|
|
28059
28485
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28060
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28486
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs24.readFileSync(filePath, "utf8")));
|
|
28061
28487
|
if (parsed.success && parsed.data.runId === runId) {
|
|
28062
28488
|
return parsed.data;
|
|
28063
28489
|
}
|
|
@@ -28069,10 +28495,10 @@ function getRun(repoPath, runId) {
|
|
|
28069
28495
|
}
|
|
28070
28496
|
function listRuns(repoPath, filter = {}) {
|
|
28071
28497
|
const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
|
|
28072
|
-
if (!
|
|
28498
|
+
if (!fs24.existsSync(runsDir)) return [];
|
|
28073
28499
|
const runs = [];
|
|
28074
28500
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28075
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28501
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs24.readFileSync(filePath, "utf8")));
|
|
28076
28502
|
if (!parsed.success) continue;
|
|
28077
28503
|
if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
|
|
28078
28504
|
runs.push(parsed.data);
|
|
@@ -28084,11 +28510,552 @@ function listRuns(repoPath, filter = {}) {
|
|
|
28084
28510
|
return runs;
|
|
28085
28511
|
}
|
|
28086
28512
|
|
|
28513
|
+
// src/core/slot-preflight.ts
|
|
28514
|
+
var path26 = __toESM(require("path"));
|
|
28515
|
+
|
|
28516
|
+
// src/core/control-port.ts
|
|
28517
|
+
var import_child_process4 = require("child_process");
|
|
28518
|
+
var fs26 = __toESM(require("fs"));
|
|
28519
|
+
var path24 = __toESM(require("path"));
|
|
28520
|
+
|
|
28521
|
+
// src/core/slot-ports.ts
|
|
28522
|
+
var import_child_process3 = require("child_process");
|
|
28523
|
+
var fs25 = __toESM(require("fs"));
|
|
28524
|
+
var path23 = __toESM(require("path"));
|
|
28525
|
+
function portStep(env3) {
|
|
28526
|
+
return Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
28527
|
+
}
|
|
28528
|
+
function defaultAppPort(base, agentId, step) {
|
|
28529
|
+
return base + agentId * step;
|
|
28530
|
+
}
|
|
28531
|
+
function slotPortLaneEnd(defaultPort, step) {
|
|
28532
|
+
return defaultPort + step - 1;
|
|
28533
|
+
}
|
|
28534
|
+
function isPortInUse(port, host = "127.0.0.1") {
|
|
28535
|
+
try {
|
|
28536
|
+
(0, import_child_process3.execSync)(`bash -c 'exec 3<>/dev/tcp/${host}/${port}'`, { stdio: "ignore" });
|
|
28537
|
+
return true;
|
|
28538
|
+
} catch {
|
|
28539
|
+
return false;
|
|
28540
|
+
}
|
|
28541
|
+
}
|
|
28542
|
+
function pickFreePort(start, end) {
|
|
28543
|
+
for (let port = start; port <= end; port++) {
|
|
28544
|
+
if (!isPortInUse(port)) return port;
|
|
28545
|
+
}
|
|
28546
|
+
return void 0;
|
|
28547
|
+
}
|
|
28548
|
+
function allocatePort(defaultPort, scanStart, scanEnd) {
|
|
28549
|
+
if (!isPortInUse(defaultPort)) return defaultPort;
|
|
28550
|
+
return pickFreePort(scanStart, scanEnd);
|
|
28551
|
+
}
|
|
28552
|
+
function allocateAppPorts(repoPath, agentId) {
|
|
28553
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28554
|
+
const env3 = readHarnessEnv(harnessRoot);
|
|
28555
|
+
const step = portStep(env3);
|
|
28556
|
+
const feDefault = defaultAppPort(Number(env3.HARNESS_FE_BASE_PORT ?? 3e3), agentId, step);
|
|
28557
|
+
const apiDefault = defaultAppPort(Number(env3.HARNESS_API_BASE_PORT ?? 8e3), agentId, step);
|
|
28558
|
+
const debugDefault = 9200 + agentId * step;
|
|
28559
|
+
const frontend = allocatePort(feDefault, feDefault, slotPortLaneEnd(feDefault, step));
|
|
28560
|
+
const api = allocatePort(apiDefault, apiDefault, slotPortLaneEnd(apiDefault, step));
|
|
28561
|
+
const debug2 = allocatePort(debugDefault, debugDefault, debugDefault + step - 1);
|
|
28562
|
+
if (frontend === void 0) {
|
|
28563
|
+
return { error: `No free frontend port in range ${feDefault}-${slotPortLaneEnd(feDefault, step)}` };
|
|
28564
|
+
}
|
|
28565
|
+
if (api === void 0) {
|
|
28566
|
+
return { error: `No free API port in range ${apiDefault}-${slotPortLaneEnd(apiDefault, step)}` };
|
|
28567
|
+
}
|
|
28568
|
+
if (debug2 === void 0) {
|
|
28569
|
+
return { error: `No free debug port in range ${debugDefault}-${debugDefault + step - 1}` };
|
|
28570
|
+
}
|
|
28571
|
+
return {
|
|
28572
|
+
frontend,
|
|
28573
|
+
api,
|
|
28574
|
+
debug: debug2,
|
|
28575
|
+
allocated: frontend !== feDefault || api !== apiDefault || debug2 !== debugDefault
|
|
28576
|
+
};
|
|
28577
|
+
}
|
|
28578
|
+
function harnessUsesPm2(repoPath) {
|
|
28579
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28580
|
+
return fs25.existsSync(path23.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
|
|
28581
|
+
}
|
|
28582
|
+
|
|
28583
|
+
// src/core/control-port.ts
|
|
28584
|
+
function parseControlHostPort(apiUrl = getControlApiUrl()) {
|
|
28585
|
+
try {
|
|
28586
|
+
const url = new URL(apiUrl);
|
|
28587
|
+
if (url.port) return Number(url.port);
|
|
28588
|
+
return url.protocol === "https:" ? 443 : 80;
|
|
28589
|
+
} catch {
|
|
28590
|
+
return 3847;
|
|
28591
|
+
}
|
|
28592
|
+
}
|
|
28593
|
+
function portPublishedInDocker(ports, port) {
|
|
28594
|
+
const re2 = new RegExp(`:${port}->|:${port}/`);
|
|
28595
|
+
return re2.test(ports) || ports.includes(`0.0.0.0:${port}`);
|
|
28596
|
+
}
|
|
28597
|
+
function dockerOnPort(containers, port) {
|
|
28598
|
+
return containers.find((c2) => portPublishedInDocker(c2.ports, port));
|
|
28599
|
+
}
|
|
28600
|
+
function controlContainerOnPort(containers, port) {
|
|
28601
|
+
return containers.find(
|
|
28602
|
+
(c2) => /control/i.test(c2.name) && portPublishedInDocker(c2.ports, port)
|
|
28603
|
+
);
|
|
28604
|
+
}
|
|
28605
|
+
function listDockerContainers() {
|
|
28606
|
+
try {
|
|
28607
|
+
const raw = (0, import_child_process4.execSync)("docker ps --format {{.Names}}\\t{{.Ports}}", {
|
|
28608
|
+
encoding: "utf8",
|
|
28609
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
28610
|
+
timeout: 3e3
|
|
28611
|
+
});
|
|
28612
|
+
return raw.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
28613
|
+
const tab = line.indexOf(" ");
|
|
28614
|
+
if (tab === -1) return { name: line, ports: "" };
|
|
28615
|
+
return { name: line.slice(0, tab), ports: line.slice(tab + 1) };
|
|
28616
|
+
});
|
|
28617
|
+
} catch {
|
|
28618
|
+
return [];
|
|
28619
|
+
}
|
|
28620
|
+
}
|
|
28621
|
+
function formatControlPortBlocker(containerName, port, label) {
|
|
28622
|
+
const suffix = label ? ` (${label})` : "";
|
|
28623
|
+
return `har control up (container "${containerName}") occupies port ${port}${suffix}.`;
|
|
28624
|
+
}
|
|
28625
|
+
function formatControlDefaultPortWarning(containerName, defaultPort, allocatedPort) {
|
|
28626
|
+
return `har control up holds port ${defaultPort} (container "${containerName}"). Harness will use port ${allocatedPort} instead. Run har control down to reclaim the default port.`;
|
|
28627
|
+
}
|
|
28628
|
+
function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFrontend) {
|
|
28629
|
+
if (allocatedFrontend === defaultFrontendPort) return [];
|
|
28630
|
+
const control = controlContainerOnPort(containers, defaultFrontendPort);
|
|
28631
|
+
if (!control) return [];
|
|
28632
|
+
return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
|
|
28633
|
+
}
|
|
28634
|
+
function inspectControlUpReadiness(repoPath) {
|
|
28635
|
+
const resolved = path24.resolve(repoPath);
|
|
28636
|
+
const port = parseControlHostPort();
|
|
28637
|
+
const containers = listDockerContainers();
|
|
28638
|
+
const control = controlContainerOnPort(containers, port);
|
|
28639
|
+
const warnings = [];
|
|
28640
|
+
const controlHarnessDir = path24.join(resolved, "control", ".har");
|
|
28641
|
+
const harnessSlot1Active = fs26.existsSync(controlHarnessDir) && readSlotRegistry(path24.join(resolved, "control"), 1)?.status === "active";
|
|
28642
|
+
if (control) {
|
|
28643
|
+
return {
|
|
28644
|
+
warnings,
|
|
28645
|
+
portInUse: true,
|
|
28646
|
+
controlAlreadyRunning: true,
|
|
28647
|
+
harnessSlot1Active
|
|
28648
|
+
};
|
|
28649
|
+
}
|
|
28650
|
+
if (harnessSlot1Active) {
|
|
28651
|
+
warnings.push(
|
|
28652
|
+
`Mission Control harness slot 1 is active under control/.har (port ${port}). Stop it with: cd control && har env teardown 1 \u2014 or use har control up only when the harness is down.`
|
|
28653
|
+
);
|
|
28654
|
+
}
|
|
28655
|
+
const portBusy = isPortInUse(port);
|
|
28656
|
+
if (portBusy) {
|
|
28657
|
+
const occupant = dockerOnPort(containers, port);
|
|
28658
|
+
const who = occupant ? `container "${occupant.name}"` : "another process";
|
|
28659
|
+
warnings.push(
|
|
28660
|
+
`Port ${port} is already in use by ${who}. har control up needs this port \u2014 free it first.`
|
|
28661
|
+
);
|
|
28662
|
+
}
|
|
28663
|
+
return {
|
|
28664
|
+
warnings,
|
|
28665
|
+
portInUse: portBusy,
|
|
28666
|
+
controlAlreadyRunning: false,
|
|
28667
|
+
harnessSlot1Active
|
|
28668
|
+
};
|
|
28669
|
+
}
|
|
28670
|
+
|
|
28671
|
+
// src/core/slot-launch-guard-occupied.ts
|
|
28672
|
+
var fs27 = __toESM(require("fs"));
|
|
28673
|
+
var os5 = __toESM(require("os"));
|
|
28674
|
+
var path25 = __toESM(require("path"));
|
|
28675
|
+
var import_child_process5 = require("child_process");
|
|
28676
|
+
function runGit(cwd, args) {
|
|
28677
|
+
try {
|
|
28678
|
+
return (0, import_child_process5.execSync)(`git ${args}`, {
|
|
28679
|
+
cwd,
|
|
28680
|
+
encoding: "utf8",
|
|
28681
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
28682
|
+
}).trim();
|
|
28683
|
+
} catch {
|
|
28684
|
+
return void 0;
|
|
28685
|
+
}
|
|
28686
|
+
}
|
|
28687
|
+
function worktreeDirty(worktreePath) {
|
|
28688
|
+
const porcelain = runGit(worktreePath, "status --porcelain");
|
|
28689
|
+
return porcelain !== void 0 && porcelain.length > 0;
|
|
28690
|
+
}
|
|
28691
|
+
function gitCommonDir2(cwd) {
|
|
28692
|
+
const out = runGit(cwd, "rev-parse --git-common-dir");
|
|
28693
|
+
return out ? path25.resolve(cwd, out) : void 0;
|
|
28694
|
+
}
|
|
28695
|
+
function sameGitCheckout2(a2, b2) {
|
|
28696
|
+
const left2 = gitCommonDir2(a2);
|
|
28697
|
+
const right2 = gitCommonDir2(b2);
|
|
28698
|
+
return left2 !== void 0 && right2 !== void 0 && left2 === right2;
|
|
28699
|
+
}
|
|
28700
|
+
function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
28701
|
+
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28702
|
+
if (session?.worktreePath && fs27.existsSync(session.worktreePath)) {
|
|
28703
|
+
return session.worktreePath;
|
|
28704
|
+
}
|
|
28705
|
+
const legacy = path25.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
28706
|
+
if (fs27.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
|
|
28707
|
+
const worktreesRoot = path25.join(os5.homedir(), "worktrees");
|
|
28708
|
+
if (!fs27.existsSync(worktreesRoot)) return void 0;
|
|
28709
|
+
const suffix = `-har-agent-${agentId}-`;
|
|
28710
|
+
const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
|
|
28711
|
+
for (const name of fs27.readdirSync(worktreesRoot)) {
|
|
28712
|
+
if (!name.includes(suffix)) continue;
|
|
28713
|
+
const candidate = path25.join(worktreesRoot, name);
|
|
28714
|
+
if (!sameGitCheckout2(harnessRoot, candidate)) continue;
|
|
28715
|
+
if (!fs27.existsSync(path25.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
|
|
28716
|
+
return candidate;
|
|
28717
|
+
}
|
|
28718
|
+
return void 0;
|
|
28719
|
+
}
|
|
28720
|
+
function collectOccupiedSlot(repoPath, agentId) {
|
|
28721
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28722
|
+
const env3 = readHarnessEnv(harnessRoot);
|
|
28723
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
|
|
28724
|
+
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28725
|
+
const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
|
|
28726
|
+
const workDir = session?.workDir;
|
|
28727
|
+
const envInWorkDir = workDir ? fs27.existsSync(path25.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
28728
|
+
const envInRoot = fs27.existsSync(path25.join(harnessRoot, `.env.agent.${agentId}`));
|
|
28729
|
+
const envInWorktree = worktreePath ? fs27.existsSync(path25.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
28730
|
+
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
28731
|
+
if (!active) return void 0;
|
|
28732
|
+
const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
|
|
28733
|
+
return {
|
|
28734
|
+
agentId,
|
|
28735
|
+
active: true,
|
|
28736
|
+
workDir: workDir ?? worktreePath,
|
|
28737
|
+
worktreePath,
|
|
28738
|
+
branch: session?.branch,
|
|
28739
|
+
sessionStatus: session?.status,
|
|
28740
|
+
lastError: session?.lastError,
|
|
28741
|
+
sessionCreatedAt: session?.createdAt,
|
|
28742
|
+
dirty,
|
|
28743
|
+
harnessUsage: "none"
|
|
28744
|
+
};
|
|
28745
|
+
}
|
|
28746
|
+
function formatOccupiedSlot(slot, resume) {
|
|
28747
|
+
const lines = [
|
|
28748
|
+
`Slot ${slot.agentId} is already in use.`,
|
|
28749
|
+
slot.worktreePath ? ` Worktree: ${slot.worktreePath}` : void 0,
|
|
28750
|
+
slot.branch ? ` Branch: ${slot.branch}` : void 0,
|
|
28751
|
+
slot.workDir ? ` Work dir: ${slot.workDir}` : void 0,
|
|
28752
|
+
slot.sessionStatus ? ` Status: ${slot.sessionStatus}` : void 0,
|
|
28753
|
+
slot.lastError ? ` Error: ${slot.lastError}` : void 0,
|
|
28754
|
+
slot.sessionCreatedAt ? ` Since: ${slot.sessionCreatedAt}` : void 0,
|
|
28755
|
+
slot.dirty ? " Git: dirty (uncommitted changes \u2014 commit or use force to discard)" : " Git: clean",
|
|
28756
|
+
""
|
|
28757
|
+
];
|
|
28758
|
+
if (resume || slot.sessionStatus === "failed" || slot.sessionStatus === "starting") {
|
|
28759
|
+
lines.push(
|
|
28760
|
+
"This session failed partway through launch. Resume without replacing:",
|
|
28761
|
+
` har env launch ${slot.agentId} --resume`,
|
|
28762
|
+
` har env recover ${slot.agentId}`,
|
|
28763
|
+
` ./.har/launch.sh ${slot.agentId} --resume`,
|
|
28764
|
+
""
|
|
28765
|
+
);
|
|
28766
|
+
}
|
|
28767
|
+
lines.push(
|
|
28768
|
+
"Replacing removes the worktree. The session branch is kept only if you committed.",
|
|
28769
|
+
"Gitignored paths (state/, runs/, local clones) are NOT preserved.",
|
|
28770
|
+
"",
|
|
28771
|
+
"To replace: pass confirmReplace=true (MCP), --replace (CLI), or answer y at the prompt.",
|
|
28772
|
+
"If the worktree is dirty, also pass force=true / --force after explicit user approval."
|
|
28773
|
+
);
|
|
28774
|
+
return lines.filter(Boolean).join("\n");
|
|
28775
|
+
}
|
|
28776
|
+
function checkLaunchGuard(repoPath, agentId, options = {}) {
|
|
28777
|
+
const slot = collectOccupiedSlot(repoPath, agentId);
|
|
28778
|
+
if (!slot?.active) {
|
|
28779
|
+
return { allowed: true };
|
|
28780
|
+
}
|
|
28781
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28782
|
+
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28783
|
+
if (options.resume) {
|
|
28784
|
+
if (!isSlotResumable(session)) {
|
|
28785
|
+
return {
|
|
28786
|
+
allowed: false,
|
|
28787
|
+
blocked: true,
|
|
28788
|
+
slot,
|
|
28789
|
+
reason: [
|
|
28790
|
+
`Slot ${agentId} is not resumable (status=${session?.status ?? "none"}).`,
|
|
28791
|
+
"Only failed or starting sessions can be resumed.",
|
|
28792
|
+
"Use a normal launch with --replace to start fresh."
|
|
28793
|
+
].join("\n")
|
|
28794
|
+
};
|
|
28795
|
+
}
|
|
28796
|
+
return { allowed: true, slot };
|
|
28797
|
+
}
|
|
28798
|
+
if (isSlotResumable(session) && !options.confirmReplace) {
|
|
28799
|
+
return {
|
|
28800
|
+
allowed: false,
|
|
28801
|
+
blocked: true,
|
|
28802
|
+
slot,
|
|
28803
|
+
reason: formatOccupiedSlot(slot)
|
|
28804
|
+
};
|
|
28805
|
+
}
|
|
28806
|
+
if (!options.confirmReplace) {
|
|
28807
|
+
return {
|
|
28808
|
+
allowed: false,
|
|
28809
|
+
blocked: true,
|
|
28810
|
+
slot,
|
|
28811
|
+
reason: formatOccupiedSlot(slot)
|
|
28812
|
+
};
|
|
28813
|
+
}
|
|
28814
|
+
if (slot.dirty && !options.force) {
|
|
28815
|
+
return {
|
|
28816
|
+
allowed: false,
|
|
28817
|
+
blocked: true,
|
|
28818
|
+
slot,
|
|
28819
|
+
reason: [
|
|
28820
|
+
formatOccupiedSlot(slot),
|
|
28821
|
+
"",
|
|
28822
|
+
"The occupied worktree has uncommitted changes.",
|
|
28823
|
+
"Pass force=true / --force to discard them (only after explicit user approval)."
|
|
28824
|
+
].join("\n")
|
|
28825
|
+
};
|
|
28826
|
+
}
|
|
28827
|
+
return { allowed: true, slot };
|
|
28828
|
+
}
|
|
28829
|
+
|
|
28830
|
+
// src/core/slot-preflight.ts
|
|
28831
|
+
var import_child_process6 = require("child_process");
|
|
28832
|
+
function listPm2Processes() {
|
|
28833
|
+
try {
|
|
28834
|
+
const raw = (0, import_child_process6.execSync)("npx --yes pm2 jlist", {
|
|
28835
|
+
encoding: "utf8",
|
|
28836
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
28837
|
+
timeout: 3e3
|
|
28838
|
+
});
|
|
28839
|
+
const procs = JSON.parse(raw);
|
|
28840
|
+
return Array.isArray(procs) ? procs : void 0;
|
|
28841
|
+
} catch {
|
|
28842
|
+
return void 0;
|
|
28843
|
+
}
|
|
28844
|
+
}
|
|
28845
|
+
function detectForeignPm2(projectName, agentId, procs) {
|
|
28846
|
+
if (!procs) return void 0;
|
|
28847
|
+
const slotPrefix = `har-${projectName}-agent-${agentId}-`;
|
|
28848
|
+
const legacyPrefix = `agent-${agentId}-`;
|
|
28849
|
+
const foreign = procs.filter(
|
|
28850
|
+
(p2) => p2.name && (p2.name.startsWith("har-") && p2.name.includes(`-agent-${agentId}-`) && !p2.name.startsWith(slotPrefix) || p2.name.startsWith(legacyPrefix) && !p2.name.startsWith("har-"))
|
|
28851
|
+
);
|
|
28852
|
+
if (foreign.length === 0) return void 0;
|
|
28853
|
+
return {
|
|
28854
|
+
processes: foreign.map((p2) => ({
|
|
28855
|
+
name: p2.name,
|
|
28856
|
+
cwd: p2.pm2_env?.pm_cwd ?? p2.pm2_env?.cwd
|
|
28857
|
+
}))
|
|
28858
|
+
};
|
|
28859
|
+
}
|
|
28860
|
+
function infraEnabled(env3, service) {
|
|
28861
|
+
return ` ${env3.HARNESS_INFRA_SERVICES ?? ""} `.includes(` ${service} `);
|
|
28862
|
+
}
|
|
28863
|
+
function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
28864
|
+
const current = env3[varName] ? Number(env3[varName]) : void 0;
|
|
28865
|
+
if (current !== void 0 && !isPortInUse(current)) {
|
|
28866
|
+
return { port: current };
|
|
28867
|
+
}
|
|
28868
|
+
if (current !== void 0 && isPortInUse(current)) {
|
|
28869
|
+
const alt = pickFreePort(scanStart, scanEnd);
|
|
28870
|
+
if (alt !== void 0) return { port: alt };
|
|
28871
|
+
return { error: `No free ${varName} port in range ${scanStart}-${scanEnd}` };
|
|
28872
|
+
}
|
|
28873
|
+
const port = pickFreePort(defaultPort, scanEnd) ?? pickFreePort(scanStart, scanEnd);
|
|
28874
|
+
if (port === void 0) {
|
|
28875
|
+
return { error: `No free ${varName} port in range ${scanStart}-${scanEnd}` };
|
|
28876
|
+
}
|
|
28877
|
+
return { port };
|
|
28878
|
+
}
|
|
28879
|
+
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
28880
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28881
|
+
const env3 = readHarnessEnv(harnessRoot);
|
|
28882
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path26.basename(harnessRoot);
|
|
28883
|
+
const usesPm2 = harnessUsesPm2(repoPath);
|
|
28884
|
+
const blockers = [];
|
|
28885
|
+
const remediations = [];
|
|
28886
|
+
const warnings = [];
|
|
28887
|
+
const ports = {};
|
|
28888
|
+
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28889
|
+
const guard = options.occupied !== void 0 ? options.occupied.active ? options.resume && isSlotResumable(session) ? { allowed: true } : !options.confirmReplace ? {
|
|
28890
|
+
allowed: false,
|
|
28891
|
+
blocked: true,
|
|
28892
|
+
reason: `Slot ${agentId} is already in use.`
|
|
28893
|
+
} : options.occupied.dirty && !options.force ? {
|
|
28894
|
+
allowed: false,
|
|
28895
|
+
blocked: true,
|
|
28896
|
+
reason: `Slot ${agentId} worktree has uncommitted changes.`
|
|
28897
|
+
} : { allowed: true } : { allowed: true } : checkLaunchGuard(repoPath, agentId, options);
|
|
28898
|
+
if (!guard.allowed) {
|
|
28899
|
+
const resumable = isSlotResumable(session);
|
|
28900
|
+
blockers.push({
|
|
28901
|
+
code: options.occupied?.dirty ? "slot_dirty" : resumable ? "slot_resumable" : "slot_occupied",
|
|
28902
|
+
message: guard.reason ?? `Slot ${agentId} is occupied.`,
|
|
28903
|
+
remediation: resumable ? `Resume the partial launch: har env launch ${agentId} --resume (or har env recover ${agentId})` : guard.slot?.dirty ? "Commit changes in the worktree, or pass --force after explicit user approval." : "Pass --replace (CLI), confirmReplace=true (MCP), or answer y at the launch prompt."
|
|
28904
|
+
});
|
|
28905
|
+
remediations.push(
|
|
28906
|
+
resumable ? `har env launch ${agentId} --resume` : guard.slot?.dirty ? "har env preflight <id> --replace --force" : "har env preflight <id> --replace"
|
|
28907
|
+
);
|
|
28908
|
+
}
|
|
28909
|
+
const pm2Procs = usesPm2 ? options.pm2Processes !== void 0 ? options.pm2Processes : listPm2Processes() : void 0;
|
|
28910
|
+
const foreign = usesPm2 ? detectForeignPm2(projectName, agentId, pm2Procs) : void 0;
|
|
28911
|
+
if (foreign) {
|
|
28912
|
+
const names = foreign.processes.map((p2) => p2.name).join(", ");
|
|
28913
|
+
blockers.push({
|
|
28914
|
+
code: "foreign_pm2",
|
|
28915
|
+
message: `Foreign PM2 processes match agent ${agentId}: ${names}`,
|
|
28916
|
+
remediation: "Stop the other harness session (./.har/teardown.sh in that repo) or use a different agent slot.",
|
|
28917
|
+
details: { processes: foreign.processes }
|
|
28918
|
+
});
|
|
28919
|
+
remediations.push("Inspect with: npx pm2 jlist | grep agent-" + agentId);
|
|
28920
|
+
}
|
|
28921
|
+
if (usesPm2 && pm2Procs) {
|
|
28922
|
+
const slotPrefix = `har-${projectName}-agent-${agentId}-`;
|
|
28923
|
+
const owned = pm2Procs.filter((p2) => p2.name?.startsWith(slotPrefix));
|
|
28924
|
+
if (owned.length > 0 && !session) {
|
|
28925
|
+
blockers.push({
|
|
28926
|
+
code: "registry_missing",
|
|
28927
|
+
message: `PM2 processes exist for ${projectName} agent ${agentId} but the slot registry is missing.`,
|
|
28928
|
+
remediation: "Run teardown in this harness or delete PM2 processes manually, then relaunch."
|
|
28929
|
+
});
|
|
28930
|
+
}
|
|
28931
|
+
if (owned.length > 0 && session?.projectName && session.projectName !== projectName) {
|
|
28932
|
+
blockers.push({
|
|
28933
|
+
code: "project_mismatch",
|
|
28934
|
+
message: `Slot registry projectName=${session.projectName} does not match harness ${projectName}.`,
|
|
28935
|
+
remediation: "Teardown the stale session and relaunch with --replace."
|
|
28936
|
+
});
|
|
28937
|
+
}
|
|
28938
|
+
}
|
|
28939
|
+
let allocatedPorts = false;
|
|
28940
|
+
if (usesPm2 && (options.allocatePorts ?? true)) {
|
|
28941
|
+
const alloc = allocateAppPorts(repoPath, agentId);
|
|
28942
|
+
if ("error" in alloc) {
|
|
28943
|
+
blockers.push({
|
|
28944
|
+
code: "ports_exhausted",
|
|
28945
|
+
message: alloc.error,
|
|
28946
|
+
remediation: "Free a port in the slot lane or stop the process/container using it."
|
|
28947
|
+
});
|
|
28948
|
+
} else {
|
|
28949
|
+
ports.frontend = alloc.frontend;
|
|
28950
|
+
ports.api = alloc.api;
|
|
28951
|
+
ports.debug = alloc.debug;
|
|
28952
|
+
allocatedPorts = alloc.allocated;
|
|
28953
|
+
const containers = options.dockerContainers ?? listDockerContainers();
|
|
28954
|
+
const feDefault = defaultAppPort(
|
|
28955
|
+
Number(env3.HARNESS_FE_BASE_PORT ?? 3e3),
|
|
28956
|
+
agentId,
|
|
28957
|
+
portStep(env3)
|
|
28958
|
+
);
|
|
28959
|
+
warnings.push(...controlDefaultPortWarnings(containers, feDefault, alloc.frontend));
|
|
28960
|
+
for (const [label, port] of Object.entries({ frontend: alloc.frontend, api: alloc.api })) {
|
|
28961
|
+
const control = controlContainerOnPort(containers, port);
|
|
28962
|
+
if (control) {
|
|
28963
|
+
blockers.push({
|
|
28964
|
+
code: "control_port_conflict",
|
|
28965
|
+
message: formatControlPortBlocker(control.name, port, label),
|
|
28966
|
+
remediation: "Run: har control down \u2014 or launch a different slot id.",
|
|
28967
|
+
details: { container: control.name, port, label }
|
|
28968
|
+
});
|
|
28969
|
+
remediations.push("har control down");
|
|
28970
|
+
} else {
|
|
28971
|
+
const occupant = dockerOnPort(containers, port);
|
|
28972
|
+
if (occupant && !occupant.name.startsWith(`har-${projectName}-`)) {
|
|
28973
|
+
blockers.push({
|
|
28974
|
+
code: "docker_port_conflict",
|
|
28975
|
+
message: `Docker container "${occupant.name}" binds port ${port} (${label}).`,
|
|
28976
|
+
remediation: `Stop the container: docker stop ${occupant.name}`,
|
|
28977
|
+
details: { container: occupant.name, port, label }
|
|
28978
|
+
});
|
|
28979
|
+
}
|
|
28980
|
+
}
|
|
28981
|
+
}
|
|
28982
|
+
}
|
|
28983
|
+
}
|
|
28984
|
+
if (infraEnabled(env3, "db")) {
|
|
28985
|
+
const dbDefault = Number(env3.HARNESS_DB_PORT_DEFAULT ?? 15432);
|
|
28986
|
+
const dbStart = Number(env3.HARNESS_DB_PORT_SCAN_START ?? dbDefault);
|
|
28987
|
+
const dbEnd = Number(env3.HARNESS_DB_PORT_SCAN_END ?? dbDefault + 67);
|
|
28988
|
+
const dbCheck = checkInfraPort(env3, "AGENT_DB_PORT", dbDefault, dbStart, dbEnd);
|
|
28989
|
+
if (dbCheck.error) {
|
|
28990
|
+
blockers.push({
|
|
28991
|
+
code: "db_port_exhausted",
|
|
28992
|
+
message: dbCheck.error,
|
|
28993
|
+
remediation: `Free a port in ${dbStart}-${dbEnd} or stop har-*-db-1 containers from other projects.`
|
|
28994
|
+
});
|
|
28995
|
+
} else if (dbCheck.port !== void 0) {
|
|
28996
|
+
ports.db = dbCheck.port;
|
|
28997
|
+
const occupant = dockerOnPort(
|
|
28998
|
+
options.dockerContainers ?? listDockerContainers(),
|
|
28999
|
+
dbCheck.port
|
|
29000
|
+
);
|
|
29001
|
+
if (occupant && !occupant.name.startsWith(`har-${projectName}-`) && !occupant.name.includes("-db-")) {
|
|
29002
|
+
blockers.push({
|
|
29003
|
+
code: "db_port_conflict",
|
|
29004
|
+
message: `Port ${dbCheck.port} (database) is held by "${occupant.name}".`,
|
|
29005
|
+
remediation: `Stop the container or pick another slot.`,
|
|
29006
|
+
details: { container: occupant.name, port: dbCheck.port }
|
|
29007
|
+
});
|
|
29008
|
+
}
|
|
29009
|
+
}
|
|
29010
|
+
}
|
|
29011
|
+
const canLaunch = blockers.length === 0;
|
|
29012
|
+
return {
|
|
29013
|
+
canLaunch,
|
|
29014
|
+
verdict: canLaunch ? "ready" : "blocked",
|
|
29015
|
+
blockers,
|
|
29016
|
+
remediations: [...new Set(remediations)],
|
|
29017
|
+
ports: Object.keys(ports).length > 0 ? ports : void 0,
|
|
29018
|
+
allocatedPorts: allocatedPorts || void 0,
|
|
29019
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
29020
|
+
};
|
|
29021
|
+
}
|
|
29022
|
+
function formatPreflightReport(agentId, readiness) {
|
|
29023
|
+
const lines = [];
|
|
29024
|
+
if (readiness.canLaunch) {
|
|
29025
|
+
lines.push(`Slot ${agentId}: ready to launch.`);
|
|
29026
|
+
if (readiness.ports) {
|
|
29027
|
+
const p2 = readiness.ports;
|
|
29028
|
+
const parts = [
|
|
29029
|
+
p2.frontend !== void 0 ? `frontend=${p2.frontend}` : void 0,
|
|
29030
|
+
p2.api !== void 0 ? `api=${p2.api}` : void 0,
|
|
29031
|
+
p2.debug !== void 0 ? `debug=${p2.debug}` : void 0,
|
|
29032
|
+
p2.db !== void 0 ? `db=${p2.db}` : void 0
|
|
29033
|
+
].filter(Boolean);
|
|
29034
|
+
if (parts.length) lines.push(` Ports: ${parts.join(" ")}`);
|
|
29035
|
+
if (readiness.allocatedPorts && !readiness.warnings?.length) {
|
|
29036
|
+
lines.push(" (alternate ports selected \u2014 defaults were busy)");
|
|
29037
|
+
}
|
|
29038
|
+
}
|
|
29039
|
+
if (readiness.warnings?.length) {
|
|
29040
|
+
for (const w2 of readiness.warnings) {
|
|
29041
|
+
lines.push(` WARN: ${w2}`);
|
|
29042
|
+
}
|
|
29043
|
+
}
|
|
29044
|
+
return lines.join("\n");
|
|
29045
|
+
}
|
|
29046
|
+
lines.push(`Slot ${agentId}: launch blocked.`);
|
|
29047
|
+
for (const b2 of readiness.blockers) {
|
|
29048
|
+
lines.push(` [${b2.code}] ${b2.message}`);
|
|
29049
|
+
if (b2.remediation) lines.push(` \u2192 ${b2.remediation}`);
|
|
29050
|
+
}
|
|
29051
|
+
return lines.join("\n");
|
|
29052
|
+
}
|
|
29053
|
+
|
|
28087
29054
|
// src/core/slot-status.ts
|
|
28088
29055
|
var BYPASS_WARNING_MS = 15 * 60 * 1e3;
|
|
28089
29056
|
function readGitRemote(repoPath) {
|
|
28090
29057
|
try {
|
|
28091
|
-
const remote = (0,
|
|
29058
|
+
const remote = (0, import_child_process7.execSync)("git remote get-url origin", {
|
|
28092
29059
|
cwd: repoPath,
|
|
28093
29060
|
encoding: "utf8",
|
|
28094
29061
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -28098,9 +29065,9 @@ function readGitRemote(repoPath) {
|
|
|
28098
29065
|
return void 0;
|
|
28099
29066
|
}
|
|
28100
29067
|
}
|
|
28101
|
-
function
|
|
29068
|
+
function runGit2(cwd, args) {
|
|
28102
29069
|
try {
|
|
28103
|
-
return (0,
|
|
29070
|
+
return (0, import_child_process7.execSync)(`git ${args}`, {
|
|
28104
29071
|
cwd,
|
|
28105
29072
|
encoding: "utf8",
|
|
28106
29073
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -28110,40 +29077,40 @@ function runGit(cwd, args) {
|
|
|
28110
29077
|
}
|
|
28111
29078
|
}
|
|
28112
29079
|
function readWorktreeBranch(worktreePath) {
|
|
28113
|
-
return
|
|
29080
|
+
return runGit2(worktreePath, "rev-parse --abbrev-ref HEAD");
|
|
28114
29081
|
}
|
|
28115
|
-
function
|
|
28116
|
-
const out =
|
|
28117
|
-
return out ?
|
|
29082
|
+
function gitCommonDir3(cwd) {
|
|
29083
|
+
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
29084
|
+
return out ? path27.resolve(cwd, out) : void 0;
|
|
28118
29085
|
}
|
|
28119
|
-
function
|
|
28120
|
-
const left2 =
|
|
28121
|
-
const right2 =
|
|
29086
|
+
function sameGitCheckout3(a2, b2) {
|
|
29087
|
+
const left2 = gitCommonDir3(a2);
|
|
29088
|
+
const right2 = gitCommonDir3(b2);
|
|
28122
29089
|
return left2 !== void 0 && right2 !== void 0 && left2 === right2;
|
|
28123
29090
|
}
|
|
28124
29091
|
function gitPrefix2(cwd) {
|
|
28125
|
-
const out =
|
|
29092
|
+
const out = runGit2(cwd, "rev-parse --show-prefix");
|
|
28126
29093
|
return out ?? "";
|
|
28127
29094
|
}
|
|
28128
29095
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
28129
|
-
const worktreesRoot =
|
|
28130
|
-
if (!
|
|
29096
|
+
const worktreesRoot = path27.join(os6.homedir(), "worktrees");
|
|
29097
|
+
if (!fs28.existsSync(worktreesRoot)) return void 0;
|
|
28131
29098
|
const suffix = `-har-agent-${agentId}-`;
|
|
28132
29099
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
28133
|
-
const matches =
|
|
28134
|
-
(candidate) =>
|
|
29100
|
+
const matches = fs28.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path27.join(worktreesRoot, entry.name)).filter(
|
|
29101
|
+
(candidate) => sameGitCheckout3(harnessRoot, candidate) && fs28.existsSync(path27.join(candidate, relPrefix, `.env.agent.${agentId}`))
|
|
28135
29102
|
);
|
|
28136
29103
|
return matches.sort()[0];
|
|
28137
29104
|
}
|
|
28138
29105
|
function collectWorktreeDrift(harnessRoot, worktreePath, baseCommit) {
|
|
28139
29106
|
const drift = {};
|
|
28140
|
-
drift.detachedHead =
|
|
28141
|
-
const porcelain =
|
|
29107
|
+
drift.detachedHead = runGit2(worktreePath, "symbolic-ref -q HEAD") === void 0;
|
|
29108
|
+
const porcelain = runGit2(worktreePath, "status --porcelain");
|
|
28142
29109
|
if (porcelain !== void 0) drift.dirty = porcelain.length > 0;
|
|
28143
29110
|
if (baseCommit) {
|
|
28144
|
-
const ahead =
|
|
29111
|
+
const ahead = runGit2(worktreePath, `rev-list --count ${baseCommit}..HEAD`);
|
|
28145
29112
|
if (ahead !== void 0) drift.ahead = Number(ahead);
|
|
28146
|
-
const behind =
|
|
29113
|
+
const behind = runGit2(harnessRoot, `rev-list --count ${baseCommit}..HEAD`);
|
|
28147
29114
|
if (behind !== void 0) {
|
|
28148
29115
|
drift.behind = Number(behind);
|
|
28149
29116
|
drift.stale = drift.behind > 0;
|
|
@@ -28174,20 +29141,50 @@ function lastBuildPass(run2) {
|
|
|
28174
29141
|
const build = data.verification?.stages?.find((s2) => s2.name === "build");
|
|
28175
29142
|
return build?.pass;
|
|
28176
29143
|
}
|
|
28177
|
-
function
|
|
29144
|
+
function listPm2Processes2() {
|
|
29145
|
+
try {
|
|
29146
|
+
const raw = (0, import_child_process7.execSync)("npx --yes pm2 jlist", {
|
|
29147
|
+
encoding: "utf8",
|
|
29148
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
29149
|
+
timeout: 3e3
|
|
29150
|
+
});
|
|
29151
|
+
const procs = JSON.parse(raw);
|
|
29152
|
+
return Array.isArray(procs) ? procs : void 0;
|
|
29153
|
+
} catch {
|
|
29154
|
+
return void 0;
|
|
29155
|
+
}
|
|
29156
|
+
}
|
|
29157
|
+
function detectPm2Issue(projectName, agentId, session, procs) {
|
|
29158
|
+
if (!procs) return void 0;
|
|
29159
|
+
const slotPrefix = `har-${projectName}-agent-${agentId}-`;
|
|
29160
|
+
const legacyPrefix = `agent-${agentId}-`;
|
|
29161
|
+
const owned = procs.filter((p2) => p2.name?.startsWith(slotPrefix));
|
|
29162
|
+
const foreign = procs.filter(
|
|
29163
|
+
(p2) => p2.name && (p2.name.startsWith("har-") && p2.name.includes(`-agent-${agentId}-`) && !p2.name.startsWith(slotPrefix) || p2.name.startsWith(legacyPrefix) && !p2.name.startsWith("har-"))
|
|
29164
|
+
);
|
|
29165
|
+
if (foreign.length > 0) return "foreign_pm2";
|
|
29166
|
+
if (owned.length > 0) {
|
|
29167
|
+
if (!session) return "registry_missing";
|
|
29168
|
+
if (session.projectName && session.projectName !== projectName) {
|
|
29169
|
+
return "project_mismatch";
|
|
29170
|
+
}
|
|
29171
|
+
}
|
|
29172
|
+
return void 0;
|
|
29173
|
+
}
|
|
29174
|
+
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
28178
29175
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28179
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29176
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path27.basename(harnessRoot);
|
|
28180
29177
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
28181
|
-
const legacyWorktreePath =
|
|
28182
|
-
|
|
29178
|
+
const legacyWorktreePath = path27.join(
|
|
29179
|
+
os6.homedir(),
|
|
28183
29180
|
"worktrees",
|
|
28184
29181
|
`${projectName}-agent-${agentId}`
|
|
28185
29182
|
);
|
|
28186
|
-
const worktreePath = session?.worktreePath &&
|
|
29183
|
+
const worktreePath = session?.worktreePath && fs28.existsSync(session.worktreePath) ? session.worktreePath : fs28.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
28187
29184
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
28188
|
-
const envInWorkDir = workDir ?
|
|
28189
|
-
const envInRoot =
|
|
28190
|
-
const envInWorktree = worktreePath ?
|
|
29185
|
+
const envInWorkDir = workDir ? fs28.existsSync(path27.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29186
|
+
const envInRoot = fs28.existsSync(path27.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29187
|
+
const envInWorktree = worktreePath ? fs28.existsSync(path27.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
28191
29188
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
28192
29189
|
const latest = latestRunForAgent(runs, agentId);
|
|
28193
29190
|
const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
|
|
@@ -28200,6 +29197,12 @@ function collectSlotStatus(harnessRoot, agentId, runs) {
|
|
|
28200
29197
|
}
|
|
28201
29198
|
}
|
|
28202
29199
|
const drift = worktreePath ? collectWorktreeDrift(harnessRoot, worktreePath, session?.baseCommit) : {};
|
|
29200
|
+
const pm2Issue = detectPm2Issue(projectName, agentId, session, pm2Procs);
|
|
29201
|
+
const readiness = inspectSlotReadiness(harnessRoot, agentId, {
|
|
29202
|
+
allocatePorts: true,
|
|
29203
|
+
occupied: { active, dirty: drift.dirty }
|
|
29204
|
+
});
|
|
29205
|
+
const resumeHint = session?.status === "failed" || session?.status === "starting" ? `har env launch ${agentId} --resume (alias: har env recover ${agentId})` : void 0;
|
|
28203
29206
|
return {
|
|
28204
29207
|
agentId,
|
|
28205
29208
|
active,
|
|
@@ -28207,6 +29210,9 @@ function collectSlotStatus(harnessRoot, agentId, runs) {
|
|
|
28207
29210
|
worktreePath,
|
|
28208
29211
|
branch: session?.branch ?? (worktreePath ? readWorktreeBranch(worktreePath) : void 0),
|
|
28209
29212
|
previewUrls,
|
|
29213
|
+
ports: session?.ports,
|
|
29214
|
+
pm2Issue,
|
|
29215
|
+
readiness,
|
|
28210
29216
|
harnessUsage: deriveHarnessUsage(latest, active),
|
|
28211
29217
|
lastRunId: latest?.runId,
|
|
28212
29218
|
lastRunAt: latest?.startedAt,
|
|
@@ -28220,6 +29226,7 @@ function collectSlotStatus(harnessRoot, agentId, runs) {
|
|
|
28220
29226
|
purpose: session?.purpose,
|
|
28221
29227
|
sessionStatus: session?.status,
|
|
28222
29228
|
lastError: session?.lastError,
|
|
29229
|
+
resumeHint,
|
|
28223
29230
|
...drift
|
|
28224
29231
|
};
|
|
28225
29232
|
}
|
|
@@ -28228,25 +29235,26 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
28228
29235
|
const runs = listRuns(harnessRoot, { limit: 200 });
|
|
28229
29236
|
const manifest = readManifest(harnessRoot);
|
|
28230
29237
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
29238
|
+
const pm2Procs = listPm2Processes2();
|
|
28231
29239
|
return {
|
|
28232
|
-
repoPath:
|
|
29240
|
+
repoPath: path27.resolve(repoPath),
|
|
28233
29241
|
harnessRoot,
|
|
28234
29242
|
gitRemote: readGitRemote(harnessRoot),
|
|
28235
29243
|
profile: manifest?.profile,
|
|
28236
|
-
slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs)),
|
|
29244
|
+
slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs)),
|
|
28237
29245
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28238
29246
|
};
|
|
28239
29247
|
}
|
|
28240
29248
|
|
|
28241
29249
|
// src/core/validations.ts
|
|
28242
29250
|
var crypto3 = __toESM(require("crypto"));
|
|
28243
|
-
var
|
|
28244
|
-
var
|
|
29251
|
+
var fs30 = __toESM(require("fs"));
|
|
29252
|
+
var path29 = __toESM(require("path"));
|
|
28245
29253
|
|
|
28246
29254
|
// src/core/change-batch.ts
|
|
28247
|
-
var
|
|
28248
|
-
var
|
|
28249
|
-
var
|
|
29255
|
+
var fs29 = __toESM(require("fs"));
|
|
29256
|
+
var os7 = __toESM(require("os"));
|
|
29257
|
+
var path28 = __toESM(require("path"));
|
|
28250
29258
|
function git(checkoutDir, args, env3) {
|
|
28251
29259
|
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
28252
29260
|
if (result.code !== 0) {
|
|
@@ -28262,7 +29270,7 @@ function isCheckoutRoot(checkoutDir) {
|
|
|
28262
29270
|
const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
|
|
28263
29271
|
if (!toplevel) return false;
|
|
28264
29272
|
try {
|
|
28265
|
-
return
|
|
29273
|
+
return fs29.realpathSync(toplevel) === fs29.realpathSync(checkoutDir);
|
|
28266
29274
|
} catch {
|
|
28267
29275
|
return false;
|
|
28268
29276
|
}
|
|
@@ -28284,7 +29292,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
|
|
|
28284
29292
|
if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
28285
29293
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
28286
29294
|
const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
28287
|
-
if (gitPath &&
|
|
29295
|
+
if (gitPath && fs29.existsSync(path28.resolve(checkoutDir, gitPath))) return true;
|
|
28288
29296
|
}
|
|
28289
29297
|
return false;
|
|
28290
29298
|
}
|
|
@@ -28307,8 +29315,8 @@ function parseNameStatus(output) {
|
|
|
28307
29315
|
return files;
|
|
28308
29316
|
}
|
|
28309
29317
|
function computeWorktreeSnapshot(checkoutDir) {
|
|
28310
|
-
const tmpDir =
|
|
28311
|
-
const tmpIndex =
|
|
29318
|
+
const tmpDir = fs29.mkdtempSync(path28.join(os7.tmpdir(), "har-idx-"));
|
|
29319
|
+
const tmpIndex = path28.join(tmpDir, "index");
|
|
28312
29320
|
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
28313
29321
|
try {
|
|
28314
29322
|
const headTree = getHeadTree(checkoutDir);
|
|
@@ -28331,35 +29339,35 @@ function computeWorktreeSnapshot(checkoutDir) {
|
|
|
28331
29339
|
changedFiles: parseNameStatus(diffOutput)
|
|
28332
29340
|
};
|
|
28333
29341
|
} finally {
|
|
28334
|
-
|
|
29342
|
+
fs29.rmSync(tmpDir, { recursive: true, force: true });
|
|
28335
29343
|
}
|
|
28336
29344
|
}
|
|
28337
29345
|
|
|
28338
29346
|
// src/core/validations.ts
|
|
28339
29347
|
var VALIDATIONS_DIR = "validations";
|
|
28340
29348
|
function getValidationsDir(checkoutDir) {
|
|
28341
|
-
return
|
|
29349
|
+
return path29.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
28342
29350
|
}
|
|
28343
29351
|
function validationPath(checkoutDir, treeHash) {
|
|
28344
|
-
return
|
|
29352
|
+
return path29.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
28345
29353
|
}
|
|
28346
29354
|
function writeRecord(checkoutDir, record2) {
|
|
28347
29355
|
const dir = getValidationsDir(checkoutDir);
|
|
28348
|
-
|
|
28349
|
-
|
|
29356
|
+
fs30.mkdirSync(dir, { recursive: true });
|
|
29357
|
+
fs30.writeFileSync(
|
|
28350
29358
|
validationPath(checkoutDir, record2.treeHash),
|
|
28351
29359
|
`${JSON.stringify(record2, null, 2)}
|
|
28352
29360
|
`
|
|
28353
29361
|
);
|
|
28354
29362
|
}
|
|
28355
29363
|
function ensureValidationsIgnored(checkoutDir) {
|
|
28356
|
-
const harDir =
|
|
28357
|
-
if (!
|
|
28358
|
-
const gitignorePath =
|
|
28359
|
-
const content =
|
|
29364
|
+
const harDir = path29.join(checkoutDir, ".har");
|
|
29365
|
+
if (!fs30.existsSync(harDir)) return;
|
|
29366
|
+
const gitignorePath = path29.join(harDir, ".gitignore");
|
|
29367
|
+
const content = fs30.existsSync(gitignorePath) ? fs30.readFileSync(gitignorePath, "utf8") : "";
|
|
28360
29368
|
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
28361
29369
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
28362
|
-
|
|
29370
|
+
fs30.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
28363
29371
|
`);
|
|
28364
29372
|
}
|
|
28365
29373
|
function recordValidation(input) {
|
|
@@ -28376,8 +29384,8 @@ function recordValidation(input) {
|
|
|
28376
29384
|
treeHash: snapshot.treeHash,
|
|
28377
29385
|
headSha: snapshot.headSha,
|
|
28378
29386
|
branch: snapshot.branch,
|
|
28379
|
-
workDir:
|
|
28380
|
-
harnessRoot:
|
|
29387
|
+
workDir: path29.resolve(input.checkoutDir),
|
|
29388
|
+
harnessRoot: path29.resolve(input.harnessRoot),
|
|
28381
29389
|
agentId: input.agentId,
|
|
28382
29390
|
status: input.status,
|
|
28383
29391
|
full: input.full,
|
|
@@ -28389,16 +29397,16 @@ function recordValidation(input) {
|
|
|
28389
29397
|
committedAt: existing?.committedAt
|
|
28390
29398
|
});
|
|
28391
29399
|
writeRecord(input.checkoutDir, record2);
|
|
28392
|
-
if (
|
|
29400
|
+
if (path29.resolve(input.harnessRoot) !== path29.resolve(input.checkoutDir)) {
|
|
28393
29401
|
writeRecord(input.harnessRoot, record2);
|
|
28394
29402
|
}
|
|
28395
29403
|
return record2;
|
|
28396
29404
|
}
|
|
28397
29405
|
function findValidation(checkoutDir, treeHash) {
|
|
28398
29406
|
const file = validationPath(checkoutDir, treeHash);
|
|
28399
|
-
if (!
|
|
29407
|
+
if (!fs30.existsSync(file)) return void 0;
|
|
28400
29408
|
try {
|
|
28401
|
-
return ValidationRecordSchema.parse(JSON.parse(
|
|
29409
|
+
return ValidationRecordSchema.parse(JSON.parse(fs30.readFileSync(file, "utf8")));
|
|
28402
29410
|
} catch {
|
|
28403
29411
|
return void 0;
|
|
28404
29412
|
}
|
|
@@ -28413,19 +29421,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
|
28413
29421
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28414
29422
|
};
|
|
28415
29423
|
writeRecord(checkoutDir, updated);
|
|
28416
|
-
if (record2.harnessRoot &&
|
|
29424
|
+
if (record2.harnessRoot && path29.resolve(record2.harnessRoot) !== path29.resolve(checkoutDir) && fs30.existsSync(path29.join(record2.harnessRoot, ".har"))) {
|
|
28417
29425
|
writeRecord(record2.harnessRoot, updated);
|
|
28418
29426
|
}
|
|
28419
29427
|
return updated;
|
|
28420
29428
|
}
|
|
28421
29429
|
function listValidations(harnessRoot) {
|
|
28422
29430
|
const dir = getValidationsDir(harnessRoot);
|
|
28423
|
-
if (!
|
|
29431
|
+
if (!fs30.existsSync(dir)) return [];
|
|
28424
29432
|
const records = [];
|
|
28425
|
-
for (const entry of
|
|
29433
|
+
for (const entry of fs30.readdirSync(dir)) {
|
|
28426
29434
|
if (!entry.endsWith(".json")) continue;
|
|
28427
29435
|
try {
|
|
28428
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(
|
|
29436
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs30.readFileSync(path29.join(dir, entry), "utf8"))));
|
|
28429
29437
|
} catch {
|
|
28430
29438
|
}
|
|
28431
29439
|
}
|
|
@@ -28483,7 +29491,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
|
|
|
28483
29491
|
const deadline = Date.now() + timeoutMs;
|
|
28484
29492
|
while (Date.now() < deadline) {
|
|
28485
29493
|
if (await isControlApiReachable(apiUrl)) return true;
|
|
28486
|
-
await new Promise((
|
|
29494
|
+
await new Promise((resolve26) => setTimeout(resolve26, intervalMs));
|
|
28487
29495
|
}
|
|
28488
29496
|
return false;
|
|
28489
29497
|
}
|
|
@@ -28495,7 +29503,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
28495
29503
|
}
|
|
28496
29504
|
const repoPaths = new Set(listRegisteredRepos());
|
|
28497
29505
|
if (options?.cwd) {
|
|
28498
|
-
const cwd =
|
|
29506
|
+
const cwd = path30.resolve(options.cwd);
|
|
28499
29507
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
28500
29508
|
}
|
|
28501
29509
|
try {
|
|
@@ -28503,7 +29511,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
28503
29511
|
if (listResponse.ok) {
|
|
28504
29512
|
const repos = await listResponse.json();
|
|
28505
29513
|
for (const repo of repos) {
|
|
28506
|
-
const resolved =
|
|
29514
|
+
const resolved = path30.resolve(repo.path);
|
|
28507
29515
|
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
28508
29516
|
}
|
|
28509
29517
|
}
|
|
@@ -28522,7 +29530,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
28522
29530
|
return { synced, failed };
|
|
28523
29531
|
}
|
|
28524
29532
|
async function registerRepoWithControl(options) {
|
|
28525
|
-
const repoPath =
|
|
29533
|
+
const repoPath = path30.resolve(options.repoPath);
|
|
28526
29534
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
28527
29535
|
const manifest = readManifest(repoPath);
|
|
28528
29536
|
const stagesRegistry = readStageRegistry(repoPath);
|
|
@@ -28535,7 +29543,7 @@ async function registerRepoWithControl(options) {
|
|
|
28535
29543
|
return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
|
|
28536
29544
|
}
|
|
28537
29545
|
async function syncRepoWithControl(options) {
|
|
28538
|
-
const repoPath =
|
|
29546
|
+
const repoPath = path30.resolve(options.repoPath);
|
|
28539
29547
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
28540
29548
|
if (options.cloud) {
|
|
28541
29549
|
const remote = createRemoteExecutor();
|
|
@@ -28563,7 +29571,7 @@ async function syncRepoWithControl(options) {
|
|
|
28563
29571
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
28564
29572
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
28565
29573
|
const repos = await listResponse.json();
|
|
28566
|
-
const existing = repos.find((r2) =>
|
|
29574
|
+
const existing = repos.find((r2) => path30.resolve(r2.path) === repoPath);
|
|
28567
29575
|
if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
|
|
28568
29576
|
await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
|
|
28569
29577
|
return;
|
|
@@ -28619,53 +29627,19 @@ async function syncRepoWithControlAsync(repoPath) {
|
|
|
28619
29627
|
}
|
|
28620
29628
|
|
|
28621
29629
|
// src/core/slot-launch-guard.ts
|
|
28622
|
-
function
|
|
28623
|
-
const
|
|
28624
|
-
|
|
28625
|
-
slot.worktreePath ? ` Worktree: ${slot.worktreePath}` : void 0,
|
|
28626
|
-
slot.branch ? ` Branch: ${slot.branch}` : void 0,
|
|
28627
|
-
slot.workDir ? ` Work dir: ${slot.workDir}` : void 0,
|
|
28628
|
-
slot.sessionStatus ? ` Status: ${slot.sessionStatus}` : void 0,
|
|
28629
|
-
slot.lastError ? ` Error: ${slot.lastError}` : void 0,
|
|
28630
|
-
slot.sessionCreatedAt ? ` Since: ${slot.sessionCreatedAt}` : void 0,
|
|
28631
|
-
slot.dirty ? " Git: dirty (uncommitted changes \u2014 commit or use force to discard)" : " Git: clean",
|
|
28632
|
-
"",
|
|
28633
|
-
"Replacing removes the worktree. The session branch is kept only if you committed.",
|
|
28634
|
-
"Gitignored paths (state/, runs/, local clones) are NOT preserved.",
|
|
28635
|
-
"",
|
|
28636
|
-
"To replace: pass confirmReplace=true (MCP), --replace (CLI), or answer y at the prompt.",
|
|
28637
|
-
"If the worktree is dirty, also pass force=true / --force after explicit user approval."
|
|
28638
|
-
];
|
|
28639
|
-
return lines.filter(Boolean).join("\n");
|
|
28640
|
-
}
|
|
28641
|
-
function checkLaunchGuard(repoPath, agentId, options = {}) {
|
|
28642
|
-
const status = collectEnvironmentStatus(repoPath);
|
|
28643
|
-
const slot = status.slots.find((s2) => s2.agentId === agentId);
|
|
28644
|
-
if (!slot?.active) {
|
|
28645
|
-
return { allowed: true };
|
|
28646
|
-
}
|
|
28647
|
-
if (!options.confirmReplace) {
|
|
29630
|
+
function checkLaunchGuard2(repoPath, agentId, options = {}) {
|
|
29631
|
+
const readiness = inspectSlotReadiness(repoPath, agentId, options);
|
|
29632
|
+
if (!readiness.canLaunch) {
|
|
28648
29633
|
return {
|
|
28649
29634
|
allowed: false,
|
|
28650
29635
|
blocked: true,
|
|
28651
|
-
|
|
28652
|
-
|
|
29636
|
+
reason: formatPreflightReport(agentId, readiness),
|
|
29637
|
+
slot: checkLaunchGuard(repoPath, agentId, options).slot,
|
|
29638
|
+
readiness
|
|
28653
29639
|
};
|
|
28654
29640
|
}
|
|
28655
|
-
|
|
28656
|
-
|
|
28657
|
-
allowed: false,
|
|
28658
|
-
blocked: true,
|
|
28659
|
-
slot,
|
|
28660
|
-
reason: [
|
|
28661
|
-
formatOccupiedSlot(slot),
|
|
28662
|
-
"",
|
|
28663
|
-
"The occupied worktree has uncommitted changes.",
|
|
28664
|
-
"Pass force=true / --force to discard them (only after explicit user approval)."
|
|
28665
|
-
].join("\n")
|
|
28666
|
-
};
|
|
28667
|
-
}
|
|
28668
|
-
return { allowed: true, slot };
|
|
29641
|
+
const occupied = checkLaunchGuard(repoPath, agentId, options);
|
|
29642
|
+
return { allowed: true, slot: occupied.slot, readiness };
|
|
28669
29643
|
}
|
|
28670
29644
|
|
|
28671
29645
|
// src/core/run-service.ts
|
|
@@ -28751,11 +29725,26 @@ var RunService = class {
|
|
|
28751
29725
|
listArtifacts(options) {
|
|
28752
29726
|
return this.executor.listArtifacts({ repoPath: options.repoPath }, { stageId: options.stageId });
|
|
28753
29727
|
}
|
|
28754
|
-
async
|
|
28755
|
-
const
|
|
29728
|
+
async preflightEnvironment(options) {
|
|
29729
|
+
const readiness = inspectSlotReadiness(options.repoPath, options.agentId, {
|
|
28756
29730
|
confirmReplace: options.confirmReplace,
|
|
28757
29731
|
force: options.force
|
|
28758
29732
|
});
|
|
29733
|
+
const report = formatPreflightReport(options.agentId, readiness);
|
|
29734
|
+
return {
|
|
29735
|
+
code: readiness.canLaunch ? 0 : 1,
|
|
29736
|
+
stdout: report + "\n",
|
|
29737
|
+
stderr: readiness.canLaunch ? "" : report + "\n",
|
|
29738
|
+
readiness,
|
|
29739
|
+
blocked: !readiness.canLaunch
|
|
29740
|
+
};
|
|
29741
|
+
}
|
|
29742
|
+
async launchEnvironment(options) {
|
|
29743
|
+
const guard = checkLaunchGuard2(options.repoPath, options.agentId, {
|
|
29744
|
+
confirmReplace: options.confirmReplace,
|
|
29745
|
+
force: options.force,
|
|
29746
|
+
resume: options.resume
|
|
29747
|
+
});
|
|
28759
29748
|
if (!guard.allowed) {
|
|
28760
29749
|
const slot = guard.slot;
|
|
28761
29750
|
return {
|
|
@@ -28782,7 +29771,8 @@ var RunService = class {
|
|
|
28782
29771
|
worktree: options.worktree,
|
|
28783
29772
|
claude: options.claude,
|
|
28784
29773
|
confirmReplace: options.confirmReplace,
|
|
28785
|
-
force: options.force
|
|
29774
|
+
force: options.force,
|
|
29775
|
+
resume: options.resume
|
|
28786
29776
|
},
|
|
28787
29777
|
trigger: "cli"
|
|
28788
29778
|
});
|
|
@@ -28953,6 +29943,7 @@ var defaultRunService = new RunService();
|
|
|
28953
29943
|
var runStage = defaultRunService.runStage.bind(defaultRunService);
|
|
28954
29944
|
var listArtifacts = defaultRunService.listArtifacts.bind(defaultRunService);
|
|
28955
29945
|
var launchEnvironment = defaultRunService.launchEnvironment.bind(defaultRunService);
|
|
29946
|
+
var preflightEnvironment = defaultRunService.preflightEnvironment.bind(defaultRunService);
|
|
28956
29947
|
var runVerification = defaultRunService.runVerification.bind(defaultRunService);
|
|
28957
29948
|
var teardownEnvironment = defaultRunService.teardownEnvironment.bind(defaultRunService);
|
|
28958
29949
|
var completeEnvironment = defaultRunService.completeEnvironment.bind(defaultRunService);
|
|
@@ -29044,8 +30035,30 @@ var envCommand = {
|
|
|
29044
30035
|
type: "boolean",
|
|
29045
30036
|
default: false,
|
|
29046
30037
|
describe: "Discard uncommitted changes when replacing a dirty worktree (only after explicit approval)"
|
|
30038
|
+
}).option("resume", {
|
|
30039
|
+
type: "boolean",
|
|
30040
|
+
default: false,
|
|
30041
|
+
describe: "Resume a failed or partial launch without creating a new worktree"
|
|
29047
30042
|
}),
|
|
29048
30043
|
handleLaunch
|
|
30044
|
+
).command(
|
|
30045
|
+
"recover <id>",
|
|
30046
|
+
"Resume a failed or partial launch (alias for launch --resume)",
|
|
30047
|
+
(y2) => y2.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }),
|
|
30048
|
+
handleRecover
|
|
30049
|
+
).command(
|
|
30050
|
+
"preflight <id>",
|
|
30051
|
+
"Check whether a slot can launch now (ports, PM2, Docker, occupied slot)",
|
|
30052
|
+
(y2) => y2.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" }).option("replace", {
|
|
30053
|
+
type: "boolean",
|
|
30054
|
+
default: false,
|
|
30055
|
+
describe: "Treat an occupied slot as replaceable (same as launch --replace)"
|
|
30056
|
+
}).option("force", {
|
|
30057
|
+
type: "boolean",
|
|
30058
|
+
default: false,
|
|
30059
|
+
describe: "Allow replacing a dirty worktree (only after explicit approval)"
|
|
30060
|
+
}),
|
|
30061
|
+
handlePreflight
|
|
29049
30062
|
).command(
|
|
29050
30063
|
"verify <id>",
|
|
29051
30064
|
"Run verification suite for an agent",
|
|
@@ -29094,12 +30107,12 @@ var envCommand = {
|
|
|
29094
30107
|
).demandCommand(1, "Use: runs list | runs get <runId>"),
|
|
29095
30108
|
() => {
|
|
29096
30109
|
}
|
|
29097
|
-
).demandCommand(1, "Please specify a subcommand: init, maintain, add-stage, launch, verify, complete, teardown, status, runs"),
|
|
30110
|
+
).demandCommand(1, "Please specify a subcommand: init, maintain, add-stage, launch, recover, verify, complete, teardown, status, runs"),
|
|
29098
30111
|
handler: () => {
|
|
29099
30112
|
}
|
|
29100
30113
|
};
|
|
29101
30114
|
async function handleInit(argv) {
|
|
29102
|
-
const repoPath =
|
|
30115
|
+
const repoPath = path31.resolve(argv.repo);
|
|
29103
30116
|
header("har env init");
|
|
29104
30117
|
info(`Repository: ${repoPath}`);
|
|
29105
30118
|
try {
|
|
@@ -29150,7 +30163,7 @@ async function handleInit(argv) {
|
|
|
29150
30163
|
}
|
|
29151
30164
|
}
|
|
29152
30165
|
async function handleMaintain(argv) {
|
|
29153
|
-
const repoPath =
|
|
30166
|
+
const repoPath = path31.resolve(argv.repo);
|
|
29154
30167
|
header("har env maintain");
|
|
29155
30168
|
info(`Repository: ${repoPath}`);
|
|
29156
30169
|
try {
|
|
@@ -29172,20 +30185,36 @@ async function handleMaintain(argv) {
|
|
|
29172
30185
|
info("Validating harness...");
|
|
29173
30186
|
printValidation(result.validation);
|
|
29174
30187
|
printDrift(result.drift);
|
|
29175
|
-
if (
|
|
29176
|
-
|
|
29177
|
-
process.exit(1);
|
|
30188
|
+
if (result.bundle) {
|
|
30189
|
+
printMaintainBundleSummary(result.bundle.report);
|
|
29178
30190
|
}
|
|
29179
|
-
if (argv.
|
|
29180
|
-
|
|
29181
|
-
|
|
30191
|
+
if (argv.finalize) {
|
|
30192
|
+
if (!result.validation.pass) {
|
|
30193
|
+
warn("Harness has validation errors \u2014 fix them before finalizing.");
|
|
30194
|
+
process.exit(1);
|
|
30195
|
+
}
|
|
29182
30196
|
info("Manifest updated \u2014 generator version and file checksums recorded.");
|
|
30197
|
+
} else if (argv.auto) {
|
|
30198
|
+
if (!result.validation.pass) {
|
|
30199
|
+
warn("Harness has validation errors after maintenance.");
|
|
30200
|
+
process.exit(1);
|
|
30201
|
+
}
|
|
30202
|
+
await handleAgentMdProposal(repoPath, argv.yes);
|
|
29183
30203
|
} else {
|
|
29184
|
-
|
|
30204
|
+
if (!result.validation.pass) {
|
|
30205
|
+
warn("Harness has validation errors \u2014 fix them before running --finalize.");
|
|
30206
|
+
}
|
|
30207
|
+
emitManualAdaptationPrompt(repoPath, "maintain", "default", result.bundle?.report);
|
|
29185
30208
|
info("After your coding agent finishes adapting, record it with: har env maintain --finalize");
|
|
29186
30209
|
}
|
|
29187
30210
|
divider();
|
|
29188
|
-
|
|
30211
|
+
if (argv.finalize) {
|
|
30212
|
+
success("Harness finalized!");
|
|
30213
|
+
} else if (result.bundle) {
|
|
30214
|
+
success("Maintenance bundle ready!");
|
|
30215
|
+
} else {
|
|
30216
|
+
success("Harness updated!");
|
|
30217
|
+
}
|
|
29189
30218
|
await handleCursorRule({
|
|
29190
30219
|
repoPath,
|
|
29191
30220
|
cursorRule: resolveCursorRuleFlag(argv.cursorRule, argv.noCursorRule),
|
|
@@ -29202,8 +30231,8 @@ function resolveCursorRuleFlag(cursorRule, noCursorRule) {
|
|
|
29202
30231
|
if (cursorRule) return true;
|
|
29203
30232
|
return void 0;
|
|
29204
30233
|
}
|
|
29205
|
-
function emitManualAdaptationPrompt(repoPath, mode, profile = "default") {
|
|
29206
|
-
const prompt = mode === "init" ? buildInitAdaptationPrompt(repoPath, profile) : buildMaintainAdaptationPrompt(repoPath);
|
|
30234
|
+
function emitManualAdaptationPrompt(repoPath, mode, profile = "default", bundleReport) {
|
|
30235
|
+
const prompt = mode === "init" ? buildInitAdaptationPrompt(repoPath, profile) : buildMaintainAdaptationPrompt(repoPath, bundleReport);
|
|
29207
30236
|
writeAdaptationPrompt(repoPath, prompt);
|
|
29208
30237
|
process.stderr.write("\n");
|
|
29209
30238
|
if (mode === "init") {
|
|
@@ -29212,14 +30241,18 @@ function emitManualAdaptationPrompt(repoPath, mode, profile = "default") {
|
|
|
29212
30241
|
info("Review harness drift with your coding agent:");
|
|
29213
30242
|
}
|
|
29214
30243
|
info(" Paste the prompt below (also saved to .har/ADAPT-PROMPT.md)");
|
|
29215
|
-
|
|
30244
|
+
if (mode === "init") {
|
|
30245
|
+
info(" TODO validation warnings are expected until adaptation is complete.");
|
|
30246
|
+
} else if (bundleReport) {
|
|
30247
|
+
info(" Reference templates are in .har/maintain/templates/");
|
|
30248
|
+
}
|
|
29216
30249
|
printAdaptationPrompt(prompt);
|
|
29217
30250
|
}
|
|
29218
30251
|
async function handleAgentMdProposal(repoPath, autoYes) {
|
|
29219
30252
|
if (autoYes) {
|
|
29220
30253
|
const proposal = readAgentMdProposal(repoPath);
|
|
29221
30254
|
if (proposal) {
|
|
29222
|
-
writeFileSafe(
|
|
30255
|
+
writeFileSafe(path31.join(repoPath, "AGENT.md"), proposal.content);
|
|
29223
30256
|
clearAgentMdProposal(repoPath);
|
|
29224
30257
|
info("Applied AGENT.md proposal (--yes)");
|
|
29225
30258
|
}
|
|
@@ -29228,7 +30261,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
29228
30261
|
await promptApplyAgentMdProposal(repoPath);
|
|
29229
30262
|
}
|
|
29230
30263
|
async function handleAddStage(argv) {
|
|
29231
|
-
const repoPath =
|
|
30264
|
+
const repoPath = path31.resolve(argv.repo);
|
|
29232
30265
|
if (argv.template !== "playwright") {
|
|
29233
30266
|
error(`Unknown stage template: ${argv.template ?? "(missing)"}. Available: playwright`);
|
|
29234
30267
|
process.exit(1);
|
|
@@ -29256,47 +30289,67 @@ async function handleAddStage(argv) {
|
|
|
29256
30289
|
process.exit(1);
|
|
29257
30290
|
}
|
|
29258
30291
|
}
|
|
30292
|
+
async function handlePreflight(argv) {
|
|
30293
|
+
const repo = path31.resolve(argv.repo);
|
|
30294
|
+
const agentId = validateAgentId(argv.id, repo);
|
|
30295
|
+
const result = await preflightEnvironment({
|
|
30296
|
+
repoPath: repo,
|
|
30297
|
+
agentId,
|
|
30298
|
+
confirmReplace: argv.replace,
|
|
30299
|
+
force: argv.force
|
|
30300
|
+
});
|
|
30301
|
+
if (argv.json) {
|
|
30302
|
+
const output = SlotReadinessSchema.parse(result.readiness);
|
|
30303
|
+
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
30304
|
+
process.exit(result.code);
|
|
30305
|
+
return;
|
|
30306
|
+
}
|
|
30307
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
30308
|
+
process.exit(result.code);
|
|
30309
|
+
}
|
|
29259
30310
|
async function handleLaunch(argv) {
|
|
29260
|
-
const repo =
|
|
30311
|
+
const repo = path31.resolve(argv.repo);
|
|
29261
30312
|
const agentId = validateAgentId(argv.id, repo);
|
|
29262
30313
|
let confirmReplace = argv.replace;
|
|
29263
30314
|
let force = argv.force;
|
|
29264
|
-
|
|
29265
|
-
|
|
29266
|
-
if (!
|
|
29267
|
-
|
|
29268
|
-
|
|
29269
|
-
|
|
29270
|
-
|
|
29271
|
-
|
|
29272
|
-
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
|
|
29277
|
-
|
|
29278
|
-
|
|
29279
|
-
|
|
29280
|
-
|
|
29281
|
-
|
|
29282
|
-
|
|
29283
|
-
|
|
29284
|
-
if (!
|
|
29285
|
-
|
|
29286
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
29291
|
-
|
|
29292
|
-
|
|
29293
|
-
|
|
30315
|
+
if (!argv.resume) {
|
|
30316
|
+
const guard = checkLaunchGuard2(repo, agentId, { confirmReplace, force });
|
|
30317
|
+
if (!guard.allowed && guard.blocked) {
|
|
30318
|
+
if (!confirmReplace && process.stdin.isTTY && process.stdout.isTTY) {
|
|
30319
|
+
warn("Occupied slot \u2014 review before replacing:");
|
|
30320
|
+
console.error(guard.reason ?? "");
|
|
30321
|
+
const readline4 = await import("readline");
|
|
30322
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
|
|
30323
|
+
const answer = await new Promise((resolve26) => {
|
|
30324
|
+
rl.question("Replace this slot? [y/N] ", resolve26);
|
|
30325
|
+
});
|
|
30326
|
+
rl.close();
|
|
30327
|
+
if (!/^[Yy]$/.test(answer.trim())) {
|
|
30328
|
+
error("Aborted \u2014 slot left unchanged.");
|
|
30329
|
+
process.exit(2);
|
|
30330
|
+
}
|
|
30331
|
+
confirmReplace = true;
|
|
30332
|
+
}
|
|
30333
|
+
}
|
|
30334
|
+
const guardAfterConfirm = checkLaunchGuard2(repo, agentId, { confirmReplace, force });
|
|
30335
|
+
if (!guardAfterConfirm.allowed && guardAfterConfirm.blocked && guardAfterConfirm.slot?.dirty) {
|
|
30336
|
+
if (!force && process.stdin.isTTY && process.stdout.isTTY) {
|
|
30337
|
+
warn("Worktree has uncommitted changes.");
|
|
30338
|
+
const readline4 = await import("readline");
|
|
30339
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
|
|
30340
|
+
const answer = await new Promise((resolve26) => {
|
|
30341
|
+
rl.question("Discard uncommitted changes? [y/N] ", resolve26);
|
|
30342
|
+
});
|
|
30343
|
+
rl.close();
|
|
30344
|
+
if (!/^[Yy]$/.test(answer.trim())) {
|
|
30345
|
+
error("Aborted \u2014 uncommitted work preserved.");
|
|
30346
|
+
process.exit(2);
|
|
30347
|
+
}
|
|
30348
|
+
force = true;
|
|
30349
|
+
} else if (!force) {
|
|
30350
|
+
error(guardAfterConfirm.reason ?? "Dirty worktree requires --force.");
|
|
29294
30351
|
process.exit(2);
|
|
29295
30352
|
}
|
|
29296
|
-
force = true;
|
|
29297
|
-
} else if (!force) {
|
|
29298
|
-
error(guardAfterConfirm.reason ?? "Dirty worktree requires --force.");
|
|
29299
|
-
process.exit(2);
|
|
29300
30353
|
}
|
|
29301
30354
|
}
|
|
29302
30355
|
const result = await launchEnvironment({
|
|
@@ -29306,6 +30359,7 @@ async function handleLaunch(argv) {
|
|
|
29306
30359
|
claude: argv.claude,
|
|
29307
30360
|
confirmReplace,
|
|
29308
30361
|
force,
|
|
30362
|
+
resume: argv.resume,
|
|
29309
30363
|
capture: false
|
|
29310
30364
|
});
|
|
29311
30365
|
if (result.blocked) {
|
|
@@ -29319,8 +30373,19 @@ async function handleLaunch(argv) {
|
|
|
29319
30373
|
}
|
|
29320
30374
|
process.exit(result.code);
|
|
29321
30375
|
}
|
|
30376
|
+
async function handleRecover(argv) {
|
|
30377
|
+
return handleLaunch({
|
|
30378
|
+
id: argv.id,
|
|
30379
|
+
repo: argv.repo,
|
|
30380
|
+
worktree: true,
|
|
30381
|
+
claude: false,
|
|
30382
|
+
replace: false,
|
|
30383
|
+
force: false,
|
|
30384
|
+
resume: true
|
|
30385
|
+
});
|
|
30386
|
+
}
|
|
29322
30387
|
async function handleVerify(argv) {
|
|
29323
|
-
const repo =
|
|
30388
|
+
const repo = path31.resolve(argv.repo);
|
|
29324
30389
|
const agentId = validateAgentId(argv.id, repo);
|
|
29325
30390
|
const result = await runVerification({
|
|
29326
30391
|
repoPath: repo,
|
|
@@ -29332,7 +30397,7 @@ async function handleVerify(argv) {
|
|
|
29332
30397
|
process.exit(result.code);
|
|
29333
30398
|
}
|
|
29334
30399
|
async function handleTeardown(argv) {
|
|
29335
|
-
const repo =
|
|
30400
|
+
const repo = path31.resolve(argv.repo);
|
|
29336
30401
|
const agentId = validateAgentId(argv.id, repo);
|
|
29337
30402
|
const result = await teardownEnvironment({
|
|
29338
30403
|
repoPath: repo,
|
|
@@ -29343,7 +30408,7 @@ async function handleTeardown(argv) {
|
|
|
29343
30408
|
process.exit(result.code);
|
|
29344
30409
|
}
|
|
29345
30410
|
async function handleComplete(argv) {
|
|
29346
|
-
const repo =
|
|
30411
|
+
const repo = path31.resolve(argv.repo);
|
|
29347
30412
|
const agentId = validateAgentId(argv.id, repo);
|
|
29348
30413
|
const result = await completeEnvironment({
|
|
29349
30414
|
repoPath: repo,
|
|
@@ -29364,7 +30429,7 @@ async function handleComplete(argv) {
|
|
|
29364
30429
|
process.exit(result.code);
|
|
29365
30430
|
}
|
|
29366
30431
|
async function handleStatus(argv) {
|
|
29367
|
-
const repoPath =
|
|
30432
|
+
const repoPath = path31.resolve(argv.repo);
|
|
29368
30433
|
if (argv.json) {
|
|
29369
30434
|
const status = collectEnvironmentStatus(repoPath);
|
|
29370
30435
|
const output = EnvironmentStatusSchema.parse(status);
|
|
@@ -29377,7 +30442,7 @@ async function handleStatus(argv) {
|
|
|
29377
30442
|
});
|
|
29378
30443
|
}
|
|
29379
30444
|
async function handleRunsList(argv) {
|
|
29380
|
-
const repoPath =
|
|
30445
|
+
const repoPath = path31.resolve(argv.repo);
|
|
29381
30446
|
const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
|
|
29382
30447
|
if (argv.json) {
|
|
29383
30448
|
process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
|
|
@@ -29394,7 +30459,7 @@ async function handleRunsList(argv) {
|
|
|
29394
30459
|
}
|
|
29395
30460
|
}
|
|
29396
30461
|
async function handleRunsGet(argv) {
|
|
29397
|
-
const run2 = getRun(
|
|
30462
|
+
const run2 = getRun(path31.resolve(argv.repo), argv.runId);
|
|
29398
30463
|
if (!run2) {
|
|
29399
30464
|
error(`Run not found: ${argv.runId}`);
|
|
29400
30465
|
process.exit(1);
|
|
@@ -29429,10 +30494,26 @@ function printDrift(drift) {
|
|
|
29429
30494
|
if (drift.extra.length > 0) {
|
|
29430
30495
|
warn(` Extra/stale files: ${drift.extra.join(", ")}`);
|
|
29431
30496
|
}
|
|
29432
|
-
if (
|
|
30497
|
+
if (drift.missingPortVars.length > 0) {
|
|
30498
|
+
warn(
|
|
30499
|
+
` Missing port documentation vars in harness.env: ${drift.missingPortVars.join(", ")}`
|
|
30500
|
+
);
|
|
30501
|
+
warn(" See maintain/templates/harness.env in the maintenance bundle.");
|
|
30502
|
+
}
|
|
30503
|
+
if (!drift.generatorVersion.outdated && drift.checksumMismatch.length === 0 && drift.missing.length === 0 && drift.extra.length === 0 && drift.missingPortVars.length === 0) {
|
|
29433
30504
|
success(" Harness matches bundled templates");
|
|
29434
30505
|
}
|
|
29435
30506
|
}
|
|
30507
|
+
function printMaintainBundleSummary(report) {
|
|
30508
|
+
const missing = report.actions.filter((a2) => a2.kind === "missing").length;
|
|
30509
|
+
const drifted = report.actions.filter((a2) => a2.kind === "drift").length;
|
|
30510
|
+
const stale = report.stale.length;
|
|
30511
|
+
info(`Maintenance bundle: .har/maintain/`);
|
|
30512
|
+
info(` ${missing} missing, ${drifted} drifted, ${stale} stale`);
|
|
30513
|
+
if (!report.validation.pass) {
|
|
30514
|
+
warn(` Validation: ${report.validation.errors.length} error(s) \u2014 blocks --finalize`);
|
|
30515
|
+
}
|
|
30516
|
+
}
|
|
29436
30517
|
function printNextSteps(auto2) {
|
|
29437
30518
|
console.error("");
|
|
29438
30519
|
console.error(" Read: .har/README.md");
|
|
@@ -29451,30 +30532,11 @@ function printNextSteps(auto2) {
|
|
|
29451
30532
|
}
|
|
29452
30533
|
|
|
29453
30534
|
// src/cli/commands/control.ts
|
|
29454
|
-
var
|
|
30535
|
+
var path33 = __toESM(require("path"));
|
|
29455
30536
|
|
|
29456
30537
|
// src/core/control-lifecycle.ts
|
|
29457
|
-
var
|
|
29458
|
-
var
|
|
29459
|
-
|
|
29460
|
-
// src/core/package-version.ts
|
|
29461
|
-
var fs26 = __toESM(require("fs"));
|
|
29462
|
-
var path26 = __toESM(require("path"));
|
|
29463
|
-
function getHarPackageVersion() {
|
|
29464
|
-
if (process.env.HAR_PACKAGE_VERSION) {
|
|
29465
|
-
return process.env.HAR_PACKAGE_VERSION;
|
|
29466
|
-
}
|
|
29467
|
-
const candidates = [
|
|
29468
|
-
path26.resolve(__dirname, "..", "package.json"),
|
|
29469
|
-
path26.resolve(__dirname, "..", "..", "package.json")
|
|
29470
|
-
];
|
|
29471
|
-
for (const packageJsonPath of candidates) {
|
|
29472
|
-
if (!fs26.existsSync(packageJsonPath)) continue;
|
|
29473
|
-
const pkg = JSON.parse(fs26.readFileSync(packageJsonPath, "utf8"));
|
|
29474
|
-
return pkg.version;
|
|
29475
|
-
}
|
|
29476
|
-
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
29477
|
-
}
|
|
30538
|
+
var import_child_process8 = require("child_process");
|
|
30539
|
+
var path32 = __toESM(require("path"));
|
|
29478
30540
|
|
|
29479
30541
|
// src/core/control-image.ts
|
|
29480
30542
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
@@ -29493,13 +30555,13 @@ function shouldBuildControlLocally() {
|
|
|
29493
30555
|
|
|
29494
30556
|
// src/core/control-lifecycle.ts
|
|
29495
30557
|
function resolveControlDir() {
|
|
29496
|
-
return
|
|
30558
|
+
return path32.resolve(__dirname, "..", "control");
|
|
29497
30559
|
}
|
|
29498
30560
|
function resolveControlComposeFiles(options) {
|
|
29499
30561
|
const controlDir = resolveControlDir();
|
|
29500
|
-
const files = [
|
|
30562
|
+
const files = [path32.join(controlDir, "docker-compose.yml")];
|
|
29501
30563
|
if (options?.build ?? shouldBuildControlLocally()) {
|
|
29502
|
-
files.push(
|
|
30564
|
+
files.push(path32.join(controlDir, "docker-compose.build.yml"));
|
|
29503
30565
|
}
|
|
29504
30566
|
return files;
|
|
29505
30567
|
}
|
|
@@ -29514,7 +30576,7 @@ function runDockerCompose(args, options) {
|
|
|
29514
30576
|
const controlDir = resolveControlDir();
|
|
29515
30577
|
const composeFiles = resolveControlComposeFiles(options);
|
|
29516
30578
|
const composeArgs = composeFiles.flatMap((file) => ["-f", file]);
|
|
29517
|
-
const result = (0,
|
|
30579
|
+
const result = (0, import_child_process8.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
|
|
29518
30580
|
cwd: controlDir,
|
|
29519
30581
|
stdio: "inherit",
|
|
29520
30582
|
env: buildDockerComposeEnv()
|
|
@@ -29614,6 +30676,27 @@ var controlCommand = {
|
|
|
29614
30676
|
};
|
|
29615
30677
|
async function handleUp(argv) {
|
|
29616
30678
|
header("har control up");
|
|
30679
|
+
const readiness = inspectControlUpReadiness(process.cwd());
|
|
30680
|
+
for (const message of readiness.warnings) {
|
|
30681
|
+
warn(message);
|
|
30682
|
+
}
|
|
30683
|
+
if (readiness.controlAlreadyRunning) {
|
|
30684
|
+
success(`Mission Control is already running at ${getControlApiUrl()}`);
|
|
30685
|
+
if (!isControlEnabled()) return;
|
|
30686
|
+
info("Syncing repositories with Mission Control...");
|
|
30687
|
+
const { synced: synced2, failed: failed2, apiReady: apiReady2 } = await syncReposAfterControlStart(process.cwd());
|
|
30688
|
+
if (!apiReady2) {
|
|
30689
|
+
warn("Mission Control API did not become ready \u2014 run har control sync later");
|
|
30690
|
+
return;
|
|
30691
|
+
}
|
|
30692
|
+
if (synced2 > 0) {
|
|
30693
|
+
success(`Synced ${synced2} ${synced2 === 1 ? "repository" : "repositories"} with Mission Control`);
|
|
30694
|
+
}
|
|
30695
|
+
if (failed2 > 0) {
|
|
30696
|
+
warn(`${failed2} ${failed2 === 1 ? "repository" : "repositories"} could not be synced`);
|
|
30697
|
+
}
|
|
30698
|
+
return;
|
|
30699
|
+
}
|
|
29617
30700
|
const { code, apiUrl, imageRef } = await startMissionControl({
|
|
29618
30701
|
detach: argv.detach,
|
|
29619
30702
|
build: argv.build
|
|
@@ -29647,7 +30730,7 @@ async function handleDown() {
|
|
|
29647
30730
|
process.exit(code);
|
|
29648
30731
|
}
|
|
29649
30732
|
async function handleRegister(argv) {
|
|
29650
|
-
const repoPath =
|
|
30733
|
+
const repoPath = path33.resolve(argv.repo);
|
|
29651
30734
|
header("har control register");
|
|
29652
30735
|
info(`Repository: ${repoPath}`);
|
|
29653
30736
|
try {
|
|
@@ -29667,7 +30750,7 @@ async function handleRegister(argv) {
|
|
|
29667
30750
|
}
|
|
29668
30751
|
}
|
|
29669
30752
|
async function handleSync(argv) {
|
|
29670
|
-
const repoPath =
|
|
30753
|
+
const repoPath = path33.resolve(argv.repo);
|
|
29671
30754
|
try {
|
|
29672
30755
|
await syncRepoWithControl({
|
|
29673
30756
|
repoPath,
|
|
@@ -29706,7 +30789,7 @@ async function handleWatch(argv) {
|
|
|
29706
30789
|
};
|
|
29707
30790
|
const tick = async () => {
|
|
29708
30791
|
if (argv.repo) {
|
|
29709
|
-
await syncOne(
|
|
30792
|
+
await syncOne(path33.resolve(argv.repo));
|
|
29710
30793
|
return;
|
|
29711
30794
|
}
|
|
29712
30795
|
try {
|
|
@@ -29737,12 +30820,12 @@ async function handleLogin(argv) {
|
|
|
29737
30820
|
}
|
|
29738
30821
|
|
|
29739
30822
|
// src/cli/commands/hooks.ts
|
|
29740
|
-
var
|
|
30823
|
+
var path35 = __toESM(require("path"));
|
|
29741
30824
|
|
|
29742
30825
|
// src/core/hooks.ts
|
|
29743
|
-
var
|
|
29744
|
-
var
|
|
29745
|
-
var
|
|
30826
|
+
var fs31 = __toESM(require("fs"));
|
|
30827
|
+
var os8 = __toESM(require("os"));
|
|
30828
|
+
var path34 = __toESM(require("path"));
|
|
29746
30829
|
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
29747
30830
|
var MARKER_END = "# <<< har commit gate <<<";
|
|
29748
30831
|
var PRE_COMMIT_BLOCK = [
|
|
@@ -29767,7 +30850,7 @@ function resolveCheckoutRoot(cwd) {
|
|
|
29767
30850
|
function resolveHooksDir(checkoutDir) {
|
|
29768
30851
|
const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
|
|
29769
30852
|
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
29770
|
-
return
|
|
30853
|
+
return path34.resolve(checkoutDir, hooksPath);
|
|
29771
30854
|
}
|
|
29772
30855
|
function getConfiguredHooksPath(checkoutDir) {
|
|
29773
30856
|
return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
|
|
@@ -29775,9 +30858,9 @@ function getConfiguredHooksPath(checkoutDir) {
|
|
|
29775
30858
|
function defaultHarInvocation() {
|
|
29776
30859
|
const entry = process.argv[1];
|
|
29777
30860
|
if (entry && entry.endsWith(".js")) {
|
|
29778
|
-
return `"${process.execPath}" "${
|
|
30861
|
+
return `"${process.execPath}" "${path34.resolve(entry)}"`;
|
|
29779
30862
|
}
|
|
29780
|
-
if (entry) return `"${
|
|
30863
|
+
if (entry) return `"${path34.resolve(entry)}"`;
|
|
29781
30864
|
return "har";
|
|
29782
30865
|
}
|
|
29783
30866
|
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
@@ -29798,40 +30881,40 @@ exit 0
|
|
|
29798
30881
|
`;
|
|
29799
30882
|
}
|
|
29800
30883
|
function upsertMarkedBlock(filePath, block) {
|
|
29801
|
-
if (!
|
|
29802
|
-
|
|
30884
|
+
if (!fs31.existsSync(filePath)) {
|
|
30885
|
+
fs31.writeFileSync(filePath, `#!/bin/sh
|
|
29803
30886
|
${block}
|
|
29804
30887
|
`, { mode: 493 });
|
|
29805
30888
|
return "created";
|
|
29806
30889
|
}
|
|
29807
|
-
const content =
|
|
30890
|
+
const content = fs31.readFileSync(filePath, "utf8");
|
|
29808
30891
|
if (content.includes(MARKER_START)) {
|
|
29809
30892
|
const pattern = new RegExp(
|
|
29810
30893
|
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
29811
30894
|
);
|
|
29812
|
-
|
|
29813
|
-
|
|
30895
|
+
fs31.writeFileSync(filePath, content.replace(pattern, block));
|
|
30896
|
+
fs31.chmodSync(filePath, 493);
|
|
29814
30897
|
return "updated";
|
|
29815
30898
|
}
|
|
29816
30899
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
29817
|
-
|
|
30900
|
+
fs31.writeFileSync(filePath, `${content}${suffix}
|
|
29818
30901
|
${block}
|
|
29819
30902
|
`);
|
|
29820
|
-
|
|
30903
|
+
fs31.chmodSync(filePath, 493);
|
|
29821
30904
|
return "appended";
|
|
29822
30905
|
}
|
|
29823
30906
|
function removeMarkedBlock(filePath) {
|
|
29824
|
-
if (!
|
|
29825
|
-
const content =
|
|
30907
|
+
if (!fs31.existsSync(filePath)) return false;
|
|
30908
|
+
const content = fs31.readFileSync(filePath, "utf8");
|
|
29826
30909
|
if (!content.includes(MARKER_START)) return false;
|
|
29827
30910
|
const pattern = new RegExp(
|
|
29828
30911
|
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
29829
30912
|
);
|
|
29830
30913
|
const stripped = content.replace(pattern, "\n");
|
|
29831
30914
|
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
29832
|
-
|
|
30915
|
+
fs31.rmSync(filePath);
|
|
29833
30916
|
} else {
|
|
29834
|
-
|
|
30917
|
+
fs31.writeFileSync(filePath, stripped);
|
|
29835
30918
|
}
|
|
29836
30919
|
return true;
|
|
29837
30920
|
}
|
|
@@ -29853,20 +30936,20 @@ Or re-run with --force to write into that directory anyway.`
|
|
|
29853
30936
|
);
|
|
29854
30937
|
}
|
|
29855
30938
|
const hooksDir = resolveHooksDir(checkout);
|
|
29856
|
-
|
|
30939
|
+
fs31.mkdirSync(hooksDir, { recursive: true });
|
|
29857
30940
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
29858
|
-
|
|
29859
|
-
|
|
30941
|
+
fs31.writeFileSync(
|
|
30942
|
+
path34.join(hooksDir, "har-pre-commit"),
|
|
29860
30943
|
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
29861
30944
|
{ mode: 493 }
|
|
29862
30945
|
);
|
|
29863
|
-
|
|
29864
|
-
|
|
30946
|
+
fs31.writeFileSync(
|
|
30947
|
+
path34.join(hooksDir, "har-post-commit"),
|
|
29865
30948
|
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
29866
30949
|
{ mode: 493 }
|
|
29867
30950
|
);
|
|
29868
|
-
const preCommit = upsertMarkedBlock(
|
|
29869
|
-
const postCommit = upsertMarkedBlock(
|
|
30951
|
+
const preCommit = upsertMarkedBlock(path34.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
30952
|
+
const postCommit = upsertMarkedBlock(path34.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
29870
30953
|
ensureValidationsIgnored(checkout);
|
|
29871
30954
|
return { hooksDir, preCommit, postCommit };
|
|
29872
30955
|
}
|
|
@@ -29875,12 +30958,12 @@ function uninstallHooks(repoPath) {
|
|
|
29875
30958
|
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
29876
30959
|
const hooksDir = resolveHooksDir(checkout);
|
|
29877
30960
|
let removed = false;
|
|
29878
|
-
removed = removeMarkedBlock(
|
|
29879
|
-
removed = removeMarkedBlock(
|
|
30961
|
+
removed = removeMarkedBlock(path34.join(hooksDir, "pre-commit")) || removed;
|
|
30962
|
+
removed = removeMarkedBlock(path34.join(hooksDir, "post-commit")) || removed;
|
|
29880
30963
|
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
29881
|
-
const full =
|
|
29882
|
-
if (
|
|
29883
|
-
|
|
30964
|
+
const full = path34.join(hooksDir, file);
|
|
30965
|
+
if (fs31.existsSync(full)) {
|
|
30966
|
+
fs31.rmSync(full);
|
|
29884
30967
|
removed = true;
|
|
29885
30968
|
}
|
|
29886
30969
|
}
|
|
@@ -29897,9 +30980,9 @@ function getCommitGateConfig(checkoutDir) {
|
|
|
29897
30980
|
function isAgentWorktree(checkoutDir) {
|
|
29898
30981
|
const branch = getCurrentBranch(checkoutDir);
|
|
29899
30982
|
if (branch && /^har-agent-\d+$/.test(branch)) return true;
|
|
29900
|
-
const worktreesRoot =
|
|
29901
|
-
const resolved =
|
|
29902
|
-
return resolved.startsWith(`${worktreesRoot}${
|
|
30983
|
+
const worktreesRoot = path34.join(os8.homedir(), "worktrees");
|
|
30984
|
+
const resolved = path34.resolve(checkoutDir);
|
|
30985
|
+
return resolved.startsWith(`${worktreesRoot}${path34.sep}`) && /-agent-\d+$/.test(path34.basename(resolved));
|
|
29903
30986
|
}
|
|
29904
30987
|
function resolveEffectiveMode(checkoutDir, gate) {
|
|
29905
30988
|
if (!gate.enabled) return "off";
|
|
@@ -29912,15 +30995,15 @@ function getHooksStatus(repoPath) {
|
|
|
29912
30995
|
const hooksDir = resolveHooksDir(checkout);
|
|
29913
30996
|
const gate = getCommitGateConfig(checkout);
|
|
29914
30997
|
const hasBlock = (name) => {
|
|
29915
|
-
const file =
|
|
29916
|
-
return
|
|
30998
|
+
const file = path34.join(hooksDir, name);
|
|
30999
|
+
return fs31.existsSync(file) && fs31.readFileSync(file, "utf8").includes(MARKER_START);
|
|
29917
31000
|
};
|
|
29918
31001
|
return {
|
|
29919
31002
|
checkout,
|
|
29920
31003
|
hooksDir,
|
|
29921
31004
|
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
29922
|
-
preCommitInstalled: hasBlock("pre-commit") &&
|
|
29923
|
-
postCommitInstalled: hasBlock("post-commit") &&
|
|
31005
|
+
preCommitInstalled: hasBlock("pre-commit") && fs31.existsSync(path34.join(hooksDir, "har-pre-commit")),
|
|
31006
|
+
postCommitInstalled: hasBlock("post-commit") && fs31.existsSync(path34.join(hooksDir, "har-post-commit")),
|
|
29924
31007
|
gate,
|
|
29925
31008
|
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
29926
31009
|
};
|
|
@@ -29931,7 +31014,7 @@ function checkCommitGate(cwd) {
|
|
|
29931
31014
|
}
|
|
29932
31015
|
const checkout = resolveCheckoutRoot(cwd);
|
|
29933
31016
|
if (!checkout) return { exitCode: 0, messages: [] };
|
|
29934
|
-
if (!
|
|
31017
|
+
if (!fs31.existsSync(path34.join(checkout, ".har", "stages.json"))) {
|
|
29935
31018
|
return { exitCode: 0, messages: [] };
|
|
29936
31019
|
}
|
|
29937
31020
|
const gate = getCommitGateConfig(checkout);
|
|
@@ -30007,7 +31090,7 @@ function repoOption(y2) {
|
|
|
30007
31090
|
}
|
|
30008
31091
|
function handleInstall(argv) {
|
|
30009
31092
|
try {
|
|
30010
|
-
const result = installHooks({ repoPath:
|
|
31093
|
+
const result = installHooks({ repoPath: path35.resolve(argv.repo), force: argv.force });
|
|
30011
31094
|
success(`Commit gate installed in ${result.hooksDir}`);
|
|
30012
31095
|
info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
|
|
30013
31096
|
info("Commits of unverified change batches will be blocked in agent worktrees.");
|
|
@@ -30019,7 +31102,7 @@ function handleInstall(argv) {
|
|
|
30019
31102
|
}
|
|
30020
31103
|
function handleUninstall(argv) {
|
|
30021
31104
|
try {
|
|
30022
|
-
const result = uninstallHooks(
|
|
31105
|
+
const result = uninstallHooks(path35.resolve(argv.repo));
|
|
30023
31106
|
if (result.removed) {
|
|
30024
31107
|
success(`Commit gate removed from ${result.hooksDir}`);
|
|
30025
31108
|
} else {
|
|
@@ -30032,7 +31115,7 @@ function handleUninstall(argv) {
|
|
|
30032
31115
|
}
|
|
30033
31116
|
function handleStatus2(argv) {
|
|
30034
31117
|
try {
|
|
30035
|
-
const status = getHooksStatus(
|
|
31118
|
+
const status = getHooksStatus(path35.resolve(argv.repo));
|
|
30036
31119
|
if (argv.json) {
|
|
30037
31120
|
process.stdout.write(`${JSON.stringify(status, null, 2)}
|
|
30038
31121
|
`);
|
|
@@ -30295,10 +31378,10 @@ function assignProp(target, prop, value) {
|
|
|
30295
31378
|
configurable: true
|
|
30296
31379
|
});
|
|
30297
31380
|
}
|
|
30298
|
-
function getElementAtPath(obj,
|
|
30299
|
-
if (!
|
|
31381
|
+
function getElementAtPath(obj, path37) {
|
|
31382
|
+
if (!path37)
|
|
30300
31383
|
return obj;
|
|
30301
|
-
return
|
|
31384
|
+
return path37.reduce((acc, key) => acc?.[key], obj);
|
|
30302
31385
|
}
|
|
30303
31386
|
function promiseAllObject(promisesObj) {
|
|
30304
31387
|
const keys = Object.keys(promisesObj);
|
|
@@ -30618,11 +31701,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
30618
31701
|
}
|
|
30619
31702
|
return false;
|
|
30620
31703
|
}
|
|
30621
|
-
function prefixIssues(
|
|
31704
|
+
function prefixIssues(path37, issues) {
|
|
30622
31705
|
return issues.map((iss) => {
|
|
30623
31706
|
var _a3;
|
|
30624
31707
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
30625
|
-
iss.path.unshift(
|
|
31708
|
+
iss.path.unshift(path37);
|
|
30626
31709
|
return iss;
|
|
30627
31710
|
});
|
|
30628
31711
|
}
|
|
@@ -35945,7 +37028,7 @@ var Protocol = class {
|
|
|
35945
37028
|
return;
|
|
35946
37029
|
}
|
|
35947
37030
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
35948
|
-
await new Promise((
|
|
37031
|
+
await new Promise((resolve26) => setTimeout(resolve26, pollInterval));
|
|
35949
37032
|
options?.signal?.throwIfAborted();
|
|
35950
37033
|
}
|
|
35951
37034
|
} catch (error3) {
|
|
@@ -35962,7 +37045,7 @@ var Protocol = class {
|
|
|
35962
37045
|
*/
|
|
35963
37046
|
request(request, resultSchema, options) {
|
|
35964
37047
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
35965
|
-
return new Promise((
|
|
37048
|
+
return new Promise((resolve26, reject) => {
|
|
35966
37049
|
const earlyReject = (error3) => {
|
|
35967
37050
|
reject(error3);
|
|
35968
37051
|
};
|
|
@@ -36040,7 +37123,7 @@ var Protocol = class {
|
|
|
36040
37123
|
if (!parseResult.success) {
|
|
36041
37124
|
reject(parseResult.error);
|
|
36042
37125
|
} else {
|
|
36043
|
-
|
|
37126
|
+
resolve26(parseResult.data);
|
|
36044
37127
|
}
|
|
36045
37128
|
} catch (error3) {
|
|
36046
37129
|
reject(error3);
|
|
@@ -36301,12 +37384,12 @@ var Protocol = class {
|
|
|
36301
37384
|
}
|
|
36302
37385
|
} catch {
|
|
36303
37386
|
}
|
|
36304
|
-
return new Promise((
|
|
37387
|
+
return new Promise((resolve26, reject) => {
|
|
36305
37388
|
if (signal.aborted) {
|
|
36306
37389
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
36307
37390
|
return;
|
|
36308
37391
|
}
|
|
36309
|
-
const timeoutId = setTimeout(
|
|
37392
|
+
const timeoutId = setTimeout(resolve26, interval);
|
|
36310
37393
|
signal.addEventListener("abort", () => {
|
|
36311
37394
|
clearTimeout(timeoutId);
|
|
36312
37395
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -37096,7 +38179,7 @@ var Server = class extends Protocol {
|
|
|
37096
38179
|
};
|
|
37097
38180
|
|
|
37098
38181
|
// src/mcp/server.ts
|
|
37099
|
-
var
|
|
38182
|
+
var path36 = __toESM(require("path"));
|
|
37100
38183
|
|
|
37101
38184
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
37102
38185
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
@@ -37179,12 +38262,12 @@ var StdioServerTransport = class {
|
|
|
37179
38262
|
this.onclose?.();
|
|
37180
38263
|
}
|
|
37181
38264
|
send(message) {
|
|
37182
|
-
return new Promise((
|
|
38265
|
+
return new Promise((resolve26) => {
|
|
37183
38266
|
const json = serializeMessage(message);
|
|
37184
38267
|
if (this._stdout.write(json)) {
|
|
37185
|
-
|
|
38268
|
+
resolve26();
|
|
37186
38269
|
} else {
|
|
37187
|
-
this._stdout.once("drain",
|
|
38270
|
+
this._stdout.once("drain", resolve26);
|
|
37188
38271
|
}
|
|
37189
38272
|
});
|
|
37190
38273
|
}
|
|
@@ -37266,7 +38349,8 @@ var LaunchEnvironmentInputSchema = external_exports.object({
|
|
|
37266
38349
|
),
|
|
37267
38350
|
force: external_exports.boolean().default(false).describe(
|
|
37268
38351
|
"Discard uncommitted changes in a dirty occupied worktree. Requires confirmReplace=true and explicit user approval \u2014 never set autonomously."
|
|
37269
|
-
)
|
|
38352
|
+
),
|
|
38353
|
+
resume: external_exports.boolean().default(false).describe("Resume a failed or partial launch without creating a new worktree.")
|
|
37270
38354
|
});
|
|
37271
38355
|
var LaunchEnvironmentOutputSchema = ShellRunOutputSchema.extend({
|
|
37272
38356
|
previewUrls: external_exports.record(external_exports.string()).optional(),
|
|
@@ -37283,6 +38367,16 @@ var LaunchEnvironmentOutputSchema = ShellRunOutputSchema.extend({
|
|
|
37283
38367
|
sessionCreatedAt: external_exports.string().optional()
|
|
37284
38368
|
}).optional()
|
|
37285
38369
|
});
|
|
38370
|
+
var PreflightEnvironmentInputSchema = external_exports.object({
|
|
38371
|
+
repo: external_exports.string().default("."),
|
|
38372
|
+
agentId: agentIdSchema,
|
|
38373
|
+
confirmReplace: external_exports.boolean().default(false).describe("Treat an occupied slot as replaceable (same as launch confirmReplace)."),
|
|
38374
|
+
force: external_exports.boolean().default(false).describe("Allow replacing a dirty worktree (requires explicit user approval).")
|
|
38375
|
+
});
|
|
38376
|
+
var PreflightEnvironmentOutputSchema = ShellRunOutputSchema.extend({
|
|
38377
|
+
readiness: SlotReadinessSchema,
|
|
38378
|
+
blocked: external_exports.boolean().optional()
|
|
38379
|
+
});
|
|
37286
38380
|
var CompleteEnvironmentInputSchema = external_exports.object({
|
|
37287
38381
|
repo: external_exports.string().default("."),
|
|
37288
38382
|
agentId: agentIdSchema,
|
|
@@ -37391,6 +38485,40 @@ var HAR_MCP_TOOLS = [
|
|
|
37391
38485
|
force: {
|
|
37392
38486
|
type: "boolean",
|
|
37393
38487
|
description: "Discard uncommitted changes when replacing a dirty worktree. Requires confirmReplace=true and explicit user approval."
|
|
38488
|
+
},
|
|
38489
|
+
resume: {
|
|
38490
|
+
type: "boolean",
|
|
38491
|
+
description: "Resume a failed or partial launch (status failed/starting) without --replace. Preserves worktree and env."
|
|
38492
|
+
}
|
|
38493
|
+
},
|
|
38494
|
+
["agentId"]
|
|
38495
|
+
)
|
|
38496
|
+
},
|
|
38497
|
+
{
|
|
38498
|
+
name: "har_recover_environment",
|
|
38499
|
+
description: "Resume a failed or partial agent launch without replacing the worktree. Alias for har_launch_environment with resume=true.",
|
|
38500
|
+
inputSchema: objectJsonSchema(
|
|
38501
|
+
{
|
|
38502
|
+
repo: repoJsonProperty,
|
|
38503
|
+
agentId: agentIdJsonProperty
|
|
38504
|
+
},
|
|
38505
|
+
["agentId"]
|
|
38506
|
+
)
|
|
38507
|
+
},
|
|
38508
|
+
{
|
|
38509
|
+
name: "har_preflight_environment",
|
|
38510
|
+
description: "Readiness gate before launch: checks ports, foreign PM2, Docker conflicts, and occupied slot. Returns canLaunch with actionable blockers. Call before har_launch_environment.",
|
|
38511
|
+
inputSchema: objectJsonSchema(
|
|
38512
|
+
{
|
|
38513
|
+
repo: repoJsonProperty,
|
|
38514
|
+
agentId: agentIdJsonProperty,
|
|
38515
|
+
confirmReplace: {
|
|
38516
|
+
type: "boolean",
|
|
38517
|
+
description: "Treat an occupied slot as replaceable (same as launch confirmReplace)."
|
|
38518
|
+
},
|
|
38519
|
+
force: {
|
|
38520
|
+
type: "boolean",
|
|
38521
|
+
description: "Allow replacing a dirty worktree (only after explicit user approval)."
|
|
37394
38522
|
}
|
|
37395
38523
|
},
|
|
37396
38524
|
["agentId"]
|
|
@@ -37543,6 +38671,26 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
37543
38671
|
claude: input.claude,
|
|
37544
38672
|
confirmReplace: input.confirmReplace,
|
|
37545
38673
|
force: input.force,
|
|
38674
|
+
resume: input.resume,
|
|
38675
|
+
capture: true
|
|
38676
|
+
});
|
|
38677
|
+
const parsed = LaunchEnvironmentOutputSchema.parse(result);
|
|
38678
|
+
return {
|
|
38679
|
+
...jsonContent(parsed),
|
|
38680
|
+
...result.blocked ? { isError: true } : {}
|
|
38681
|
+
};
|
|
38682
|
+
}
|
|
38683
|
+
case "har_recover_environment": {
|
|
38684
|
+
const input = LaunchEnvironmentInputSchema.parse({ ...args, repo, resume: true });
|
|
38685
|
+
const agentId = validateAgentId(input.agentId, repo);
|
|
38686
|
+
const result = await launchEnvironment({
|
|
38687
|
+
repoPath: repo,
|
|
38688
|
+
agentId,
|
|
38689
|
+
worktree: input.worktree,
|
|
38690
|
+
claude: input.claude,
|
|
38691
|
+
confirmReplace: false,
|
|
38692
|
+
force: false,
|
|
38693
|
+
resume: true,
|
|
37546
38694
|
capture: true
|
|
37547
38695
|
});
|
|
37548
38696
|
const parsed = LaunchEnvironmentOutputSchema.parse(result);
|
|
@@ -37551,6 +38699,21 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
37551
38699
|
...result.blocked ? { isError: true } : {}
|
|
37552
38700
|
};
|
|
37553
38701
|
}
|
|
38702
|
+
case "har_preflight_environment": {
|
|
38703
|
+
const input = PreflightEnvironmentInputSchema.parse({ ...args, repo });
|
|
38704
|
+
const agentId = validateAgentId(input.agentId, repo);
|
|
38705
|
+
const result = await preflightEnvironment({
|
|
38706
|
+
repoPath: repo,
|
|
38707
|
+
agentId,
|
|
38708
|
+
confirmReplace: input.confirmReplace,
|
|
38709
|
+
force: input.force
|
|
38710
|
+
});
|
|
38711
|
+
const parsed = PreflightEnvironmentOutputSchema.parse(result);
|
|
38712
|
+
return {
|
|
38713
|
+
...jsonContent(parsed),
|
|
38714
|
+
...result.blocked ? { isError: true } : {}
|
|
38715
|
+
};
|
|
38716
|
+
}
|
|
37554
38717
|
case "har_run_stage": {
|
|
37555
38718
|
const input = RunStageInputSchema.parse({ ...args, repo });
|
|
37556
38719
|
const agentId = input.agentId !== void 0 ? validateAgentId(input.agentId, repo) : void 0;
|
|
@@ -37648,7 +38811,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
37648
38811
|
const input = ControlUpInputSchema.parse({ ...args, repo });
|
|
37649
38812
|
const result = await startControlAndSync({
|
|
37650
38813
|
detach: input.detach,
|
|
37651
|
-
cwd:
|
|
38814
|
+
cwd: path36.resolve(input.repo)
|
|
37652
38815
|
});
|
|
37653
38816
|
if (result.code !== 0) {
|
|
37654
38817
|
return {
|
|
@@ -37712,7 +38875,7 @@ var mcpCommand = {
|
|
|
37712
38875
|
|
|
37713
38876
|
// src/cli/index.ts
|
|
37714
38877
|
async function runCli() {
|
|
37715
|
-
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().parse();
|
|
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();
|
|
37716
38879
|
}
|
|
37717
38880
|
|
|
37718
38881
|
// src/index.ts
|