@osfactory/har 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +829 -718
- package/dist/prompts/system-authoring.md +1 -1
- package/dist/templates/adaptation-prompt-init.md +1 -1
- package/dist/templates/cursor-rule.mdc.template +3 -3
- package/dist/templates/har-boilerplate/README.md +1 -1
- package/dist/templates/har-boilerplate/agent-cli.sh +1 -1
- package/dist/templates/har-boilerplate/agent-slot.sh +38 -1
- package/dist/templates/har-boilerplate/gitignore.template +15 -0
- package/dist/templates/har-boilerplate/harness.env +2 -1
- package/dist/templates/har-boilerplate/launch.sh +3 -2
- package/dist/templates/har-boilerplate/verify.sh +2 -1
- package/dist/templates/har-boilerplate-cli/agent-cli.sh +2 -2
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +38 -1
- package/dist/templates/har-boilerplate-cli/gitignore.template +15 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +3 -2
- package/dist/templates/har-boilerplate-cli/verify.sh +2 -1
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +57 -0
- package/dist/templates/har-boilerplate-ios/agent-cli.sh +2 -2
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +38 -1
- package/dist/templates/har-boilerplate-ios/gitignore.template +16 -0
- package/dist/templates/har-boilerplate-ios/launch.sh +1 -0
- package/dist/templates/har-boilerplate-ios/verify.sh +2 -1
- package/dist/templates/stage-templates/custom-stage-skeleton.sh +2 -1
- package/dist/templates/stage-templates/playwright/.har/stages/browser-e2e.sh +2 -1
- package/dist/templates/stage-templates/rocketsim/.har/stages/rocketsim-flows.sh +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -765,14 +765,14 @@ var require_url_state_machine = __commonJS({
|
|
|
765
765
|
return url.replace(/\u0009|\u000A|\u000D/g, "");
|
|
766
766
|
}
|
|
767
767
|
function shortenPath(url) {
|
|
768
|
-
const
|
|
769
|
-
if (
|
|
768
|
+
const path48 = url.path;
|
|
769
|
+
if (path48.length === 0) {
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
|
-
if (url.scheme === "file" &&
|
|
772
|
+
if (url.scheme === "file" && path48.length === 1 && isNormalizedWindowsDriveLetter(path48[0])) {
|
|
773
773
|
return;
|
|
774
774
|
}
|
|
775
|
-
|
|
775
|
+
path48.pop();
|
|
776
776
|
}
|
|
777
777
|
function includesCredentials(url) {
|
|
778
778
|
return url.username !== "" || url.password !== "";
|
|
@@ -6351,14 +6351,14 @@ __export(fileFromPath_exports, {
|
|
|
6351
6351
|
fileFromPathSync: () => fileFromPathSync,
|
|
6352
6352
|
isFile: () => isFile
|
|
6353
6353
|
});
|
|
6354
|
-
function createFileFromPath(
|
|
6354
|
+
function createFileFromPath(path48, { 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: path48, size, lastModified: mtimeMs });
|
|
6362
6362
|
if (!filename) {
|
|
6363
6363
|
filename = file.name;
|
|
6364
6364
|
}
|
|
@@ -6367,13 +6367,13 @@ function createFileFromPath(path47, { 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(path48, filenameOrOptions, options = {}) {
|
|
6371
|
+
const stats = (0, import_fs5.statSync)(path48);
|
|
6372
|
+
return createFileFromPath(path48, stats, filenameOrOptions, options);
|
|
6373
6373
|
}
|
|
6374
|
-
async function fileFromPath2(
|
|
6375
|
-
const stats = await import_fs5.promises.stat(
|
|
6376
|
-
return createFileFromPath(
|
|
6374
|
+
async function fileFromPath2(path48, filenameOrOptions, options) {
|
|
6375
|
+
const stats = await import_fs5.promises.stat(path48);
|
|
6376
|
+
return createFileFromPath(path48, 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({
|
|
@@ -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(path48) {
|
|
9633
|
+
let input = path48;
|
|
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 [path48, query] = wsComponent.resourceName.split("?");
|
|
9886
|
+
wsComponent.path = path48 && path48 !== "/" ? path48 : void 0;
|
|
9887
9887
|
wsComponent.query = query;
|
|
9888
9888
|
wsComponent.resourceName = void 0;
|
|
9889
9889
|
}
|
|
@@ -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, fs41, 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, fs41[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: (path48) => {
|
|
14569
14569
|
if (typeof require !== "undefined") {
|
|
14570
|
-
return require(
|
|
14571
|
-
} else if (
|
|
14572
|
-
return JSON.parse((0, import_fs2.readFileSync)(
|
|
14570
|
+
return require(path48);
|
|
14571
|
+
} else if (path48.match(/\.json$/)) {
|
|
14572
|
+
return JSON.parse((0, import_fs2.readFileSync)(path48, "utf8"));
|
|
14573
14573
|
} else {
|
|
14574
14574
|
throw Error("only .json config files are supported in ESM");
|
|
14575
14575
|
}
|
|
@@ -19250,8 +19250,8 @@ function getErrorMap() {
|
|
|
19250
19250
|
|
|
19251
19251
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
19252
19252
|
var makeIssue = (params) => {
|
|
19253
|
-
const { data, path:
|
|
19254
|
-
const fullPath = [...
|
|
19253
|
+
const { data, path: path48, errorMaps, issueData } = params;
|
|
19254
|
+
const fullPath = [...path48, ...issueData.path || []];
|
|
19255
19255
|
const fullIssue = {
|
|
19256
19256
|
...issueData,
|
|
19257
19257
|
path: fullPath
|
|
@@ -19367,11 +19367,11 @@ var errorUtil;
|
|
|
19367
19367
|
|
|
19368
19368
|
// node_modules/zod/v3/types.js
|
|
19369
19369
|
var ParseInputLazyPath = class {
|
|
19370
|
-
constructor(parent, value,
|
|
19370
|
+
constructor(parent, value, path48, key) {
|
|
19371
19371
|
this._cachedPath = [];
|
|
19372
19372
|
this.parent = parent;
|
|
19373
19373
|
this.data = value;
|
|
19374
|
-
this._path =
|
|
19374
|
+
this._path = path48;
|
|
19375
19375
|
this._key = key;
|
|
19376
19376
|
}
|
|
19377
19377
|
get path() {
|
|
@@ -23498,15 +23498,15 @@ function handleRemove(argv) {
|
|
|
23498
23498
|
}
|
|
23499
23499
|
|
|
23500
23500
|
// src/cli/commands/env.ts
|
|
23501
|
-
var
|
|
23501
|
+
var path41 = __toESM(require("path"));
|
|
23502
23502
|
|
|
23503
23503
|
// src/core/harness.ts
|
|
23504
|
-
var
|
|
23505
|
-
var
|
|
23504
|
+
var fs20 = __toESM(require("fs"));
|
|
23505
|
+
var path19 = __toESM(require("path"));
|
|
23506
23506
|
|
|
23507
23507
|
// src/harness/generator.ts
|
|
23508
|
-
var
|
|
23509
|
-
var
|
|
23508
|
+
var fs10 = __toESM(require("fs"));
|
|
23509
|
+
var path11 = __toESM(require("path"));
|
|
23510
23510
|
|
|
23511
23511
|
// src/core/gitignore.ts
|
|
23512
23512
|
var fs6 = __toESM(require("fs"));
|
|
@@ -23529,6 +23529,277 @@ function ensureRootGitignorePatterns(checkoutDir) {
|
|
|
23529
23529
|
fs6.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
|
|
23530
23530
|
}
|
|
23531
23531
|
|
|
23532
|
+
// src/harness/gitignore-template.ts
|
|
23533
|
+
var fs7 = __toESM(require("fs"));
|
|
23534
|
+
var path8 = __toESM(require("path"));
|
|
23535
|
+
var HARNESS_GITIGNORE_TEMPLATE = "gitignore.template";
|
|
23536
|
+
var HARNESS_GITIGNORE_FILE = ".gitignore";
|
|
23537
|
+
function harnessFileForTemplate(templateFile) {
|
|
23538
|
+
return templateFile === HARNESS_GITIGNORE_TEMPLATE ? HARNESS_GITIGNORE_FILE : templateFile;
|
|
23539
|
+
}
|
|
23540
|
+
function templateFileForHarness(harnessFile) {
|
|
23541
|
+
return harnessFile === HARNESS_GITIGNORE_FILE ? HARNESS_GITIGNORE_TEMPLATE : harnessFile;
|
|
23542
|
+
}
|
|
23543
|
+
function resolveGitignoreTemplateSource(boilerplateDir) {
|
|
23544
|
+
const primary = path8.join(boilerplateDir, HARNESS_GITIGNORE_TEMPLATE);
|
|
23545
|
+
if (fs7.existsSync(primary)) return primary;
|
|
23546
|
+
const legacy = path8.join(boilerplateDir, HARNESS_GITIGNORE_FILE);
|
|
23547
|
+
if (fs7.existsSync(legacy)) return legacy;
|
|
23548
|
+
return null;
|
|
23549
|
+
}
|
|
23550
|
+
function writeHarnessGitignore(harnessDir, boilerplateDir) {
|
|
23551
|
+
const src = resolveGitignoreTemplateSource(boilerplateDir);
|
|
23552
|
+
if (!src) return;
|
|
23553
|
+
fs7.writeFileSync(path8.join(harnessDir, HARNESS_GITIGNORE_FILE), fs7.readFileSync(src, "utf8"));
|
|
23554
|
+
}
|
|
23555
|
+
function isExpectedHarnessOnlyFile(harnessFile, templateFiles) {
|
|
23556
|
+
return harnessFile === HARNESS_GITIGNORE_FILE && templateFiles.includes(HARNESS_GITIGNORE_TEMPLATE);
|
|
23557
|
+
}
|
|
23558
|
+
|
|
23559
|
+
// src/harness/stages.ts
|
|
23560
|
+
var fs9 = __toESM(require("fs"));
|
|
23561
|
+
var path10 = __toESM(require("path"));
|
|
23562
|
+
|
|
23563
|
+
// src/harness/env.ts
|
|
23564
|
+
var fs8 = __toESM(require("fs"));
|
|
23565
|
+
var path9 = __toESM(require("path"));
|
|
23566
|
+
function readHarnessEnv(repoPath) {
|
|
23567
|
+
const envPath = path9.join(getHarnessDir(repoPath), "harness.env");
|
|
23568
|
+
if (!fs8.existsSync(envPath)) return {};
|
|
23569
|
+
const values = {};
|
|
23570
|
+
for (const line of fs8.readFileSync(envPath, "utf8").split("\n")) {
|
|
23571
|
+
const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
|
|
23572
|
+
if (!match) continue;
|
|
23573
|
+
values[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
23574
|
+
}
|
|
23575
|
+
return values;
|
|
23576
|
+
}
|
|
23577
|
+
function parseHarnessEnvInt(env3, key) {
|
|
23578
|
+
const raw = env3[key];
|
|
23579
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
23580
|
+
const value = Number(raw);
|
|
23581
|
+
if (!Number.isInteger(value)) return void 0;
|
|
23582
|
+
return value;
|
|
23583
|
+
}
|
|
23584
|
+
|
|
23585
|
+
// src/harness/stages.ts
|
|
23586
|
+
var STAGE_REGISTRY_FILE = "stages.json";
|
|
23587
|
+
var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
|
|
23588
|
+
"launch",
|
|
23589
|
+
"verify",
|
|
23590
|
+
"test",
|
|
23591
|
+
"inspect",
|
|
23592
|
+
"reset",
|
|
23593
|
+
"teardown",
|
|
23594
|
+
"custom"
|
|
23595
|
+
]);
|
|
23596
|
+
function getStageRegistryPath(repoPath) {
|
|
23597
|
+
return path10.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
|
|
23598
|
+
}
|
|
23599
|
+
function synthesizeStageRegistry(repoPath) {
|
|
23600
|
+
const harnessDir = getHarnessDir(repoPath);
|
|
23601
|
+
const stages = [];
|
|
23602
|
+
addStageIfRunnable(stages, harnessDir, {
|
|
23603
|
+
id: "setup-infra",
|
|
23604
|
+
kind: "setup",
|
|
23605
|
+
description: "Start shared harness infrastructure.",
|
|
23606
|
+
command: "./.har/setup-infra.sh",
|
|
23607
|
+
requiresAgentId: false
|
|
23608
|
+
});
|
|
23609
|
+
addStageIfRunnable(stages, harnessDir, {
|
|
23610
|
+
id: "launch",
|
|
23611
|
+
kind: "launch",
|
|
23612
|
+
description: "Launch an isolated agent environment slot.",
|
|
23613
|
+
command: "./.har/launch.sh {agentId}",
|
|
23614
|
+
requiresAgentId: true
|
|
23615
|
+
});
|
|
23616
|
+
addStageIfRunnable(stages, harnessDir, {
|
|
23617
|
+
id: "verify",
|
|
23618
|
+
kind: "verify",
|
|
23619
|
+
description: "Run the project verification pipeline.",
|
|
23620
|
+
command: "./.har/verify.sh {agentId}",
|
|
23621
|
+
requiresAgentId: true,
|
|
23622
|
+
acceptsArgs: ["--full"]
|
|
23623
|
+
});
|
|
23624
|
+
addStageIfRunnable(stages, harnessDir, {
|
|
23625
|
+
id: "status",
|
|
23626
|
+
kind: "inspect",
|
|
23627
|
+
description: "Inspect a running agent environment slot.",
|
|
23628
|
+
command: "./.har/agent-cli.sh {agentId} status",
|
|
23629
|
+
requiresAgentId: true
|
|
23630
|
+
});
|
|
23631
|
+
addStageIfRunnable(stages, harnessDir, {
|
|
23632
|
+
id: "logs",
|
|
23633
|
+
kind: "inspect",
|
|
23634
|
+
description: "Read recent logs for an agent slot.",
|
|
23635
|
+
command: "./.har/agent-cli.sh {agentId} logs",
|
|
23636
|
+
requiresAgentId: true
|
|
23637
|
+
});
|
|
23638
|
+
addStageIfRunnable(stages, harnessDir, {
|
|
23639
|
+
id: "teardown",
|
|
23640
|
+
kind: "teardown",
|
|
23641
|
+
description: "Tear down an isolated agent environment slot.",
|
|
23642
|
+
command: "./.har/teardown.sh {agentId}",
|
|
23643
|
+
requiresAgentId: true
|
|
23644
|
+
});
|
|
23645
|
+
const harnessEnv = readHarnessEnv(repoPath);
|
|
23646
|
+
const agentSlots = readAgentSlotsFromHarnessEnv(harnessEnv);
|
|
23647
|
+
if (!agentSlots) {
|
|
23648
|
+
throw new Error(
|
|
23649
|
+
"Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
|
|
23650
|
+
);
|
|
23651
|
+
}
|
|
23652
|
+
return {
|
|
23653
|
+
version: "1",
|
|
23654
|
+
artifactsDir: "artifacts",
|
|
23655
|
+
logsDir: "logs",
|
|
23656
|
+
agentSlots,
|
|
23657
|
+
verificationStages: ["typecheck", "unit-tests", "api-health"],
|
|
23658
|
+
stages
|
|
23659
|
+
};
|
|
23660
|
+
}
|
|
23661
|
+
function readAgentSlotsFromHarnessEnv(harnessEnv) {
|
|
23662
|
+
const max = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MAX");
|
|
23663
|
+
if (max === void 0) return null;
|
|
23664
|
+
const min = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
|
|
23665
|
+
if (max < min) {
|
|
23666
|
+
throw new Error("HARNESS_AGENT_SLOT_MAX must be >= HARNESS_AGENT_SLOT_MIN in harness.env");
|
|
23667
|
+
}
|
|
23668
|
+
return { min, max };
|
|
23669
|
+
}
|
|
23670
|
+
function stageScriptExists(harnessDir, stage) {
|
|
23671
|
+
if (stage.script) {
|
|
23672
|
+
return fs9.existsSync(path10.join(harnessDir, stage.script));
|
|
23673
|
+
}
|
|
23674
|
+
if (stage.command) {
|
|
23675
|
+
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
23676
|
+
return fs9.existsSync(path10.join(harnessDir, scriptName));
|
|
23677
|
+
}
|
|
23678
|
+
return fs9.existsSync(path10.join(harnessDir, "stages", `${stage.id}.sh`));
|
|
23679
|
+
}
|
|
23680
|
+
function addStageIfRunnable(stages, harnessDir, stage) {
|
|
23681
|
+
const parsed = HarnessStageSchema.parse(stage);
|
|
23682
|
+
if (stageScriptExists(harnessDir, parsed)) {
|
|
23683
|
+
stages.push(parsed);
|
|
23684
|
+
}
|
|
23685
|
+
}
|
|
23686
|
+
function readStageRegistry(repoPath) {
|
|
23687
|
+
const registryPath = getStageRegistryPath(repoPath);
|
|
23688
|
+
if (!fs9.existsSync(registryPath)) {
|
|
23689
|
+
return synthesizeStageRegistry(repoPath);
|
|
23690
|
+
}
|
|
23691
|
+
const raw = JSON.parse(fs9.readFileSync(registryPath, "utf8"));
|
|
23692
|
+
const parsed = HarnessStageRegistrySchema.safeParse(raw);
|
|
23693
|
+
if (!parsed.success) {
|
|
23694
|
+
throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
|
|
23695
|
+
}
|
|
23696
|
+
return parsed.data;
|
|
23697
|
+
}
|
|
23698
|
+
function writeStageRegistry(repoPath, registry2) {
|
|
23699
|
+
const registryPath = getStageRegistryPath(repoPath);
|
|
23700
|
+
const parsed = HarnessStageRegistrySchema.safeParse(registry2);
|
|
23701
|
+
if (!parsed.success) {
|
|
23702
|
+
throw new Error(`Invalid stage registry: ${parsed.error.message}`);
|
|
23703
|
+
}
|
|
23704
|
+
fs9.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
|
|
23705
|
+
}
|
|
23706
|
+
function listStages(repoPath) {
|
|
23707
|
+
return readStageRegistry(repoPath).stages;
|
|
23708
|
+
}
|
|
23709
|
+
function getAgentSlotRange(repoPath) {
|
|
23710
|
+
const registry2 = readStageRegistry(repoPath);
|
|
23711
|
+
if (registry2.agentSlots) {
|
|
23712
|
+
return registry2.agentSlots;
|
|
23713
|
+
}
|
|
23714
|
+
const fromEnv = readAgentSlotsFromHarnessEnv(readHarnessEnv(repoPath));
|
|
23715
|
+
if (fromEnv) {
|
|
23716
|
+
return fromEnv;
|
|
23717
|
+
}
|
|
23718
|
+
throw new Error(
|
|
23719
|
+
"Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
|
|
23720
|
+
);
|
|
23721
|
+
}
|
|
23722
|
+
function getAgentSlotIds(repoPath) {
|
|
23723
|
+
const { min, max } = getAgentSlotRange(repoPath);
|
|
23724
|
+
const ids = [];
|
|
23725
|
+
for (let id = min; id <= max; id++) ids.push(id);
|
|
23726
|
+
return ids;
|
|
23727
|
+
}
|
|
23728
|
+
function detectAgentSlotEnvMismatch(repoPath) {
|
|
23729
|
+
const registryPath = getStageRegistryPath(repoPath);
|
|
23730
|
+
if (!fs9.existsSync(registryPath)) return null;
|
|
23731
|
+
let stagesSlots = null;
|
|
23732
|
+
try {
|
|
23733
|
+
const raw = JSON.parse(fs9.readFileSync(registryPath, "utf8"));
|
|
23734
|
+
if (raw.agentSlots && Number.isInteger(raw.agentSlots.min) && Number.isInteger(raw.agentSlots.max)) {
|
|
23735
|
+
stagesSlots = { min: raw.agentSlots.min, max: raw.agentSlots.max };
|
|
23736
|
+
}
|
|
23737
|
+
} catch {
|
|
23738
|
+
return null;
|
|
23739
|
+
}
|
|
23740
|
+
if (!stagesSlots) return null;
|
|
23741
|
+
const env3 = readHarnessEnv(repoPath);
|
|
23742
|
+
const envMax = parseHarnessEnvInt(env3, "HARNESS_AGENT_SLOT_MAX");
|
|
23743
|
+
if (envMax === void 0) return null;
|
|
23744
|
+
const envMin = parseHarnessEnvInt(env3, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
|
|
23745
|
+
if (envMin === stagesSlots.min && envMax === stagesSlots.max) return null;
|
|
23746
|
+
return { stages: stagesSlots, env: { min: envMin, max: envMax } };
|
|
23747
|
+
}
|
|
23748
|
+
function syncAgentSlotsToHarnessEnv(repoPath) {
|
|
23749
|
+
const mismatch = detectAgentSlotEnvMismatch(repoPath);
|
|
23750
|
+
if (!mismatch) return false;
|
|
23751
|
+
const envPath = path10.join(getHarnessDir(repoPath), "harness.env");
|
|
23752
|
+
if (!fs9.existsSync(envPath)) return false;
|
|
23753
|
+
let content = fs9.readFileSync(envPath, "utf8");
|
|
23754
|
+
const minLine = `export HARNESS_AGENT_SLOT_MIN=${mismatch.stages.min}`;
|
|
23755
|
+
const maxLine = `export HARNESS_AGENT_SLOT_MAX=${mismatch.stages.max}`;
|
|
23756
|
+
if (/^export HARNESS_AGENT_SLOT_MIN=/m.test(content)) {
|
|
23757
|
+
content = content.replace(/^export HARNESS_AGENT_SLOT_MIN=.*$/m, minLine);
|
|
23758
|
+
} else {
|
|
23759
|
+
content = `${content.replace(/\s*$/, "")}
|
|
23760
|
+
${minLine}
|
|
23761
|
+
`;
|
|
23762
|
+
}
|
|
23763
|
+
if (/^export HARNESS_AGENT_SLOT_MAX=/m.test(content)) {
|
|
23764
|
+
content = content.replace(/^export HARNESS_AGENT_SLOT_MAX=.*$/m, maxLine);
|
|
23765
|
+
} else {
|
|
23766
|
+
content = `${content.replace(/\s*$/, "")}
|
|
23767
|
+
${maxLine}
|
|
23768
|
+
`;
|
|
23769
|
+
}
|
|
23770
|
+
fs9.writeFileSync(envPath, content);
|
|
23771
|
+
return true;
|
|
23772
|
+
}
|
|
23773
|
+
function stageRequiresAgentId(stage) {
|
|
23774
|
+
if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
|
|
23775
|
+
return AGENT_REQUIRED_KINDS.has(stage.kind);
|
|
23776
|
+
}
|
|
23777
|
+
function resolveStage(repoPath, options) {
|
|
23778
|
+
if (!options.id && !options.kind) return null;
|
|
23779
|
+
const stages = listStages(repoPath);
|
|
23780
|
+
if (options.id) {
|
|
23781
|
+
return stages.find((stage) => stage.id === options.id) ?? null;
|
|
23782
|
+
}
|
|
23783
|
+
const matches = stages.filter((stage) => stage.kind === options.kind);
|
|
23784
|
+
if (matches.length === 1) return matches[0];
|
|
23785
|
+
if (matches.length > 1) {
|
|
23786
|
+
throw new Error(
|
|
23787
|
+
`Multiple stages with kind "${options.kind}". Specify stage id. Available: ${matches.map((s2) => s2.id).join(", ")}`
|
|
23788
|
+
);
|
|
23789
|
+
}
|
|
23790
|
+
return null;
|
|
23791
|
+
}
|
|
23792
|
+
function getVerificationStageIds(repoPath) {
|
|
23793
|
+
const registry2 = readStageRegistry(repoPath);
|
|
23794
|
+
if (Array.isArray(registry2.verificationStages) && registry2.verificationStages.length > 0) {
|
|
23795
|
+
return registry2.verificationStages;
|
|
23796
|
+
}
|
|
23797
|
+
return registry2.stages.filter((stage) => stage.group === "verification").map((stage) => stage.id);
|
|
23798
|
+
}
|
|
23799
|
+
function getArtifactsDir(repoPath) {
|
|
23800
|
+
return readStageRegistry(repoPath).artifactsDir ?? "artifacts";
|
|
23801
|
+
}
|
|
23802
|
+
|
|
23532
23803
|
// src/harness/generator.ts
|
|
23533
23804
|
var PROFILE_DIRS = {
|
|
23534
23805
|
default: "har-boilerplate",
|
|
@@ -23542,46 +23813,48 @@ var CLI_PRUNE_FILES = [
|
|
|
23542
23813
|
];
|
|
23543
23814
|
function pruneCliProfile(harnessDir) {
|
|
23544
23815
|
for (const file of CLI_PRUNE_FILES) {
|
|
23545
|
-
const filePath =
|
|
23546
|
-
if (
|
|
23547
|
-
|
|
23816
|
+
const filePath = path11.join(harnessDir, file);
|
|
23817
|
+
if (fs10.existsSync(filePath)) {
|
|
23818
|
+
fs10.unlinkSync(filePath);
|
|
23548
23819
|
}
|
|
23549
23820
|
}
|
|
23550
23821
|
}
|
|
23551
23822
|
function scaffoldClaudeMd(repoPath, projectName, force) {
|
|
23552
23823
|
const templatePath = resolveTemplateFile("CLAUDE.md.template");
|
|
23553
23824
|
if (!templatePath) return;
|
|
23554
|
-
const dest =
|
|
23555
|
-
if (
|
|
23825
|
+
const dest = path11.join(repoPath, "CLAUDE.md");
|
|
23826
|
+
if (fs10.existsSync(dest) && !force) return;
|
|
23556
23827
|
const displayName = projectName.replace(/_/g, " ");
|
|
23557
|
-
const content =
|
|
23558
|
-
|
|
23828
|
+
const content = fs10.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
|
|
23829
|
+
fs10.writeFileSync(dest, content);
|
|
23559
23830
|
}
|
|
23560
23831
|
function scaffoldHarnessBoilerplate(repoPath, options = {}) {
|
|
23561
|
-
const harnessDir =
|
|
23562
|
-
const projectName =
|
|
23832
|
+
const harnessDir = path11.join(repoPath, DEFAULT_HAR_DIR);
|
|
23833
|
+
const projectName = path11.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
23563
23834
|
const profile = options.profile ?? "default";
|
|
23564
|
-
const boilerplateDir =
|
|
23565
|
-
if (
|
|
23835
|
+
const boilerplateDir = path11.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
|
|
23836
|
+
if (fs10.existsSync(harnessDir) && !options.force) {
|
|
23566
23837
|
throw new Error(
|
|
23567
23838
|
'.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
|
|
23568
23839
|
);
|
|
23569
23840
|
}
|
|
23570
|
-
if (!
|
|
23841
|
+
if (!fs10.existsSync(boilerplateDir)) {
|
|
23571
23842
|
throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
|
|
23572
23843
|
}
|
|
23573
|
-
if (options.force &&
|
|
23574
|
-
|
|
23844
|
+
if (options.force && fs10.existsSync(harnessDir)) {
|
|
23845
|
+
fs10.rmSync(harnessDir, { recursive: true, force: true });
|
|
23575
23846
|
}
|
|
23576
23847
|
copyDirRecursive(boilerplateDir, harnessDir);
|
|
23848
|
+
writeHarnessGitignore(harnessDir, boilerplateDir);
|
|
23577
23849
|
if (profile === "cli") {
|
|
23578
23850
|
pruneCliProfile(harnessDir);
|
|
23579
23851
|
}
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23852
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
23853
|
+
const harnessEnvPath = path11.join(harnessDir, "harness.env");
|
|
23854
|
+
if (fs10.existsSync(harnessEnvPath)) {
|
|
23855
|
+
let content = fs10.readFileSync(harnessEnvPath, "utf8");
|
|
23583
23856
|
content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
23584
|
-
|
|
23857
|
+
fs10.writeFileSync(harnessEnvPath, content);
|
|
23585
23858
|
}
|
|
23586
23859
|
const manifest = createManifest(
|
|
23587
23860
|
repoPath,
|
|
@@ -23604,8 +23877,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
|
|
|
23604
23877
|
}
|
|
23605
23878
|
|
|
23606
23879
|
// src/llm/authoring-agent.ts
|
|
23607
|
-
var
|
|
23608
|
-
var
|
|
23880
|
+
var fs14 = __toESM(require("fs"));
|
|
23881
|
+
var path14 = __toESM(require("path"));
|
|
23609
23882
|
|
|
23610
23883
|
// node_modules/@anthropic-ai/sdk/error.mjs
|
|
23611
23884
|
var error_exports = {};
|
|
@@ -24004,13 +24277,13 @@ var MultipartBody = class {
|
|
|
24004
24277
|
// node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
|
|
24005
24278
|
var import_web = require("node:stream/web");
|
|
24006
24279
|
var fileFromPathWarned = false;
|
|
24007
|
-
async function fileFromPath3(
|
|
24280
|
+
async function fileFromPath3(path48, ...args) {
|
|
24008
24281
|
const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
|
|
24009
24282
|
if (!fileFromPathWarned) {
|
|
24010
|
-
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(
|
|
24283
|
+
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path48)}) instead`);
|
|
24011
24284
|
fileFromPathWarned = true;
|
|
24012
24285
|
}
|
|
24013
|
-
return await _fileFromPath(
|
|
24286
|
+
return await _fileFromPath(path48, ...args);
|
|
24014
24287
|
}
|
|
24015
24288
|
var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
24016
24289
|
var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
@@ -24607,29 +24880,29 @@ var APIClient = class {
|
|
|
24607
24880
|
defaultIdempotencyKey() {
|
|
24608
24881
|
return `stainless-node-retry-${uuid4()}`;
|
|
24609
24882
|
}
|
|
24610
|
-
get(
|
|
24611
|
-
return this.methodRequest("get",
|
|
24883
|
+
get(path48, opts) {
|
|
24884
|
+
return this.methodRequest("get", path48, opts);
|
|
24612
24885
|
}
|
|
24613
|
-
post(
|
|
24614
|
-
return this.methodRequest("post",
|
|
24886
|
+
post(path48, opts) {
|
|
24887
|
+
return this.methodRequest("post", path48, opts);
|
|
24615
24888
|
}
|
|
24616
|
-
patch(
|
|
24617
|
-
return this.methodRequest("patch",
|
|
24889
|
+
patch(path48, opts) {
|
|
24890
|
+
return this.methodRequest("patch", path48, opts);
|
|
24618
24891
|
}
|
|
24619
|
-
put(
|
|
24620
|
-
return this.methodRequest("put",
|
|
24892
|
+
put(path48, opts) {
|
|
24893
|
+
return this.methodRequest("put", path48, opts);
|
|
24621
24894
|
}
|
|
24622
|
-
delete(
|
|
24623
|
-
return this.methodRequest("delete",
|
|
24895
|
+
delete(path48, opts) {
|
|
24896
|
+
return this.methodRequest("delete", path48, opts);
|
|
24624
24897
|
}
|
|
24625
|
-
methodRequest(method,
|
|
24898
|
+
methodRequest(method, path48, opts) {
|
|
24626
24899
|
return this.request(Promise.resolve(opts).then(async (opts2) => {
|
|
24627
24900
|
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;
|
|
24628
|
-
return { method, path:
|
|
24901
|
+
return { method, path: path48, ...opts2, body };
|
|
24629
24902
|
}));
|
|
24630
24903
|
}
|
|
24631
|
-
getAPIList(
|
|
24632
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
24904
|
+
getAPIList(path48, Page, opts) {
|
|
24905
|
+
return this.requestAPIList(Page, { method: "get", path: path48, ...opts });
|
|
24633
24906
|
}
|
|
24634
24907
|
calculateContentLength(body) {
|
|
24635
24908
|
if (typeof body === "string") {
|
|
@@ -24647,10 +24920,10 @@ var APIClient = class {
|
|
|
24647
24920
|
return null;
|
|
24648
24921
|
}
|
|
24649
24922
|
buildRequest(options) {
|
|
24650
|
-
const { method, path:
|
|
24923
|
+
const { method, path: path48, query, headers = {} } = options;
|
|
24651
24924
|
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;
|
|
24652
24925
|
const contentLength = this.calculateContentLength(body);
|
|
24653
|
-
const url = this.buildURL(
|
|
24926
|
+
const url = this.buildURL(path48, query);
|
|
24654
24927
|
if ("timeout" in options)
|
|
24655
24928
|
validatePositiveInteger("timeout", options.timeout);
|
|
24656
24929
|
const timeout = options.timeout ?? this.timeout;
|
|
@@ -24759,8 +25032,8 @@ var APIClient = class {
|
|
|
24759
25032
|
const request = this.makeRequest(options, null);
|
|
24760
25033
|
return new PagePromise(this, request, Page);
|
|
24761
25034
|
}
|
|
24762
|
-
buildURL(
|
|
24763
|
-
const url = isAbsoluteURL(
|
|
25035
|
+
buildURL(path48, query) {
|
|
25036
|
+
const url = isAbsoluteURL(path48) ? new URL(path48) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path48.startsWith("/") ? path48.slice(1) : path48));
|
|
24764
25037
|
const defaultQuery = this.defaultQuery();
|
|
24765
25038
|
if (!isEmptyObj(defaultQuery)) {
|
|
24766
25039
|
query = { ...defaultQuery, ...query };
|
|
@@ -26583,34 +26856,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
|
|
|
26583
26856
|
var sdk_default = Anthropic;
|
|
26584
26857
|
|
|
26585
26858
|
// src/llm/tools.ts
|
|
26586
|
-
var
|
|
26587
|
-
var
|
|
26859
|
+
var fs13 = __toESM(require("fs"));
|
|
26860
|
+
var path13 = __toESM(require("path"));
|
|
26588
26861
|
var readline3 = __toESM(require("readline"));
|
|
26589
26862
|
|
|
26590
26863
|
// src/harness/agent-md.ts
|
|
26591
|
-
var
|
|
26592
|
-
var
|
|
26864
|
+
var fs12 = __toESM(require("fs"));
|
|
26865
|
+
var path12 = __toESM(require("path"));
|
|
26593
26866
|
var readline2 = __toESM(require("readline"));
|
|
26594
26867
|
var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
|
|
26595
26868
|
var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
|
|
26596
26869
|
function writeAgentMdProposal(repoPath, content, rationale) {
|
|
26597
26870
|
const harnessDir = getHarnessDir(repoPath);
|
|
26598
|
-
writeFileSafe(
|
|
26871
|
+
writeFileSafe(path12.join(harnessDir, AGENT_MD_PROPOSAL), content);
|
|
26599
26872
|
writeFileSafe(
|
|
26600
|
-
|
|
26873
|
+
path12.join(harnessDir, AGENT_MD_PROPOSAL_META),
|
|
26601
26874
|
JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
26602
26875
|
);
|
|
26603
26876
|
}
|
|
26604
26877
|
function readAgentMdProposal(repoPath) {
|
|
26605
26878
|
const harnessDir = getHarnessDir(repoPath);
|
|
26606
|
-
const proposalPath =
|
|
26607
|
-
const metaPath =
|
|
26608
|
-
if (!
|
|
26609
|
-
const content =
|
|
26879
|
+
const proposalPath = path12.join(harnessDir, AGENT_MD_PROPOSAL);
|
|
26880
|
+
const metaPath = path12.join(harnessDir, AGENT_MD_PROPOSAL_META);
|
|
26881
|
+
if (!fs12.existsSync(proposalPath)) return null;
|
|
26882
|
+
const content = fs12.readFileSync(proposalPath, "utf8");
|
|
26610
26883
|
let rationale = "";
|
|
26611
|
-
if (
|
|
26884
|
+
if (fs12.existsSync(metaPath)) {
|
|
26612
26885
|
try {
|
|
26613
|
-
rationale = JSON.parse(
|
|
26886
|
+
rationale = JSON.parse(fs12.readFileSync(metaPath, "utf8")).rationale ?? "";
|
|
26614
26887
|
} catch {
|
|
26615
26888
|
rationale = "";
|
|
26616
26889
|
}
|
|
@@ -26618,21 +26891,21 @@ function readAgentMdProposal(repoPath) {
|
|
|
26618
26891
|
return {
|
|
26619
26892
|
content,
|
|
26620
26893
|
rationale,
|
|
26621
|
-
createdAt:
|
|
26894
|
+
createdAt: fs12.existsSync(metaPath) ? JSON.parse(fs12.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
26622
26895
|
};
|
|
26623
26896
|
}
|
|
26624
26897
|
function clearAgentMdProposal(repoPath) {
|
|
26625
26898
|
const harnessDir = getHarnessDir(repoPath);
|
|
26626
26899
|
for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
|
|
26627
|
-
const p2 =
|
|
26628
|
-
if (
|
|
26900
|
+
const p2 = path12.join(harnessDir, file);
|
|
26901
|
+
if (fs12.existsSync(p2)) fs12.unlinkSync(p2);
|
|
26629
26902
|
}
|
|
26630
26903
|
}
|
|
26631
26904
|
async function promptApplyAgentMdProposal(repoPath) {
|
|
26632
26905
|
const proposal = readAgentMdProposal(repoPath);
|
|
26633
26906
|
if (!proposal) return false;
|
|
26634
|
-
const agentMdPath =
|
|
26635
|
-
const exists =
|
|
26907
|
+
const agentMdPath = path12.join(repoPath, "AGENT.md");
|
|
26908
|
+
const exists = fs12.existsSync(agentMdPath);
|
|
26636
26909
|
process.stderr.write("\n");
|
|
26637
26910
|
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");
|
|
26638
26911
|
process.stderr.write("Proposed AGENT.md (repo root)\n");
|
|
@@ -26820,13 +27093,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
|
|
|
26820
27093
|
async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
|
|
26821
27094
|
switch (toolName) {
|
|
26822
27095
|
case "readRepoFile": {
|
|
26823
|
-
const filePath =
|
|
27096
|
+
const filePath = path13.join(repoPath, input.path);
|
|
26824
27097
|
const content = readFile(filePath, input.maxChars ?? 8e3);
|
|
26825
27098
|
info(`Read repo: ${input.path}`);
|
|
26826
27099
|
return content;
|
|
26827
27100
|
}
|
|
26828
27101
|
case "listRepoDir": {
|
|
26829
|
-
const dirPath =
|
|
27102
|
+
const dirPath = path13.join(repoPath, input.path ?? ".");
|
|
26830
27103
|
const entries = listDir(dirPath, input.maxFiles ?? 60);
|
|
26831
27104
|
info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
|
|
26832
27105
|
return entries.length > 0 ? entries.join("\n") : "(empty directory)";
|
|
@@ -26851,17 +27124,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26851
27124
|
const filePath = resolveSafePath(harnessDir, relPath);
|
|
26852
27125
|
writeFileSafe(filePath, input.content);
|
|
26853
27126
|
if (relPath.endsWith(".sh")) {
|
|
26854
|
-
|
|
27127
|
+
fs13.chmodSync(filePath, 493);
|
|
26855
27128
|
}
|
|
26856
27129
|
info(`Wrote harness: ${relPath}`);
|
|
26857
27130
|
return `Written ${relPath} (${input.content.length} bytes)`;
|
|
26858
27131
|
}
|
|
26859
27132
|
case "editHarnessFile": {
|
|
26860
27133
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26861
|
-
if (!
|
|
27134
|
+
if (!fs13.existsSync(filePath)) {
|
|
26862
27135
|
return `Error: file not found: ${input.path}`;
|
|
26863
27136
|
}
|
|
26864
|
-
const content =
|
|
27137
|
+
const content = fs13.readFileSync(filePath, "utf8");
|
|
26865
27138
|
const oldStr = input.old_string;
|
|
26866
27139
|
const newStr = input.new_string;
|
|
26867
27140
|
const count = content.split(oldStr).length - 1;
|
|
@@ -26878,10 +27151,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26878
27151
|
}
|
|
26879
27152
|
case "deleteHarnessFile": {
|
|
26880
27153
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26881
|
-
if (!
|
|
27154
|
+
if (!fs13.existsSync(filePath)) {
|
|
26882
27155
|
return `File not found (already deleted): ${input.path}`;
|
|
26883
27156
|
}
|
|
26884
|
-
|
|
27157
|
+
fs13.unlinkSync(filePath);
|
|
26885
27158
|
info(`Deleted harness: ${input.path}`);
|
|
26886
27159
|
return `Deleted ${input.path}`;
|
|
26887
27160
|
}
|
|
@@ -26932,29 +27205,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
|
|
|
26932
27205
|
const mode = options.mode ?? "init";
|
|
26933
27206
|
const systemPromptPath = resolvePromptPath("system-authoring.md");
|
|
26934
27207
|
const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
|
|
26935
|
-
let systemPrompt =
|
|
27208
|
+
let systemPrompt = fs14.existsSync(systemPromptPath) ? fs14.readFileSync(systemPromptPath, "utf8") : "Adapt the .har/ boilerplate to match the repository. Edit files directly. Call finishAuthoring when done.";
|
|
26936
27209
|
if (agentMdTemplatePath) {
|
|
26937
27210
|
systemPrompt += `
|
|
26938
27211
|
|
|
26939
27212
|
## AGENT.md template (starting point for proposeAgentMd)
|
|
26940
27213
|
|
|
26941
|
-
${
|
|
27214
|
+
${fs14.readFileSync(agentMdTemplatePath, "utf8")}`;
|
|
26942
27215
|
}
|
|
26943
27216
|
const tools = buildAuthoringTools();
|
|
26944
27217
|
const messages = [];
|
|
26945
27218
|
let rootListing;
|
|
26946
27219
|
try {
|
|
26947
|
-
rootListing =
|
|
27220
|
+
rootListing = fs14.readdirSync(repoPath).slice(0, 50).join("\n");
|
|
26948
27221
|
} catch {
|
|
26949
27222
|
throw new Error(`Cannot read repo at path: ${repoPath}`);
|
|
26950
27223
|
}
|
|
26951
27224
|
let harnessListing;
|
|
26952
27225
|
try {
|
|
26953
|
-
harnessListing =
|
|
27226
|
+
harnessListing = fs14.readdirSync(harnessDir).join("\n");
|
|
26954
27227
|
} catch {
|
|
26955
27228
|
throw new Error(`Cannot read harness dir at: ${harnessDir}`);
|
|
26956
27229
|
}
|
|
26957
|
-
const existingAgentMd =
|
|
27230
|
+
const existingAgentMd = fs14.existsSync(path14.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.";
|
|
26958
27231
|
const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
|
|
26959
27232
|
Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
|
|
26960
27233
|
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/.
|
|
@@ -27034,8 +27307,8 @@ Try running with --verbose to see what the agent is doing.`
|
|
|
27034
27307
|
}
|
|
27035
27308
|
|
|
27036
27309
|
// src/harness/validator.ts
|
|
27037
|
-
var
|
|
27038
|
-
var
|
|
27310
|
+
var fs15 = __toESM(require("fs"));
|
|
27311
|
+
var path15 = __toESM(require("path"));
|
|
27039
27312
|
|
|
27040
27313
|
// src/utils/shell.ts
|
|
27041
27314
|
var import_child_process = require("child_process");
|
|
@@ -27103,205 +27376,6 @@ function runShellCommand(command2, options = {}) {
|
|
|
27103
27376
|
});
|
|
27104
27377
|
}
|
|
27105
27378
|
|
|
27106
|
-
// src/harness/stages.ts
|
|
27107
|
-
var fs13 = __toESM(require("fs"));
|
|
27108
|
-
var path13 = __toESM(require("path"));
|
|
27109
|
-
|
|
27110
|
-
// src/harness/env.ts
|
|
27111
|
-
var fs12 = __toESM(require("fs"));
|
|
27112
|
-
var path12 = __toESM(require("path"));
|
|
27113
|
-
function readHarnessEnv(repoPath) {
|
|
27114
|
-
const envPath = path12.join(getHarnessDir(repoPath), "harness.env");
|
|
27115
|
-
if (!fs12.existsSync(envPath)) return {};
|
|
27116
|
-
const values = {};
|
|
27117
|
-
for (const line of fs12.readFileSync(envPath, "utf8").split("\n")) {
|
|
27118
|
-
const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
|
|
27119
|
-
if (!match) continue;
|
|
27120
|
-
values[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
27121
|
-
}
|
|
27122
|
-
return values;
|
|
27123
|
-
}
|
|
27124
|
-
function parseHarnessEnvInt(env3, key) {
|
|
27125
|
-
const raw = env3[key];
|
|
27126
|
-
if (raw === void 0 || raw === "") return void 0;
|
|
27127
|
-
const value = Number(raw);
|
|
27128
|
-
if (!Number.isInteger(value)) return void 0;
|
|
27129
|
-
return value;
|
|
27130
|
-
}
|
|
27131
|
-
|
|
27132
|
-
// src/harness/stages.ts
|
|
27133
|
-
var STAGE_REGISTRY_FILE = "stages.json";
|
|
27134
|
-
var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
|
|
27135
|
-
"launch",
|
|
27136
|
-
"verify",
|
|
27137
|
-
"test",
|
|
27138
|
-
"inspect",
|
|
27139
|
-
"reset",
|
|
27140
|
-
"teardown",
|
|
27141
|
-
"custom"
|
|
27142
|
-
]);
|
|
27143
|
-
function getStageRegistryPath(repoPath) {
|
|
27144
|
-
return path13.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
|
|
27145
|
-
}
|
|
27146
|
-
function synthesizeStageRegistry(repoPath) {
|
|
27147
|
-
const harnessDir = getHarnessDir(repoPath);
|
|
27148
|
-
const stages = [];
|
|
27149
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27150
|
-
id: "setup-infra",
|
|
27151
|
-
kind: "setup",
|
|
27152
|
-
description: "Start shared harness infrastructure.",
|
|
27153
|
-
command: "./.har/setup-infra.sh",
|
|
27154
|
-
requiresAgentId: false
|
|
27155
|
-
});
|
|
27156
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27157
|
-
id: "launch",
|
|
27158
|
-
kind: "launch",
|
|
27159
|
-
description: "Launch an isolated agent environment slot.",
|
|
27160
|
-
command: "./.har/launch.sh {agentId}",
|
|
27161
|
-
requiresAgentId: true
|
|
27162
|
-
});
|
|
27163
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27164
|
-
id: "verify",
|
|
27165
|
-
kind: "verify",
|
|
27166
|
-
description: "Run the project verification pipeline.",
|
|
27167
|
-
command: "./.har/verify.sh {agentId}",
|
|
27168
|
-
requiresAgentId: true,
|
|
27169
|
-
acceptsArgs: ["--full"]
|
|
27170
|
-
});
|
|
27171
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27172
|
-
id: "status",
|
|
27173
|
-
kind: "inspect",
|
|
27174
|
-
description: "Inspect a running agent environment slot.",
|
|
27175
|
-
command: "./.har/agent-cli.sh {agentId} status",
|
|
27176
|
-
requiresAgentId: true
|
|
27177
|
-
});
|
|
27178
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27179
|
-
id: "logs",
|
|
27180
|
-
kind: "inspect",
|
|
27181
|
-
description: "Read recent logs for an agent slot.",
|
|
27182
|
-
command: "./.har/agent-cli.sh {agentId} logs",
|
|
27183
|
-
requiresAgentId: true
|
|
27184
|
-
});
|
|
27185
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27186
|
-
id: "teardown",
|
|
27187
|
-
kind: "teardown",
|
|
27188
|
-
description: "Tear down an isolated agent environment slot.",
|
|
27189
|
-
command: "./.har/teardown.sh {agentId}",
|
|
27190
|
-
requiresAgentId: true
|
|
27191
|
-
});
|
|
27192
|
-
const harnessEnv = readHarnessEnv(repoPath);
|
|
27193
|
-
const agentSlots = readAgentSlotsFromHarnessEnv(harnessEnv);
|
|
27194
|
-
if (!agentSlots) {
|
|
27195
|
-
throw new Error(
|
|
27196
|
-
"Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
|
|
27197
|
-
);
|
|
27198
|
-
}
|
|
27199
|
-
return {
|
|
27200
|
-
version: "1",
|
|
27201
|
-
artifactsDir: "artifacts",
|
|
27202
|
-
logsDir: "logs",
|
|
27203
|
-
agentSlots,
|
|
27204
|
-
verificationStages: ["typecheck", "unit-tests", "api-health"],
|
|
27205
|
-
stages
|
|
27206
|
-
};
|
|
27207
|
-
}
|
|
27208
|
-
function readAgentSlotsFromHarnessEnv(harnessEnv) {
|
|
27209
|
-
const max = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MAX");
|
|
27210
|
-
if (max === void 0) return null;
|
|
27211
|
-
const min = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
|
|
27212
|
-
if (max < min) {
|
|
27213
|
-
throw new Error("HARNESS_AGENT_SLOT_MAX must be >= HARNESS_AGENT_SLOT_MIN in harness.env");
|
|
27214
|
-
}
|
|
27215
|
-
return { min, max };
|
|
27216
|
-
}
|
|
27217
|
-
function stageScriptExists(harnessDir, stage) {
|
|
27218
|
-
if (stage.script) {
|
|
27219
|
-
return fs13.existsSync(path13.join(harnessDir, stage.script));
|
|
27220
|
-
}
|
|
27221
|
-
if (stage.command) {
|
|
27222
|
-
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
27223
|
-
return fs13.existsSync(path13.join(harnessDir, scriptName));
|
|
27224
|
-
}
|
|
27225
|
-
return fs13.existsSync(path13.join(harnessDir, "stages", `${stage.id}.sh`));
|
|
27226
|
-
}
|
|
27227
|
-
function addStageIfRunnable(stages, harnessDir, stage) {
|
|
27228
|
-
const parsed = HarnessStageSchema.parse(stage);
|
|
27229
|
-
if (stageScriptExists(harnessDir, parsed)) {
|
|
27230
|
-
stages.push(parsed);
|
|
27231
|
-
}
|
|
27232
|
-
}
|
|
27233
|
-
function readStageRegistry(repoPath) {
|
|
27234
|
-
const registryPath = getStageRegistryPath(repoPath);
|
|
27235
|
-
if (!fs13.existsSync(registryPath)) {
|
|
27236
|
-
return synthesizeStageRegistry(repoPath);
|
|
27237
|
-
}
|
|
27238
|
-
const raw = JSON.parse(fs13.readFileSync(registryPath, "utf8"));
|
|
27239
|
-
const parsed = HarnessStageRegistrySchema.safeParse(raw);
|
|
27240
|
-
if (!parsed.success) {
|
|
27241
|
-
throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
|
|
27242
|
-
}
|
|
27243
|
-
return parsed.data;
|
|
27244
|
-
}
|
|
27245
|
-
function writeStageRegistry(repoPath, registry2) {
|
|
27246
|
-
const registryPath = getStageRegistryPath(repoPath);
|
|
27247
|
-
const parsed = HarnessStageRegistrySchema.safeParse(registry2);
|
|
27248
|
-
if (!parsed.success) {
|
|
27249
|
-
throw new Error(`Invalid stage registry: ${parsed.error.message}`);
|
|
27250
|
-
}
|
|
27251
|
-
fs13.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
|
|
27252
|
-
}
|
|
27253
|
-
function listStages(repoPath) {
|
|
27254
|
-
return readStageRegistry(repoPath).stages;
|
|
27255
|
-
}
|
|
27256
|
-
function getAgentSlotRange(repoPath) {
|
|
27257
|
-
const registry2 = readStageRegistry(repoPath);
|
|
27258
|
-
if (registry2.agentSlots) {
|
|
27259
|
-
return registry2.agentSlots;
|
|
27260
|
-
}
|
|
27261
|
-
const fromEnv = readAgentSlotsFromHarnessEnv(readHarnessEnv(repoPath));
|
|
27262
|
-
if (fromEnv) {
|
|
27263
|
-
return fromEnv;
|
|
27264
|
-
}
|
|
27265
|
-
throw new Error(
|
|
27266
|
-
"Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
|
|
27267
|
-
);
|
|
27268
|
-
}
|
|
27269
|
-
function getAgentSlotIds(repoPath) {
|
|
27270
|
-
const { min, max } = getAgentSlotRange(repoPath);
|
|
27271
|
-
const ids = [];
|
|
27272
|
-
for (let id = min; id <= max; id++) ids.push(id);
|
|
27273
|
-
return ids;
|
|
27274
|
-
}
|
|
27275
|
-
function stageRequiresAgentId(stage) {
|
|
27276
|
-
if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
|
|
27277
|
-
return AGENT_REQUIRED_KINDS.has(stage.kind);
|
|
27278
|
-
}
|
|
27279
|
-
function resolveStage(repoPath, options) {
|
|
27280
|
-
if (!options.id && !options.kind) return null;
|
|
27281
|
-
const stages = listStages(repoPath);
|
|
27282
|
-
if (options.id) {
|
|
27283
|
-
return stages.find((stage) => stage.id === options.id) ?? null;
|
|
27284
|
-
}
|
|
27285
|
-
const matches = stages.filter((stage) => stage.kind === options.kind);
|
|
27286
|
-
if (matches.length === 1) return matches[0];
|
|
27287
|
-
if (matches.length > 1) {
|
|
27288
|
-
throw new Error(
|
|
27289
|
-
`Multiple stages with kind "${options.kind}". Specify stage id. Available: ${matches.map((s2) => s2.id).join(", ")}`
|
|
27290
|
-
);
|
|
27291
|
-
}
|
|
27292
|
-
return null;
|
|
27293
|
-
}
|
|
27294
|
-
function getVerificationStageIds(repoPath) {
|
|
27295
|
-
const registry2 = readStageRegistry(repoPath);
|
|
27296
|
-
if (Array.isArray(registry2.verificationStages) && registry2.verificationStages.length > 0) {
|
|
27297
|
-
return registry2.verificationStages;
|
|
27298
|
-
}
|
|
27299
|
-
return registry2.stages.filter((stage) => stage.group === "verification").map((stage) => stage.id);
|
|
27300
|
-
}
|
|
27301
|
-
function getArtifactsDir(repoPath) {
|
|
27302
|
-
return readStageRegistry(repoPath).artifactsDir ?? "artifacts";
|
|
27303
|
-
}
|
|
27304
|
-
|
|
27305
27379
|
// src/harness/validator.ts
|
|
27306
27380
|
var REQUIRED_FILES_DEFAULT = [
|
|
27307
27381
|
"README.md",
|
|
@@ -27354,22 +27428,22 @@ var SHELL_SCRIPTS = [
|
|
|
27354
27428
|
function validateHarness(repoPath) {
|
|
27355
27429
|
const harnessDir = getHarnessDir(repoPath);
|
|
27356
27430
|
const issues = [];
|
|
27357
|
-
if (!
|
|
27431
|
+
if (!fs15.existsSync(harnessDir)) {
|
|
27358
27432
|
return {
|
|
27359
27433
|
pass: false,
|
|
27360
27434
|
issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
|
|
27361
27435
|
};
|
|
27362
27436
|
}
|
|
27363
27437
|
for (const file of getRequiredFiles(repoPath)) {
|
|
27364
|
-
const filePath =
|
|
27365
|
-
if (!
|
|
27438
|
+
const filePath = path15.join(harnessDir, file);
|
|
27439
|
+
if (!fs15.existsSync(filePath)) {
|
|
27366
27440
|
issues.push({ file, message: "Required file missing", severity: "error" });
|
|
27367
27441
|
}
|
|
27368
27442
|
}
|
|
27369
27443
|
for (const script of SHELL_SCRIPTS) {
|
|
27370
|
-
const scriptPath =
|
|
27371
|
-
if (!
|
|
27372
|
-
const stat =
|
|
27444
|
+
const scriptPath = path15.join(harnessDir, script);
|
|
27445
|
+
if (!fs15.existsSync(scriptPath)) continue;
|
|
27446
|
+
const stat = fs15.statSync(scriptPath);
|
|
27373
27447
|
if (!(stat.mode & 73)) {
|
|
27374
27448
|
issues.push({ file: script, message: "Script is not executable", severity: "warning" });
|
|
27375
27449
|
}
|
|
@@ -27384,9 +27458,9 @@ function validateHarness(repoPath) {
|
|
|
27384
27458
|
}
|
|
27385
27459
|
const manifest = readManifest(repoPath);
|
|
27386
27460
|
const profile = manifest?.profile ?? "default";
|
|
27387
|
-
const ecosystemPath =
|
|
27388
|
-
if (profile !== "cli" &&
|
|
27389
|
-
const content =
|
|
27461
|
+
const ecosystemPath = path15.join(harnessDir, "ecosystem.agent.template.cjs");
|
|
27462
|
+
if (profile !== "cli" && fs15.existsSync(ecosystemPath)) {
|
|
27463
|
+
const content = fs15.readFileSync(ecosystemPath, "utf8");
|
|
27390
27464
|
if (!content.includes("module.exports")) {
|
|
27391
27465
|
issues.push({
|
|
27392
27466
|
file: "ecosystem.agent.template.cjs",
|
|
@@ -27395,9 +27469,9 @@ function validateHarness(repoPath) {
|
|
|
27395
27469
|
});
|
|
27396
27470
|
}
|
|
27397
27471
|
}
|
|
27398
|
-
const harnessEnvPath =
|
|
27399
|
-
if (
|
|
27400
|
-
const content =
|
|
27472
|
+
const harnessEnvPath = path15.join(harnessDir, "harness.env");
|
|
27473
|
+
if (fs15.existsSync(harnessEnvPath)) {
|
|
27474
|
+
const content = fs15.readFileSync(harnessEnvPath, "utf8");
|
|
27401
27475
|
if (content.includes("TODO: set migrate command")) {
|
|
27402
27476
|
issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
|
|
27403
27477
|
}
|
|
@@ -27405,17 +27479,17 @@ function validateHarness(repoPath) {
|
|
|
27405
27479
|
issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
|
|
27406
27480
|
}
|
|
27407
27481
|
}
|
|
27408
|
-
const verifyPath =
|
|
27409
|
-
if (
|
|
27410
|
-
const content =
|
|
27482
|
+
const verifyPath = path15.join(harnessDir, "verify.sh");
|
|
27483
|
+
if (fs15.existsSync(verifyPath)) {
|
|
27484
|
+
const content = fs15.readFileSync(verifyPath, "utf8");
|
|
27411
27485
|
if (content.includes("echo 'TODO:")) {
|
|
27412
27486
|
issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
|
|
27413
27487
|
}
|
|
27414
27488
|
}
|
|
27415
|
-
const stagesPath =
|
|
27416
|
-
if (
|
|
27489
|
+
const stagesPath = path15.join(harnessDir, "stages.json");
|
|
27490
|
+
if (fs15.existsSync(stagesPath)) {
|
|
27417
27491
|
try {
|
|
27418
|
-
JSON.parse(
|
|
27492
|
+
JSON.parse(fs15.readFileSync(stagesPath, "utf8"));
|
|
27419
27493
|
const registry2 = readStageRegistry(repoPath);
|
|
27420
27494
|
if (registry2.stages.length === 0) {
|
|
27421
27495
|
issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
|
|
@@ -27430,9 +27504,9 @@ function validateHarness(repoPath) {
|
|
|
27430
27504
|
} else {
|
|
27431
27505
|
issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
|
|
27432
27506
|
}
|
|
27433
|
-
const readmePath =
|
|
27434
|
-
if (
|
|
27435
|
-
const content =
|
|
27507
|
+
const readmePath = path15.join(harnessDir, "README.md");
|
|
27508
|
+
if (fs15.existsSync(readmePath)) {
|
|
27509
|
+
const content = fs15.readFileSync(readmePath, "utf8");
|
|
27436
27510
|
if (!content.includes(".har")) {
|
|
27437
27511
|
issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
|
|
27438
27512
|
}
|
|
@@ -27446,8 +27520,8 @@ function validateHarness(repoPath) {
|
|
|
27446
27520
|
async function smokeTestHarness(repoPath) {
|
|
27447
27521
|
const harnessDir = getHarnessDir(repoPath);
|
|
27448
27522
|
const issues = [];
|
|
27449
|
-
const setupScript =
|
|
27450
|
-
if (
|
|
27523
|
+
const setupScript = path15.join(harnessDir, "setup-infra.sh");
|
|
27524
|
+
if (fs15.existsSync(setupScript)) {
|
|
27451
27525
|
const result = run(`bash "${setupScript}"`, { cwd: repoPath });
|
|
27452
27526
|
if (result.code !== 0) {
|
|
27453
27527
|
issues.push({
|
|
@@ -27462,8 +27536,8 @@ async function smokeTestHarness(repoPath) {
|
|
|
27462
27536
|
}
|
|
27463
27537
|
|
|
27464
27538
|
// src/harness/drift.ts
|
|
27465
|
-
var
|
|
27466
|
-
var
|
|
27539
|
+
var fs16 = __toESM(require("fs"));
|
|
27540
|
+
var path16 = __toESM(require("path"));
|
|
27467
27541
|
var PROFILE_DIRS2 = {
|
|
27468
27542
|
default: "har-boilerplate",
|
|
27469
27543
|
cli: "har-boilerplate-cli",
|
|
@@ -27531,48 +27605,51 @@ function substituteProjectName(content, projectName) {
|
|
|
27531
27605
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27532
27606
|
}
|
|
27533
27607
|
function listBoilerplateFiles(boilerplateDir) {
|
|
27534
|
-
if (!
|
|
27535
|
-
return
|
|
27608
|
+
if (!fs16.existsSync(boilerplateDir)) return [];
|
|
27609
|
+
return fs16.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
|
|
27536
27610
|
}
|
|
27537
27611
|
function compareHarnessToTemplate(repoPath) {
|
|
27538
|
-
const resolved =
|
|
27612
|
+
const resolved = path16.resolve(repoPath);
|
|
27539
27613
|
const manifest = readManifest(resolved);
|
|
27540
27614
|
const profile = manifest?.profile ?? "default";
|
|
27541
27615
|
const harnessDir = getHarnessDir(resolved);
|
|
27542
|
-
const projectName =
|
|
27543
|
-
const boilerplateDir =
|
|
27616
|
+
const projectName = path16.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27617
|
+
const boilerplateDir = path16.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
|
|
27544
27618
|
const templateFiles = listBoilerplateFiles(boilerplateDir);
|
|
27545
27619
|
const missing = [];
|
|
27546
27620
|
const checksumMismatch = [];
|
|
27547
27621
|
const extra = [];
|
|
27548
27622
|
const unchanged = [];
|
|
27549
27623
|
for (const file of templateFiles) {
|
|
27550
|
-
const
|
|
27551
|
-
const
|
|
27552
|
-
|
|
27624
|
+
const harnessFile = harnessFileForTemplate(file);
|
|
27625
|
+
const templatePath = path16.join(boilerplateDir, file);
|
|
27626
|
+
const harnessPath = path16.join(harnessDir, harnessFile);
|
|
27627
|
+
let templateContent = fs16.readFileSync(templatePath, "utf8");
|
|
27553
27628
|
if (file === "harness.env") {
|
|
27554
27629
|
templateContent = substituteProjectName(templateContent, projectName);
|
|
27555
27630
|
}
|
|
27556
|
-
if (!
|
|
27557
|
-
missing.push(
|
|
27631
|
+
if (!fs16.existsSync(harnessPath)) {
|
|
27632
|
+
missing.push(harnessFile);
|
|
27558
27633
|
continue;
|
|
27559
27634
|
}
|
|
27560
|
-
const harnessChecksum = computeFileChecksum(
|
|
27635
|
+
const harnessChecksum = computeFileChecksum(fs16.readFileSync(harnessPath, "utf8"));
|
|
27561
27636
|
const templateChecksum = computeFileChecksum(templateContent);
|
|
27562
27637
|
if (harnessChecksum === templateChecksum) {
|
|
27563
|
-
unchanged.push(
|
|
27638
|
+
unchanged.push(harnessFile);
|
|
27564
27639
|
} else {
|
|
27565
|
-
checksumMismatch.push(
|
|
27640
|
+
checksumMismatch.push(harnessFile);
|
|
27566
27641
|
}
|
|
27567
27642
|
}
|
|
27568
|
-
if (
|
|
27569
|
-
for (const file of
|
|
27570
|
-
const full =
|
|
27571
|
-
if (!
|
|
27643
|
+
if (fs16.existsSync(harnessDir)) {
|
|
27644
|
+
for (const file of fs16.readdirSync(harnessDir)) {
|
|
27645
|
+
const full = path16.join(harnessDir, file);
|
|
27646
|
+
if (!fs16.statSync(full).isFile()) continue;
|
|
27572
27647
|
if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
|
|
27573
27648
|
if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
|
|
27574
27649
|
extra.push(file);
|
|
27575
|
-
} else if (
|
|
27650
|
+
} else if (isExpectedHarnessOnlyFile(file, templateFiles)) {
|
|
27651
|
+
continue;
|
|
27652
|
+
} else if (!templateFiles.includes(file) && !templateFiles.includes(templateFileForHarness(file))) {
|
|
27576
27653
|
extra.push(file);
|
|
27577
27654
|
}
|
|
27578
27655
|
}
|
|
@@ -27580,6 +27657,7 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27580
27657
|
const installed = manifest?.generatorVersion;
|
|
27581
27658
|
const harnessEnv = readHarnessEnv(resolved);
|
|
27582
27659
|
const missingPortVars = missingPortDocumentationVars(profile, harnessEnv);
|
|
27660
|
+
const agentSlotMismatch = detectAgentSlotEnvMismatch(resolved);
|
|
27583
27661
|
return {
|
|
27584
27662
|
generatorVersion: {
|
|
27585
27663
|
installed,
|
|
@@ -27590,14 +27668,15 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27590
27668
|
checksumMismatch,
|
|
27591
27669
|
extra,
|
|
27592
27670
|
unchanged,
|
|
27593
|
-
missingPortVars
|
|
27671
|
+
missingPortVars,
|
|
27672
|
+
agentSlotMismatch
|
|
27594
27673
|
};
|
|
27595
27674
|
}
|
|
27596
27675
|
|
|
27597
27676
|
// src/harness/maintain-bundle.ts
|
|
27598
|
-
var
|
|
27677
|
+
var fs17 = __toESM(require("fs"));
|
|
27599
27678
|
var os3 = __toESM(require("os"));
|
|
27600
|
-
var
|
|
27679
|
+
var path17 = __toESM(require("path"));
|
|
27601
27680
|
var MAINTAIN_DIR = "maintain";
|
|
27602
27681
|
var PROFILE_DIRS3 = {
|
|
27603
27682
|
default: "har-boilerplate",
|
|
@@ -27608,14 +27687,14 @@ function substituteProjectName2(content, projectName) {
|
|
|
27608
27687
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27609
27688
|
}
|
|
27610
27689
|
function projectNameFromRepo(repoPath) {
|
|
27611
|
-
return
|
|
27690
|
+
return path17.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27612
27691
|
}
|
|
27613
27692
|
function boilerplateDirForProfile(profile) {
|
|
27614
|
-
return
|
|
27693
|
+
return path17.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
|
|
27615
27694
|
}
|
|
27616
27695
|
function readBundledTemplateContent(repoPath, profile, file) {
|
|
27617
|
-
const templatePath =
|
|
27618
|
-
let content =
|
|
27696
|
+
const templatePath = path17.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
|
|
27697
|
+
let content = fs17.readFileSync(templatePath, "utf8");
|
|
27619
27698
|
if (file === "harness.env") {
|
|
27620
27699
|
content = substituteProjectName2(content, projectNameFromRepo(repoPath));
|
|
27621
27700
|
}
|
|
@@ -27646,12 +27725,12 @@ function actionHint(file, kind2) {
|
|
|
27646
27725
|
return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
|
|
27647
27726
|
}
|
|
27648
27727
|
function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
|
|
27649
|
-
const tmpDir =
|
|
27728
|
+
const tmpDir = fs17.mkdtempSync(path17.join(os3.tmpdir(), "har-maintain-diff-"));
|
|
27650
27729
|
try {
|
|
27651
|
-
const installedPath =
|
|
27652
|
-
const templatePath =
|
|
27653
|
-
|
|
27654
|
-
|
|
27730
|
+
const installedPath = path17.join(tmpDir, "installed");
|
|
27731
|
+
const templatePath = path17.join(tmpDir, "template");
|
|
27732
|
+
fs17.writeFileSync(installedPath, installedContent);
|
|
27733
|
+
fs17.writeFileSync(templatePath, templateContent);
|
|
27655
27734
|
const result = run(`diff -u "${installedPath}" "${templatePath}"`);
|
|
27656
27735
|
const body = result.stdout.trim();
|
|
27657
27736
|
if (!body) return "";
|
|
@@ -27660,7 +27739,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
|
|
|
27660
27739
|
${body.split("\n").slice(2).join("\n")}
|
|
27661
27740
|
`;
|
|
27662
27741
|
} finally {
|
|
27663
|
-
|
|
27742
|
+
fs17.rmSync(tmpDir, { recursive: true, force: true });
|
|
27664
27743
|
}
|
|
27665
27744
|
}
|
|
27666
27745
|
function buildActions(repoPath, profile, drift) {
|
|
@@ -27675,7 +27754,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
27675
27754
|
});
|
|
27676
27755
|
}
|
|
27677
27756
|
for (const file of drift.checksumMismatch) {
|
|
27678
|
-
const installedPath =
|
|
27757
|
+
const installedPath = path17.join(harnessDir, file);
|
|
27679
27758
|
const installedRel = `maintain/installed/${file}`;
|
|
27680
27759
|
const templateRel = `maintain/templates/${file}`;
|
|
27681
27760
|
const diffRel = `maintain/diffs/${file}.diff`;
|
|
@@ -27683,7 +27762,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
27683
27762
|
file,
|
|
27684
27763
|
kind: "drift",
|
|
27685
27764
|
template: templateRel,
|
|
27686
|
-
installed:
|
|
27765
|
+
installed: fs17.existsSync(installedPath) ? installedRel : void 0,
|
|
27687
27766
|
diff: diffRel,
|
|
27688
27767
|
hint: actionHint(file, "drift")
|
|
27689
27768
|
});
|
|
@@ -27732,6 +27811,18 @@ function buildReadme(report) {
|
|
|
27732
27811
|
lines.push(`- \`${v2}\` \u2014 copy from \`maintain/templates/harness.env\``);
|
|
27733
27812
|
}
|
|
27734
27813
|
}
|
|
27814
|
+
if (report.agentSlotMismatch) {
|
|
27815
|
+
lines.push(
|
|
27816
|
+
"",
|
|
27817
|
+
"## Agent slot limit mismatch",
|
|
27818
|
+
"",
|
|
27819
|
+
`- \`stages.json\` agentSlots: ${report.agentSlotMismatch.stages.min}\u2013${report.agentSlotMismatch.stages.max}`,
|
|
27820
|
+
`- \`harness.env\` HARNESS_AGENT_SLOT_*: ${report.agentSlotMismatch.env.min}\u2013${report.agentSlotMismatch.env.max}`,
|
|
27821
|
+
"",
|
|
27822
|
+
"Canonical source is `.har/stages.json`. Run `har env maintain` to sync legacy exports in `harness.env`, or edit `agentSlots` there.",
|
|
27823
|
+
""
|
|
27824
|
+
);
|
|
27825
|
+
}
|
|
27735
27826
|
lines.push("", "## Stale files (review)", "");
|
|
27736
27827
|
if (report.stale.length === 0) {
|
|
27737
27828
|
lines.push("None detected.");
|
|
@@ -27754,28 +27845,28 @@ function buildReadme(report) {
|
|
|
27754
27845
|
}
|
|
27755
27846
|
function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
27756
27847
|
const harnessDir = getHarnessDir(repoPath);
|
|
27757
|
-
const bundleDir =
|
|
27758
|
-
if (
|
|
27759
|
-
|
|
27848
|
+
const bundleDir = path17.join(harnessDir, MAINTAIN_DIR);
|
|
27849
|
+
if (fs17.existsSync(bundleDir)) {
|
|
27850
|
+
fs17.rmSync(bundleDir, { recursive: true, force: true });
|
|
27760
27851
|
}
|
|
27761
27852
|
const dirs = [
|
|
27762
27853
|
bundleDir,
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27854
|
+
path17.join(bundleDir, "templates"),
|
|
27855
|
+
path17.join(bundleDir, "installed"),
|
|
27856
|
+
path17.join(bundleDir, "diffs"),
|
|
27857
|
+
path17.join(bundleDir, "stale")
|
|
27767
27858
|
];
|
|
27768
27859
|
for (const dir of dirs) {
|
|
27769
|
-
|
|
27860
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
27770
27861
|
}
|
|
27771
27862
|
const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
|
|
27772
27863
|
for (const file of affectedFiles) {
|
|
27773
27864
|
const templateContent = readBundledTemplateContent(repoPath, profile, file);
|
|
27774
|
-
writeFileSafe(
|
|
27775
|
-
const harnessPath =
|
|
27776
|
-
if (
|
|
27777
|
-
const installedContent =
|
|
27778
|
-
writeFileSafe(
|
|
27865
|
+
writeFileSafe(path17.join(bundleDir, "templates", file), templateContent);
|
|
27866
|
+
const harnessPath = path17.join(harnessDir, file);
|
|
27867
|
+
if (fs17.existsSync(harnessPath)) {
|
|
27868
|
+
const installedContent = fs17.readFileSync(harnessPath, "utf8");
|
|
27869
|
+
writeFileSafe(path17.join(bundleDir, "installed", file), installedContent);
|
|
27779
27870
|
const diff = createUnifiedDiff(
|
|
27780
27871
|
installedContent,
|
|
27781
27872
|
templateContent,
|
|
@@ -27783,14 +27874,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
27783
27874
|
`templates/${file}`
|
|
27784
27875
|
);
|
|
27785
27876
|
if (diff) {
|
|
27786
|
-
writeFileSafe(
|
|
27877
|
+
writeFileSafe(path17.join(bundleDir, "diffs", `${file}.diff`), diff);
|
|
27787
27878
|
}
|
|
27788
27879
|
}
|
|
27789
27880
|
}
|
|
27790
|
-
writeFileSafe(
|
|
27791
|
-
writeFileSafe(
|
|
27881
|
+
writeFileSafe(path17.join(bundleDir, "README.md"), buildReadme(report));
|
|
27882
|
+
writeFileSafe(path17.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
|
|
27792
27883
|
writeFileSafe(
|
|
27793
|
-
|
|
27884
|
+
path17.join(bundleDir, "validation.json"),
|
|
27794
27885
|
JSON.stringify(report.validation, null, 2) + "\n"
|
|
27795
27886
|
);
|
|
27796
27887
|
if (report.stale.length > 0) {
|
|
@@ -27802,7 +27893,7 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
27802
27893
|
...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
|
|
27803
27894
|
""
|
|
27804
27895
|
];
|
|
27805
|
-
writeFileSafe(
|
|
27896
|
+
writeFileSafe(path17.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
|
|
27806
27897
|
}
|
|
27807
27898
|
return bundleDir;
|
|
27808
27899
|
}
|
|
@@ -27818,6 +27909,7 @@ function buildMaintainBundle(repoPath, validation2, drift) {
|
|
|
27818
27909
|
actions: buildActions(repoPath, profile, drift),
|
|
27819
27910
|
stale: buildStale(drift),
|
|
27820
27911
|
missingPortVars: drift.missingPortVars,
|
|
27912
|
+
agentSlotMismatch: drift.agentSlotMismatch,
|
|
27821
27913
|
validation: {
|
|
27822
27914
|
pass: validation2.pass,
|
|
27823
27915
|
errors,
|
|
@@ -27828,9 +27920,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
|
|
|
27828
27920
|
return { bundleDir, report };
|
|
27829
27921
|
}
|
|
27830
27922
|
function removeMaintainBundle(repoPath) {
|
|
27831
|
-
const bundleDir =
|
|
27832
|
-
if (
|
|
27833
|
-
|
|
27923
|
+
const bundleDir = path17.join(getHarnessDir(repoPath), MAINTAIN_DIR);
|
|
27924
|
+
if (fs17.existsSync(bundleDir)) {
|
|
27925
|
+
fs17.rmSync(bundleDir, { recursive: true, force: true });
|
|
27834
27926
|
}
|
|
27835
27927
|
}
|
|
27836
27928
|
function formatMaintainBundlePromptSection(report) {
|
|
@@ -27871,20 +27963,31 @@ function formatMaintainBundlePromptSection(report) {
|
|
|
27871
27963
|
}
|
|
27872
27964
|
lines.push("");
|
|
27873
27965
|
}
|
|
27874
|
-
if (report.
|
|
27966
|
+
if (report.agentSlotMismatch) {
|
|
27967
|
+
lines.push(
|
|
27968
|
+
"### Agent slot limit mismatch",
|
|
27969
|
+
"",
|
|
27970
|
+
`- \`stages.json\` agentSlots: ${report.agentSlotMismatch.stages.min}\u2013${report.agentSlotMismatch.stages.max}`,
|
|
27971
|
+
`- \`harness.env\` HARNESS_AGENT_SLOT_*: ${report.agentSlotMismatch.env.min}\u2013${report.agentSlotMismatch.env.max}`,
|
|
27972
|
+
"",
|
|
27973
|
+
"Canonical source is `.har/stages.json`. `har env maintain --finalize` syncs legacy exports in `harness.env`.",
|
|
27974
|
+
""
|
|
27975
|
+
);
|
|
27976
|
+
}
|
|
27977
|
+
if (report.actions.length === 0 && report.stale.length === 0 && report.missingPortVars.length === 0 && !report.agentSlotMismatch && report.validation.errors.length === 0) {
|
|
27875
27978
|
lines.push("No template drift detected. Review repo stack changes manually if needed.", "");
|
|
27876
27979
|
}
|
|
27877
27980
|
return lines.join("\n");
|
|
27878
27981
|
}
|
|
27879
27982
|
|
|
27880
27983
|
// src/harness/stage-templates.ts
|
|
27881
|
-
var
|
|
27882
|
-
var
|
|
27984
|
+
var fs19 = __toESM(require("fs"));
|
|
27985
|
+
var path18 = __toESM(require("path"));
|
|
27883
27986
|
|
|
27884
27987
|
// src/harness/parser.ts
|
|
27885
|
-
var
|
|
27988
|
+
var fs18 = __toESM(require("fs"));
|
|
27886
27989
|
function harnessExists(repoPath) {
|
|
27887
|
-
return
|
|
27990
|
+
return fs18.existsSync(`${repoPath}/.har/setup-infra.sh`);
|
|
27888
27991
|
}
|
|
27889
27992
|
|
|
27890
27993
|
// src/harness/stage-templates.ts
|
|
@@ -27907,16 +28010,16 @@ var StageTemplateManifestSchema = external_exports.object({
|
|
|
27907
28010
|
docsPath: external_exports.string().min(1)
|
|
27908
28011
|
});
|
|
27909
28012
|
function resolveTemplateDir(templateId) {
|
|
27910
|
-
const dir =
|
|
27911
|
-
if (!
|
|
28013
|
+
const dir = path18.join(resolveTemplatesDir(), "stage-templates", templateId);
|
|
28014
|
+
if (!fs19.existsSync(dir)) {
|
|
27912
28015
|
throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
|
|
27913
28016
|
}
|
|
27914
28017
|
return dir;
|
|
27915
28018
|
}
|
|
27916
28019
|
function readTemplateManifest(templateId) {
|
|
27917
|
-
const manifestPath =
|
|
28020
|
+
const manifestPath = path18.join(resolveTemplateDir(templateId), "template.manifest.json");
|
|
27918
28021
|
const parsed = StageTemplateManifestSchema.safeParse(
|
|
27919
|
-
JSON.parse(
|
|
28022
|
+
JSON.parse(fs19.readFileSync(manifestPath, "utf8"))
|
|
27920
28023
|
);
|
|
27921
28024
|
if (!parsed.success) {
|
|
27922
28025
|
throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
|
|
@@ -27927,40 +28030,40 @@ function readTemplateManifest(templateId) {
|
|
|
27927
28030
|
return parsed.data;
|
|
27928
28031
|
}
|
|
27929
28032
|
function ensureParentDir(filePath) {
|
|
27930
|
-
const parent =
|
|
27931
|
-
if (!
|
|
27932
|
-
|
|
28033
|
+
const parent = path18.dirname(filePath);
|
|
28034
|
+
if (!fs19.existsSync(parent)) {
|
|
28035
|
+
fs19.mkdirSync(parent, { recursive: true });
|
|
27933
28036
|
}
|
|
27934
28037
|
}
|
|
27935
28038
|
function copyTemplateFile(templateDir, file, repoPath, force) {
|
|
27936
|
-
const srcPath =
|
|
27937
|
-
const destPath =
|
|
27938
|
-
if (!
|
|
28039
|
+
const srcPath = path18.join(templateDir, file.src);
|
|
28040
|
+
const destPath = path18.join(repoPath, file.dest);
|
|
28041
|
+
if (!fs19.existsSync(srcPath)) {
|
|
27939
28042
|
throw new Error(`Template file missing: ${file.src}`);
|
|
27940
28043
|
}
|
|
27941
|
-
if (
|
|
28044
|
+
if (fs19.existsSync(destPath) && !force) {
|
|
27942
28045
|
throw new Error(
|
|
27943
28046
|
`File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
|
|
27944
28047
|
);
|
|
27945
28048
|
}
|
|
27946
28049
|
ensureParentDir(destPath);
|
|
27947
|
-
|
|
28050
|
+
fs19.copyFileSync(srcPath, destPath);
|
|
27948
28051
|
if (file.executable) {
|
|
27949
|
-
|
|
28052
|
+
fs19.chmodSync(destPath, 493);
|
|
27950
28053
|
}
|
|
27951
28054
|
return { written: true, path: file.dest };
|
|
27952
28055
|
}
|
|
27953
28056
|
function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
27954
|
-
const packagePath =
|
|
27955
|
-
if (!
|
|
28057
|
+
const packagePath = path18.join(repoPath, "package.json");
|
|
28058
|
+
if (!fs19.existsSync(packagePath)) {
|
|
27956
28059
|
throw new Error("No package.json in repo root. Add one before applying this stage template.");
|
|
27957
28060
|
}
|
|
27958
|
-
const fragmentPath =
|
|
27959
|
-
if (!
|
|
28061
|
+
const fragmentPath = path18.join(templateDir, fragmentRelPath);
|
|
28062
|
+
if (!fs19.existsSync(fragmentPath)) {
|
|
27960
28063
|
throw new Error(`Package fragment missing: ${fragmentRelPath}`);
|
|
27961
28064
|
}
|
|
27962
|
-
const pkg = JSON.parse(
|
|
27963
|
-
const fragment = JSON.parse(
|
|
28065
|
+
const pkg = JSON.parse(fs19.readFileSync(packagePath, "utf8"));
|
|
28066
|
+
const fragment = JSON.parse(fs19.readFileSync(fragmentPath, "utf8"));
|
|
27964
28067
|
for (const section of ["scripts", "devDependencies"]) {
|
|
27965
28068
|
const existing = pkg[section] ?? {};
|
|
27966
28069
|
const incoming = fragment[section] ?? {};
|
|
@@ -27973,7 +28076,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
|
27973
28076
|
}
|
|
27974
28077
|
pkg[section] = existing;
|
|
27975
28078
|
}
|
|
27976
|
-
|
|
28079
|
+
fs19.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
27977
28080
|
}
|
|
27978
28081
|
function patchStageRegistry(repoPath, manifest, force) {
|
|
27979
28082
|
const registry2 = readStageRegistry(repoPath);
|
|
@@ -28013,8 +28116,8 @@ function assertHarnessPresent(repoPath) {
|
|
|
28013
28116
|
}
|
|
28014
28117
|
function assertStageNotPresent(repoPath, stageId, force) {
|
|
28015
28118
|
if (force) return;
|
|
28016
|
-
const scriptPath =
|
|
28017
|
-
if (
|
|
28119
|
+
const scriptPath = path18.join(repoPath, ".har", "stages", `${stageId}.sh`);
|
|
28120
|
+
if (fs19.existsSync(scriptPath)) {
|
|
28018
28121
|
throw new Error(
|
|
28019
28122
|
`Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
|
|
28020
28123
|
);
|
|
@@ -28027,7 +28130,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
|
|
|
28027
28130
|
}
|
|
28028
28131
|
}
|
|
28029
28132
|
function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
28030
|
-
const resolved =
|
|
28133
|
+
const resolved = path18.resolve(repoPath);
|
|
28031
28134
|
const force = options.force ?? false;
|
|
28032
28135
|
const warnings = [];
|
|
28033
28136
|
const filesWritten = [];
|
|
@@ -28046,7 +28149,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28046
28149
|
if (file.skipFlag === "skipCi" && options.skipCi) {
|
|
28047
28150
|
continue;
|
|
28048
28151
|
}
|
|
28049
|
-
if (
|
|
28152
|
+
if (fs19.existsSync(path18.join(resolved, file.dest)) && !force) {
|
|
28050
28153
|
warnings.push(`Skipped optional file (exists): ${file.dest}`);
|
|
28051
28154
|
continue;
|
|
28052
28155
|
}
|
|
@@ -28081,9 +28184,9 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28081
28184
|
};
|
|
28082
28185
|
}
|
|
28083
28186
|
function listStageTemplateIds() {
|
|
28084
|
-
const root =
|
|
28085
|
-
if (!
|
|
28086
|
-
return
|
|
28187
|
+
const root = path18.join(resolveTemplatesDir(), "stage-templates");
|
|
28188
|
+
if (!fs19.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
|
|
28189
|
+
return fs19.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
|
|
28087
28190
|
(name) => STAGE_TEMPLATE_IDS.includes(name)
|
|
28088
28191
|
);
|
|
28089
28192
|
}
|
|
@@ -28110,11 +28213,11 @@ function validateAgentId(id, repoPath) {
|
|
|
28110
28213
|
// src/core/harness.ts
|
|
28111
28214
|
function listHarnessScripts(repoPath) {
|
|
28112
28215
|
const harnessDir = getHarnessDir(repoPath);
|
|
28113
|
-
if (!
|
|
28114
|
-
return
|
|
28216
|
+
if (!fs20.existsSync(harnessDir)) return [];
|
|
28217
|
+
return fs20.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
|
|
28115
28218
|
}
|
|
28116
28219
|
function describeProject(repoPath) {
|
|
28117
|
-
const resolved =
|
|
28220
|
+
const resolved = path19.resolve(repoPath);
|
|
28118
28221
|
const manifest = readManifest(resolved);
|
|
28119
28222
|
const present = harnessExists(resolved);
|
|
28120
28223
|
return {
|
|
@@ -28134,14 +28237,15 @@ function describeProject(repoPath) {
|
|
|
28134
28237
|
};
|
|
28135
28238
|
}
|
|
28136
28239
|
async function initHarness(options) {
|
|
28137
|
-
const repoPath =
|
|
28138
|
-
if (!
|
|
28240
|
+
const repoPath = path19.resolve(options.repoPath);
|
|
28241
|
+
if (!fs20.existsSync(repoPath)) {
|
|
28139
28242
|
throw new Error(`Path not found: ${repoPath}`);
|
|
28140
28243
|
}
|
|
28141
28244
|
const scaffold = scaffoldHarnessBoilerplate(repoPath, {
|
|
28142
28245
|
force: options.force,
|
|
28143
28246
|
profile: options.profile
|
|
28144
28247
|
});
|
|
28248
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
28145
28249
|
let adaptationSummary;
|
|
28146
28250
|
if (options.auto) {
|
|
28147
28251
|
const apiKey = requireApiKey();
|
|
@@ -28166,9 +28270,9 @@ async function initHarness(options) {
|
|
|
28166
28270
|
};
|
|
28167
28271
|
}
|
|
28168
28272
|
async function maintainHarness(options) {
|
|
28169
|
-
const repoPath =
|
|
28273
|
+
const repoPath = path19.resolve(options.repoPath);
|
|
28170
28274
|
const harnessDir = getHarnessDir(repoPath);
|
|
28171
|
-
if (!
|
|
28275
|
+
if (!fs20.existsSync(harnessDir)) {
|
|
28172
28276
|
throw new Error('No .har/ found. Run "har env init" first.');
|
|
28173
28277
|
}
|
|
28174
28278
|
if (options.auto) {
|
|
@@ -28193,6 +28297,7 @@ async function maintainHarness(options) {
|
|
|
28193
28297
|
if (!validation2.pass) {
|
|
28194
28298
|
throw new Error("Cannot finalize: harness validation has errors. Fix them first.");
|
|
28195
28299
|
}
|
|
28300
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
28196
28301
|
const existing = readManifest(repoPath);
|
|
28197
28302
|
finalizeHarness(
|
|
28198
28303
|
repoPath,
|
|
@@ -28219,11 +28324,11 @@ function addStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28219
28324
|
}
|
|
28220
28325
|
|
|
28221
28326
|
// src/harness/custom-stage.ts
|
|
28222
|
-
var
|
|
28223
|
-
var
|
|
28327
|
+
var fs21 = __toESM(require("fs"));
|
|
28328
|
+
var path20 = __toESM(require("path"));
|
|
28224
28329
|
var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
28225
28330
|
function addCustomStage(repoPath, options) {
|
|
28226
|
-
const resolved =
|
|
28331
|
+
const resolved = path20.resolve(repoPath);
|
|
28227
28332
|
if (!harnessExists(resolved)) {
|
|
28228
28333
|
throw new Error('No .har/ harness found. Run "har env init" first.');
|
|
28229
28334
|
}
|
|
@@ -28260,20 +28365,20 @@ function addCustomStage(repoPath, options) {
|
|
|
28260
28365
|
if (options.command) {
|
|
28261
28366
|
stage.command = options.command;
|
|
28262
28367
|
} else {
|
|
28263
|
-
const scriptRel =
|
|
28264
|
-
const scriptAbs =
|
|
28265
|
-
if (
|
|
28368
|
+
const scriptRel = path20.join("stages", `${id}.sh`);
|
|
28369
|
+
const scriptAbs = path20.join(resolved, ".har", scriptRel);
|
|
28370
|
+
if (fs21.existsSync(scriptAbs) && !options.force) {
|
|
28266
28371
|
throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
|
|
28267
28372
|
}
|
|
28268
|
-
const skeletonPath =
|
|
28373
|
+
const skeletonPath = path20.join(
|
|
28269
28374
|
resolveTemplatesDir(),
|
|
28270
28375
|
"stage-templates",
|
|
28271
28376
|
"custom-stage-skeleton.sh"
|
|
28272
28377
|
);
|
|
28273
|
-
const skeleton =
|
|
28274
|
-
|
|
28275
|
-
|
|
28276
|
-
|
|
28378
|
+
const skeleton = fs21.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
|
|
28379
|
+
fs21.mkdirSync(path20.dirname(scriptAbs), { recursive: true });
|
|
28380
|
+
fs21.writeFileSync(scriptAbs, skeleton);
|
|
28381
|
+
fs21.chmodSync(scriptAbs, 493);
|
|
28277
28382
|
filesWritten.push(`.har/${scriptRel}`);
|
|
28278
28383
|
stage.script = scriptRel;
|
|
28279
28384
|
stage.artifacts = [
|
|
@@ -28321,8 +28426,8 @@ function addCustomStage(repoPath, options) {
|
|
|
28321
28426
|
}
|
|
28322
28427
|
|
|
28323
28428
|
// src/harness/adaptation-prompt.ts
|
|
28324
|
-
var
|
|
28325
|
-
var
|
|
28429
|
+
var fs22 = __toESM(require("fs"));
|
|
28430
|
+
var path21 = __toESM(require("path"));
|
|
28326
28431
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
28327
28432
|
var PROFILE_HINTS = {
|
|
28328
28433
|
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.",
|
|
@@ -28334,7 +28439,7 @@ function loadTemplate(name) {
|
|
|
28334
28439
|
if (!filePath) {
|
|
28335
28440
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
28336
28441
|
}
|
|
28337
|
-
return
|
|
28442
|
+
return fs22.readFileSync(filePath, "utf8");
|
|
28338
28443
|
}
|
|
28339
28444
|
function applyProfilePlaceholders(content, profile) {
|
|
28340
28445
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -28356,7 +28461,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
|
28356
28461
|
}
|
|
28357
28462
|
function writeAdaptationPrompt(repoPath, content) {
|
|
28358
28463
|
const harnessDir = getHarnessDir(repoPath);
|
|
28359
|
-
const filePath =
|
|
28464
|
+
const filePath = path21.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
28360
28465
|
writeFileSafe(filePath, content);
|
|
28361
28466
|
return filePath;
|
|
28362
28467
|
}
|
|
@@ -28375,25 +28480,25 @@ function printAdaptationPrompt(content) {
|
|
|
28375
28480
|
}
|
|
28376
28481
|
|
|
28377
28482
|
// src/harness/cursor-rule.ts
|
|
28378
|
-
var
|
|
28379
|
-
var
|
|
28483
|
+
var fs23 = __toESM(require("fs"));
|
|
28484
|
+
var path22 = __toESM(require("path"));
|
|
28380
28485
|
var readline4 = __toESM(require("readline"));
|
|
28381
28486
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
28382
28487
|
function getCursorRulePath(repoPath) {
|
|
28383
|
-
return
|
|
28488
|
+
return path22.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
28384
28489
|
}
|
|
28385
28490
|
function isCursorWorkspace(repoPath) {
|
|
28386
|
-
return
|
|
28491
|
+
return fs23.existsSync(path22.join(repoPath, ".cursor"));
|
|
28387
28492
|
}
|
|
28388
28493
|
function cursorRuleExists(repoPath) {
|
|
28389
|
-
return
|
|
28494
|
+
return fs23.existsSync(getCursorRulePath(repoPath));
|
|
28390
28495
|
}
|
|
28391
28496
|
function scaffoldCursorRule(repoPath) {
|
|
28392
28497
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
28393
28498
|
if (!templatePath) {
|
|
28394
28499
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
28395
28500
|
}
|
|
28396
|
-
const content =
|
|
28501
|
+
const content = fs23.readFileSync(templatePath, "utf8");
|
|
28397
28502
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
28398
28503
|
}
|
|
28399
28504
|
async function handleCursorRule(options) {
|
|
@@ -28519,26 +28624,26 @@ function buildStageResult(input) {
|
|
|
28519
28624
|
}
|
|
28520
28625
|
|
|
28521
28626
|
// src/core/local-executor.ts
|
|
28522
|
-
var
|
|
28523
|
-
var
|
|
28627
|
+
var fs25 = __toESM(require("fs"));
|
|
28628
|
+
var path24 = __toESM(require("path"));
|
|
28524
28629
|
|
|
28525
28630
|
// src/core/slot-registry.ts
|
|
28526
|
-
var
|
|
28527
|
-
var
|
|
28631
|
+
var fs24 = __toESM(require("fs"));
|
|
28632
|
+
var path23 = __toESM(require("path"));
|
|
28528
28633
|
function getSlotRegistryDir(repoPath) {
|
|
28529
|
-
return
|
|
28634
|
+
return path23.join(getHarnessDir(repoPath), "slots");
|
|
28530
28635
|
}
|
|
28531
28636
|
function getSlotRegistryPath(repoPath, agentId) {
|
|
28532
|
-
return
|
|
28637
|
+
return path23.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
28533
28638
|
}
|
|
28534
28639
|
function isSlotResumable(session) {
|
|
28535
28640
|
return session?.status === "failed" || session?.status === "starting";
|
|
28536
28641
|
}
|
|
28537
28642
|
function readSlotRegistry(repoPath, agentId) {
|
|
28538
28643
|
const file = getSlotRegistryPath(repoPath, agentId);
|
|
28539
|
-
if (!
|
|
28644
|
+
if (!fs24.existsSync(file)) return void 0;
|
|
28540
28645
|
try {
|
|
28541
|
-
const raw = JSON.parse(
|
|
28646
|
+
const raw = JSON.parse(fs24.readFileSync(file, "utf8"));
|
|
28542
28647
|
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
28543
28648
|
return result.success ? result.data : void 0;
|
|
28544
28649
|
} catch {
|
|
@@ -28548,7 +28653,7 @@ function readSlotRegistry(repoPath, agentId) {
|
|
|
28548
28653
|
|
|
28549
28654
|
// src/core/local-executor.ts
|
|
28550
28655
|
function resolveRepoPath(repoPath) {
|
|
28551
|
-
return
|
|
28656
|
+
return path24.resolve(repoPath);
|
|
28552
28657
|
}
|
|
28553
28658
|
function buildPreviewUrlsFromPorts(ports, env3) {
|
|
28554
28659
|
const urls = {};
|
|
@@ -28600,14 +28705,14 @@ function substituteAgentId(value, agentId) {
|
|
|
28600
28705
|
function resolveStageScriptPath(repoPath, stage) {
|
|
28601
28706
|
const harnessDir = getHarnessDir(repoPath);
|
|
28602
28707
|
if (stage.script) {
|
|
28603
|
-
return
|
|
28708
|
+
return path24.join(harnessDir, stage.script);
|
|
28604
28709
|
}
|
|
28605
28710
|
if (stage.command) {
|
|
28606
28711
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
28607
|
-
return
|
|
28712
|
+
return path24.join(harnessDir, scriptName);
|
|
28608
28713
|
}
|
|
28609
|
-
const stageScript =
|
|
28610
|
-
if (
|
|
28714
|
+
const stageScript = path24.join(harnessDir, "stages", `${stage.id}.sh`);
|
|
28715
|
+
if (fs25.existsSync(stageScript)) {
|
|
28611
28716
|
return stageScript;
|
|
28612
28717
|
}
|
|
28613
28718
|
throw new Error(
|
|
@@ -28622,7 +28727,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
28622
28727
|
...harnessEnv,
|
|
28623
28728
|
...stage.env ?? {}
|
|
28624
28729
|
};
|
|
28625
|
-
const cwd = stage.cwd ?
|
|
28730
|
+
const cwd = stage.cwd ? path24.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
|
|
28626
28731
|
const extraArgs = options.args ?? [];
|
|
28627
28732
|
const launchFlagArgs = [];
|
|
28628
28733
|
if (stage.kind === "launch" && options.launchFlags) {
|
|
@@ -28699,19 +28804,19 @@ var LocalScriptExecutor = class {
|
|
|
28699
28804
|
const repoPath = resolveRepoPath(ctx.repoPath);
|
|
28700
28805
|
const harnessDir = getHarnessDir(repoPath);
|
|
28701
28806
|
const artifactsDirName = getArtifactsDir(repoPath);
|
|
28702
|
-
const artifactsDir =
|
|
28703
|
-
if (!
|
|
28807
|
+
const artifactsDir = path24.join(harnessDir, artifactsDirName);
|
|
28808
|
+
if (!fs25.existsSync(artifactsDir)) return [];
|
|
28704
28809
|
const entries = [];
|
|
28705
28810
|
const walk = (dir, prefix) => {
|
|
28706
|
-
for (const entry of
|
|
28707
|
-
const full =
|
|
28708
|
-
const relative2 =
|
|
28811
|
+
for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
|
|
28812
|
+
const full = path24.join(dir, entry.name);
|
|
28813
|
+
const relative2 = path24.join(prefix, entry.name);
|
|
28709
28814
|
if (entry.isDirectory()) {
|
|
28710
28815
|
walk(full, relative2);
|
|
28711
28816
|
continue;
|
|
28712
28817
|
}
|
|
28713
28818
|
if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
|
|
28714
|
-
const stat =
|
|
28819
|
+
const stat = fs25.statSync(full);
|
|
28715
28820
|
entries.push({
|
|
28716
28821
|
path: full,
|
|
28717
28822
|
relativePath: relative2,
|
|
@@ -28727,7 +28832,7 @@ var LocalScriptExecutor = class {
|
|
|
28727
28832
|
var localScriptExecutor = new LocalScriptExecutor();
|
|
28728
28833
|
|
|
28729
28834
|
// src/core/control-sync.ts
|
|
28730
|
-
var
|
|
28835
|
+
var path38 = __toESM(require("path"));
|
|
28731
28836
|
|
|
28732
28837
|
// src/core/control-config.ts
|
|
28733
28838
|
var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
|
|
@@ -28739,32 +28844,32 @@ function isControlEnabled() {
|
|
|
28739
28844
|
}
|
|
28740
28845
|
|
|
28741
28846
|
// src/core/control-registry.ts
|
|
28742
|
-
var
|
|
28847
|
+
var fs26 = __toESM(require("fs"));
|
|
28743
28848
|
var os4 = __toESM(require("os"));
|
|
28744
|
-
var
|
|
28849
|
+
var path25 = __toESM(require("path"));
|
|
28745
28850
|
function getRegistryPath() {
|
|
28746
28851
|
if (process.env.HAR_CONTROL_REGISTRY_PATH) {
|
|
28747
|
-
return
|
|
28852
|
+
return path25.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
|
|
28748
28853
|
}
|
|
28749
|
-
return
|
|
28854
|
+
return path25.join(os4.homedir(), ".har", "repos.json");
|
|
28750
28855
|
}
|
|
28751
28856
|
function readRegistry() {
|
|
28752
28857
|
const registryPath = getRegistryPath();
|
|
28753
28858
|
try {
|
|
28754
|
-
if (!
|
|
28755
|
-
return JSON.parse(
|
|
28859
|
+
if (!fs26.existsSync(registryPath)) return { repos: [] };
|
|
28860
|
+
return JSON.parse(fs26.readFileSync(registryPath, "utf8"));
|
|
28756
28861
|
} catch {
|
|
28757
28862
|
return { repos: [] };
|
|
28758
28863
|
}
|
|
28759
28864
|
}
|
|
28760
28865
|
function writeRegistry(registry2) {
|
|
28761
28866
|
const registryPath = getRegistryPath();
|
|
28762
|
-
|
|
28763
|
-
|
|
28867
|
+
fs26.mkdirSync(path25.dirname(registryPath), { recursive: true });
|
|
28868
|
+
fs26.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
|
|
28764
28869
|
}
|
|
28765
28870
|
function recordRepoForControlSync(repoPath) {
|
|
28766
28871
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
28767
|
-
const resolved =
|
|
28872
|
+
const resolved = path25.resolve(repoPath);
|
|
28768
28873
|
if (!readManifest(resolved)) return;
|
|
28769
28874
|
const registry2 = readRegistry();
|
|
28770
28875
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -28773,7 +28878,7 @@ function recordRepoForControlSync(repoPath) {
|
|
|
28773
28878
|
}
|
|
28774
28879
|
function listRegisteredRepos() {
|
|
28775
28880
|
const registry2 = readRegistry();
|
|
28776
|
-
const kept = registry2.repos.filter((repoPath) =>
|
|
28881
|
+
const kept = registry2.repos.filter((repoPath) => fs26.existsSync(repoPath) && readManifest(repoPath));
|
|
28777
28882
|
if (kept.length !== registry2.repos.length) {
|
|
28778
28883
|
writeRegistry({ repos: kept });
|
|
28779
28884
|
}
|
|
@@ -28781,17 +28886,17 @@ function listRegisteredRepos() {
|
|
|
28781
28886
|
}
|
|
28782
28887
|
|
|
28783
28888
|
// src/core/slot-status.ts
|
|
28784
|
-
var
|
|
28889
|
+
var fs31 = __toESM(require("fs"));
|
|
28785
28890
|
var os7 = __toESM(require("os"));
|
|
28786
|
-
var
|
|
28891
|
+
var path31 = __toESM(require("path"));
|
|
28787
28892
|
var import_child_process7 = require("child_process");
|
|
28788
28893
|
|
|
28789
28894
|
// src/core/runs.ts
|
|
28790
28895
|
var crypto2 = __toESM(require("crypto"));
|
|
28791
28896
|
var import_child_process2 = require("child_process");
|
|
28792
|
-
var
|
|
28897
|
+
var fs27 = __toESM(require("fs"));
|
|
28793
28898
|
var os5 = __toESM(require("os"));
|
|
28794
|
-
var
|
|
28899
|
+
var path26 = __toESM(require("path"));
|
|
28795
28900
|
var RUNS_DIR = "runs";
|
|
28796
28901
|
function gitCommonDir(cwd) {
|
|
28797
28902
|
try {
|
|
@@ -28800,7 +28905,7 @@ function gitCommonDir(cwd) {
|
|
|
28800
28905
|
encoding: "utf8",
|
|
28801
28906
|
stdio: ["pipe", "pipe", "ignore"]
|
|
28802
28907
|
}).trim();
|
|
28803
|
-
return out ?
|
|
28908
|
+
return out ? path26.resolve(cwd, out) : void 0;
|
|
28804
28909
|
} catch {
|
|
28805
28910
|
return void 0;
|
|
28806
28911
|
}
|
|
@@ -28822,7 +28927,7 @@ function gitPrefix(cwd) {
|
|
|
28822
28927
|
}
|
|
28823
28928
|
}
|
|
28824
28929
|
function getRunsDir(harnessRoot) {
|
|
28825
|
-
return
|
|
28930
|
+
return path26.join(getHarnessDir(harnessRoot), RUNS_DIR);
|
|
28826
28931
|
}
|
|
28827
28932
|
function formatLocalDate(d2) {
|
|
28828
28933
|
const y2 = d2.getFullYear();
|
|
@@ -28842,24 +28947,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
|
|
|
28842
28947
|
const timePart = formatLocalTime(started);
|
|
28843
28948
|
const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
|
|
28844
28949
|
let filename = `${timePart}_${stageId}${agentPart}.json`;
|
|
28845
|
-
const fullPath =
|
|
28846
|
-
if (
|
|
28950
|
+
const fullPath = path26.join(runsDir, dateFolder, filename);
|
|
28951
|
+
if (fs27.existsSync(fullPath)) {
|
|
28847
28952
|
filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
|
|
28848
28953
|
}
|
|
28849
|
-
return
|
|
28954
|
+
return path26.join(dateFolder, filename);
|
|
28850
28955
|
}
|
|
28851
28956
|
function resolveRunFilePath(harnessRoot, run2) {
|
|
28852
28957
|
if (run2.relativePath) {
|
|
28853
|
-
return
|
|
28958
|
+
return path26.join(getRunsDir(harnessRoot), run2.relativePath);
|
|
28854
28959
|
}
|
|
28855
|
-
return
|
|
28960
|
+
return path26.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
|
|
28856
28961
|
}
|
|
28857
28962
|
function collectRunFiles(runsDir) {
|
|
28858
|
-
if (!
|
|
28963
|
+
if (!fs27.existsSync(runsDir)) return [];
|
|
28859
28964
|
const files = [];
|
|
28860
28965
|
const walk = (dir) => {
|
|
28861
|
-
for (const entry of
|
|
28862
|
-
const full =
|
|
28966
|
+
for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
|
|
28967
|
+
const full = path26.join(dir, entry.name);
|
|
28863
28968
|
if (entry.isDirectory()) {
|
|
28864
28969
|
walk(full);
|
|
28865
28970
|
} else if (entry.name.endsWith(".json")) {
|
|
@@ -28873,30 +28978,30 @@ function collectRunFiles(runsDir) {
|
|
|
28873
28978
|
function resolveAgentWorkDir(harnessRoot, agentId) {
|
|
28874
28979
|
if (agentId === void 0) return void 0;
|
|
28875
28980
|
const entry = readSlotRegistry(harnessRoot, agentId);
|
|
28876
|
-
if (entry?.workDir &&
|
|
28981
|
+
if (entry?.workDir && fs27.existsSync(entry.workDir)) return entry.workDir;
|
|
28877
28982
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28878
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
28879
|
-
const worktreeDir =
|
|
28983
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path26.basename(harnessRoot);
|
|
28984
|
+
const worktreeDir = path26.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
28880
28985
|
const relPrefix = gitPrefix(harnessRoot);
|
|
28881
28986
|
const sessionEnvFiles = [];
|
|
28882
|
-
const worktreesRoot =
|
|
28883
|
-
if (
|
|
28987
|
+
const worktreesRoot = path26.join(os5.homedir(), "worktrees");
|
|
28988
|
+
if (fs27.existsSync(worktreesRoot)) {
|
|
28884
28989
|
const suffix = `-har-agent-${agentId}-`;
|
|
28885
|
-
for (const entry2 of
|
|
28990
|
+
for (const entry2 of fs27.readdirSync(worktreesRoot, { withFileTypes: true })) {
|
|
28886
28991
|
if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
|
|
28887
|
-
const sessionDir =
|
|
28992
|
+
const sessionDir = path26.join(worktreesRoot, entry2.name);
|
|
28888
28993
|
if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
|
|
28889
|
-
sessionEnvFiles.push(
|
|
28994
|
+
sessionEnvFiles.push(path26.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
|
|
28890
28995
|
}
|
|
28891
28996
|
}
|
|
28892
28997
|
const candidates = [
|
|
28893
|
-
|
|
28894
|
-
|
|
28998
|
+
path26.join(worktreeDir, `.env.agent.${agentId}`),
|
|
28999
|
+
path26.join(harnessRoot, `.env.agent.${agentId}`),
|
|
28895
29000
|
...sessionEnvFiles.sort()
|
|
28896
29001
|
];
|
|
28897
29002
|
for (const envFile of candidates) {
|
|
28898
|
-
if (!
|
|
28899
|
-
const content =
|
|
29003
|
+
if (!fs27.existsSync(envFile)) continue;
|
|
29004
|
+
const content = fs27.readFileSync(envFile, "utf8");
|
|
28900
29005
|
const match = content.match(/^REPO_ROOT=(.+)$/m);
|
|
28901
29006
|
if (match) return match[1].trim();
|
|
28902
29007
|
}
|
|
@@ -28908,11 +29013,11 @@ function createRun(ctx, meta) {
|
|
|
28908
29013
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28909
29014
|
const runId = crypto2.randomUUID();
|
|
28910
29015
|
const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
|
|
28911
|
-
const runFilePath =
|
|
28912
|
-
|
|
29016
|
+
const runFilePath = path26.join(runsDir, relativePath);
|
|
29017
|
+
fs27.mkdirSync(path26.dirname(runFilePath), { recursive: true });
|
|
28913
29018
|
const run2 = RunRecordSchema.parse({
|
|
28914
29019
|
runId,
|
|
28915
|
-
repoPath:
|
|
29020
|
+
repoPath: path26.resolve(ctx.repoPath),
|
|
28916
29021
|
harnessRoot,
|
|
28917
29022
|
stageId: meta.stageId,
|
|
28918
29023
|
kind: meta.kind,
|
|
@@ -28923,7 +29028,7 @@ function createRun(ctx, meta) {
|
|
|
28923
29028
|
relativePath,
|
|
28924
29029
|
trigger: ctx.trigger ?? "cli"
|
|
28925
29030
|
});
|
|
28926
|
-
|
|
29031
|
+
fs27.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
|
|
28927
29032
|
return run2;
|
|
28928
29033
|
}
|
|
28929
29034
|
function finishRun(repoPath, runId, update) {
|
|
@@ -28942,13 +29047,13 @@ function finishRun(repoPath, runId, update) {
|
|
|
28942
29047
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28943
29048
|
});
|
|
28944
29049
|
const runPath = resolveRunFilePath(harnessRoot, finished);
|
|
28945
|
-
|
|
29050
|
+
fs27.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
|
|
28946
29051
|
return finished;
|
|
28947
29052
|
}
|
|
28948
29053
|
function findRunRecord(harnessRoot, runId) {
|
|
28949
29054
|
const runsDir = getRunsDir(harnessRoot);
|
|
28950
29055
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28951
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
29056
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
|
|
28952
29057
|
if (parsed.success && parsed.data.runId === runId) {
|
|
28953
29058
|
return parsed.data;
|
|
28954
29059
|
}
|
|
@@ -28960,10 +29065,10 @@ function getRun(repoPath, runId) {
|
|
|
28960
29065
|
}
|
|
28961
29066
|
function listRuns(repoPath, filter = {}) {
|
|
28962
29067
|
const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
|
|
28963
|
-
if (!
|
|
29068
|
+
if (!fs27.existsSync(runsDir)) return [];
|
|
28964
29069
|
const runs = [];
|
|
28965
29070
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28966
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
29071
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
|
|
28967
29072
|
if (!parsed.success) continue;
|
|
28968
29073
|
if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
|
|
28969
29074
|
runs.push(parsed.data);
|
|
@@ -28976,17 +29081,17 @@ function listRuns(repoPath, filter = {}) {
|
|
|
28976
29081
|
}
|
|
28977
29082
|
|
|
28978
29083
|
// src/core/slot-preflight.ts
|
|
28979
|
-
var
|
|
29084
|
+
var path30 = __toESM(require("path"));
|
|
28980
29085
|
|
|
28981
29086
|
// src/core/control-port.ts
|
|
28982
29087
|
var import_child_process4 = require("child_process");
|
|
28983
|
-
var
|
|
28984
|
-
var
|
|
29088
|
+
var fs29 = __toESM(require("fs"));
|
|
29089
|
+
var path28 = __toESM(require("path"));
|
|
28985
29090
|
|
|
28986
29091
|
// src/core/slot-ports.ts
|
|
28987
29092
|
var import_child_process3 = require("child_process");
|
|
28988
|
-
var
|
|
28989
|
-
var
|
|
29093
|
+
var fs28 = __toESM(require("fs"));
|
|
29094
|
+
var path27 = __toESM(require("path"));
|
|
28990
29095
|
function portStep(env3) {
|
|
28991
29096
|
return Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
28992
29097
|
}
|
|
@@ -29042,7 +29147,7 @@ function allocateAppPorts(repoPath, agentId) {
|
|
|
29042
29147
|
}
|
|
29043
29148
|
function harnessUsesPm2(repoPath) {
|
|
29044
29149
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29045
|
-
return
|
|
29150
|
+
return fs28.existsSync(path27.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
|
|
29046
29151
|
}
|
|
29047
29152
|
|
|
29048
29153
|
// src/core/control-port.ts
|
|
@@ -29097,13 +29202,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
|
|
|
29097
29202
|
return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
|
|
29098
29203
|
}
|
|
29099
29204
|
function inspectControlUpReadiness(repoPath) {
|
|
29100
|
-
const resolved =
|
|
29205
|
+
const resolved = path28.resolve(repoPath);
|
|
29101
29206
|
const port = parseControlHostPort();
|
|
29102
29207
|
const containers = listDockerContainers();
|
|
29103
29208
|
const control = controlContainerOnPort(containers, port);
|
|
29104
29209
|
const warnings = [];
|
|
29105
|
-
const controlHarnessDir =
|
|
29106
|
-
const harnessSlot1Active =
|
|
29210
|
+
const controlHarnessDir = path28.join(resolved, "control", ".har");
|
|
29211
|
+
const harnessSlot1Active = fs29.existsSync(controlHarnessDir) && readSlotRegistry(path28.join(resolved, "control"), 1)?.status === "active";
|
|
29107
29212
|
if (control) {
|
|
29108
29213
|
return {
|
|
29109
29214
|
warnings,
|
|
@@ -29134,9 +29239,9 @@ function inspectControlUpReadiness(repoPath) {
|
|
|
29134
29239
|
}
|
|
29135
29240
|
|
|
29136
29241
|
// src/core/slot-launch-guard-occupied.ts
|
|
29137
|
-
var
|
|
29242
|
+
var fs30 = __toESM(require("fs"));
|
|
29138
29243
|
var os6 = __toESM(require("os"));
|
|
29139
|
-
var
|
|
29244
|
+
var path29 = __toESM(require("path"));
|
|
29140
29245
|
var import_child_process5 = require("child_process");
|
|
29141
29246
|
function runGit(cwd, args) {
|
|
29142
29247
|
try {
|
|
@@ -29155,7 +29260,7 @@ function worktreeDirty(worktreePath) {
|
|
|
29155
29260
|
}
|
|
29156
29261
|
function gitCommonDir2(cwd) {
|
|
29157
29262
|
const out = runGit(cwd, "rev-parse --git-common-dir");
|
|
29158
|
-
return out ?
|
|
29263
|
+
return out ? path29.resolve(cwd, out) : void 0;
|
|
29159
29264
|
}
|
|
29160
29265
|
function sameGitCheckout2(a2, b2) {
|
|
29161
29266
|
const left2 = gitCommonDir2(a2);
|
|
@@ -29164,20 +29269,20 @@ function sameGitCheckout2(a2, b2) {
|
|
|
29164
29269
|
}
|
|
29165
29270
|
function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
29166
29271
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29167
|
-
if (session?.worktreePath &&
|
|
29272
|
+
if (session?.worktreePath && fs30.existsSync(session.worktreePath)) {
|
|
29168
29273
|
return session.worktreePath;
|
|
29169
29274
|
}
|
|
29170
|
-
const legacy =
|
|
29171
|
-
if (
|
|
29172
|
-
const worktreesRoot =
|
|
29173
|
-
if (!
|
|
29275
|
+
const legacy = path29.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
29276
|
+
if (fs30.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
|
|
29277
|
+
const worktreesRoot = path29.join(os6.homedir(), "worktrees");
|
|
29278
|
+
if (!fs30.existsSync(worktreesRoot)) return void 0;
|
|
29174
29279
|
const suffix = `-har-agent-${agentId}-`;
|
|
29175
29280
|
const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
|
|
29176
|
-
for (const name of
|
|
29281
|
+
for (const name of fs30.readdirSync(worktreesRoot)) {
|
|
29177
29282
|
if (!name.includes(suffix)) continue;
|
|
29178
|
-
const candidate =
|
|
29283
|
+
const candidate = path29.join(worktreesRoot, name);
|
|
29179
29284
|
if (!sameGitCheckout2(harnessRoot, candidate)) continue;
|
|
29180
|
-
if (!
|
|
29285
|
+
if (!fs30.existsSync(path29.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
|
|
29181
29286
|
return candidate;
|
|
29182
29287
|
}
|
|
29183
29288
|
return void 0;
|
|
@@ -29185,13 +29290,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
|
29185
29290
|
function collectOccupiedSlot(repoPath, agentId) {
|
|
29186
29291
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29187
29292
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29188
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29293
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
|
|
29189
29294
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29190
29295
|
const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
|
|
29191
29296
|
const workDir = session?.workDir;
|
|
29192
|
-
const envInWorkDir = workDir ?
|
|
29193
|
-
const envInRoot =
|
|
29194
|
-
const envInWorktree = worktreePath ?
|
|
29297
|
+
const envInWorkDir = workDir ? fs30.existsSync(path29.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29298
|
+
const envInRoot = fs30.existsSync(path29.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29299
|
+
const envInWorktree = worktreePath ? fs30.existsSync(path29.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
29195
29300
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29196
29301
|
if (!active) return void 0;
|
|
29197
29302
|
const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
|
|
@@ -29344,7 +29449,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
|
29344
29449
|
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
29345
29450
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29346
29451
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29347
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29452
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
|
|
29348
29453
|
const usesPm2 = harnessUsesPm2(repoPath);
|
|
29349
29454
|
const blockers = [];
|
|
29350
29455
|
const remediations = [];
|
|
@@ -29546,7 +29651,7 @@ function readWorktreeBranch(worktreePath) {
|
|
|
29546
29651
|
}
|
|
29547
29652
|
function gitCommonDir3(cwd) {
|
|
29548
29653
|
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
29549
|
-
return out ?
|
|
29654
|
+
return out ? path31.resolve(cwd, out) : void 0;
|
|
29550
29655
|
}
|
|
29551
29656
|
function sameGitCheckout3(a2, b2) {
|
|
29552
29657
|
const left2 = gitCommonDir3(a2);
|
|
@@ -29558,12 +29663,12 @@ function gitPrefix2(cwd) {
|
|
|
29558
29663
|
return out ?? "";
|
|
29559
29664
|
}
|
|
29560
29665
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
29561
|
-
const worktreesRoot =
|
|
29562
|
-
if (!
|
|
29666
|
+
const worktreesRoot = path31.join(os7.homedir(), "worktrees");
|
|
29667
|
+
if (!fs31.existsSync(worktreesRoot)) return void 0;
|
|
29563
29668
|
const suffix = `-har-agent-${agentId}-`;
|
|
29564
29669
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
29565
|
-
const matches =
|
|
29566
|
-
(candidate) => sameGitCheckout3(harnessRoot, candidate) &&
|
|
29670
|
+
const matches = fs31.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path31.join(worktreesRoot, entry.name)).filter(
|
|
29671
|
+
(candidate) => sameGitCheckout3(harnessRoot, candidate) && fs31.existsSync(path31.join(candidate, relPrefix, `.env.agent.${agentId}`))
|
|
29567
29672
|
);
|
|
29568
29673
|
return matches.sort()[0];
|
|
29569
29674
|
}
|
|
@@ -29638,18 +29743,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
|
|
|
29638
29743
|
}
|
|
29639
29744
|
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
29640
29745
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29641
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29746
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path31.basename(harnessRoot);
|
|
29642
29747
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29643
|
-
const legacyWorktreePath =
|
|
29748
|
+
const legacyWorktreePath = path31.join(
|
|
29644
29749
|
os7.homedir(),
|
|
29645
29750
|
"worktrees",
|
|
29646
29751
|
`${projectName}-agent-${agentId}`
|
|
29647
29752
|
);
|
|
29648
|
-
const worktreePath = session?.worktreePath &&
|
|
29753
|
+
const worktreePath = session?.worktreePath && fs31.existsSync(session.worktreePath) ? session.worktreePath : fs31.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
29649
29754
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
29650
|
-
const envInWorkDir = workDir ?
|
|
29651
|
-
const envInRoot =
|
|
29652
|
-
const envInWorktree = worktreePath ?
|
|
29755
|
+
const envInWorkDir = workDir ? fs31.existsSync(path31.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29756
|
+
const envInRoot = fs31.existsSync(path31.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29757
|
+
const envInWorktree = worktreePath ? fs31.existsSync(path31.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
29653
29758
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29654
29759
|
const latest = latestRunForAgent(runs, agentId);
|
|
29655
29760
|
const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
|
|
@@ -29702,7 +29807,7 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29702
29807
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
29703
29808
|
const pm2Procs = listPm2Processes2();
|
|
29704
29809
|
return {
|
|
29705
|
-
repoPath:
|
|
29810
|
+
repoPath: path31.resolve(repoPath),
|
|
29706
29811
|
harnessRoot,
|
|
29707
29812
|
gitRemote: readGitRemote(harnessRoot),
|
|
29708
29813
|
profile: manifest?.profile,
|
|
@@ -29713,13 +29818,13 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29713
29818
|
|
|
29714
29819
|
// src/core/validations.ts
|
|
29715
29820
|
var crypto3 = __toESM(require("crypto"));
|
|
29716
|
-
var
|
|
29717
|
-
var
|
|
29821
|
+
var fs33 = __toESM(require("fs"));
|
|
29822
|
+
var path33 = __toESM(require("path"));
|
|
29718
29823
|
|
|
29719
29824
|
// src/core/change-batch.ts
|
|
29720
|
-
var
|
|
29825
|
+
var fs32 = __toESM(require("fs"));
|
|
29721
29826
|
var os8 = __toESM(require("os"));
|
|
29722
|
-
var
|
|
29827
|
+
var path32 = __toESM(require("path"));
|
|
29723
29828
|
function git(checkoutDir, args, env3) {
|
|
29724
29829
|
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
29725
29830
|
if (result.code !== 0) {
|
|
@@ -29735,7 +29840,7 @@ function isCheckoutRoot(checkoutDir) {
|
|
|
29735
29840
|
const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
|
|
29736
29841
|
if (!toplevel) return false;
|
|
29737
29842
|
try {
|
|
29738
|
-
return
|
|
29843
|
+
return fs32.realpathSync(toplevel) === fs32.realpathSync(checkoutDir);
|
|
29739
29844
|
} catch {
|
|
29740
29845
|
return false;
|
|
29741
29846
|
}
|
|
@@ -29757,7 +29862,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
|
|
|
29757
29862
|
if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
29758
29863
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
29759
29864
|
const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
29760
|
-
if (gitPath &&
|
|
29865
|
+
if (gitPath && fs32.existsSync(path32.resolve(checkoutDir, gitPath))) return true;
|
|
29761
29866
|
}
|
|
29762
29867
|
return false;
|
|
29763
29868
|
}
|
|
@@ -29780,8 +29885,8 @@ function parseNameStatus(output) {
|
|
|
29780
29885
|
return files;
|
|
29781
29886
|
}
|
|
29782
29887
|
function computeWorktreeSnapshot(checkoutDir) {
|
|
29783
|
-
const tmpDir =
|
|
29784
|
-
const tmpIndex =
|
|
29888
|
+
const tmpDir = fs32.mkdtempSync(path32.join(os8.tmpdir(), "har-idx-"));
|
|
29889
|
+
const tmpIndex = path32.join(tmpDir, "index");
|
|
29785
29890
|
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
29786
29891
|
try {
|
|
29787
29892
|
const headTree = getHeadTree(checkoutDir);
|
|
@@ -29804,35 +29909,35 @@ function computeWorktreeSnapshot(checkoutDir) {
|
|
|
29804
29909
|
changedFiles: parseNameStatus(diffOutput)
|
|
29805
29910
|
};
|
|
29806
29911
|
} finally {
|
|
29807
|
-
|
|
29912
|
+
fs32.rmSync(tmpDir, { recursive: true, force: true });
|
|
29808
29913
|
}
|
|
29809
29914
|
}
|
|
29810
29915
|
|
|
29811
29916
|
// src/core/validations.ts
|
|
29812
29917
|
var VALIDATIONS_DIR = "validations";
|
|
29813
29918
|
function getValidationsDir(checkoutDir) {
|
|
29814
|
-
return
|
|
29919
|
+
return path33.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
29815
29920
|
}
|
|
29816
29921
|
function validationPath(checkoutDir, treeHash) {
|
|
29817
|
-
return
|
|
29922
|
+
return path33.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
29818
29923
|
}
|
|
29819
29924
|
function writeRecord(checkoutDir, record2) {
|
|
29820
29925
|
const dir = getValidationsDir(checkoutDir);
|
|
29821
|
-
|
|
29822
|
-
|
|
29926
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
29927
|
+
fs33.writeFileSync(
|
|
29823
29928
|
validationPath(checkoutDir, record2.treeHash),
|
|
29824
29929
|
`${JSON.stringify(record2, null, 2)}
|
|
29825
29930
|
`
|
|
29826
29931
|
);
|
|
29827
29932
|
}
|
|
29828
29933
|
function ensureValidationsIgnored(checkoutDir) {
|
|
29829
|
-
const harDir =
|
|
29830
|
-
if (!
|
|
29831
|
-
const gitignorePath =
|
|
29832
|
-
const content =
|
|
29934
|
+
const harDir = path33.join(checkoutDir, ".har");
|
|
29935
|
+
if (!fs33.existsSync(harDir)) return;
|
|
29936
|
+
const gitignorePath = path33.join(harDir, ".gitignore");
|
|
29937
|
+
const content = fs33.existsSync(gitignorePath) ? fs33.readFileSync(gitignorePath, "utf8") : "";
|
|
29833
29938
|
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
29834
29939
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
29835
|
-
|
|
29940
|
+
fs33.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
29836
29941
|
`);
|
|
29837
29942
|
}
|
|
29838
29943
|
function recordValidation(input) {
|
|
@@ -29849,8 +29954,8 @@ function recordValidation(input) {
|
|
|
29849
29954
|
treeHash: snapshot.treeHash,
|
|
29850
29955
|
headSha: snapshot.headSha,
|
|
29851
29956
|
branch: snapshot.branch,
|
|
29852
|
-
workDir:
|
|
29853
|
-
harnessRoot:
|
|
29957
|
+
workDir: path33.resolve(input.checkoutDir),
|
|
29958
|
+
harnessRoot: path33.resolve(input.harnessRoot),
|
|
29854
29959
|
agentId: input.agentId,
|
|
29855
29960
|
status: input.status,
|
|
29856
29961
|
full: input.full,
|
|
@@ -29862,16 +29967,16 @@ function recordValidation(input) {
|
|
|
29862
29967
|
committedAt: existing?.committedAt
|
|
29863
29968
|
});
|
|
29864
29969
|
writeRecord(input.checkoutDir, record2);
|
|
29865
|
-
if (
|
|
29970
|
+
if (path33.resolve(input.harnessRoot) !== path33.resolve(input.checkoutDir)) {
|
|
29866
29971
|
writeRecord(input.harnessRoot, record2);
|
|
29867
29972
|
}
|
|
29868
29973
|
return record2;
|
|
29869
29974
|
}
|
|
29870
29975
|
function findValidation(checkoutDir, treeHash) {
|
|
29871
29976
|
const file = validationPath(checkoutDir, treeHash);
|
|
29872
|
-
if (!
|
|
29977
|
+
if (!fs33.existsSync(file)) return void 0;
|
|
29873
29978
|
try {
|
|
29874
|
-
return ValidationRecordSchema.parse(JSON.parse(
|
|
29979
|
+
return ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(file, "utf8")));
|
|
29875
29980
|
} catch {
|
|
29876
29981
|
return void 0;
|
|
29877
29982
|
}
|
|
@@ -29886,19 +29991,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
|
29886
29991
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29887
29992
|
};
|
|
29888
29993
|
writeRecord(checkoutDir, updated);
|
|
29889
|
-
if (record2.harnessRoot &&
|
|
29994
|
+
if (record2.harnessRoot && path33.resolve(record2.harnessRoot) !== path33.resolve(checkoutDir) && fs33.existsSync(path33.join(record2.harnessRoot, ".har"))) {
|
|
29890
29995
|
writeRecord(record2.harnessRoot, updated);
|
|
29891
29996
|
}
|
|
29892
29997
|
return updated;
|
|
29893
29998
|
}
|
|
29894
29999
|
function listValidations(harnessRoot) {
|
|
29895
30000
|
const dir = getValidationsDir(harnessRoot);
|
|
29896
|
-
if (!
|
|
30001
|
+
if (!fs33.existsSync(dir)) return [];
|
|
29897
30002
|
const records = [];
|
|
29898
|
-
for (const entry of
|
|
30003
|
+
for (const entry of fs33.readdirSync(dir)) {
|
|
29899
30004
|
if (!entry.endsWith(".json")) continue;
|
|
29900
30005
|
try {
|
|
29901
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(
|
|
30006
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(path33.join(dir, entry), "utf8"))));
|
|
29902
30007
|
} catch {
|
|
29903
30008
|
}
|
|
29904
30009
|
}
|
|
@@ -29928,9 +30033,9 @@ function createRemoteExecutor(apiUrl, apiKey) {
|
|
|
29928
30033
|
}
|
|
29929
30034
|
|
|
29930
30035
|
// src/core/telemetry-config.ts
|
|
29931
|
-
var
|
|
30036
|
+
var fs34 = __toESM(require("fs"));
|
|
29932
30037
|
var os9 = __toESM(require("os"));
|
|
29933
|
-
var
|
|
30038
|
+
var path34 = __toESM(require("path"));
|
|
29934
30039
|
var DEFAULT_SIGNALS_ON = {
|
|
29935
30040
|
metrics: true,
|
|
29936
30041
|
logs: true,
|
|
@@ -29945,9 +30050,9 @@ var DEFAULT_SIGNALS_OFF = {
|
|
|
29945
30050
|
};
|
|
29946
30051
|
function getPreferencePath() {
|
|
29947
30052
|
if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
|
|
29948
|
-
return
|
|
30053
|
+
return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
|
|
29949
30054
|
}
|
|
29950
|
-
return
|
|
30055
|
+
return path34.join(os9.homedir(), ".har", "telemetry.json");
|
|
29951
30056
|
}
|
|
29952
30057
|
function parseEnvOverride(raw) {
|
|
29953
30058
|
if (raw === void 0 || raw === "") return void 0;
|
|
@@ -29968,10 +30073,10 @@ function normalizeSignals(enabled, raw) {
|
|
|
29968
30073
|
function readTelemetryPreference() {
|
|
29969
30074
|
const preferencePath = getPreferencePath();
|
|
29970
30075
|
try {
|
|
29971
|
-
if (!
|
|
30076
|
+
if (!fs34.existsSync(preferencePath)) {
|
|
29972
30077
|
return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
|
|
29973
30078
|
}
|
|
29974
|
-
const parsed = JSON.parse(
|
|
30079
|
+
const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
|
|
29975
30080
|
const enabled = parsed.enabled !== false;
|
|
29976
30081
|
return {
|
|
29977
30082
|
enabled,
|
|
@@ -29994,8 +30099,8 @@ function writeTelemetryPreference(enabled, signals) {
|
|
|
29994
30099
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29995
30100
|
};
|
|
29996
30101
|
const preferencePath = getPreferencePath();
|
|
29997
|
-
|
|
29998
|
-
|
|
30102
|
+
fs34.mkdirSync(path34.dirname(preferencePath), { recursive: true });
|
|
30103
|
+
fs34.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
|
|
29999
30104
|
return preference;
|
|
30000
30105
|
}
|
|
30001
30106
|
function isTelemetryEnabled() {
|
|
@@ -30019,13 +30124,13 @@ var TELEMETRY_SIGNALS = [
|
|
|
30019
30124
|
];
|
|
30020
30125
|
|
|
30021
30126
|
// src/core/usage-harvest/claude.ts
|
|
30022
|
-
var
|
|
30127
|
+
var fs36 = __toESM(require("fs"));
|
|
30023
30128
|
var os10 = __toESM(require("os"));
|
|
30024
|
-
var
|
|
30129
|
+
var path36 = __toESM(require("path"));
|
|
30025
30130
|
|
|
30026
30131
|
// src/core/telemetry-env.ts
|
|
30027
|
-
var
|
|
30028
|
-
var
|
|
30132
|
+
var fs35 = __toESM(require("fs"));
|
|
30133
|
+
var path35 = __toESM(require("path"));
|
|
30029
30134
|
function escapeAttrValue(value) {
|
|
30030
30135
|
return value.replace(/[,=]/g, "_");
|
|
30031
30136
|
}
|
|
@@ -30092,8 +30197,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs, options) {
|
|
|
30092
30197
|
${TELEMETRY_MARKER_END}
|
|
30093
30198
|
`;
|
|
30094
30199
|
let existing = "";
|
|
30095
|
-
if (
|
|
30096
|
-
existing =
|
|
30200
|
+
if (fs35.existsSync(envFilePath)) {
|
|
30201
|
+
existing = fs35.readFileSync(envFilePath, "utf8");
|
|
30097
30202
|
const start = existing.indexOf(TELEMETRY_MARKER_START);
|
|
30098
30203
|
if (start >= 0) {
|
|
30099
30204
|
const end = existing.indexOf(TELEMETRY_MARKER_END, start);
|
|
@@ -30104,9 +30209,9 @@ ${TELEMETRY_MARKER_END}
|
|
|
30104
30209
|
}
|
|
30105
30210
|
}
|
|
30106
30211
|
}
|
|
30107
|
-
|
|
30212
|
+
fs35.mkdirSync(path35.dirname(envFilePath), { recursive: true });
|
|
30108
30213
|
const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
|
|
30109
|
-
|
|
30214
|
+
fs35.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
|
|
30110
30215
|
}
|
|
30111
30216
|
function buildCodexOtelSnippet(attrs) {
|
|
30112
30217
|
const apiUrl = getControlApiUrl().replace(/\/$/, "");
|
|
@@ -30127,21 +30232,21 @@ metrics_exporter = "otlp-http"
|
|
|
30127
30232
|
|
|
30128
30233
|
// src/core/usage-harvest/claude.ts
|
|
30129
30234
|
function pathsMatch(candidate, targets) {
|
|
30130
|
-
const norm =
|
|
30235
|
+
const norm = path36.resolve(candidate);
|
|
30131
30236
|
return targets.some((t2) => {
|
|
30132
|
-
const target =
|
|
30133
|
-
return norm === target || norm.startsWith(target +
|
|
30237
|
+
const target = path36.resolve(t2);
|
|
30238
|
+
return norm === target || norm.startsWith(target + path36.sep) || target.startsWith(norm + path36.sep);
|
|
30134
30239
|
});
|
|
30135
30240
|
}
|
|
30136
30241
|
function claudeProjectsRoot() {
|
|
30137
|
-
return process.env.HAR_CLAUDE_PROJECTS_DIR ?
|
|
30242
|
+
return process.env.HAR_CLAUDE_PROJECTS_DIR ? path36.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path36.join(os10.homedir(), ".claude", "projects");
|
|
30138
30243
|
}
|
|
30139
30244
|
function encodeClaudeProjectDir(cwd) {
|
|
30140
30245
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
30141
30246
|
}
|
|
30142
30247
|
function readJsonlRecords(filePath) {
|
|
30143
|
-
if (!
|
|
30144
|
-
const lines =
|
|
30248
|
+
if (!fs36.existsSync(filePath)) return [];
|
|
30249
|
+
const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
30145
30250
|
const out = [];
|
|
30146
30251
|
for (const line of lines) {
|
|
30147
30252
|
const trimmed = line.trim();
|
|
@@ -30184,16 +30289,16 @@ function findMatchingClaudeTranscripts(slot) {
|
|
|
30184
30289
|
const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
|
|
30185
30290
|
if (targets.length === 0) return [];
|
|
30186
30291
|
const root = claudeProjectsRoot();
|
|
30187
|
-
if (!
|
|
30292
|
+
if (!fs36.existsSync(root)) return [];
|
|
30188
30293
|
const matches = [];
|
|
30189
|
-
for (const entry of
|
|
30294
|
+
for (const entry of fs36.readdirSync(root, { withFileTypes: true })) {
|
|
30190
30295
|
if (!entry.isDirectory()) continue;
|
|
30191
|
-
const projectDir =
|
|
30296
|
+
const projectDir = path36.join(root, entry.name);
|
|
30192
30297
|
const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
|
|
30193
|
-
for (const file of
|
|
30298
|
+
for (const file of fs36.readdirSync(projectDir)) {
|
|
30194
30299
|
if (!file.endsWith(".jsonl")) continue;
|
|
30195
|
-
const filePath =
|
|
30196
|
-
const stat =
|
|
30300
|
+
const filePath = path36.join(projectDir, file);
|
|
30301
|
+
const stat = fs36.statSync(filePath);
|
|
30197
30302
|
const records = readJsonlRecords(filePath);
|
|
30198
30303
|
let cwdHit = encodedHit;
|
|
30199
30304
|
for (const record2 of records) {
|
|
@@ -30304,30 +30409,30 @@ function harvestClaudeEvents(slot) {
|
|
|
30304
30409
|
}
|
|
30305
30410
|
|
|
30306
30411
|
// src/core/usage-harvest/codex.ts
|
|
30307
|
-
var
|
|
30412
|
+
var fs37 = __toESM(require("fs"));
|
|
30308
30413
|
var os11 = __toESM(require("os"));
|
|
30309
|
-
var
|
|
30414
|
+
var path37 = __toESM(require("path"));
|
|
30310
30415
|
function pathsMatch2(candidate, targets) {
|
|
30311
|
-
const norm =
|
|
30416
|
+
const norm = path37.resolve(candidate);
|
|
30312
30417
|
return targets.some((t2) => {
|
|
30313
|
-
const target =
|
|
30314
|
-
return norm === target || norm.startsWith(target +
|
|
30418
|
+
const target = path37.resolve(t2);
|
|
30419
|
+
return norm === target || norm.startsWith(target + path37.sep) || target.startsWith(norm + path37.sep);
|
|
30315
30420
|
});
|
|
30316
30421
|
}
|
|
30317
30422
|
function codexSessionsRoot() {
|
|
30318
|
-
return process.env.HAR_CODEX_SESSIONS_DIR ?
|
|
30423
|
+
return process.env.HAR_CODEX_SESSIONS_DIR ? path37.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path37.join(os11.homedir(), ".codex", "sessions");
|
|
30319
30424
|
}
|
|
30320
30425
|
function walkJsonlFiles(dir, out = []) {
|
|
30321
|
-
if (!
|
|
30322
|
-
for (const entry of
|
|
30323
|
-
const full =
|
|
30426
|
+
if (!fs37.existsSync(dir)) return out;
|
|
30427
|
+
for (const entry of fs37.readdirSync(dir, { withFileTypes: true })) {
|
|
30428
|
+
const full = path37.join(dir, entry.name);
|
|
30324
30429
|
if (entry.isDirectory()) walkJsonlFiles(full, out);
|
|
30325
30430
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
|
|
30326
30431
|
}
|
|
30327
30432
|
return out;
|
|
30328
30433
|
}
|
|
30329
30434
|
function readJsonl(filePath) {
|
|
30330
|
-
const lines =
|
|
30435
|
+
const lines = fs37.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
30331
30436
|
const out = [];
|
|
30332
30437
|
for (const line of lines) {
|
|
30333
30438
|
const trimmed = line.trim();
|
|
@@ -30391,7 +30496,7 @@ function harvestCodexUsage(slot) {
|
|
|
30391
30496
|
const extracted = extractCodexTokens(records);
|
|
30392
30497
|
if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
|
|
30393
30498
|
if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
|
|
30394
|
-
const mtime =
|
|
30499
|
+
const mtime = fs37.statSync(file).mtimeMs;
|
|
30395
30500
|
if (mtime >= bestMtime) {
|
|
30396
30501
|
bestMtime = mtime;
|
|
30397
30502
|
best = extracted;
|
|
@@ -30473,7 +30578,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
30473
30578
|
}
|
|
30474
30579
|
const repoPaths = new Set(listRegisteredRepos());
|
|
30475
30580
|
if (options?.cwd) {
|
|
30476
|
-
const cwd =
|
|
30581
|
+
const cwd = path38.resolve(options.cwd);
|
|
30477
30582
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
30478
30583
|
}
|
|
30479
30584
|
try {
|
|
@@ -30481,7 +30586,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
30481
30586
|
if (listResponse.ok) {
|
|
30482
30587
|
const repos = await listResponse.json();
|
|
30483
30588
|
for (const repo of repos) {
|
|
30484
|
-
const resolved =
|
|
30589
|
+
const resolved = path38.resolve(repo.path);
|
|
30485
30590
|
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
30486
30591
|
}
|
|
30487
30592
|
}
|
|
@@ -30500,7 +30605,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
30500
30605
|
return { synced, failed };
|
|
30501
30606
|
}
|
|
30502
30607
|
async function registerRepoWithControl(options) {
|
|
30503
|
-
const repoPath =
|
|
30608
|
+
const repoPath = path38.resolve(options.repoPath);
|
|
30504
30609
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
30505
30610
|
const manifest = readManifest(repoPath);
|
|
30506
30611
|
const stagesRegistry = readStageRegistry(repoPath);
|
|
@@ -30513,7 +30618,7 @@ async function registerRepoWithControl(options) {
|
|
|
30513
30618
|
return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
|
|
30514
30619
|
}
|
|
30515
30620
|
async function syncRepoWithControl(options) {
|
|
30516
|
-
const repoPath =
|
|
30621
|
+
const repoPath = path38.resolve(options.repoPath);
|
|
30517
30622
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
30518
30623
|
if (options.cloud) {
|
|
30519
30624
|
const remote = createRemoteExecutor();
|
|
@@ -30541,7 +30646,7 @@ async function syncRepoWithControl(options) {
|
|
|
30541
30646
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
30542
30647
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
30543
30648
|
const repos = await listResponse.json();
|
|
30544
|
-
const existing = repos.find((r2) =>
|
|
30649
|
+
const existing = repos.find((r2) => path38.resolve(r2.path) === repoPath);
|
|
30545
30650
|
if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
|
|
30546
30651
|
await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
|
|
30547
30652
|
return;
|
|
@@ -30660,7 +30765,7 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
|
|
|
30660
30765
|
|
|
30661
30766
|
// src/core/control-lifecycle.ts
|
|
30662
30767
|
var import_child_process8 = require("child_process");
|
|
30663
|
-
var
|
|
30768
|
+
var path39 = __toESM(require("path"));
|
|
30664
30769
|
|
|
30665
30770
|
// src/core/control-image.ts
|
|
30666
30771
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
@@ -30683,10 +30788,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
|
|
|
30683
30788
|
var CONTROL_CONTAINER_PORT = 3847;
|
|
30684
30789
|
var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
|
|
30685
30790
|
function resolveControlDir() {
|
|
30686
|
-
return
|
|
30791
|
+
return path39.resolve(__dirname, "..", "control");
|
|
30687
30792
|
}
|
|
30688
30793
|
function resolveControlBuildContext() {
|
|
30689
|
-
return
|
|
30794
|
+
return path39.resolve(resolveControlDir(), "..");
|
|
30690
30795
|
}
|
|
30691
30796
|
function buildDockerRunArgs(options) {
|
|
30692
30797
|
const name = options.containerName ?? CONTROL_CONTAINER_NAME;
|
|
@@ -30742,7 +30847,7 @@ async function startMissionControl(options) {
|
|
|
30742
30847
|
"-t",
|
|
30743
30848
|
imageRef,
|
|
30744
30849
|
"-f",
|
|
30745
|
-
|
|
30850
|
+
path39.join(resolveControlDir(), "Dockerfile"),
|
|
30746
30851
|
resolveControlBuildContext()
|
|
30747
30852
|
]);
|
|
30748
30853
|
if (buildCode !== 0) {
|
|
@@ -30884,7 +30989,7 @@ async function ensureTelemetryInfrastructure(options) {
|
|
|
30884
30989
|
}
|
|
30885
30990
|
|
|
30886
30991
|
// src/core/run-service.ts
|
|
30887
|
-
var
|
|
30992
|
+
var path40 = __toESM(require("path"));
|
|
30888
30993
|
function extractShellOutput(result) {
|
|
30889
30994
|
const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
|
|
30890
30995
|
const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
|
|
@@ -31048,14 +31153,14 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
|
|
|
31048
31153
|
suffix: session.suffix,
|
|
31049
31154
|
createdAt: session.createdAt
|
|
31050
31155
|
});
|
|
31051
|
-
const envFile =
|
|
31156
|
+
const envFile = path40.join(session.workDir, `.env.agent.${options.agentId}`);
|
|
31052
31157
|
try {
|
|
31053
31158
|
appendTelemetryEnvToFile(
|
|
31054
31159
|
envFile,
|
|
31055
31160
|
{
|
|
31056
31161
|
sessionKey,
|
|
31057
31162
|
agentId: options.agentId,
|
|
31058
|
-
repoPath:
|
|
31163
|
+
repoPath: path40.resolve(options.repoPath),
|
|
31059
31164
|
workDir: session.workDir,
|
|
31060
31165
|
branch: session.branch,
|
|
31061
31166
|
suffix: session.suffix,
|
|
@@ -31421,7 +31526,7 @@ var envCommand = {
|
|
|
31421
31526
|
}
|
|
31422
31527
|
};
|
|
31423
31528
|
async function handleInit(argv) {
|
|
31424
|
-
const repoPath =
|
|
31529
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31425
31530
|
header("har env init");
|
|
31426
31531
|
info(`Repository: ${repoPath}`);
|
|
31427
31532
|
try {
|
|
@@ -31479,7 +31584,7 @@ async function handleInit(argv) {
|
|
|
31479
31584
|
}
|
|
31480
31585
|
}
|
|
31481
31586
|
async function handleMaintain(argv) {
|
|
31482
|
-
const repoPath =
|
|
31587
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31483
31588
|
header("har env maintain");
|
|
31484
31589
|
info(`Repository: ${repoPath}`);
|
|
31485
31590
|
try {
|
|
@@ -31577,7 +31682,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
31577
31682
|
if (autoYes) {
|
|
31578
31683
|
const proposal = readAgentMdProposal(repoPath);
|
|
31579
31684
|
if (proposal) {
|
|
31580
|
-
writeFileSafe(
|
|
31685
|
+
writeFileSafe(path41.join(repoPath, "AGENT.md"), proposal.content);
|
|
31581
31686
|
clearAgentMdProposal(repoPath);
|
|
31582
31687
|
info("Applied AGENT.md proposal (--yes)");
|
|
31583
31688
|
}
|
|
@@ -31586,7 +31691,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
31586
31691
|
await promptApplyAgentMdProposal(repoPath);
|
|
31587
31692
|
}
|
|
31588
31693
|
async function handleAddStage(argv) {
|
|
31589
|
-
const repoPath =
|
|
31694
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31590
31695
|
const available = listStageTemplateIds();
|
|
31591
31696
|
if (argv.list) {
|
|
31592
31697
|
for (const id of available) {
|
|
@@ -31663,7 +31768,7 @@ async function handleAddStage(argv) {
|
|
|
31663
31768
|
}
|
|
31664
31769
|
}
|
|
31665
31770
|
async function handlePreflight(argv) {
|
|
31666
|
-
const repo =
|
|
31771
|
+
const repo = path41.resolve(argv.repo);
|
|
31667
31772
|
const agentId = validateAgentId(argv.id, repo);
|
|
31668
31773
|
const result = await preflightEnvironment({
|
|
31669
31774
|
repoPath: repo,
|
|
@@ -31681,7 +31786,7 @@ async function handlePreflight(argv) {
|
|
|
31681
31786
|
process.exit(result.code);
|
|
31682
31787
|
}
|
|
31683
31788
|
async function handleLaunch(argv) {
|
|
31684
|
-
const repo =
|
|
31789
|
+
const repo = path41.resolve(argv.repo);
|
|
31685
31790
|
const agentId = validateAgentId(argv.id, repo);
|
|
31686
31791
|
let confirmReplace = argv.replace;
|
|
31687
31792
|
let force = argv.force;
|
|
@@ -31765,7 +31870,7 @@ async function handleRecover(argv) {
|
|
|
31765
31870
|
});
|
|
31766
31871
|
}
|
|
31767
31872
|
async function handleVerify(argv) {
|
|
31768
|
-
const repo =
|
|
31873
|
+
const repo = path41.resolve(argv.repo);
|
|
31769
31874
|
const agentId = validateAgentId(argv.id, repo);
|
|
31770
31875
|
const result = await runVerification({
|
|
31771
31876
|
repoPath: repo,
|
|
@@ -31777,7 +31882,7 @@ async function handleVerify(argv) {
|
|
|
31777
31882
|
process.exit(result.code);
|
|
31778
31883
|
}
|
|
31779
31884
|
async function handleTeardown(argv) {
|
|
31780
|
-
const repo =
|
|
31885
|
+
const repo = path41.resolve(argv.repo);
|
|
31781
31886
|
const agentId = validateAgentId(argv.id, repo);
|
|
31782
31887
|
const result = await teardownEnvironment({
|
|
31783
31888
|
repoPath: repo,
|
|
@@ -31788,7 +31893,7 @@ async function handleTeardown(argv) {
|
|
|
31788
31893
|
process.exit(result.code);
|
|
31789
31894
|
}
|
|
31790
31895
|
async function handleComplete(argv) {
|
|
31791
|
-
const repo =
|
|
31896
|
+
const repo = path41.resolve(argv.repo);
|
|
31792
31897
|
const agentId = validateAgentId(argv.id, repo);
|
|
31793
31898
|
const result = await completeEnvironment({
|
|
31794
31899
|
repoPath: repo,
|
|
@@ -31809,7 +31914,7 @@ async function handleComplete(argv) {
|
|
|
31809
31914
|
process.exit(result.code);
|
|
31810
31915
|
}
|
|
31811
31916
|
async function handleStatus(argv) {
|
|
31812
|
-
const repoPath =
|
|
31917
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31813
31918
|
if (argv.json) {
|
|
31814
31919
|
const status = collectEnvironmentStatus(repoPath);
|
|
31815
31920
|
const output = EnvironmentStatusSchema.parse(status);
|
|
@@ -31822,7 +31927,7 @@ async function handleStatus(argv) {
|
|
|
31822
31927
|
});
|
|
31823
31928
|
}
|
|
31824
31929
|
async function handleRunsList(argv) {
|
|
31825
|
-
const repoPath =
|
|
31930
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31826
31931
|
const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
|
|
31827
31932
|
if (argv.json) {
|
|
31828
31933
|
process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
|
|
@@ -31839,7 +31944,7 @@ async function handleRunsList(argv) {
|
|
|
31839
31944
|
}
|
|
31840
31945
|
}
|
|
31841
31946
|
async function handleRunsGet(argv) {
|
|
31842
|
-
const run2 = getRun(
|
|
31947
|
+
const run2 = getRun(path41.resolve(argv.repo), argv.runId);
|
|
31843
31948
|
if (!run2) {
|
|
31844
31949
|
error(`Run not found: ${argv.runId}`);
|
|
31845
31950
|
process.exit(1);
|
|
@@ -31880,7 +31985,13 @@ function printDrift(drift) {
|
|
|
31880
31985
|
);
|
|
31881
31986
|
warn(" See maintain/templates/harness.env in the maintenance bundle.");
|
|
31882
31987
|
}
|
|
31883
|
-
if (
|
|
31988
|
+
if (drift.agentSlotMismatch) {
|
|
31989
|
+
warn(
|
|
31990
|
+
` Agent slot limits disagree: stages.json ${drift.agentSlotMismatch.stages.min}\u2013${drift.agentSlotMismatch.stages.max}, harness.env ${drift.agentSlotMismatch.env.min}\u2013${drift.agentSlotMismatch.env.max}`
|
|
31991
|
+
);
|
|
31992
|
+
warn(" Canonical source is .har/stages.json \u2014 har env maintain --finalize syncs harness.env.");
|
|
31993
|
+
}
|
|
31994
|
+
if (!drift.generatorVersion.outdated && drift.checksumMismatch.length === 0 && drift.missing.length === 0 && drift.extra.length === 0 && drift.missingPortVars.length === 0 && !drift.agentSlotMismatch) {
|
|
31884
31995
|
success(" Harness matches bundled templates");
|
|
31885
31996
|
}
|
|
31886
31997
|
}
|
|
@@ -31912,7 +32023,7 @@ function printNextSteps(auto2) {
|
|
|
31912
32023
|
}
|
|
31913
32024
|
|
|
31914
32025
|
// src/cli/commands/control.ts
|
|
31915
|
-
var
|
|
32026
|
+
var path42 = __toESM(require("path"));
|
|
31916
32027
|
var controlCommand = {
|
|
31917
32028
|
command: "control <subcommand>",
|
|
31918
32029
|
describe: "Mission Control dashboard (local)",
|
|
@@ -32015,7 +32126,7 @@ async function handleDown() {
|
|
|
32015
32126
|
process.exit(code);
|
|
32016
32127
|
}
|
|
32017
32128
|
async function handleRegister(argv) {
|
|
32018
|
-
const repoPath =
|
|
32129
|
+
const repoPath = path42.resolve(argv.repo);
|
|
32019
32130
|
header("har control register");
|
|
32020
32131
|
info(`Repository: ${repoPath}`);
|
|
32021
32132
|
try {
|
|
@@ -32035,7 +32146,7 @@ async function handleRegister(argv) {
|
|
|
32035
32146
|
}
|
|
32036
32147
|
}
|
|
32037
32148
|
async function handleSync(argv) {
|
|
32038
|
-
const repoPath =
|
|
32149
|
+
const repoPath = path42.resolve(argv.repo);
|
|
32039
32150
|
try {
|
|
32040
32151
|
await syncRepoWithControl({
|
|
32041
32152
|
repoPath,
|
|
@@ -32074,7 +32185,7 @@ async function handleWatch(argv) {
|
|
|
32074
32185
|
};
|
|
32075
32186
|
const tick = async () => {
|
|
32076
32187
|
if (argv.repo) {
|
|
32077
|
-
await syncOne(
|
|
32188
|
+
await syncOne(path42.resolve(argv.repo));
|
|
32078
32189
|
return;
|
|
32079
32190
|
}
|
|
32080
32191
|
try {
|
|
@@ -32105,12 +32216,12 @@ async function handleLogin(argv) {
|
|
|
32105
32216
|
}
|
|
32106
32217
|
|
|
32107
32218
|
// src/cli/commands/hooks.ts
|
|
32108
|
-
var
|
|
32219
|
+
var path45 = __toESM(require("path"));
|
|
32109
32220
|
|
|
32110
32221
|
// src/core/hooks.ts
|
|
32111
|
-
var
|
|
32222
|
+
var fs38 = __toESM(require("fs"));
|
|
32112
32223
|
var os12 = __toESM(require("os"));
|
|
32113
|
-
var
|
|
32224
|
+
var path43 = __toESM(require("path"));
|
|
32114
32225
|
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
32115
32226
|
var MARKER_END = "# <<< har commit gate <<<";
|
|
32116
32227
|
var PRE_COMMIT_BLOCK = [
|
|
@@ -32135,7 +32246,7 @@ function resolveCheckoutRoot(cwd) {
|
|
|
32135
32246
|
function resolveHooksDir(checkoutDir) {
|
|
32136
32247
|
const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
|
|
32137
32248
|
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
32138
|
-
return
|
|
32249
|
+
return path43.resolve(checkoutDir, hooksPath);
|
|
32139
32250
|
}
|
|
32140
32251
|
function getConfiguredHooksPath(checkoutDir) {
|
|
32141
32252
|
return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
|
|
@@ -32143,9 +32254,9 @@ function getConfiguredHooksPath(checkoutDir) {
|
|
|
32143
32254
|
function defaultHarInvocation() {
|
|
32144
32255
|
const entry = process.argv[1];
|
|
32145
32256
|
if (entry && entry.endsWith(".js")) {
|
|
32146
|
-
return `"${process.execPath}" "${
|
|
32257
|
+
return `"${process.execPath}" "${path43.resolve(entry)}"`;
|
|
32147
32258
|
}
|
|
32148
|
-
if (entry) return `"${
|
|
32259
|
+
if (entry) return `"${path43.resolve(entry)}"`;
|
|
32149
32260
|
return "har";
|
|
32150
32261
|
}
|
|
32151
32262
|
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
@@ -32166,40 +32277,40 @@ exit 0
|
|
|
32166
32277
|
`;
|
|
32167
32278
|
}
|
|
32168
32279
|
function upsertMarkedBlock(filePath, block) {
|
|
32169
|
-
if (!
|
|
32170
|
-
|
|
32280
|
+
if (!fs38.existsSync(filePath)) {
|
|
32281
|
+
fs38.writeFileSync(filePath, `#!/bin/sh
|
|
32171
32282
|
${block}
|
|
32172
32283
|
`, { mode: 493 });
|
|
32173
32284
|
return "created";
|
|
32174
32285
|
}
|
|
32175
|
-
const content =
|
|
32286
|
+
const content = fs38.readFileSync(filePath, "utf8");
|
|
32176
32287
|
if (content.includes(MARKER_START)) {
|
|
32177
32288
|
const pattern = new RegExp(
|
|
32178
32289
|
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
32179
32290
|
);
|
|
32180
|
-
|
|
32181
|
-
|
|
32291
|
+
fs38.writeFileSync(filePath, content.replace(pattern, block));
|
|
32292
|
+
fs38.chmodSync(filePath, 493);
|
|
32182
32293
|
return "updated";
|
|
32183
32294
|
}
|
|
32184
32295
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
32185
|
-
|
|
32296
|
+
fs38.writeFileSync(filePath, `${content}${suffix}
|
|
32186
32297
|
${block}
|
|
32187
32298
|
`);
|
|
32188
|
-
|
|
32299
|
+
fs38.chmodSync(filePath, 493);
|
|
32189
32300
|
return "appended";
|
|
32190
32301
|
}
|
|
32191
32302
|
function removeMarkedBlock(filePath) {
|
|
32192
|
-
if (!
|
|
32193
|
-
const content =
|
|
32303
|
+
if (!fs38.existsSync(filePath)) return false;
|
|
32304
|
+
const content = fs38.readFileSync(filePath, "utf8");
|
|
32194
32305
|
if (!content.includes(MARKER_START)) return false;
|
|
32195
32306
|
const pattern = new RegExp(
|
|
32196
32307
|
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
32197
32308
|
);
|
|
32198
32309
|
const stripped = content.replace(pattern, "\n");
|
|
32199
32310
|
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
32200
|
-
|
|
32311
|
+
fs38.rmSync(filePath);
|
|
32201
32312
|
} else {
|
|
32202
|
-
|
|
32313
|
+
fs38.writeFileSync(filePath, stripped);
|
|
32203
32314
|
}
|
|
32204
32315
|
return true;
|
|
32205
32316
|
}
|
|
@@ -32221,20 +32332,20 @@ Or re-run with --force to write into that directory anyway.`
|
|
|
32221
32332
|
);
|
|
32222
32333
|
}
|
|
32223
32334
|
const hooksDir = resolveHooksDir(checkout);
|
|
32224
|
-
|
|
32335
|
+
fs38.mkdirSync(hooksDir, { recursive: true });
|
|
32225
32336
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
32226
|
-
|
|
32227
|
-
|
|
32337
|
+
fs38.writeFileSync(
|
|
32338
|
+
path43.join(hooksDir, "har-pre-commit"),
|
|
32228
32339
|
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
32229
32340
|
{ mode: 493 }
|
|
32230
32341
|
);
|
|
32231
|
-
|
|
32232
|
-
|
|
32342
|
+
fs38.writeFileSync(
|
|
32343
|
+
path43.join(hooksDir, "har-post-commit"),
|
|
32233
32344
|
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
32234
32345
|
{ mode: 493 }
|
|
32235
32346
|
);
|
|
32236
|
-
const preCommit = upsertMarkedBlock(
|
|
32237
|
-
const postCommit = upsertMarkedBlock(
|
|
32347
|
+
const preCommit = upsertMarkedBlock(path43.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
32348
|
+
const postCommit = upsertMarkedBlock(path43.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
32238
32349
|
ensureValidationsIgnored(checkout);
|
|
32239
32350
|
return { hooksDir, preCommit, postCommit };
|
|
32240
32351
|
}
|
|
@@ -32243,12 +32354,12 @@ function uninstallHooks(repoPath) {
|
|
|
32243
32354
|
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
32244
32355
|
const hooksDir = resolveHooksDir(checkout);
|
|
32245
32356
|
let removed = false;
|
|
32246
|
-
removed = removeMarkedBlock(
|
|
32247
|
-
removed = removeMarkedBlock(
|
|
32357
|
+
removed = removeMarkedBlock(path43.join(hooksDir, "pre-commit")) || removed;
|
|
32358
|
+
removed = removeMarkedBlock(path43.join(hooksDir, "post-commit")) || removed;
|
|
32248
32359
|
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
32249
|
-
const full =
|
|
32250
|
-
if (
|
|
32251
|
-
|
|
32360
|
+
const full = path43.join(hooksDir, file);
|
|
32361
|
+
if (fs38.existsSync(full)) {
|
|
32362
|
+
fs38.rmSync(full);
|
|
32252
32363
|
removed = true;
|
|
32253
32364
|
}
|
|
32254
32365
|
}
|
|
@@ -32265,9 +32376,9 @@ function getCommitGateConfig(checkoutDir) {
|
|
|
32265
32376
|
function isAgentWorktree(checkoutDir) {
|
|
32266
32377
|
const branch = getCurrentBranch(checkoutDir);
|
|
32267
32378
|
if (branch && /^har-agent-\d+$/.test(branch)) return true;
|
|
32268
|
-
const worktreesRoot =
|
|
32269
|
-
const resolved =
|
|
32270
|
-
return resolved.startsWith(`${worktreesRoot}${
|
|
32379
|
+
const worktreesRoot = path43.join(os12.homedir(), "worktrees");
|
|
32380
|
+
const resolved = path43.resolve(checkoutDir);
|
|
32381
|
+
return resolved.startsWith(`${worktreesRoot}${path43.sep}`) && /-agent-\d+$/.test(path43.basename(resolved));
|
|
32271
32382
|
}
|
|
32272
32383
|
function resolveEffectiveMode(checkoutDir, gate) {
|
|
32273
32384
|
if (!gate.enabled) return "off";
|
|
@@ -32280,15 +32391,15 @@ function getHooksStatus(repoPath) {
|
|
|
32280
32391
|
const hooksDir = resolveHooksDir(checkout);
|
|
32281
32392
|
const gate = getCommitGateConfig(checkout);
|
|
32282
32393
|
const hasBlock = (name) => {
|
|
32283
|
-
const file =
|
|
32284
|
-
return
|
|
32394
|
+
const file = path43.join(hooksDir, name);
|
|
32395
|
+
return fs38.existsSync(file) && fs38.readFileSync(file, "utf8").includes(MARKER_START);
|
|
32285
32396
|
};
|
|
32286
32397
|
return {
|
|
32287
32398
|
checkout,
|
|
32288
32399
|
hooksDir,
|
|
32289
32400
|
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
32290
|
-
preCommitInstalled: hasBlock("pre-commit") &&
|
|
32291
|
-
postCommitInstalled: hasBlock("post-commit") &&
|
|
32401
|
+
preCommitInstalled: hasBlock("pre-commit") && fs38.existsSync(path43.join(hooksDir, "har-pre-commit")),
|
|
32402
|
+
postCommitInstalled: hasBlock("post-commit") && fs38.existsSync(path43.join(hooksDir, "har-post-commit")),
|
|
32292
32403
|
gate,
|
|
32293
32404
|
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
32294
32405
|
};
|
|
@@ -32299,7 +32410,7 @@ function checkCommitGate(cwd) {
|
|
|
32299
32410
|
}
|
|
32300
32411
|
const checkout = resolveCheckoutRoot(cwd);
|
|
32301
32412
|
if (!checkout) return { exitCode: 0, messages: [] };
|
|
32302
|
-
if (!
|
|
32413
|
+
if (!fs38.existsSync(path43.join(checkout, ".har", "stages.json"))) {
|
|
32303
32414
|
return { exitCode: 0, messages: [] };
|
|
32304
32415
|
}
|
|
32305
32416
|
const gate = getCommitGateConfig(checkout);
|
|
@@ -32370,8 +32481,8 @@ async function recordCommitAssociation(cwd) {
|
|
|
32370
32481
|
}
|
|
32371
32482
|
|
|
32372
32483
|
// src/core/claude-hooks.ts
|
|
32373
|
-
var
|
|
32374
|
-
var
|
|
32484
|
+
var fs39 = __toESM(require("fs"));
|
|
32485
|
+
var path44 = __toESM(require("path"));
|
|
32375
32486
|
var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
|
|
32376
32487
|
var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
32377
32488
|
var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
|
|
@@ -32382,8 +32493,8 @@ function isHarGuardEntry(entry) {
|
|
|
32382
32493
|
);
|
|
32383
32494
|
}
|
|
32384
32495
|
function readSettings(settingsPath) {
|
|
32385
|
-
if (!
|
|
32386
|
-
const raw =
|
|
32496
|
+
if (!fs39.existsSync(settingsPath)) return {};
|
|
32497
|
+
const raw = fs39.readFileSync(settingsPath, "utf8").trim();
|
|
32387
32498
|
if (raw === "") return {};
|
|
32388
32499
|
const parsed = JSON.parse(raw);
|
|
32389
32500
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -32392,19 +32503,19 @@ function readSettings(settingsPath) {
|
|
|
32392
32503
|
return parsed;
|
|
32393
32504
|
}
|
|
32394
32505
|
function writeSettings(settingsPath, settings) {
|
|
32395
|
-
|
|
32396
|
-
|
|
32506
|
+
fs39.mkdirSync(path44.dirname(settingsPath), { recursive: true });
|
|
32507
|
+
fs39.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
32397
32508
|
}
|
|
32398
32509
|
function installClaudeGuard(repoPath) {
|
|
32399
32510
|
const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
|
|
32400
32511
|
if (!templatePath) {
|
|
32401
32512
|
throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
|
|
32402
32513
|
}
|
|
32403
|
-
const guardScript =
|
|
32404
|
-
|
|
32405
|
-
|
|
32406
|
-
|
|
32407
|
-
const settingsPath =
|
|
32514
|
+
const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
32515
|
+
fs39.mkdirSync(path44.dirname(guardScript), { recursive: true });
|
|
32516
|
+
fs39.copyFileSync(templatePath, guardScript);
|
|
32517
|
+
fs39.chmodSync(guardScript, 493);
|
|
32518
|
+
const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32408
32519
|
const settings = readSettings(settingsPath);
|
|
32409
32520
|
const hooks = settings.hooks ?? {};
|
|
32410
32521
|
const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
|
|
@@ -32418,15 +32529,15 @@ function installClaudeGuard(repoPath) {
|
|
|
32418
32529
|
}
|
|
32419
32530
|
function uninstallClaudeGuard(repoPath) {
|
|
32420
32531
|
let removed = false;
|
|
32421
|
-
const guardScript =
|
|
32422
|
-
if (
|
|
32423
|
-
|
|
32532
|
+
const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
32533
|
+
if (fs39.existsSync(guardScript)) {
|
|
32534
|
+
fs39.rmSync(guardScript);
|
|
32424
32535
|
removed = true;
|
|
32425
|
-
const parent =
|
|
32426
|
-
if (
|
|
32536
|
+
const parent = path44.dirname(guardScript);
|
|
32537
|
+
if (fs39.existsSync(parent) && fs39.readdirSync(parent).length === 0) fs39.rmdirSync(parent);
|
|
32427
32538
|
}
|
|
32428
|
-
const settingsPath =
|
|
32429
|
-
if (
|
|
32539
|
+
const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32540
|
+
if (fs39.existsSync(settingsPath)) {
|
|
32430
32541
|
const settings = readSettings(settingsPath);
|
|
32431
32542
|
const preToolUse = settings.hooks?.PreToolUse;
|
|
32432
32543
|
if (preToolUse) {
|
|
@@ -32451,8 +32562,8 @@ function uninstallClaudeGuard(repoPath) {
|
|
|
32451
32562
|
return { removed };
|
|
32452
32563
|
}
|
|
32453
32564
|
function claudeGuardInstalled(repoPath) {
|
|
32454
|
-
const settingsPath =
|
|
32455
|
-
if (!
|
|
32565
|
+
const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32566
|
+
if (!fs39.existsSync(settingsPath)) return false;
|
|
32456
32567
|
try {
|
|
32457
32568
|
const settings = readSettings(settingsPath);
|
|
32458
32569
|
return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
|
|
@@ -32468,7 +32579,7 @@ function repoOption(y2) {
|
|
|
32468
32579
|
function handleInstall2(argv) {
|
|
32469
32580
|
try {
|
|
32470
32581
|
if (argv.claude) {
|
|
32471
|
-
const result2 = installClaudeGuard(
|
|
32582
|
+
const result2 = installClaudeGuard(path45.resolve(argv.repo));
|
|
32472
32583
|
success("Claude Code worktree guard installed.");
|
|
32473
32584
|
info(`Guard script: ${result2.guardScript}`);
|
|
32474
32585
|
info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
|
|
@@ -32476,7 +32587,7 @@ function handleInstall2(argv) {
|
|
|
32476
32587
|
info("Commit both files so the guard travels with the repo.");
|
|
32477
32588
|
return;
|
|
32478
32589
|
}
|
|
32479
|
-
const result = installHooks({ repoPath:
|
|
32590
|
+
const result = installHooks({ repoPath: path45.resolve(argv.repo), force: argv.force });
|
|
32480
32591
|
success(`Commit gate installed in ${result.hooksDir}`);
|
|
32481
32592
|
info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
|
|
32482
32593
|
info("Commits of unverified change batches will be blocked in agent worktrees.");
|
|
@@ -32489,7 +32600,7 @@ function handleInstall2(argv) {
|
|
|
32489
32600
|
function handleUninstall(argv) {
|
|
32490
32601
|
try {
|
|
32491
32602
|
if (argv.claude) {
|
|
32492
|
-
const result2 = uninstallClaudeGuard(
|
|
32603
|
+
const result2 = uninstallClaudeGuard(path45.resolve(argv.repo));
|
|
32493
32604
|
if (result2.removed) {
|
|
32494
32605
|
success("Claude Code worktree guard removed.");
|
|
32495
32606
|
} else {
|
|
@@ -32497,7 +32608,7 @@ function handleUninstall(argv) {
|
|
|
32497
32608
|
}
|
|
32498
32609
|
return;
|
|
32499
32610
|
}
|
|
32500
|
-
const result = uninstallHooks(
|
|
32611
|
+
const result = uninstallHooks(path45.resolve(argv.repo));
|
|
32501
32612
|
if (result.removed) {
|
|
32502
32613
|
success(`Commit gate removed from ${result.hooksDir}`);
|
|
32503
32614
|
} else {
|
|
@@ -32510,7 +32621,7 @@ function handleUninstall(argv) {
|
|
|
32510
32621
|
}
|
|
32511
32622
|
function handleStatus2(argv) {
|
|
32512
32623
|
try {
|
|
32513
|
-
const status = getHooksStatus(
|
|
32624
|
+
const status = getHooksStatus(path45.resolve(argv.repo));
|
|
32514
32625
|
if (argv.json) {
|
|
32515
32626
|
process.stdout.write(`${JSON.stringify(status, null, 2)}
|
|
32516
32627
|
`);
|
|
@@ -32528,7 +32639,7 @@ function handleStatus2(argv) {
|
|
|
32528
32639
|
);
|
|
32529
32640
|
info(`Effective: ${status.effectiveMode} (in this checkout)`);
|
|
32530
32641
|
info(
|
|
32531
|
-
`Claude guard: ${claudeGuardInstalled(
|
|
32642
|
+
`Claude guard: ${claudeGuardInstalled(path45.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
|
|
32532
32643
|
);
|
|
32533
32644
|
} catch (err) {
|
|
32534
32645
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -32789,10 +32900,10 @@ function assignProp(target, prop, value) {
|
|
|
32789
32900
|
configurable: true
|
|
32790
32901
|
});
|
|
32791
32902
|
}
|
|
32792
|
-
function getElementAtPath(obj,
|
|
32793
|
-
if (!
|
|
32903
|
+
function getElementAtPath(obj, path48) {
|
|
32904
|
+
if (!path48)
|
|
32794
32905
|
return obj;
|
|
32795
|
-
return
|
|
32906
|
+
return path48.reduce((acc, key) => acc?.[key], obj);
|
|
32796
32907
|
}
|
|
32797
32908
|
function promiseAllObject(promisesObj) {
|
|
32798
32909
|
const keys = Object.keys(promisesObj);
|
|
@@ -33112,11 +33223,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
33112
33223
|
}
|
|
33113
33224
|
return false;
|
|
33114
33225
|
}
|
|
33115
|
-
function prefixIssues(
|
|
33226
|
+
function prefixIssues(path48, issues) {
|
|
33116
33227
|
return issues.map((iss) => {
|
|
33117
33228
|
var _a3;
|
|
33118
33229
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
33119
|
-
iss.path.unshift(
|
|
33230
|
+
iss.path.unshift(path48);
|
|
33120
33231
|
return iss;
|
|
33121
33232
|
});
|
|
33122
33233
|
}
|
|
@@ -39590,7 +39701,7 @@ var Server = class extends Protocol {
|
|
|
39590
39701
|
};
|
|
39591
39702
|
|
|
39592
39703
|
// src/mcp/server.ts
|
|
39593
|
-
var
|
|
39704
|
+
var path46 = __toESM(require("path"));
|
|
39594
39705
|
|
|
39595
39706
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
39596
39707
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
@@ -40222,7 +40333,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
40222
40333
|
const input = ControlUpInputSchema.parse({ ...args, repo });
|
|
40223
40334
|
const result = await startControlAndSync({
|
|
40224
40335
|
detach: input.detach,
|
|
40225
|
-
cwd:
|
|
40336
|
+
cwd: path46.resolve(input.repo)
|
|
40226
40337
|
});
|
|
40227
40338
|
if (result.code !== 0) {
|
|
40228
40339
|
return {
|
|
@@ -40285,8 +40396,8 @@ var mcpCommand = {
|
|
|
40285
40396
|
};
|
|
40286
40397
|
|
|
40287
40398
|
// src/cli/commands/telemetry.ts
|
|
40288
|
-
var
|
|
40289
|
-
var
|
|
40399
|
+
var fs40 = __toESM(require("fs"));
|
|
40400
|
+
var path47 = __toESM(require("path"));
|
|
40290
40401
|
function printSignals() {
|
|
40291
40402
|
info("Collected when enabled (local Mission Control only):");
|
|
40292
40403
|
for (const signal of TELEMETRY_SIGNALS) {
|
|
@@ -40357,7 +40468,7 @@ async function handleOff() {
|
|
|
40357
40468
|
info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
|
|
40358
40469
|
}
|
|
40359
40470
|
async function handleWriteEnv(argv) {
|
|
40360
|
-
const repoPath =
|
|
40471
|
+
const repoPath = path47.resolve(argv.repo);
|
|
40361
40472
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
40362
40473
|
const session = readSlotRegistry(harnessRoot, argv.agentId);
|
|
40363
40474
|
const workDir = argv.workDir ?? session?.workDir ?? repoPath;
|
|
@@ -40370,7 +40481,7 @@ async function handleWriteEnv(argv) {
|
|
|
40370
40481
|
suffix,
|
|
40371
40482
|
createdAt: session?.createdAt
|
|
40372
40483
|
});
|
|
40373
|
-
const envFile = argv.envFile ??
|
|
40484
|
+
const envFile = argv.envFile ?? path47.join(workDir, `.env.agent.${argv.agentId}`);
|
|
40374
40485
|
let otelReady = argv.otelReady;
|
|
40375
40486
|
if (otelReady === void 0 && isTelemetryEnabled()) {
|
|
40376
40487
|
const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
|
|
@@ -40389,7 +40500,7 @@ async function handleWriteEnv(argv) {
|
|
|
40389
40500
|
success(`Wrote telemetry env to ${envFile}`);
|
|
40390
40501
|
}
|
|
40391
40502
|
function handlePrintEnv(argv) {
|
|
40392
|
-
const repoPath =
|
|
40503
|
+
const repoPath = path47.resolve(argv.repo);
|
|
40393
40504
|
const workDir = argv.workDir ?? repoPath;
|
|
40394
40505
|
const sessionKey = buildSessionKey({
|
|
40395
40506
|
branch: argv.branch,
|
|
@@ -40411,7 +40522,7 @@ function handlePrintEnv(argv) {
|
|
|
40411
40522
|
);
|
|
40412
40523
|
}
|
|
40413
40524
|
function handleCodexSnippet(argv) {
|
|
40414
|
-
const repoPath =
|
|
40525
|
+
const repoPath = path47.resolve(argv.repo);
|
|
40415
40526
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
40416
40527
|
const session = readSlotRegistry(harnessRoot, argv.agentId);
|
|
40417
40528
|
const workDir = argv.workDir ?? session?.workDir ?? repoPath;
|
|
@@ -40433,10 +40544,10 @@ function handleCodexSnippet(argv) {
|
|
|
40433
40544
|
purpose: session?.purpose
|
|
40434
40545
|
});
|
|
40435
40546
|
if (argv.write) {
|
|
40436
|
-
const outDir =
|
|
40437
|
-
|
|
40438
|
-
const outPath =
|
|
40439
|
-
|
|
40547
|
+
const outDir = path47.join(harnessRoot, ".har", "telemetry");
|
|
40548
|
+
fs40.mkdirSync(outDir, { recursive: true });
|
|
40549
|
+
const outPath = path47.join(outDir, "codex.otel.toml.snippet");
|
|
40550
|
+
fs40.writeFileSync(outPath, snippet);
|
|
40440
40551
|
success(`Wrote ${outPath}`);
|
|
40441
40552
|
info("Merge the [otel] table into ~/.codex/config.toml manually.");
|
|
40442
40553
|
return;
|