@osfactory/har 0.13.1 → 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 +1032 -751
- 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() {
|
|
@@ -23129,6 +23129,22 @@ var AgentSessionUsageSchema = external_exports.object({
|
|
|
23129
23129
|
var SyncUsageInputSchema = external_exports.object({
|
|
23130
23130
|
usage: external_exports.array(AgentSessionUsageSchema)
|
|
23131
23131
|
});
|
|
23132
|
+
var AgentSessionEventSchema = external_exports.object({
|
|
23133
|
+
sessionKey: external_exports.string().min(1),
|
|
23134
|
+
agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
|
|
23135
|
+
agentTool: AgentToolSchema,
|
|
23136
|
+
eventName: external_exports.string().min(1),
|
|
23137
|
+
sequence: external_exports.number().int().nonnegative().default(0),
|
|
23138
|
+
timestamp: external_exports.string(),
|
|
23139
|
+
attributes: external_exports.record(external_exports.unknown()).optional(),
|
|
23140
|
+
promptText: external_exports.string().nullable().optional(),
|
|
23141
|
+
responseText: external_exports.string().nullable().optional(),
|
|
23142
|
+
rawTruncated: external_exports.string().nullable().optional(),
|
|
23143
|
+
source: external_exports.enum(["otel", "harvest"]).default("otel")
|
|
23144
|
+
});
|
|
23145
|
+
var SyncSessionEventsInputSchema = external_exports.object({
|
|
23146
|
+
events: external_exports.array(AgentSessionEventSchema)
|
|
23147
|
+
});
|
|
23132
23148
|
|
|
23133
23149
|
// src/harness/manifest.ts
|
|
23134
23150
|
var GENERATOR_VERSION = "0.4.0";
|
|
@@ -23482,15 +23498,15 @@ function handleRemove(argv) {
|
|
|
23482
23498
|
}
|
|
23483
23499
|
|
|
23484
23500
|
// src/cli/commands/env.ts
|
|
23485
|
-
var
|
|
23501
|
+
var path41 = __toESM(require("path"));
|
|
23486
23502
|
|
|
23487
23503
|
// src/core/harness.ts
|
|
23488
|
-
var
|
|
23489
|
-
var
|
|
23504
|
+
var fs20 = __toESM(require("fs"));
|
|
23505
|
+
var path19 = __toESM(require("path"));
|
|
23490
23506
|
|
|
23491
23507
|
// src/harness/generator.ts
|
|
23492
|
-
var
|
|
23493
|
-
var
|
|
23508
|
+
var fs10 = __toESM(require("fs"));
|
|
23509
|
+
var path11 = __toESM(require("path"));
|
|
23494
23510
|
|
|
23495
23511
|
// src/core/gitignore.ts
|
|
23496
23512
|
var fs6 = __toESM(require("fs"));
|
|
@@ -23513,6 +23529,277 @@ function ensureRootGitignorePatterns(checkoutDir) {
|
|
|
23513
23529
|
fs6.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
|
|
23514
23530
|
}
|
|
23515
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
|
+
|
|
23516
23803
|
// src/harness/generator.ts
|
|
23517
23804
|
var PROFILE_DIRS = {
|
|
23518
23805
|
default: "har-boilerplate",
|
|
@@ -23526,46 +23813,48 @@ var CLI_PRUNE_FILES = [
|
|
|
23526
23813
|
];
|
|
23527
23814
|
function pruneCliProfile(harnessDir) {
|
|
23528
23815
|
for (const file of CLI_PRUNE_FILES) {
|
|
23529
|
-
const filePath =
|
|
23530
|
-
if (
|
|
23531
|
-
|
|
23816
|
+
const filePath = path11.join(harnessDir, file);
|
|
23817
|
+
if (fs10.existsSync(filePath)) {
|
|
23818
|
+
fs10.unlinkSync(filePath);
|
|
23532
23819
|
}
|
|
23533
23820
|
}
|
|
23534
23821
|
}
|
|
23535
23822
|
function scaffoldClaudeMd(repoPath, projectName, force) {
|
|
23536
23823
|
const templatePath = resolveTemplateFile("CLAUDE.md.template");
|
|
23537
23824
|
if (!templatePath) return;
|
|
23538
|
-
const dest =
|
|
23539
|
-
if (
|
|
23825
|
+
const dest = path11.join(repoPath, "CLAUDE.md");
|
|
23826
|
+
if (fs10.existsSync(dest) && !force) return;
|
|
23540
23827
|
const displayName = projectName.replace(/_/g, " ");
|
|
23541
|
-
const content =
|
|
23542
|
-
|
|
23828
|
+
const content = fs10.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
|
|
23829
|
+
fs10.writeFileSync(dest, content);
|
|
23543
23830
|
}
|
|
23544
23831
|
function scaffoldHarnessBoilerplate(repoPath, options = {}) {
|
|
23545
|
-
const harnessDir =
|
|
23546
|
-
const projectName =
|
|
23832
|
+
const harnessDir = path11.join(repoPath, DEFAULT_HAR_DIR);
|
|
23833
|
+
const projectName = path11.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
23547
23834
|
const profile = options.profile ?? "default";
|
|
23548
|
-
const boilerplateDir =
|
|
23549
|
-
if (
|
|
23835
|
+
const boilerplateDir = path11.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
|
|
23836
|
+
if (fs10.existsSync(harnessDir) && !options.force) {
|
|
23550
23837
|
throw new Error(
|
|
23551
23838
|
'.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
|
|
23552
23839
|
);
|
|
23553
23840
|
}
|
|
23554
|
-
if (!
|
|
23841
|
+
if (!fs10.existsSync(boilerplateDir)) {
|
|
23555
23842
|
throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
|
|
23556
23843
|
}
|
|
23557
|
-
if (options.force &&
|
|
23558
|
-
|
|
23844
|
+
if (options.force && fs10.existsSync(harnessDir)) {
|
|
23845
|
+
fs10.rmSync(harnessDir, { recursive: true, force: true });
|
|
23559
23846
|
}
|
|
23560
23847
|
copyDirRecursive(boilerplateDir, harnessDir);
|
|
23848
|
+
writeHarnessGitignore(harnessDir, boilerplateDir);
|
|
23561
23849
|
if (profile === "cli") {
|
|
23562
23850
|
pruneCliProfile(harnessDir);
|
|
23563
23851
|
}
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
23852
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
23853
|
+
const harnessEnvPath = path11.join(harnessDir, "harness.env");
|
|
23854
|
+
if (fs10.existsSync(harnessEnvPath)) {
|
|
23855
|
+
let content = fs10.readFileSync(harnessEnvPath, "utf8");
|
|
23567
23856
|
content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
23568
|
-
|
|
23857
|
+
fs10.writeFileSync(harnessEnvPath, content);
|
|
23569
23858
|
}
|
|
23570
23859
|
const manifest = createManifest(
|
|
23571
23860
|
repoPath,
|
|
@@ -23588,8 +23877,8 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
|
|
|
23588
23877
|
}
|
|
23589
23878
|
|
|
23590
23879
|
// src/llm/authoring-agent.ts
|
|
23591
|
-
var
|
|
23592
|
-
var
|
|
23880
|
+
var fs14 = __toESM(require("fs"));
|
|
23881
|
+
var path14 = __toESM(require("path"));
|
|
23593
23882
|
|
|
23594
23883
|
// node_modules/@anthropic-ai/sdk/error.mjs
|
|
23595
23884
|
var error_exports = {};
|
|
@@ -23988,13 +24277,13 @@ var MultipartBody = class {
|
|
|
23988
24277
|
// node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
|
|
23989
24278
|
var import_web = require("node:stream/web");
|
|
23990
24279
|
var fileFromPathWarned = false;
|
|
23991
|
-
async function fileFromPath3(
|
|
24280
|
+
async function fileFromPath3(path48, ...args) {
|
|
23992
24281
|
const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
|
|
23993
24282
|
if (!fileFromPathWarned) {
|
|
23994
|
-
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(
|
|
24283
|
+
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path48)}) instead`);
|
|
23995
24284
|
fileFromPathWarned = true;
|
|
23996
24285
|
}
|
|
23997
|
-
return await _fileFromPath(
|
|
24286
|
+
return await _fileFromPath(path48, ...args);
|
|
23998
24287
|
}
|
|
23999
24288
|
var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
24000
24289
|
var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
@@ -24591,29 +24880,29 @@ var APIClient = class {
|
|
|
24591
24880
|
defaultIdempotencyKey() {
|
|
24592
24881
|
return `stainless-node-retry-${uuid4()}`;
|
|
24593
24882
|
}
|
|
24594
|
-
get(
|
|
24595
|
-
return this.methodRequest("get",
|
|
24883
|
+
get(path48, opts) {
|
|
24884
|
+
return this.methodRequest("get", path48, opts);
|
|
24596
24885
|
}
|
|
24597
|
-
post(
|
|
24598
|
-
return this.methodRequest("post",
|
|
24886
|
+
post(path48, opts) {
|
|
24887
|
+
return this.methodRequest("post", path48, opts);
|
|
24599
24888
|
}
|
|
24600
|
-
patch(
|
|
24601
|
-
return this.methodRequest("patch",
|
|
24889
|
+
patch(path48, opts) {
|
|
24890
|
+
return this.methodRequest("patch", path48, opts);
|
|
24602
24891
|
}
|
|
24603
|
-
put(
|
|
24604
|
-
return this.methodRequest("put",
|
|
24892
|
+
put(path48, opts) {
|
|
24893
|
+
return this.methodRequest("put", path48, opts);
|
|
24605
24894
|
}
|
|
24606
|
-
delete(
|
|
24607
|
-
return this.methodRequest("delete",
|
|
24895
|
+
delete(path48, opts) {
|
|
24896
|
+
return this.methodRequest("delete", path48, opts);
|
|
24608
24897
|
}
|
|
24609
|
-
methodRequest(method,
|
|
24898
|
+
methodRequest(method, path48, opts) {
|
|
24610
24899
|
return this.request(Promise.resolve(opts).then(async (opts2) => {
|
|
24611
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;
|
|
24612
|
-
return { method, path:
|
|
24901
|
+
return { method, path: path48, ...opts2, body };
|
|
24613
24902
|
}));
|
|
24614
24903
|
}
|
|
24615
|
-
getAPIList(
|
|
24616
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
24904
|
+
getAPIList(path48, Page, opts) {
|
|
24905
|
+
return this.requestAPIList(Page, { method: "get", path: path48, ...opts });
|
|
24617
24906
|
}
|
|
24618
24907
|
calculateContentLength(body) {
|
|
24619
24908
|
if (typeof body === "string") {
|
|
@@ -24631,10 +24920,10 @@ var APIClient = class {
|
|
|
24631
24920
|
return null;
|
|
24632
24921
|
}
|
|
24633
24922
|
buildRequest(options) {
|
|
24634
|
-
const { method, path:
|
|
24923
|
+
const { method, path: path48, query, headers = {} } = options;
|
|
24635
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;
|
|
24636
24925
|
const contentLength = this.calculateContentLength(body);
|
|
24637
|
-
const url = this.buildURL(
|
|
24926
|
+
const url = this.buildURL(path48, query);
|
|
24638
24927
|
if ("timeout" in options)
|
|
24639
24928
|
validatePositiveInteger("timeout", options.timeout);
|
|
24640
24929
|
const timeout = options.timeout ?? this.timeout;
|
|
@@ -24743,8 +25032,8 @@ var APIClient = class {
|
|
|
24743
25032
|
const request = this.makeRequest(options, null);
|
|
24744
25033
|
return new PagePromise(this, request, Page);
|
|
24745
25034
|
}
|
|
24746
|
-
buildURL(
|
|
24747
|
-
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));
|
|
24748
25037
|
const defaultQuery = this.defaultQuery();
|
|
24749
25038
|
if (!isEmptyObj(defaultQuery)) {
|
|
24750
25039
|
query = { ...defaultQuery, ...query };
|
|
@@ -26567,34 +26856,34 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
|
|
|
26567
26856
|
var sdk_default = Anthropic;
|
|
26568
26857
|
|
|
26569
26858
|
// src/llm/tools.ts
|
|
26570
|
-
var
|
|
26571
|
-
var
|
|
26859
|
+
var fs13 = __toESM(require("fs"));
|
|
26860
|
+
var path13 = __toESM(require("path"));
|
|
26572
26861
|
var readline3 = __toESM(require("readline"));
|
|
26573
26862
|
|
|
26574
26863
|
// src/harness/agent-md.ts
|
|
26575
|
-
var
|
|
26576
|
-
var
|
|
26864
|
+
var fs12 = __toESM(require("fs"));
|
|
26865
|
+
var path12 = __toESM(require("path"));
|
|
26577
26866
|
var readline2 = __toESM(require("readline"));
|
|
26578
26867
|
var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
|
|
26579
26868
|
var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
|
|
26580
26869
|
function writeAgentMdProposal(repoPath, content, rationale) {
|
|
26581
26870
|
const harnessDir = getHarnessDir(repoPath);
|
|
26582
|
-
writeFileSafe(
|
|
26871
|
+
writeFileSafe(path12.join(harnessDir, AGENT_MD_PROPOSAL), content);
|
|
26583
26872
|
writeFileSafe(
|
|
26584
|
-
|
|
26873
|
+
path12.join(harnessDir, AGENT_MD_PROPOSAL_META),
|
|
26585
26874
|
JSON.stringify({ rationale, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
26586
26875
|
);
|
|
26587
26876
|
}
|
|
26588
26877
|
function readAgentMdProposal(repoPath) {
|
|
26589
26878
|
const harnessDir = getHarnessDir(repoPath);
|
|
26590
|
-
const proposalPath =
|
|
26591
|
-
const metaPath =
|
|
26592
|
-
if (!
|
|
26593
|
-
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");
|
|
26594
26883
|
let rationale = "";
|
|
26595
|
-
if (
|
|
26884
|
+
if (fs12.existsSync(metaPath)) {
|
|
26596
26885
|
try {
|
|
26597
|
-
rationale = JSON.parse(
|
|
26886
|
+
rationale = JSON.parse(fs12.readFileSync(metaPath, "utf8")).rationale ?? "";
|
|
26598
26887
|
} catch {
|
|
26599
26888
|
rationale = "";
|
|
26600
26889
|
}
|
|
@@ -26602,21 +26891,21 @@ function readAgentMdProposal(repoPath) {
|
|
|
26602
26891
|
return {
|
|
26603
26892
|
content,
|
|
26604
26893
|
rationale,
|
|
26605
|
-
createdAt:
|
|
26894
|
+
createdAt: fs12.existsSync(metaPath) ? JSON.parse(fs12.readFileSync(metaPath, "utf8")).createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
26606
26895
|
};
|
|
26607
26896
|
}
|
|
26608
26897
|
function clearAgentMdProposal(repoPath) {
|
|
26609
26898
|
const harnessDir = getHarnessDir(repoPath);
|
|
26610
26899
|
for (const file of [AGENT_MD_PROPOSAL, AGENT_MD_PROPOSAL_META]) {
|
|
26611
|
-
const p2 =
|
|
26612
|
-
if (
|
|
26900
|
+
const p2 = path12.join(harnessDir, file);
|
|
26901
|
+
if (fs12.existsSync(p2)) fs12.unlinkSync(p2);
|
|
26613
26902
|
}
|
|
26614
26903
|
}
|
|
26615
26904
|
async function promptApplyAgentMdProposal(repoPath) {
|
|
26616
26905
|
const proposal = readAgentMdProposal(repoPath);
|
|
26617
26906
|
if (!proposal) return false;
|
|
26618
|
-
const agentMdPath =
|
|
26619
|
-
const exists =
|
|
26907
|
+
const agentMdPath = path12.join(repoPath, "AGENT.md");
|
|
26908
|
+
const exists = fs12.existsSync(agentMdPath);
|
|
26620
26909
|
process.stderr.write("\n");
|
|
26621
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");
|
|
26622
26911
|
process.stderr.write("Proposed AGENT.md (repo root)\n");
|
|
@@ -26804,13 +27093,13 @@ async function handleAuthoringToolCall(toolName, input, repoPath, harnessDir, ca
|
|
|
26804
27093
|
async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, callbacks) {
|
|
26805
27094
|
switch (toolName) {
|
|
26806
27095
|
case "readRepoFile": {
|
|
26807
|
-
const filePath =
|
|
27096
|
+
const filePath = path13.join(repoPath, input.path);
|
|
26808
27097
|
const content = readFile(filePath, input.maxChars ?? 8e3);
|
|
26809
27098
|
info(`Read repo: ${input.path}`);
|
|
26810
27099
|
return content;
|
|
26811
27100
|
}
|
|
26812
27101
|
case "listRepoDir": {
|
|
26813
|
-
const dirPath =
|
|
27102
|
+
const dirPath = path13.join(repoPath, input.path ?? ".");
|
|
26814
27103
|
const entries = listDir(dirPath, input.maxFiles ?? 60);
|
|
26815
27104
|
info(`List repo: ${input.path} \u2192 ${entries.length} entries`);
|
|
26816
27105
|
return entries.length > 0 ? entries.join("\n") : "(empty directory)";
|
|
@@ -26835,17 +27124,17 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26835
27124
|
const filePath = resolveSafePath(harnessDir, relPath);
|
|
26836
27125
|
writeFileSafe(filePath, input.content);
|
|
26837
27126
|
if (relPath.endsWith(".sh")) {
|
|
26838
|
-
|
|
27127
|
+
fs13.chmodSync(filePath, 493);
|
|
26839
27128
|
}
|
|
26840
27129
|
info(`Wrote harness: ${relPath}`);
|
|
26841
27130
|
return `Written ${relPath} (${input.content.length} bytes)`;
|
|
26842
27131
|
}
|
|
26843
27132
|
case "editHarnessFile": {
|
|
26844
27133
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26845
|
-
if (!
|
|
27134
|
+
if (!fs13.existsSync(filePath)) {
|
|
26846
27135
|
return `Error: file not found: ${input.path}`;
|
|
26847
27136
|
}
|
|
26848
|
-
const content =
|
|
27137
|
+
const content = fs13.readFileSync(filePath, "utf8");
|
|
26849
27138
|
const oldStr = input.old_string;
|
|
26850
27139
|
const newStr = input.new_string;
|
|
26851
27140
|
const count = content.split(oldStr).length - 1;
|
|
@@ -26862,10 +27151,10 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26862
27151
|
}
|
|
26863
27152
|
case "deleteHarnessFile": {
|
|
26864
27153
|
const filePath = resolveSafePath(harnessDir, input.path);
|
|
26865
|
-
if (!
|
|
27154
|
+
if (!fs13.existsSync(filePath)) {
|
|
26866
27155
|
return `File not found (already deleted): ${input.path}`;
|
|
26867
27156
|
}
|
|
26868
|
-
|
|
27157
|
+
fs13.unlinkSync(filePath);
|
|
26869
27158
|
info(`Deleted harness: ${input.path}`);
|
|
26870
27159
|
return `Deleted ${input.path}`;
|
|
26871
27160
|
}
|
|
@@ -26916,29 +27205,29 @@ async function authorHarness(repoPath, apiKey, options = {}) {
|
|
|
26916
27205
|
const mode = options.mode ?? "init";
|
|
26917
27206
|
const systemPromptPath = resolvePromptPath("system-authoring.md");
|
|
26918
27207
|
const agentMdTemplatePath = resolveTemplateFile("AGENT.md.template");
|
|
26919
|
-
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.";
|
|
26920
27209
|
if (agentMdTemplatePath) {
|
|
26921
27210
|
systemPrompt += `
|
|
26922
27211
|
|
|
26923
27212
|
## AGENT.md template (starting point for proposeAgentMd)
|
|
26924
27213
|
|
|
26925
|
-
${
|
|
27214
|
+
${fs14.readFileSync(agentMdTemplatePath, "utf8")}`;
|
|
26926
27215
|
}
|
|
26927
27216
|
const tools = buildAuthoringTools();
|
|
26928
27217
|
const messages = [];
|
|
26929
27218
|
let rootListing;
|
|
26930
27219
|
try {
|
|
26931
|
-
rootListing =
|
|
27220
|
+
rootListing = fs14.readdirSync(repoPath).slice(0, 50).join("\n");
|
|
26932
27221
|
} catch {
|
|
26933
27222
|
throw new Error(`Cannot read repo at path: ${repoPath}`);
|
|
26934
27223
|
}
|
|
26935
27224
|
let harnessListing;
|
|
26936
27225
|
try {
|
|
26937
|
-
harnessListing =
|
|
27226
|
+
harnessListing = fs14.readdirSync(harnessDir).join("\n");
|
|
26938
27227
|
} catch {
|
|
26939
27228
|
throw new Error(`Cannot read harness dir at: ${harnessDir}`);
|
|
26940
27229
|
}
|
|
26941
|
-
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.";
|
|
26942
27231
|
const modeInstructions = mode === "maintain" ? `This is a MAINTENANCE run. .har/ already exists and may have been edited by humans.
|
|
26943
27232
|
Update it to reflect current repo changes. Prefer editHarnessFile over writeHarnessFile.
|
|
26944
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/.
|
|
@@ -27018,8 +27307,8 @@ Try running with --verbose to see what the agent is doing.`
|
|
|
27018
27307
|
}
|
|
27019
27308
|
|
|
27020
27309
|
// src/harness/validator.ts
|
|
27021
|
-
var
|
|
27022
|
-
var
|
|
27310
|
+
var fs15 = __toESM(require("fs"));
|
|
27311
|
+
var path15 = __toESM(require("path"));
|
|
27023
27312
|
|
|
27024
27313
|
// src/utils/shell.ts
|
|
27025
27314
|
var import_child_process = require("child_process");
|
|
@@ -27087,205 +27376,6 @@ function runShellCommand(command2, options = {}) {
|
|
|
27087
27376
|
});
|
|
27088
27377
|
}
|
|
27089
27378
|
|
|
27090
|
-
// src/harness/stages.ts
|
|
27091
|
-
var fs13 = __toESM(require("fs"));
|
|
27092
|
-
var path13 = __toESM(require("path"));
|
|
27093
|
-
|
|
27094
|
-
// src/harness/env.ts
|
|
27095
|
-
var fs12 = __toESM(require("fs"));
|
|
27096
|
-
var path12 = __toESM(require("path"));
|
|
27097
|
-
function readHarnessEnv(repoPath) {
|
|
27098
|
-
const envPath = path12.join(getHarnessDir(repoPath), "harness.env");
|
|
27099
|
-
if (!fs12.existsSync(envPath)) return {};
|
|
27100
|
-
const values = {};
|
|
27101
|
-
for (const line of fs12.readFileSync(envPath, "utf8").split("\n")) {
|
|
27102
|
-
const match = line.match(/^export\s+([A-Z0-9_]+)=(.*)$/);
|
|
27103
|
-
if (!match) continue;
|
|
27104
|
-
values[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
27105
|
-
}
|
|
27106
|
-
return values;
|
|
27107
|
-
}
|
|
27108
|
-
function parseHarnessEnvInt(env3, key) {
|
|
27109
|
-
const raw = env3[key];
|
|
27110
|
-
if (raw === void 0 || raw === "") return void 0;
|
|
27111
|
-
const value = Number(raw);
|
|
27112
|
-
if (!Number.isInteger(value)) return void 0;
|
|
27113
|
-
return value;
|
|
27114
|
-
}
|
|
27115
|
-
|
|
27116
|
-
// src/harness/stages.ts
|
|
27117
|
-
var STAGE_REGISTRY_FILE = "stages.json";
|
|
27118
|
-
var AGENT_REQUIRED_KINDS = /* @__PURE__ */ new Set([
|
|
27119
|
-
"launch",
|
|
27120
|
-
"verify",
|
|
27121
|
-
"test",
|
|
27122
|
-
"inspect",
|
|
27123
|
-
"reset",
|
|
27124
|
-
"teardown",
|
|
27125
|
-
"custom"
|
|
27126
|
-
]);
|
|
27127
|
-
function getStageRegistryPath(repoPath) {
|
|
27128
|
-
return path13.join(getHarnessDir(repoPath), STAGE_REGISTRY_FILE);
|
|
27129
|
-
}
|
|
27130
|
-
function synthesizeStageRegistry(repoPath) {
|
|
27131
|
-
const harnessDir = getHarnessDir(repoPath);
|
|
27132
|
-
const stages = [];
|
|
27133
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27134
|
-
id: "setup-infra",
|
|
27135
|
-
kind: "setup",
|
|
27136
|
-
description: "Start shared harness infrastructure.",
|
|
27137
|
-
command: "./.har/setup-infra.sh",
|
|
27138
|
-
requiresAgentId: false
|
|
27139
|
-
});
|
|
27140
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27141
|
-
id: "launch",
|
|
27142
|
-
kind: "launch",
|
|
27143
|
-
description: "Launch an isolated agent environment slot.",
|
|
27144
|
-
command: "./.har/launch.sh {agentId}",
|
|
27145
|
-
requiresAgentId: true
|
|
27146
|
-
});
|
|
27147
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27148
|
-
id: "verify",
|
|
27149
|
-
kind: "verify",
|
|
27150
|
-
description: "Run the project verification pipeline.",
|
|
27151
|
-
command: "./.har/verify.sh {agentId}",
|
|
27152
|
-
requiresAgentId: true,
|
|
27153
|
-
acceptsArgs: ["--full"]
|
|
27154
|
-
});
|
|
27155
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27156
|
-
id: "status",
|
|
27157
|
-
kind: "inspect",
|
|
27158
|
-
description: "Inspect a running agent environment slot.",
|
|
27159
|
-
command: "./.har/agent-cli.sh {agentId} status",
|
|
27160
|
-
requiresAgentId: true
|
|
27161
|
-
});
|
|
27162
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27163
|
-
id: "logs",
|
|
27164
|
-
kind: "inspect",
|
|
27165
|
-
description: "Read recent logs for an agent slot.",
|
|
27166
|
-
command: "./.har/agent-cli.sh {agentId} logs",
|
|
27167
|
-
requiresAgentId: true
|
|
27168
|
-
});
|
|
27169
|
-
addStageIfRunnable(stages, harnessDir, {
|
|
27170
|
-
id: "teardown",
|
|
27171
|
-
kind: "teardown",
|
|
27172
|
-
description: "Tear down an isolated agent environment slot.",
|
|
27173
|
-
command: "./.har/teardown.sh {agentId}",
|
|
27174
|
-
requiresAgentId: true
|
|
27175
|
-
});
|
|
27176
|
-
const harnessEnv = readHarnessEnv(repoPath);
|
|
27177
|
-
const agentSlots = readAgentSlotsFromHarnessEnv(harnessEnv);
|
|
27178
|
-
if (!agentSlots) {
|
|
27179
|
-
throw new Error(
|
|
27180
|
-
"Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
|
|
27181
|
-
);
|
|
27182
|
-
}
|
|
27183
|
-
return {
|
|
27184
|
-
version: "1",
|
|
27185
|
-
artifactsDir: "artifacts",
|
|
27186
|
-
logsDir: "logs",
|
|
27187
|
-
agentSlots,
|
|
27188
|
-
verificationStages: ["typecheck", "unit-tests", "api-health"],
|
|
27189
|
-
stages
|
|
27190
|
-
};
|
|
27191
|
-
}
|
|
27192
|
-
function readAgentSlotsFromHarnessEnv(harnessEnv) {
|
|
27193
|
-
const max = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MAX");
|
|
27194
|
-
if (max === void 0) return null;
|
|
27195
|
-
const min = parseHarnessEnvInt(harnessEnv, "HARNESS_AGENT_SLOT_MIN") ?? HAR_AGENT_SLOT_MIN;
|
|
27196
|
-
if (max < min) {
|
|
27197
|
-
throw new Error("HARNESS_AGENT_SLOT_MAX must be >= HARNESS_AGENT_SLOT_MIN in harness.env");
|
|
27198
|
-
}
|
|
27199
|
-
return { min, max };
|
|
27200
|
-
}
|
|
27201
|
-
function stageScriptExists(harnessDir, stage) {
|
|
27202
|
-
if (stage.script) {
|
|
27203
|
-
return fs13.existsSync(path13.join(harnessDir, stage.script));
|
|
27204
|
-
}
|
|
27205
|
-
if (stage.command) {
|
|
27206
|
-
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
27207
|
-
return fs13.existsSync(path13.join(harnessDir, scriptName));
|
|
27208
|
-
}
|
|
27209
|
-
return fs13.existsSync(path13.join(harnessDir, "stages", `${stage.id}.sh`));
|
|
27210
|
-
}
|
|
27211
|
-
function addStageIfRunnable(stages, harnessDir, stage) {
|
|
27212
|
-
const parsed = HarnessStageSchema.parse(stage);
|
|
27213
|
-
if (stageScriptExists(harnessDir, parsed)) {
|
|
27214
|
-
stages.push(parsed);
|
|
27215
|
-
}
|
|
27216
|
-
}
|
|
27217
|
-
function readStageRegistry(repoPath) {
|
|
27218
|
-
const registryPath = getStageRegistryPath(repoPath);
|
|
27219
|
-
if (!fs13.existsSync(registryPath)) {
|
|
27220
|
-
return synthesizeStageRegistry(repoPath);
|
|
27221
|
-
}
|
|
27222
|
-
const raw = JSON.parse(fs13.readFileSync(registryPath, "utf8"));
|
|
27223
|
-
const parsed = HarnessStageRegistrySchema.safeParse(raw);
|
|
27224
|
-
if (!parsed.success) {
|
|
27225
|
-
throw new Error(`Invalid .har/${STAGE_REGISTRY_FILE}: ${parsed.error.message}`);
|
|
27226
|
-
}
|
|
27227
|
-
return parsed.data;
|
|
27228
|
-
}
|
|
27229
|
-
function writeStageRegistry(repoPath, registry2) {
|
|
27230
|
-
const registryPath = getStageRegistryPath(repoPath);
|
|
27231
|
-
const parsed = HarnessStageRegistrySchema.safeParse(registry2);
|
|
27232
|
-
if (!parsed.success) {
|
|
27233
|
-
throw new Error(`Invalid stage registry: ${parsed.error.message}`);
|
|
27234
|
-
}
|
|
27235
|
-
fs13.writeFileSync(registryPath, JSON.stringify(parsed.data, null, 2) + "\n");
|
|
27236
|
-
}
|
|
27237
|
-
function listStages(repoPath) {
|
|
27238
|
-
return readStageRegistry(repoPath).stages;
|
|
27239
|
-
}
|
|
27240
|
-
function getAgentSlotRange(repoPath) {
|
|
27241
|
-
const registry2 = readStageRegistry(repoPath);
|
|
27242
|
-
if (registry2.agentSlots) {
|
|
27243
|
-
return registry2.agentSlots;
|
|
27244
|
-
}
|
|
27245
|
-
const fromEnv = readAgentSlotsFromHarnessEnv(readHarnessEnv(repoPath));
|
|
27246
|
-
if (fromEnv) {
|
|
27247
|
-
return fromEnv;
|
|
27248
|
-
}
|
|
27249
|
-
throw new Error(
|
|
27250
|
-
"Configure agent slot limits in .har/stages.json (agentSlots) or .har/harness.env (HARNESS_AGENT_SLOT_MIN/MAX)"
|
|
27251
|
-
);
|
|
27252
|
-
}
|
|
27253
|
-
function getAgentSlotIds(repoPath) {
|
|
27254
|
-
const { min, max } = getAgentSlotRange(repoPath);
|
|
27255
|
-
const ids = [];
|
|
27256
|
-
for (let id = min; id <= max; id++) ids.push(id);
|
|
27257
|
-
return ids;
|
|
27258
|
-
}
|
|
27259
|
-
function stageRequiresAgentId(stage) {
|
|
27260
|
-
if (typeof stage.requiresAgentId === "boolean") return stage.requiresAgentId;
|
|
27261
|
-
return AGENT_REQUIRED_KINDS.has(stage.kind);
|
|
27262
|
-
}
|
|
27263
|
-
function resolveStage(repoPath, options) {
|
|
27264
|
-
if (!options.id && !options.kind) return null;
|
|
27265
|
-
const stages = listStages(repoPath);
|
|
27266
|
-
if (options.id) {
|
|
27267
|
-
return stages.find((stage) => stage.id === options.id) ?? null;
|
|
27268
|
-
}
|
|
27269
|
-
const matches = stages.filter((stage) => stage.kind === options.kind);
|
|
27270
|
-
if (matches.length === 1) return matches[0];
|
|
27271
|
-
if (matches.length > 1) {
|
|
27272
|
-
throw new Error(
|
|
27273
|
-
`Multiple stages with kind "${options.kind}". Specify stage id. Available: ${matches.map((s2) => s2.id).join(", ")}`
|
|
27274
|
-
);
|
|
27275
|
-
}
|
|
27276
|
-
return null;
|
|
27277
|
-
}
|
|
27278
|
-
function getVerificationStageIds(repoPath) {
|
|
27279
|
-
const registry2 = readStageRegistry(repoPath);
|
|
27280
|
-
if (Array.isArray(registry2.verificationStages) && registry2.verificationStages.length > 0) {
|
|
27281
|
-
return registry2.verificationStages;
|
|
27282
|
-
}
|
|
27283
|
-
return registry2.stages.filter((stage) => stage.group === "verification").map((stage) => stage.id);
|
|
27284
|
-
}
|
|
27285
|
-
function getArtifactsDir(repoPath) {
|
|
27286
|
-
return readStageRegistry(repoPath).artifactsDir ?? "artifacts";
|
|
27287
|
-
}
|
|
27288
|
-
|
|
27289
27379
|
// src/harness/validator.ts
|
|
27290
27380
|
var REQUIRED_FILES_DEFAULT = [
|
|
27291
27381
|
"README.md",
|
|
@@ -27338,22 +27428,22 @@ var SHELL_SCRIPTS = [
|
|
|
27338
27428
|
function validateHarness(repoPath) {
|
|
27339
27429
|
const harnessDir = getHarnessDir(repoPath);
|
|
27340
27430
|
const issues = [];
|
|
27341
|
-
if (!
|
|
27431
|
+
if (!fs15.existsSync(harnessDir)) {
|
|
27342
27432
|
return {
|
|
27343
27433
|
pass: false,
|
|
27344
27434
|
issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
|
|
27345
27435
|
};
|
|
27346
27436
|
}
|
|
27347
27437
|
for (const file of getRequiredFiles(repoPath)) {
|
|
27348
|
-
const filePath =
|
|
27349
|
-
if (!
|
|
27438
|
+
const filePath = path15.join(harnessDir, file);
|
|
27439
|
+
if (!fs15.existsSync(filePath)) {
|
|
27350
27440
|
issues.push({ file, message: "Required file missing", severity: "error" });
|
|
27351
27441
|
}
|
|
27352
27442
|
}
|
|
27353
27443
|
for (const script of SHELL_SCRIPTS) {
|
|
27354
|
-
const scriptPath =
|
|
27355
|
-
if (!
|
|
27356
|
-
const stat =
|
|
27444
|
+
const scriptPath = path15.join(harnessDir, script);
|
|
27445
|
+
if (!fs15.existsSync(scriptPath)) continue;
|
|
27446
|
+
const stat = fs15.statSync(scriptPath);
|
|
27357
27447
|
if (!(stat.mode & 73)) {
|
|
27358
27448
|
issues.push({ file: script, message: "Script is not executable", severity: "warning" });
|
|
27359
27449
|
}
|
|
@@ -27368,9 +27458,9 @@ function validateHarness(repoPath) {
|
|
|
27368
27458
|
}
|
|
27369
27459
|
const manifest = readManifest(repoPath);
|
|
27370
27460
|
const profile = manifest?.profile ?? "default";
|
|
27371
|
-
const ecosystemPath =
|
|
27372
|
-
if (profile !== "cli" &&
|
|
27373
|
-
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");
|
|
27374
27464
|
if (!content.includes("module.exports")) {
|
|
27375
27465
|
issues.push({
|
|
27376
27466
|
file: "ecosystem.agent.template.cjs",
|
|
@@ -27379,9 +27469,9 @@ function validateHarness(repoPath) {
|
|
|
27379
27469
|
});
|
|
27380
27470
|
}
|
|
27381
27471
|
}
|
|
27382
|
-
const harnessEnvPath =
|
|
27383
|
-
if (
|
|
27384
|
-
const content =
|
|
27472
|
+
const harnessEnvPath = path15.join(harnessDir, "harness.env");
|
|
27473
|
+
if (fs15.existsSync(harnessEnvPath)) {
|
|
27474
|
+
const content = fs15.readFileSync(harnessEnvPath, "utf8");
|
|
27385
27475
|
if (content.includes("TODO: set migrate command")) {
|
|
27386
27476
|
issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
|
|
27387
27477
|
}
|
|
@@ -27389,17 +27479,17 @@ function validateHarness(repoPath) {
|
|
|
27389
27479
|
issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
|
|
27390
27480
|
}
|
|
27391
27481
|
}
|
|
27392
|
-
const verifyPath =
|
|
27393
|
-
if (
|
|
27394
|
-
const content =
|
|
27482
|
+
const verifyPath = path15.join(harnessDir, "verify.sh");
|
|
27483
|
+
if (fs15.existsSync(verifyPath)) {
|
|
27484
|
+
const content = fs15.readFileSync(verifyPath, "utf8");
|
|
27395
27485
|
if (content.includes("echo 'TODO:")) {
|
|
27396
27486
|
issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
|
|
27397
27487
|
}
|
|
27398
27488
|
}
|
|
27399
|
-
const stagesPath =
|
|
27400
|
-
if (
|
|
27489
|
+
const stagesPath = path15.join(harnessDir, "stages.json");
|
|
27490
|
+
if (fs15.existsSync(stagesPath)) {
|
|
27401
27491
|
try {
|
|
27402
|
-
JSON.parse(
|
|
27492
|
+
JSON.parse(fs15.readFileSync(stagesPath, "utf8"));
|
|
27403
27493
|
const registry2 = readStageRegistry(repoPath);
|
|
27404
27494
|
if (registry2.stages.length === 0) {
|
|
27405
27495
|
issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
|
|
@@ -27414,9 +27504,9 @@ function validateHarness(repoPath) {
|
|
|
27414
27504
|
} else {
|
|
27415
27505
|
issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
|
|
27416
27506
|
}
|
|
27417
|
-
const readmePath =
|
|
27418
|
-
if (
|
|
27419
|
-
const content =
|
|
27507
|
+
const readmePath = path15.join(harnessDir, "README.md");
|
|
27508
|
+
if (fs15.existsSync(readmePath)) {
|
|
27509
|
+
const content = fs15.readFileSync(readmePath, "utf8");
|
|
27420
27510
|
if (!content.includes(".har")) {
|
|
27421
27511
|
issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
|
|
27422
27512
|
}
|
|
@@ -27430,8 +27520,8 @@ function validateHarness(repoPath) {
|
|
|
27430
27520
|
async function smokeTestHarness(repoPath) {
|
|
27431
27521
|
const harnessDir = getHarnessDir(repoPath);
|
|
27432
27522
|
const issues = [];
|
|
27433
|
-
const setupScript =
|
|
27434
|
-
if (
|
|
27523
|
+
const setupScript = path15.join(harnessDir, "setup-infra.sh");
|
|
27524
|
+
if (fs15.existsSync(setupScript)) {
|
|
27435
27525
|
const result = run(`bash "${setupScript}"`, { cwd: repoPath });
|
|
27436
27526
|
if (result.code !== 0) {
|
|
27437
27527
|
issues.push({
|
|
@@ -27446,8 +27536,8 @@ async function smokeTestHarness(repoPath) {
|
|
|
27446
27536
|
}
|
|
27447
27537
|
|
|
27448
27538
|
// src/harness/drift.ts
|
|
27449
|
-
var
|
|
27450
|
-
var
|
|
27539
|
+
var fs16 = __toESM(require("fs"));
|
|
27540
|
+
var path16 = __toESM(require("path"));
|
|
27451
27541
|
var PROFILE_DIRS2 = {
|
|
27452
27542
|
default: "har-boilerplate",
|
|
27453
27543
|
cli: "har-boilerplate-cli",
|
|
@@ -27515,48 +27605,51 @@ function substituteProjectName(content, projectName) {
|
|
|
27515
27605
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27516
27606
|
}
|
|
27517
27607
|
function listBoilerplateFiles(boilerplateDir) {
|
|
27518
|
-
if (!
|
|
27519
|
-
return
|
|
27608
|
+
if (!fs16.existsSync(boilerplateDir)) return [];
|
|
27609
|
+
return fs16.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
|
|
27520
27610
|
}
|
|
27521
27611
|
function compareHarnessToTemplate(repoPath) {
|
|
27522
|
-
const resolved =
|
|
27612
|
+
const resolved = path16.resolve(repoPath);
|
|
27523
27613
|
const manifest = readManifest(resolved);
|
|
27524
27614
|
const profile = manifest?.profile ?? "default";
|
|
27525
27615
|
const harnessDir = getHarnessDir(resolved);
|
|
27526
|
-
const projectName =
|
|
27527
|
-
const boilerplateDir =
|
|
27616
|
+
const projectName = path16.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27617
|
+
const boilerplateDir = path16.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
|
|
27528
27618
|
const templateFiles = listBoilerplateFiles(boilerplateDir);
|
|
27529
27619
|
const missing = [];
|
|
27530
27620
|
const checksumMismatch = [];
|
|
27531
27621
|
const extra = [];
|
|
27532
27622
|
const unchanged = [];
|
|
27533
27623
|
for (const file of templateFiles) {
|
|
27534
|
-
const
|
|
27535
|
-
const
|
|
27536
|
-
|
|
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");
|
|
27537
27628
|
if (file === "harness.env") {
|
|
27538
27629
|
templateContent = substituteProjectName(templateContent, projectName);
|
|
27539
27630
|
}
|
|
27540
|
-
if (!
|
|
27541
|
-
missing.push(
|
|
27631
|
+
if (!fs16.existsSync(harnessPath)) {
|
|
27632
|
+
missing.push(harnessFile);
|
|
27542
27633
|
continue;
|
|
27543
27634
|
}
|
|
27544
|
-
const harnessChecksum = computeFileChecksum(
|
|
27635
|
+
const harnessChecksum = computeFileChecksum(fs16.readFileSync(harnessPath, "utf8"));
|
|
27545
27636
|
const templateChecksum = computeFileChecksum(templateContent);
|
|
27546
27637
|
if (harnessChecksum === templateChecksum) {
|
|
27547
|
-
unchanged.push(
|
|
27638
|
+
unchanged.push(harnessFile);
|
|
27548
27639
|
} else {
|
|
27549
|
-
checksumMismatch.push(
|
|
27640
|
+
checksumMismatch.push(harnessFile);
|
|
27550
27641
|
}
|
|
27551
27642
|
}
|
|
27552
|
-
if (
|
|
27553
|
-
for (const file of
|
|
27554
|
-
const full =
|
|
27555
|
-
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;
|
|
27556
27647
|
if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
|
|
27557
27648
|
if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
|
|
27558
27649
|
extra.push(file);
|
|
27559
|
-
} else if (
|
|
27650
|
+
} else if (isExpectedHarnessOnlyFile(file, templateFiles)) {
|
|
27651
|
+
continue;
|
|
27652
|
+
} else if (!templateFiles.includes(file) && !templateFiles.includes(templateFileForHarness(file))) {
|
|
27560
27653
|
extra.push(file);
|
|
27561
27654
|
}
|
|
27562
27655
|
}
|
|
@@ -27564,6 +27657,7 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27564
27657
|
const installed = manifest?.generatorVersion;
|
|
27565
27658
|
const harnessEnv = readHarnessEnv(resolved);
|
|
27566
27659
|
const missingPortVars = missingPortDocumentationVars(profile, harnessEnv);
|
|
27660
|
+
const agentSlotMismatch = detectAgentSlotEnvMismatch(resolved);
|
|
27567
27661
|
return {
|
|
27568
27662
|
generatorVersion: {
|
|
27569
27663
|
installed,
|
|
@@ -27574,14 +27668,15 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
27574
27668
|
checksumMismatch,
|
|
27575
27669
|
extra,
|
|
27576
27670
|
unchanged,
|
|
27577
|
-
missingPortVars
|
|
27671
|
+
missingPortVars,
|
|
27672
|
+
agentSlotMismatch
|
|
27578
27673
|
};
|
|
27579
27674
|
}
|
|
27580
27675
|
|
|
27581
27676
|
// src/harness/maintain-bundle.ts
|
|
27582
|
-
var
|
|
27677
|
+
var fs17 = __toESM(require("fs"));
|
|
27583
27678
|
var os3 = __toESM(require("os"));
|
|
27584
|
-
var
|
|
27679
|
+
var path17 = __toESM(require("path"));
|
|
27585
27680
|
var MAINTAIN_DIR = "maintain";
|
|
27586
27681
|
var PROFILE_DIRS3 = {
|
|
27587
27682
|
default: "har-boilerplate",
|
|
@@ -27592,14 +27687,14 @@ function substituteProjectName2(content, projectName) {
|
|
|
27592
27687
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
27593
27688
|
}
|
|
27594
27689
|
function projectNameFromRepo(repoPath) {
|
|
27595
|
-
return
|
|
27690
|
+
return path17.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
27596
27691
|
}
|
|
27597
27692
|
function boilerplateDirForProfile(profile) {
|
|
27598
|
-
return
|
|
27693
|
+
return path17.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
|
|
27599
27694
|
}
|
|
27600
27695
|
function readBundledTemplateContent(repoPath, profile, file) {
|
|
27601
|
-
const templatePath =
|
|
27602
|
-
let content =
|
|
27696
|
+
const templatePath = path17.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
|
|
27697
|
+
let content = fs17.readFileSync(templatePath, "utf8");
|
|
27603
27698
|
if (file === "harness.env") {
|
|
27604
27699
|
content = substituteProjectName2(content, projectNameFromRepo(repoPath));
|
|
27605
27700
|
}
|
|
@@ -27630,12 +27725,12 @@ function actionHint(file, kind2) {
|
|
|
27630
27725
|
return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
|
|
27631
27726
|
}
|
|
27632
27727
|
function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
|
|
27633
|
-
const tmpDir =
|
|
27728
|
+
const tmpDir = fs17.mkdtempSync(path17.join(os3.tmpdir(), "har-maintain-diff-"));
|
|
27634
27729
|
try {
|
|
27635
|
-
const installedPath =
|
|
27636
|
-
const templatePath =
|
|
27637
|
-
|
|
27638
|
-
|
|
27730
|
+
const installedPath = path17.join(tmpDir, "installed");
|
|
27731
|
+
const templatePath = path17.join(tmpDir, "template");
|
|
27732
|
+
fs17.writeFileSync(installedPath, installedContent);
|
|
27733
|
+
fs17.writeFileSync(templatePath, templateContent);
|
|
27639
27734
|
const result = run(`diff -u "${installedPath}" "${templatePath}"`);
|
|
27640
27735
|
const body = result.stdout.trim();
|
|
27641
27736
|
if (!body) return "";
|
|
@@ -27644,7 +27739,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
|
|
|
27644
27739
|
${body.split("\n").slice(2).join("\n")}
|
|
27645
27740
|
`;
|
|
27646
27741
|
} finally {
|
|
27647
|
-
|
|
27742
|
+
fs17.rmSync(tmpDir, { recursive: true, force: true });
|
|
27648
27743
|
}
|
|
27649
27744
|
}
|
|
27650
27745
|
function buildActions(repoPath, profile, drift) {
|
|
@@ -27659,7 +27754,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
27659
27754
|
});
|
|
27660
27755
|
}
|
|
27661
27756
|
for (const file of drift.checksumMismatch) {
|
|
27662
|
-
const installedPath =
|
|
27757
|
+
const installedPath = path17.join(harnessDir, file);
|
|
27663
27758
|
const installedRel = `maintain/installed/${file}`;
|
|
27664
27759
|
const templateRel = `maintain/templates/${file}`;
|
|
27665
27760
|
const diffRel = `maintain/diffs/${file}.diff`;
|
|
@@ -27667,7 +27762,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
27667
27762
|
file,
|
|
27668
27763
|
kind: "drift",
|
|
27669
27764
|
template: templateRel,
|
|
27670
|
-
installed:
|
|
27765
|
+
installed: fs17.existsSync(installedPath) ? installedRel : void 0,
|
|
27671
27766
|
diff: diffRel,
|
|
27672
27767
|
hint: actionHint(file, "drift")
|
|
27673
27768
|
});
|
|
@@ -27716,6 +27811,18 @@ function buildReadme(report) {
|
|
|
27716
27811
|
lines.push(`- \`${v2}\` \u2014 copy from \`maintain/templates/harness.env\``);
|
|
27717
27812
|
}
|
|
27718
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
|
+
}
|
|
27719
27826
|
lines.push("", "## Stale files (review)", "");
|
|
27720
27827
|
if (report.stale.length === 0) {
|
|
27721
27828
|
lines.push("None detected.");
|
|
@@ -27738,28 +27845,28 @@ function buildReadme(report) {
|
|
|
27738
27845
|
}
|
|
27739
27846
|
function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
27740
27847
|
const harnessDir = getHarnessDir(repoPath);
|
|
27741
|
-
const bundleDir =
|
|
27742
|
-
if (
|
|
27743
|
-
|
|
27848
|
+
const bundleDir = path17.join(harnessDir, MAINTAIN_DIR);
|
|
27849
|
+
if (fs17.existsSync(bundleDir)) {
|
|
27850
|
+
fs17.rmSync(bundleDir, { recursive: true, force: true });
|
|
27744
27851
|
}
|
|
27745
27852
|
const dirs = [
|
|
27746
27853
|
bundleDir,
|
|
27747
|
-
|
|
27748
|
-
|
|
27749
|
-
|
|
27750
|
-
|
|
27854
|
+
path17.join(bundleDir, "templates"),
|
|
27855
|
+
path17.join(bundleDir, "installed"),
|
|
27856
|
+
path17.join(bundleDir, "diffs"),
|
|
27857
|
+
path17.join(bundleDir, "stale")
|
|
27751
27858
|
];
|
|
27752
27859
|
for (const dir of dirs) {
|
|
27753
|
-
|
|
27860
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
27754
27861
|
}
|
|
27755
27862
|
const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
|
|
27756
27863
|
for (const file of affectedFiles) {
|
|
27757
27864
|
const templateContent = readBundledTemplateContent(repoPath, profile, file);
|
|
27758
|
-
writeFileSafe(
|
|
27759
|
-
const harnessPath =
|
|
27760
|
-
if (
|
|
27761
|
-
const installedContent =
|
|
27762
|
-
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);
|
|
27763
27870
|
const diff = createUnifiedDiff(
|
|
27764
27871
|
installedContent,
|
|
27765
27872
|
templateContent,
|
|
@@ -27767,14 +27874,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
27767
27874
|
`templates/${file}`
|
|
27768
27875
|
);
|
|
27769
27876
|
if (diff) {
|
|
27770
|
-
writeFileSafe(
|
|
27877
|
+
writeFileSafe(path17.join(bundleDir, "diffs", `${file}.diff`), diff);
|
|
27771
27878
|
}
|
|
27772
27879
|
}
|
|
27773
27880
|
}
|
|
27774
|
-
writeFileSafe(
|
|
27775
|
-
writeFileSafe(
|
|
27881
|
+
writeFileSafe(path17.join(bundleDir, "README.md"), buildReadme(report));
|
|
27882
|
+
writeFileSafe(path17.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
|
|
27776
27883
|
writeFileSafe(
|
|
27777
|
-
|
|
27884
|
+
path17.join(bundleDir, "validation.json"),
|
|
27778
27885
|
JSON.stringify(report.validation, null, 2) + "\n"
|
|
27779
27886
|
);
|
|
27780
27887
|
if (report.stale.length > 0) {
|
|
@@ -27786,7 +27893,7 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
27786
27893
|
...report.stale.map((s2) => `- **${s2.file}** \u2014 ${s2.hint}`),
|
|
27787
27894
|
""
|
|
27788
27895
|
];
|
|
27789
|
-
writeFileSafe(
|
|
27896
|
+
writeFileSafe(path17.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
|
|
27790
27897
|
}
|
|
27791
27898
|
return bundleDir;
|
|
27792
27899
|
}
|
|
@@ -27802,6 +27909,7 @@ function buildMaintainBundle(repoPath, validation2, drift) {
|
|
|
27802
27909
|
actions: buildActions(repoPath, profile, drift),
|
|
27803
27910
|
stale: buildStale(drift),
|
|
27804
27911
|
missingPortVars: drift.missingPortVars,
|
|
27912
|
+
agentSlotMismatch: drift.agentSlotMismatch,
|
|
27805
27913
|
validation: {
|
|
27806
27914
|
pass: validation2.pass,
|
|
27807
27915
|
errors,
|
|
@@ -27812,9 +27920,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
|
|
|
27812
27920
|
return { bundleDir, report };
|
|
27813
27921
|
}
|
|
27814
27922
|
function removeMaintainBundle(repoPath) {
|
|
27815
|
-
const bundleDir =
|
|
27816
|
-
if (
|
|
27817
|
-
|
|
27923
|
+
const bundleDir = path17.join(getHarnessDir(repoPath), MAINTAIN_DIR);
|
|
27924
|
+
if (fs17.existsSync(bundleDir)) {
|
|
27925
|
+
fs17.rmSync(bundleDir, { recursive: true, force: true });
|
|
27818
27926
|
}
|
|
27819
27927
|
}
|
|
27820
27928
|
function formatMaintainBundlePromptSection(report) {
|
|
@@ -27855,20 +27963,31 @@ function formatMaintainBundlePromptSection(report) {
|
|
|
27855
27963
|
}
|
|
27856
27964
|
lines.push("");
|
|
27857
27965
|
}
|
|
27858
|
-
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) {
|
|
27859
27978
|
lines.push("No template drift detected. Review repo stack changes manually if needed.", "");
|
|
27860
27979
|
}
|
|
27861
27980
|
return lines.join("\n");
|
|
27862
27981
|
}
|
|
27863
27982
|
|
|
27864
27983
|
// src/harness/stage-templates.ts
|
|
27865
|
-
var
|
|
27866
|
-
var
|
|
27984
|
+
var fs19 = __toESM(require("fs"));
|
|
27985
|
+
var path18 = __toESM(require("path"));
|
|
27867
27986
|
|
|
27868
27987
|
// src/harness/parser.ts
|
|
27869
|
-
var
|
|
27988
|
+
var fs18 = __toESM(require("fs"));
|
|
27870
27989
|
function harnessExists(repoPath) {
|
|
27871
|
-
return
|
|
27990
|
+
return fs18.existsSync(`${repoPath}/.har/setup-infra.sh`);
|
|
27872
27991
|
}
|
|
27873
27992
|
|
|
27874
27993
|
// src/harness/stage-templates.ts
|
|
@@ -27891,16 +28010,16 @@ var StageTemplateManifestSchema = external_exports.object({
|
|
|
27891
28010
|
docsPath: external_exports.string().min(1)
|
|
27892
28011
|
});
|
|
27893
28012
|
function resolveTemplateDir(templateId) {
|
|
27894
|
-
const dir =
|
|
27895
|
-
if (!
|
|
28013
|
+
const dir = path18.join(resolveTemplatesDir(), "stage-templates", templateId);
|
|
28014
|
+
if (!fs19.existsSync(dir)) {
|
|
27896
28015
|
throw new Error(`Stage template not found: ${templateId}. Run npm run build.`);
|
|
27897
28016
|
}
|
|
27898
28017
|
return dir;
|
|
27899
28018
|
}
|
|
27900
28019
|
function readTemplateManifest(templateId) {
|
|
27901
|
-
const manifestPath =
|
|
28020
|
+
const manifestPath = path18.join(resolveTemplateDir(templateId), "template.manifest.json");
|
|
27902
28021
|
const parsed = StageTemplateManifestSchema.safeParse(
|
|
27903
|
-
JSON.parse(
|
|
28022
|
+
JSON.parse(fs19.readFileSync(manifestPath, "utf8"))
|
|
27904
28023
|
);
|
|
27905
28024
|
if (!parsed.success) {
|
|
27906
28025
|
throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
|
|
@@ -27911,40 +28030,40 @@ function readTemplateManifest(templateId) {
|
|
|
27911
28030
|
return parsed.data;
|
|
27912
28031
|
}
|
|
27913
28032
|
function ensureParentDir(filePath) {
|
|
27914
|
-
const parent =
|
|
27915
|
-
if (!
|
|
27916
|
-
|
|
28033
|
+
const parent = path18.dirname(filePath);
|
|
28034
|
+
if (!fs19.existsSync(parent)) {
|
|
28035
|
+
fs19.mkdirSync(parent, { recursive: true });
|
|
27917
28036
|
}
|
|
27918
28037
|
}
|
|
27919
28038
|
function copyTemplateFile(templateDir, file, repoPath, force) {
|
|
27920
|
-
const srcPath =
|
|
27921
|
-
const destPath =
|
|
27922
|
-
if (!
|
|
28039
|
+
const srcPath = path18.join(templateDir, file.src);
|
|
28040
|
+
const destPath = path18.join(repoPath, file.dest);
|
|
28041
|
+
if (!fs19.existsSync(srcPath)) {
|
|
27923
28042
|
throw new Error(`Template file missing: ${file.src}`);
|
|
27924
28043
|
}
|
|
27925
|
-
if (
|
|
28044
|
+
if (fs19.existsSync(destPath) && !force) {
|
|
27926
28045
|
throw new Error(
|
|
27927
28046
|
`File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
|
|
27928
28047
|
);
|
|
27929
28048
|
}
|
|
27930
28049
|
ensureParentDir(destPath);
|
|
27931
|
-
|
|
28050
|
+
fs19.copyFileSync(srcPath, destPath);
|
|
27932
28051
|
if (file.executable) {
|
|
27933
|
-
|
|
28052
|
+
fs19.chmodSync(destPath, 493);
|
|
27934
28053
|
}
|
|
27935
28054
|
return { written: true, path: file.dest };
|
|
27936
28055
|
}
|
|
27937
28056
|
function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
27938
|
-
const packagePath =
|
|
27939
|
-
if (!
|
|
28057
|
+
const packagePath = path18.join(repoPath, "package.json");
|
|
28058
|
+
if (!fs19.existsSync(packagePath)) {
|
|
27940
28059
|
throw new Error("No package.json in repo root. Add one before applying this stage template.");
|
|
27941
28060
|
}
|
|
27942
|
-
const fragmentPath =
|
|
27943
|
-
if (!
|
|
28061
|
+
const fragmentPath = path18.join(templateDir, fragmentRelPath);
|
|
28062
|
+
if (!fs19.existsSync(fragmentPath)) {
|
|
27944
28063
|
throw new Error(`Package fragment missing: ${fragmentRelPath}`);
|
|
27945
28064
|
}
|
|
27946
|
-
const pkg = JSON.parse(
|
|
27947
|
-
const fragment = JSON.parse(
|
|
28065
|
+
const pkg = JSON.parse(fs19.readFileSync(packagePath, "utf8"));
|
|
28066
|
+
const fragment = JSON.parse(fs19.readFileSync(fragmentPath, "utf8"));
|
|
27948
28067
|
for (const section of ["scripts", "devDependencies"]) {
|
|
27949
28068
|
const existing = pkg[section] ?? {};
|
|
27950
28069
|
const incoming = fragment[section] ?? {};
|
|
@@ -27957,7 +28076,7 @@ function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
|
27957
28076
|
}
|
|
27958
28077
|
pkg[section] = existing;
|
|
27959
28078
|
}
|
|
27960
|
-
|
|
28079
|
+
fs19.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
27961
28080
|
}
|
|
27962
28081
|
function patchStageRegistry(repoPath, manifest, force) {
|
|
27963
28082
|
const registry2 = readStageRegistry(repoPath);
|
|
@@ -27997,8 +28116,8 @@ function assertHarnessPresent(repoPath) {
|
|
|
27997
28116
|
}
|
|
27998
28117
|
function assertStageNotPresent(repoPath, stageId, force) {
|
|
27999
28118
|
if (force) return;
|
|
28000
|
-
const scriptPath =
|
|
28001
|
-
if (
|
|
28119
|
+
const scriptPath = path18.join(repoPath, ".har", "stages", `${stageId}.sh`);
|
|
28120
|
+
if (fs19.existsSync(scriptPath)) {
|
|
28002
28121
|
throw new Error(
|
|
28003
28122
|
`Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
|
|
28004
28123
|
);
|
|
@@ -28011,7 +28130,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
|
|
|
28011
28130
|
}
|
|
28012
28131
|
}
|
|
28013
28132
|
function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
28014
|
-
const resolved =
|
|
28133
|
+
const resolved = path18.resolve(repoPath);
|
|
28015
28134
|
const force = options.force ?? false;
|
|
28016
28135
|
const warnings = [];
|
|
28017
28136
|
const filesWritten = [];
|
|
@@ -28030,7 +28149,7 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28030
28149
|
if (file.skipFlag === "skipCi" && options.skipCi) {
|
|
28031
28150
|
continue;
|
|
28032
28151
|
}
|
|
28033
|
-
if (
|
|
28152
|
+
if (fs19.existsSync(path18.join(resolved, file.dest)) && !force) {
|
|
28034
28153
|
warnings.push(`Skipped optional file (exists): ${file.dest}`);
|
|
28035
28154
|
continue;
|
|
28036
28155
|
}
|
|
@@ -28065,9 +28184,9 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28065
28184
|
};
|
|
28066
28185
|
}
|
|
28067
28186
|
function listStageTemplateIds() {
|
|
28068
|
-
const root =
|
|
28069
|
-
if (!
|
|
28070
|
-
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(
|
|
28071
28190
|
(name) => STAGE_TEMPLATE_IDS.includes(name)
|
|
28072
28191
|
);
|
|
28073
28192
|
}
|
|
@@ -28094,11 +28213,11 @@ function validateAgentId(id, repoPath) {
|
|
|
28094
28213
|
// src/core/harness.ts
|
|
28095
28214
|
function listHarnessScripts(repoPath) {
|
|
28096
28215
|
const harnessDir = getHarnessDir(repoPath);
|
|
28097
|
-
if (!
|
|
28098
|
-
return
|
|
28216
|
+
if (!fs20.existsSync(harnessDir)) return [];
|
|
28217
|
+
return fs20.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
|
|
28099
28218
|
}
|
|
28100
28219
|
function describeProject(repoPath) {
|
|
28101
|
-
const resolved =
|
|
28220
|
+
const resolved = path19.resolve(repoPath);
|
|
28102
28221
|
const manifest = readManifest(resolved);
|
|
28103
28222
|
const present = harnessExists(resolved);
|
|
28104
28223
|
return {
|
|
@@ -28118,14 +28237,15 @@ function describeProject(repoPath) {
|
|
|
28118
28237
|
};
|
|
28119
28238
|
}
|
|
28120
28239
|
async function initHarness(options) {
|
|
28121
|
-
const repoPath =
|
|
28122
|
-
if (!
|
|
28240
|
+
const repoPath = path19.resolve(options.repoPath);
|
|
28241
|
+
if (!fs20.existsSync(repoPath)) {
|
|
28123
28242
|
throw new Error(`Path not found: ${repoPath}`);
|
|
28124
28243
|
}
|
|
28125
28244
|
const scaffold = scaffoldHarnessBoilerplate(repoPath, {
|
|
28126
28245
|
force: options.force,
|
|
28127
28246
|
profile: options.profile
|
|
28128
28247
|
});
|
|
28248
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
28129
28249
|
let adaptationSummary;
|
|
28130
28250
|
if (options.auto) {
|
|
28131
28251
|
const apiKey = requireApiKey();
|
|
@@ -28150,9 +28270,9 @@ async function initHarness(options) {
|
|
|
28150
28270
|
};
|
|
28151
28271
|
}
|
|
28152
28272
|
async function maintainHarness(options) {
|
|
28153
|
-
const repoPath =
|
|
28273
|
+
const repoPath = path19.resolve(options.repoPath);
|
|
28154
28274
|
const harnessDir = getHarnessDir(repoPath);
|
|
28155
|
-
if (!
|
|
28275
|
+
if (!fs20.existsSync(harnessDir)) {
|
|
28156
28276
|
throw new Error('No .har/ found. Run "har env init" first.');
|
|
28157
28277
|
}
|
|
28158
28278
|
if (options.auto) {
|
|
@@ -28177,6 +28297,7 @@ async function maintainHarness(options) {
|
|
|
28177
28297
|
if (!validation2.pass) {
|
|
28178
28298
|
throw new Error("Cannot finalize: harness validation has errors. Fix them first.");
|
|
28179
28299
|
}
|
|
28300
|
+
syncAgentSlotsToHarnessEnv(repoPath);
|
|
28180
28301
|
const existing = readManifest(repoPath);
|
|
28181
28302
|
finalizeHarness(
|
|
28182
28303
|
repoPath,
|
|
@@ -28203,11 +28324,11 @@ function addStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28203
28324
|
}
|
|
28204
28325
|
|
|
28205
28326
|
// src/harness/custom-stage.ts
|
|
28206
|
-
var
|
|
28207
|
-
var
|
|
28327
|
+
var fs21 = __toESM(require("fs"));
|
|
28328
|
+
var path20 = __toESM(require("path"));
|
|
28208
28329
|
var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
28209
28330
|
function addCustomStage(repoPath, options) {
|
|
28210
|
-
const resolved =
|
|
28331
|
+
const resolved = path20.resolve(repoPath);
|
|
28211
28332
|
if (!harnessExists(resolved)) {
|
|
28212
28333
|
throw new Error('No .har/ harness found. Run "har env init" first.');
|
|
28213
28334
|
}
|
|
@@ -28244,20 +28365,20 @@ function addCustomStage(repoPath, options) {
|
|
|
28244
28365
|
if (options.command) {
|
|
28245
28366
|
stage.command = options.command;
|
|
28246
28367
|
} else {
|
|
28247
|
-
const scriptRel =
|
|
28248
|
-
const scriptAbs =
|
|
28249
|
-
if (
|
|
28368
|
+
const scriptRel = path20.join("stages", `${id}.sh`);
|
|
28369
|
+
const scriptAbs = path20.join(resolved, ".har", scriptRel);
|
|
28370
|
+
if (fs21.existsSync(scriptAbs) && !options.force) {
|
|
28250
28371
|
throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
|
|
28251
28372
|
}
|
|
28252
|
-
const skeletonPath =
|
|
28373
|
+
const skeletonPath = path20.join(
|
|
28253
28374
|
resolveTemplatesDir(),
|
|
28254
28375
|
"stage-templates",
|
|
28255
28376
|
"custom-stage-skeleton.sh"
|
|
28256
28377
|
);
|
|
28257
|
-
const skeleton =
|
|
28258
|
-
|
|
28259
|
-
|
|
28260
|
-
|
|
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);
|
|
28261
28382
|
filesWritten.push(`.har/${scriptRel}`);
|
|
28262
28383
|
stage.script = scriptRel;
|
|
28263
28384
|
stage.artifacts = [
|
|
@@ -28305,8 +28426,8 @@ function addCustomStage(repoPath, options) {
|
|
|
28305
28426
|
}
|
|
28306
28427
|
|
|
28307
28428
|
// src/harness/adaptation-prompt.ts
|
|
28308
|
-
var
|
|
28309
|
-
var
|
|
28429
|
+
var fs22 = __toESM(require("fs"));
|
|
28430
|
+
var path21 = __toESM(require("path"));
|
|
28310
28431
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
28311
28432
|
var PROFILE_HINTS = {
|
|
28312
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.",
|
|
@@ -28318,7 +28439,7 @@ function loadTemplate(name) {
|
|
|
28318
28439
|
if (!filePath) {
|
|
28319
28440
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
28320
28441
|
}
|
|
28321
|
-
return
|
|
28442
|
+
return fs22.readFileSync(filePath, "utf8");
|
|
28322
28443
|
}
|
|
28323
28444
|
function applyProfilePlaceholders(content, profile) {
|
|
28324
28445
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -28340,7 +28461,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
|
28340
28461
|
}
|
|
28341
28462
|
function writeAdaptationPrompt(repoPath, content) {
|
|
28342
28463
|
const harnessDir = getHarnessDir(repoPath);
|
|
28343
|
-
const filePath =
|
|
28464
|
+
const filePath = path21.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
28344
28465
|
writeFileSafe(filePath, content);
|
|
28345
28466
|
return filePath;
|
|
28346
28467
|
}
|
|
@@ -28359,25 +28480,25 @@ function printAdaptationPrompt(content) {
|
|
|
28359
28480
|
}
|
|
28360
28481
|
|
|
28361
28482
|
// src/harness/cursor-rule.ts
|
|
28362
|
-
var
|
|
28363
|
-
var
|
|
28483
|
+
var fs23 = __toESM(require("fs"));
|
|
28484
|
+
var path22 = __toESM(require("path"));
|
|
28364
28485
|
var readline4 = __toESM(require("readline"));
|
|
28365
28486
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
28366
28487
|
function getCursorRulePath(repoPath) {
|
|
28367
|
-
return
|
|
28488
|
+
return path22.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
28368
28489
|
}
|
|
28369
28490
|
function isCursorWorkspace(repoPath) {
|
|
28370
|
-
return
|
|
28491
|
+
return fs23.existsSync(path22.join(repoPath, ".cursor"));
|
|
28371
28492
|
}
|
|
28372
28493
|
function cursorRuleExists(repoPath) {
|
|
28373
|
-
return
|
|
28494
|
+
return fs23.existsSync(getCursorRulePath(repoPath));
|
|
28374
28495
|
}
|
|
28375
28496
|
function scaffoldCursorRule(repoPath) {
|
|
28376
28497
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
28377
28498
|
if (!templatePath) {
|
|
28378
28499
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
28379
28500
|
}
|
|
28380
|
-
const content =
|
|
28501
|
+
const content = fs23.readFileSync(templatePath, "utf8");
|
|
28381
28502
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
28382
28503
|
}
|
|
28383
28504
|
async function handleCursorRule(options) {
|
|
@@ -28503,26 +28624,26 @@ function buildStageResult(input) {
|
|
|
28503
28624
|
}
|
|
28504
28625
|
|
|
28505
28626
|
// src/core/local-executor.ts
|
|
28506
|
-
var
|
|
28507
|
-
var
|
|
28627
|
+
var fs25 = __toESM(require("fs"));
|
|
28628
|
+
var path24 = __toESM(require("path"));
|
|
28508
28629
|
|
|
28509
28630
|
// src/core/slot-registry.ts
|
|
28510
|
-
var
|
|
28511
|
-
var
|
|
28631
|
+
var fs24 = __toESM(require("fs"));
|
|
28632
|
+
var path23 = __toESM(require("path"));
|
|
28512
28633
|
function getSlotRegistryDir(repoPath) {
|
|
28513
|
-
return
|
|
28634
|
+
return path23.join(getHarnessDir(repoPath), "slots");
|
|
28514
28635
|
}
|
|
28515
28636
|
function getSlotRegistryPath(repoPath, agentId) {
|
|
28516
|
-
return
|
|
28637
|
+
return path23.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
28517
28638
|
}
|
|
28518
28639
|
function isSlotResumable(session) {
|
|
28519
28640
|
return session?.status === "failed" || session?.status === "starting";
|
|
28520
28641
|
}
|
|
28521
28642
|
function readSlotRegistry(repoPath, agentId) {
|
|
28522
28643
|
const file = getSlotRegistryPath(repoPath, agentId);
|
|
28523
|
-
if (!
|
|
28644
|
+
if (!fs24.existsSync(file)) return void 0;
|
|
28524
28645
|
try {
|
|
28525
|
-
const raw = JSON.parse(
|
|
28646
|
+
const raw = JSON.parse(fs24.readFileSync(file, "utf8"));
|
|
28526
28647
|
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
28527
28648
|
return result.success ? result.data : void 0;
|
|
28528
28649
|
} catch {
|
|
@@ -28532,7 +28653,7 @@ function readSlotRegistry(repoPath, agentId) {
|
|
|
28532
28653
|
|
|
28533
28654
|
// src/core/local-executor.ts
|
|
28534
28655
|
function resolveRepoPath(repoPath) {
|
|
28535
|
-
return
|
|
28656
|
+
return path24.resolve(repoPath);
|
|
28536
28657
|
}
|
|
28537
28658
|
function buildPreviewUrlsFromPorts(ports, env3) {
|
|
28538
28659
|
const urls = {};
|
|
@@ -28584,14 +28705,14 @@ function substituteAgentId(value, agentId) {
|
|
|
28584
28705
|
function resolveStageScriptPath(repoPath, stage) {
|
|
28585
28706
|
const harnessDir = getHarnessDir(repoPath);
|
|
28586
28707
|
if (stage.script) {
|
|
28587
|
-
return
|
|
28708
|
+
return path24.join(harnessDir, stage.script);
|
|
28588
28709
|
}
|
|
28589
28710
|
if (stage.command) {
|
|
28590
28711
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
28591
|
-
return
|
|
28712
|
+
return path24.join(harnessDir, scriptName);
|
|
28592
28713
|
}
|
|
28593
|
-
const stageScript =
|
|
28594
|
-
if (
|
|
28714
|
+
const stageScript = path24.join(harnessDir, "stages", `${stage.id}.sh`);
|
|
28715
|
+
if (fs25.existsSync(stageScript)) {
|
|
28595
28716
|
return stageScript;
|
|
28596
28717
|
}
|
|
28597
28718
|
throw new Error(
|
|
@@ -28606,7 +28727,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
28606
28727
|
...harnessEnv,
|
|
28607
28728
|
...stage.env ?? {}
|
|
28608
28729
|
};
|
|
28609
|
-
const cwd = stage.cwd ?
|
|
28730
|
+
const cwd = stage.cwd ? path24.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
|
|
28610
28731
|
const extraArgs = options.args ?? [];
|
|
28611
28732
|
const launchFlagArgs = [];
|
|
28612
28733
|
if (stage.kind === "launch" && options.launchFlags) {
|
|
@@ -28683,19 +28804,19 @@ var LocalScriptExecutor = class {
|
|
|
28683
28804
|
const repoPath = resolveRepoPath(ctx.repoPath);
|
|
28684
28805
|
const harnessDir = getHarnessDir(repoPath);
|
|
28685
28806
|
const artifactsDirName = getArtifactsDir(repoPath);
|
|
28686
|
-
const artifactsDir =
|
|
28687
|
-
if (!
|
|
28807
|
+
const artifactsDir = path24.join(harnessDir, artifactsDirName);
|
|
28808
|
+
if (!fs25.existsSync(artifactsDir)) return [];
|
|
28688
28809
|
const entries = [];
|
|
28689
28810
|
const walk = (dir, prefix) => {
|
|
28690
|
-
for (const entry of
|
|
28691
|
-
const full =
|
|
28692
|
-
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);
|
|
28693
28814
|
if (entry.isDirectory()) {
|
|
28694
28815
|
walk(full, relative2);
|
|
28695
28816
|
continue;
|
|
28696
28817
|
}
|
|
28697
28818
|
if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
|
|
28698
|
-
const stat =
|
|
28819
|
+
const stat = fs25.statSync(full);
|
|
28699
28820
|
entries.push({
|
|
28700
28821
|
path: full,
|
|
28701
28822
|
relativePath: relative2,
|
|
@@ -28711,7 +28832,7 @@ var LocalScriptExecutor = class {
|
|
|
28711
28832
|
var localScriptExecutor = new LocalScriptExecutor();
|
|
28712
28833
|
|
|
28713
28834
|
// src/core/control-sync.ts
|
|
28714
|
-
var
|
|
28835
|
+
var path38 = __toESM(require("path"));
|
|
28715
28836
|
|
|
28716
28837
|
// src/core/control-config.ts
|
|
28717
28838
|
var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
|
|
@@ -28723,32 +28844,32 @@ function isControlEnabled() {
|
|
|
28723
28844
|
}
|
|
28724
28845
|
|
|
28725
28846
|
// src/core/control-registry.ts
|
|
28726
|
-
var
|
|
28847
|
+
var fs26 = __toESM(require("fs"));
|
|
28727
28848
|
var os4 = __toESM(require("os"));
|
|
28728
|
-
var
|
|
28849
|
+
var path25 = __toESM(require("path"));
|
|
28729
28850
|
function getRegistryPath() {
|
|
28730
28851
|
if (process.env.HAR_CONTROL_REGISTRY_PATH) {
|
|
28731
|
-
return
|
|
28852
|
+
return path25.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
|
|
28732
28853
|
}
|
|
28733
|
-
return
|
|
28854
|
+
return path25.join(os4.homedir(), ".har", "repos.json");
|
|
28734
28855
|
}
|
|
28735
28856
|
function readRegistry() {
|
|
28736
28857
|
const registryPath = getRegistryPath();
|
|
28737
28858
|
try {
|
|
28738
|
-
if (!
|
|
28739
|
-
return JSON.parse(
|
|
28859
|
+
if (!fs26.existsSync(registryPath)) return { repos: [] };
|
|
28860
|
+
return JSON.parse(fs26.readFileSync(registryPath, "utf8"));
|
|
28740
28861
|
} catch {
|
|
28741
28862
|
return { repos: [] };
|
|
28742
28863
|
}
|
|
28743
28864
|
}
|
|
28744
28865
|
function writeRegistry(registry2) {
|
|
28745
28866
|
const registryPath = getRegistryPath();
|
|
28746
|
-
|
|
28747
|
-
|
|
28867
|
+
fs26.mkdirSync(path25.dirname(registryPath), { recursive: true });
|
|
28868
|
+
fs26.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
|
|
28748
28869
|
}
|
|
28749
28870
|
function recordRepoForControlSync(repoPath) {
|
|
28750
28871
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
28751
|
-
const resolved =
|
|
28872
|
+
const resolved = path25.resolve(repoPath);
|
|
28752
28873
|
if (!readManifest(resolved)) return;
|
|
28753
28874
|
const registry2 = readRegistry();
|
|
28754
28875
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -28757,7 +28878,7 @@ function recordRepoForControlSync(repoPath) {
|
|
|
28757
28878
|
}
|
|
28758
28879
|
function listRegisteredRepos() {
|
|
28759
28880
|
const registry2 = readRegistry();
|
|
28760
|
-
const kept = registry2.repos.filter((repoPath) =>
|
|
28881
|
+
const kept = registry2.repos.filter((repoPath) => fs26.existsSync(repoPath) && readManifest(repoPath));
|
|
28761
28882
|
if (kept.length !== registry2.repos.length) {
|
|
28762
28883
|
writeRegistry({ repos: kept });
|
|
28763
28884
|
}
|
|
@@ -28765,17 +28886,17 @@ function listRegisteredRepos() {
|
|
|
28765
28886
|
}
|
|
28766
28887
|
|
|
28767
28888
|
// src/core/slot-status.ts
|
|
28768
|
-
var
|
|
28889
|
+
var fs31 = __toESM(require("fs"));
|
|
28769
28890
|
var os7 = __toESM(require("os"));
|
|
28770
|
-
var
|
|
28891
|
+
var path31 = __toESM(require("path"));
|
|
28771
28892
|
var import_child_process7 = require("child_process");
|
|
28772
28893
|
|
|
28773
28894
|
// src/core/runs.ts
|
|
28774
28895
|
var crypto2 = __toESM(require("crypto"));
|
|
28775
28896
|
var import_child_process2 = require("child_process");
|
|
28776
|
-
var
|
|
28897
|
+
var fs27 = __toESM(require("fs"));
|
|
28777
28898
|
var os5 = __toESM(require("os"));
|
|
28778
|
-
var
|
|
28899
|
+
var path26 = __toESM(require("path"));
|
|
28779
28900
|
var RUNS_DIR = "runs";
|
|
28780
28901
|
function gitCommonDir(cwd) {
|
|
28781
28902
|
try {
|
|
@@ -28784,7 +28905,7 @@ function gitCommonDir(cwd) {
|
|
|
28784
28905
|
encoding: "utf8",
|
|
28785
28906
|
stdio: ["pipe", "pipe", "ignore"]
|
|
28786
28907
|
}).trim();
|
|
28787
|
-
return out ?
|
|
28908
|
+
return out ? path26.resolve(cwd, out) : void 0;
|
|
28788
28909
|
} catch {
|
|
28789
28910
|
return void 0;
|
|
28790
28911
|
}
|
|
@@ -28806,7 +28927,7 @@ function gitPrefix(cwd) {
|
|
|
28806
28927
|
}
|
|
28807
28928
|
}
|
|
28808
28929
|
function getRunsDir(harnessRoot) {
|
|
28809
|
-
return
|
|
28930
|
+
return path26.join(getHarnessDir(harnessRoot), RUNS_DIR);
|
|
28810
28931
|
}
|
|
28811
28932
|
function formatLocalDate(d2) {
|
|
28812
28933
|
const y2 = d2.getFullYear();
|
|
@@ -28826,24 +28947,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
|
|
|
28826
28947
|
const timePart = formatLocalTime(started);
|
|
28827
28948
|
const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
|
|
28828
28949
|
let filename = `${timePart}_${stageId}${agentPart}.json`;
|
|
28829
|
-
const fullPath =
|
|
28830
|
-
if (
|
|
28950
|
+
const fullPath = path26.join(runsDir, dateFolder, filename);
|
|
28951
|
+
if (fs27.existsSync(fullPath)) {
|
|
28831
28952
|
filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
|
|
28832
28953
|
}
|
|
28833
|
-
return
|
|
28954
|
+
return path26.join(dateFolder, filename);
|
|
28834
28955
|
}
|
|
28835
28956
|
function resolveRunFilePath(harnessRoot, run2) {
|
|
28836
28957
|
if (run2.relativePath) {
|
|
28837
|
-
return
|
|
28958
|
+
return path26.join(getRunsDir(harnessRoot), run2.relativePath);
|
|
28838
28959
|
}
|
|
28839
|
-
return
|
|
28960
|
+
return path26.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
|
|
28840
28961
|
}
|
|
28841
28962
|
function collectRunFiles(runsDir) {
|
|
28842
|
-
if (!
|
|
28963
|
+
if (!fs27.existsSync(runsDir)) return [];
|
|
28843
28964
|
const files = [];
|
|
28844
28965
|
const walk = (dir) => {
|
|
28845
|
-
for (const entry of
|
|
28846
|
-
const full =
|
|
28966
|
+
for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
|
|
28967
|
+
const full = path26.join(dir, entry.name);
|
|
28847
28968
|
if (entry.isDirectory()) {
|
|
28848
28969
|
walk(full);
|
|
28849
28970
|
} else if (entry.name.endsWith(".json")) {
|
|
@@ -28857,30 +28978,30 @@ function collectRunFiles(runsDir) {
|
|
|
28857
28978
|
function resolveAgentWorkDir(harnessRoot, agentId) {
|
|
28858
28979
|
if (agentId === void 0) return void 0;
|
|
28859
28980
|
const entry = readSlotRegistry(harnessRoot, agentId);
|
|
28860
|
-
if (entry?.workDir &&
|
|
28981
|
+
if (entry?.workDir && fs27.existsSync(entry.workDir)) return entry.workDir;
|
|
28861
28982
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28862
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
28863
|
-
const worktreeDir =
|
|
28983
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path26.basename(harnessRoot);
|
|
28984
|
+
const worktreeDir = path26.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
28864
28985
|
const relPrefix = gitPrefix(harnessRoot);
|
|
28865
28986
|
const sessionEnvFiles = [];
|
|
28866
|
-
const worktreesRoot =
|
|
28867
|
-
if (
|
|
28987
|
+
const worktreesRoot = path26.join(os5.homedir(), "worktrees");
|
|
28988
|
+
if (fs27.existsSync(worktreesRoot)) {
|
|
28868
28989
|
const suffix = `-har-agent-${agentId}-`;
|
|
28869
|
-
for (const entry2 of
|
|
28990
|
+
for (const entry2 of fs27.readdirSync(worktreesRoot, { withFileTypes: true })) {
|
|
28870
28991
|
if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
|
|
28871
|
-
const sessionDir =
|
|
28992
|
+
const sessionDir = path26.join(worktreesRoot, entry2.name);
|
|
28872
28993
|
if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
|
|
28873
|
-
sessionEnvFiles.push(
|
|
28994
|
+
sessionEnvFiles.push(path26.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
|
|
28874
28995
|
}
|
|
28875
28996
|
}
|
|
28876
28997
|
const candidates = [
|
|
28877
|
-
|
|
28878
|
-
|
|
28998
|
+
path26.join(worktreeDir, `.env.agent.${agentId}`),
|
|
28999
|
+
path26.join(harnessRoot, `.env.agent.${agentId}`),
|
|
28879
29000
|
...sessionEnvFiles.sort()
|
|
28880
29001
|
];
|
|
28881
29002
|
for (const envFile of candidates) {
|
|
28882
|
-
if (!
|
|
28883
|
-
const content =
|
|
29003
|
+
if (!fs27.existsSync(envFile)) continue;
|
|
29004
|
+
const content = fs27.readFileSync(envFile, "utf8");
|
|
28884
29005
|
const match = content.match(/^REPO_ROOT=(.+)$/m);
|
|
28885
29006
|
if (match) return match[1].trim();
|
|
28886
29007
|
}
|
|
@@ -28892,11 +29013,11 @@ function createRun(ctx, meta) {
|
|
|
28892
29013
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28893
29014
|
const runId = crypto2.randomUUID();
|
|
28894
29015
|
const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
|
|
28895
|
-
const runFilePath =
|
|
28896
|
-
|
|
29016
|
+
const runFilePath = path26.join(runsDir, relativePath);
|
|
29017
|
+
fs27.mkdirSync(path26.dirname(runFilePath), { recursive: true });
|
|
28897
29018
|
const run2 = RunRecordSchema.parse({
|
|
28898
29019
|
runId,
|
|
28899
|
-
repoPath:
|
|
29020
|
+
repoPath: path26.resolve(ctx.repoPath),
|
|
28900
29021
|
harnessRoot,
|
|
28901
29022
|
stageId: meta.stageId,
|
|
28902
29023
|
kind: meta.kind,
|
|
@@ -28907,7 +29028,7 @@ function createRun(ctx, meta) {
|
|
|
28907
29028
|
relativePath,
|
|
28908
29029
|
trigger: ctx.trigger ?? "cli"
|
|
28909
29030
|
});
|
|
28910
|
-
|
|
29031
|
+
fs27.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
|
|
28911
29032
|
return run2;
|
|
28912
29033
|
}
|
|
28913
29034
|
function finishRun(repoPath, runId, update) {
|
|
@@ -28926,13 +29047,13 @@ function finishRun(repoPath, runId, update) {
|
|
|
28926
29047
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28927
29048
|
});
|
|
28928
29049
|
const runPath = resolveRunFilePath(harnessRoot, finished);
|
|
28929
|
-
|
|
29050
|
+
fs27.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
|
|
28930
29051
|
return finished;
|
|
28931
29052
|
}
|
|
28932
29053
|
function findRunRecord(harnessRoot, runId) {
|
|
28933
29054
|
const runsDir = getRunsDir(harnessRoot);
|
|
28934
29055
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28935
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
29056
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
|
|
28936
29057
|
if (parsed.success && parsed.data.runId === runId) {
|
|
28937
29058
|
return parsed.data;
|
|
28938
29059
|
}
|
|
@@ -28944,10 +29065,10 @@ function getRun(repoPath, runId) {
|
|
|
28944
29065
|
}
|
|
28945
29066
|
function listRuns(repoPath, filter = {}) {
|
|
28946
29067
|
const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
|
|
28947
|
-
if (!
|
|
29068
|
+
if (!fs27.existsSync(runsDir)) return [];
|
|
28948
29069
|
const runs = [];
|
|
28949
29070
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28950
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
29071
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
|
|
28951
29072
|
if (!parsed.success) continue;
|
|
28952
29073
|
if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
|
|
28953
29074
|
runs.push(parsed.data);
|
|
@@ -28960,17 +29081,17 @@ function listRuns(repoPath, filter = {}) {
|
|
|
28960
29081
|
}
|
|
28961
29082
|
|
|
28962
29083
|
// src/core/slot-preflight.ts
|
|
28963
|
-
var
|
|
29084
|
+
var path30 = __toESM(require("path"));
|
|
28964
29085
|
|
|
28965
29086
|
// src/core/control-port.ts
|
|
28966
29087
|
var import_child_process4 = require("child_process");
|
|
28967
|
-
var
|
|
28968
|
-
var
|
|
29088
|
+
var fs29 = __toESM(require("fs"));
|
|
29089
|
+
var path28 = __toESM(require("path"));
|
|
28969
29090
|
|
|
28970
29091
|
// src/core/slot-ports.ts
|
|
28971
29092
|
var import_child_process3 = require("child_process");
|
|
28972
|
-
var
|
|
28973
|
-
var
|
|
29093
|
+
var fs28 = __toESM(require("fs"));
|
|
29094
|
+
var path27 = __toESM(require("path"));
|
|
28974
29095
|
function portStep(env3) {
|
|
28975
29096
|
return Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
28976
29097
|
}
|
|
@@ -29026,7 +29147,7 @@ function allocateAppPorts(repoPath, agentId) {
|
|
|
29026
29147
|
}
|
|
29027
29148
|
function harnessUsesPm2(repoPath) {
|
|
29028
29149
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29029
|
-
return
|
|
29150
|
+
return fs28.existsSync(path27.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
|
|
29030
29151
|
}
|
|
29031
29152
|
|
|
29032
29153
|
// src/core/control-port.ts
|
|
@@ -29081,13 +29202,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
|
|
|
29081
29202
|
return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
|
|
29082
29203
|
}
|
|
29083
29204
|
function inspectControlUpReadiness(repoPath) {
|
|
29084
|
-
const resolved =
|
|
29205
|
+
const resolved = path28.resolve(repoPath);
|
|
29085
29206
|
const port = parseControlHostPort();
|
|
29086
29207
|
const containers = listDockerContainers();
|
|
29087
29208
|
const control = controlContainerOnPort(containers, port);
|
|
29088
29209
|
const warnings = [];
|
|
29089
|
-
const controlHarnessDir =
|
|
29090
|
-
const harnessSlot1Active =
|
|
29210
|
+
const controlHarnessDir = path28.join(resolved, "control", ".har");
|
|
29211
|
+
const harnessSlot1Active = fs29.existsSync(controlHarnessDir) && readSlotRegistry(path28.join(resolved, "control"), 1)?.status === "active";
|
|
29091
29212
|
if (control) {
|
|
29092
29213
|
return {
|
|
29093
29214
|
warnings,
|
|
@@ -29118,9 +29239,9 @@ function inspectControlUpReadiness(repoPath) {
|
|
|
29118
29239
|
}
|
|
29119
29240
|
|
|
29120
29241
|
// src/core/slot-launch-guard-occupied.ts
|
|
29121
|
-
var
|
|
29242
|
+
var fs30 = __toESM(require("fs"));
|
|
29122
29243
|
var os6 = __toESM(require("os"));
|
|
29123
|
-
var
|
|
29244
|
+
var path29 = __toESM(require("path"));
|
|
29124
29245
|
var import_child_process5 = require("child_process");
|
|
29125
29246
|
function runGit(cwd, args) {
|
|
29126
29247
|
try {
|
|
@@ -29139,7 +29260,7 @@ function worktreeDirty(worktreePath) {
|
|
|
29139
29260
|
}
|
|
29140
29261
|
function gitCommonDir2(cwd) {
|
|
29141
29262
|
const out = runGit(cwd, "rev-parse --git-common-dir");
|
|
29142
|
-
return out ?
|
|
29263
|
+
return out ? path29.resolve(cwd, out) : void 0;
|
|
29143
29264
|
}
|
|
29144
29265
|
function sameGitCheckout2(a2, b2) {
|
|
29145
29266
|
const left2 = gitCommonDir2(a2);
|
|
@@ -29148,20 +29269,20 @@ function sameGitCheckout2(a2, b2) {
|
|
|
29148
29269
|
}
|
|
29149
29270
|
function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
29150
29271
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29151
|
-
if (session?.worktreePath &&
|
|
29272
|
+
if (session?.worktreePath && fs30.existsSync(session.worktreePath)) {
|
|
29152
29273
|
return session.worktreePath;
|
|
29153
29274
|
}
|
|
29154
|
-
const legacy =
|
|
29155
|
-
if (
|
|
29156
|
-
const worktreesRoot =
|
|
29157
|
-
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;
|
|
29158
29279
|
const suffix = `-har-agent-${agentId}-`;
|
|
29159
29280
|
const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
|
|
29160
|
-
for (const name of
|
|
29281
|
+
for (const name of fs30.readdirSync(worktreesRoot)) {
|
|
29161
29282
|
if (!name.includes(suffix)) continue;
|
|
29162
|
-
const candidate =
|
|
29283
|
+
const candidate = path29.join(worktreesRoot, name);
|
|
29163
29284
|
if (!sameGitCheckout2(harnessRoot, candidate)) continue;
|
|
29164
|
-
if (!
|
|
29285
|
+
if (!fs30.existsSync(path29.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
|
|
29165
29286
|
return candidate;
|
|
29166
29287
|
}
|
|
29167
29288
|
return void 0;
|
|
@@ -29169,13 +29290,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
|
29169
29290
|
function collectOccupiedSlot(repoPath, agentId) {
|
|
29170
29291
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29171
29292
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29172
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29293
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
|
|
29173
29294
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29174
29295
|
const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
|
|
29175
29296
|
const workDir = session?.workDir;
|
|
29176
|
-
const envInWorkDir = workDir ?
|
|
29177
|
-
const envInRoot =
|
|
29178
|
-
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;
|
|
29179
29300
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29180
29301
|
if (!active) return void 0;
|
|
29181
29302
|
const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
|
|
@@ -29328,7 +29449,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
|
29328
29449
|
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
29329
29450
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29330
29451
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29331
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29452
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
|
|
29332
29453
|
const usesPm2 = harnessUsesPm2(repoPath);
|
|
29333
29454
|
const blockers = [];
|
|
29334
29455
|
const remediations = [];
|
|
@@ -29530,7 +29651,7 @@ function readWorktreeBranch(worktreePath) {
|
|
|
29530
29651
|
}
|
|
29531
29652
|
function gitCommonDir3(cwd) {
|
|
29532
29653
|
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
29533
|
-
return out ?
|
|
29654
|
+
return out ? path31.resolve(cwd, out) : void 0;
|
|
29534
29655
|
}
|
|
29535
29656
|
function sameGitCheckout3(a2, b2) {
|
|
29536
29657
|
const left2 = gitCommonDir3(a2);
|
|
@@ -29542,12 +29663,12 @@ function gitPrefix2(cwd) {
|
|
|
29542
29663
|
return out ?? "";
|
|
29543
29664
|
}
|
|
29544
29665
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
29545
|
-
const worktreesRoot =
|
|
29546
|
-
if (!
|
|
29666
|
+
const worktreesRoot = path31.join(os7.homedir(), "worktrees");
|
|
29667
|
+
if (!fs31.existsSync(worktreesRoot)) return void 0;
|
|
29547
29668
|
const suffix = `-har-agent-${agentId}-`;
|
|
29548
29669
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
29549
|
-
const matches =
|
|
29550
|
-
(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}`))
|
|
29551
29672
|
);
|
|
29552
29673
|
return matches.sort()[0];
|
|
29553
29674
|
}
|
|
@@ -29622,18 +29743,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
|
|
|
29622
29743
|
}
|
|
29623
29744
|
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
29624
29745
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29625
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29746
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path31.basename(harnessRoot);
|
|
29626
29747
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29627
|
-
const legacyWorktreePath =
|
|
29748
|
+
const legacyWorktreePath = path31.join(
|
|
29628
29749
|
os7.homedir(),
|
|
29629
29750
|
"worktrees",
|
|
29630
29751
|
`${projectName}-agent-${agentId}`
|
|
29631
29752
|
);
|
|
29632
|
-
const worktreePath = session?.worktreePath &&
|
|
29753
|
+
const worktreePath = session?.worktreePath && fs31.existsSync(session.worktreePath) ? session.worktreePath : fs31.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
29633
29754
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
29634
|
-
const envInWorkDir = workDir ?
|
|
29635
|
-
const envInRoot =
|
|
29636
|
-
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;
|
|
29637
29758
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29638
29759
|
const latest = latestRunForAgent(runs, agentId);
|
|
29639
29760
|
const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
|
|
@@ -29686,7 +29807,7 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29686
29807
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
29687
29808
|
const pm2Procs = listPm2Processes2();
|
|
29688
29809
|
return {
|
|
29689
|
-
repoPath:
|
|
29810
|
+
repoPath: path31.resolve(repoPath),
|
|
29690
29811
|
harnessRoot,
|
|
29691
29812
|
gitRemote: readGitRemote(harnessRoot),
|
|
29692
29813
|
profile: manifest?.profile,
|
|
@@ -29697,13 +29818,13 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29697
29818
|
|
|
29698
29819
|
// src/core/validations.ts
|
|
29699
29820
|
var crypto3 = __toESM(require("crypto"));
|
|
29700
|
-
var
|
|
29701
|
-
var
|
|
29821
|
+
var fs33 = __toESM(require("fs"));
|
|
29822
|
+
var path33 = __toESM(require("path"));
|
|
29702
29823
|
|
|
29703
29824
|
// src/core/change-batch.ts
|
|
29704
|
-
var
|
|
29825
|
+
var fs32 = __toESM(require("fs"));
|
|
29705
29826
|
var os8 = __toESM(require("os"));
|
|
29706
|
-
var
|
|
29827
|
+
var path32 = __toESM(require("path"));
|
|
29707
29828
|
function git(checkoutDir, args, env3) {
|
|
29708
29829
|
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
29709
29830
|
if (result.code !== 0) {
|
|
@@ -29719,7 +29840,7 @@ function isCheckoutRoot(checkoutDir) {
|
|
|
29719
29840
|
const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
|
|
29720
29841
|
if (!toplevel) return false;
|
|
29721
29842
|
try {
|
|
29722
|
-
return
|
|
29843
|
+
return fs32.realpathSync(toplevel) === fs32.realpathSync(checkoutDir);
|
|
29723
29844
|
} catch {
|
|
29724
29845
|
return false;
|
|
29725
29846
|
}
|
|
@@ -29741,7 +29862,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
|
|
|
29741
29862
|
if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
29742
29863
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
29743
29864
|
const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
29744
|
-
if (gitPath &&
|
|
29865
|
+
if (gitPath && fs32.existsSync(path32.resolve(checkoutDir, gitPath))) return true;
|
|
29745
29866
|
}
|
|
29746
29867
|
return false;
|
|
29747
29868
|
}
|
|
@@ -29764,8 +29885,8 @@ function parseNameStatus(output) {
|
|
|
29764
29885
|
return files;
|
|
29765
29886
|
}
|
|
29766
29887
|
function computeWorktreeSnapshot(checkoutDir) {
|
|
29767
|
-
const tmpDir =
|
|
29768
|
-
const tmpIndex =
|
|
29888
|
+
const tmpDir = fs32.mkdtempSync(path32.join(os8.tmpdir(), "har-idx-"));
|
|
29889
|
+
const tmpIndex = path32.join(tmpDir, "index");
|
|
29769
29890
|
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
29770
29891
|
try {
|
|
29771
29892
|
const headTree = getHeadTree(checkoutDir);
|
|
@@ -29788,35 +29909,35 @@ function computeWorktreeSnapshot(checkoutDir) {
|
|
|
29788
29909
|
changedFiles: parseNameStatus(diffOutput)
|
|
29789
29910
|
};
|
|
29790
29911
|
} finally {
|
|
29791
|
-
|
|
29912
|
+
fs32.rmSync(tmpDir, { recursive: true, force: true });
|
|
29792
29913
|
}
|
|
29793
29914
|
}
|
|
29794
29915
|
|
|
29795
29916
|
// src/core/validations.ts
|
|
29796
29917
|
var VALIDATIONS_DIR = "validations";
|
|
29797
29918
|
function getValidationsDir(checkoutDir) {
|
|
29798
|
-
return
|
|
29919
|
+
return path33.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
29799
29920
|
}
|
|
29800
29921
|
function validationPath(checkoutDir, treeHash) {
|
|
29801
|
-
return
|
|
29922
|
+
return path33.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
29802
29923
|
}
|
|
29803
29924
|
function writeRecord(checkoutDir, record2) {
|
|
29804
29925
|
const dir = getValidationsDir(checkoutDir);
|
|
29805
|
-
|
|
29806
|
-
|
|
29926
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
29927
|
+
fs33.writeFileSync(
|
|
29807
29928
|
validationPath(checkoutDir, record2.treeHash),
|
|
29808
29929
|
`${JSON.stringify(record2, null, 2)}
|
|
29809
29930
|
`
|
|
29810
29931
|
);
|
|
29811
29932
|
}
|
|
29812
29933
|
function ensureValidationsIgnored(checkoutDir) {
|
|
29813
|
-
const harDir =
|
|
29814
|
-
if (!
|
|
29815
|
-
const gitignorePath =
|
|
29816
|
-
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") : "";
|
|
29817
29938
|
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
29818
29939
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
29819
|
-
|
|
29940
|
+
fs33.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
29820
29941
|
`);
|
|
29821
29942
|
}
|
|
29822
29943
|
function recordValidation(input) {
|
|
@@ -29833,8 +29954,8 @@ function recordValidation(input) {
|
|
|
29833
29954
|
treeHash: snapshot.treeHash,
|
|
29834
29955
|
headSha: snapshot.headSha,
|
|
29835
29956
|
branch: snapshot.branch,
|
|
29836
|
-
workDir:
|
|
29837
|
-
harnessRoot:
|
|
29957
|
+
workDir: path33.resolve(input.checkoutDir),
|
|
29958
|
+
harnessRoot: path33.resolve(input.harnessRoot),
|
|
29838
29959
|
agentId: input.agentId,
|
|
29839
29960
|
status: input.status,
|
|
29840
29961
|
full: input.full,
|
|
@@ -29846,16 +29967,16 @@ function recordValidation(input) {
|
|
|
29846
29967
|
committedAt: existing?.committedAt
|
|
29847
29968
|
});
|
|
29848
29969
|
writeRecord(input.checkoutDir, record2);
|
|
29849
|
-
if (
|
|
29970
|
+
if (path33.resolve(input.harnessRoot) !== path33.resolve(input.checkoutDir)) {
|
|
29850
29971
|
writeRecord(input.harnessRoot, record2);
|
|
29851
29972
|
}
|
|
29852
29973
|
return record2;
|
|
29853
29974
|
}
|
|
29854
29975
|
function findValidation(checkoutDir, treeHash) {
|
|
29855
29976
|
const file = validationPath(checkoutDir, treeHash);
|
|
29856
|
-
if (!
|
|
29977
|
+
if (!fs33.existsSync(file)) return void 0;
|
|
29857
29978
|
try {
|
|
29858
|
-
return ValidationRecordSchema.parse(JSON.parse(
|
|
29979
|
+
return ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(file, "utf8")));
|
|
29859
29980
|
} catch {
|
|
29860
29981
|
return void 0;
|
|
29861
29982
|
}
|
|
@@ -29870,19 +29991,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
|
29870
29991
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29871
29992
|
};
|
|
29872
29993
|
writeRecord(checkoutDir, updated);
|
|
29873
|
-
if (record2.harnessRoot &&
|
|
29994
|
+
if (record2.harnessRoot && path33.resolve(record2.harnessRoot) !== path33.resolve(checkoutDir) && fs33.existsSync(path33.join(record2.harnessRoot, ".har"))) {
|
|
29874
29995
|
writeRecord(record2.harnessRoot, updated);
|
|
29875
29996
|
}
|
|
29876
29997
|
return updated;
|
|
29877
29998
|
}
|
|
29878
29999
|
function listValidations(harnessRoot) {
|
|
29879
30000
|
const dir = getValidationsDir(harnessRoot);
|
|
29880
|
-
if (!
|
|
30001
|
+
if (!fs33.existsSync(dir)) return [];
|
|
29881
30002
|
const records = [];
|
|
29882
|
-
for (const entry of
|
|
30003
|
+
for (const entry of fs33.readdirSync(dir)) {
|
|
29883
30004
|
if (!entry.endsWith(".json")) continue;
|
|
29884
30005
|
try {
|
|
29885
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(
|
|
30006
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(path33.join(dir, entry), "utf8"))));
|
|
29886
30007
|
} catch {
|
|
29887
30008
|
}
|
|
29888
30009
|
}
|
|
@@ -29912,14 +30033,26 @@ function createRemoteExecutor(apiUrl, apiKey) {
|
|
|
29912
30033
|
}
|
|
29913
30034
|
|
|
29914
30035
|
// src/core/telemetry-config.ts
|
|
29915
|
-
var
|
|
30036
|
+
var fs34 = __toESM(require("fs"));
|
|
29916
30037
|
var os9 = __toESM(require("os"));
|
|
29917
|
-
var
|
|
30038
|
+
var path34 = __toESM(require("path"));
|
|
30039
|
+
var DEFAULT_SIGNALS_ON = {
|
|
30040
|
+
metrics: true,
|
|
30041
|
+
logs: true,
|
|
30042
|
+
prompts: false,
|
|
30043
|
+
traces: false
|
|
30044
|
+
};
|
|
30045
|
+
var DEFAULT_SIGNALS_OFF = {
|
|
30046
|
+
metrics: false,
|
|
30047
|
+
logs: false,
|
|
30048
|
+
prompts: false,
|
|
30049
|
+
traces: false
|
|
30050
|
+
};
|
|
29918
30051
|
function getPreferencePath() {
|
|
29919
30052
|
if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
|
|
29920
|
-
return
|
|
30053
|
+
return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
|
|
29921
30054
|
}
|
|
29922
|
-
return
|
|
30055
|
+
return path34.join(os9.homedir(), ".har", "telemetry.json");
|
|
29923
30056
|
}
|
|
29924
30057
|
function parseEnvOverride(raw) {
|
|
29925
30058
|
if (raw === void 0 || raw === "") return void 0;
|
|
@@ -29928,29 +30061,46 @@ function parseEnvOverride(raw) {
|
|
|
29928
30061
|
if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
|
|
29929
30062
|
return void 0;
|
|
29930
30063
|
}
|
|
30064
|
+
function normalizeSignals(enabled, raw) {
|
|
30065
|
+
if (!enabled) return { ...DEFAULT_SIGNALS_OFF };
|
|
30066
|
+
return {
|
|
30067
|
+
metrics: raw?.metrics !== false,
|
|
30068
|
+
logs: raw?.logs !== false,
|
|
30069
|
+
prompts: raw?.prompts === true,
|
|
30070
|
+
traces: raw?.traces === true
|
|
30071
|
+
};
|
|
30072
|
+
}
|
|
29931
30073
|
function readTelemetryPreference() {
|
|
29932
30074
|
const preferencePath = getPreferencePath();
|
|
29933
30075
|
try {
|
|
29934
|
-
if (!
|
|
29935
|
-
return { enabled: true };
|
|
30076
|
+
if (!fs34.existsSync(preferencePath)) {
|
|
30077
|
+
return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
|
|
29936
30078
|
}
|
|
29937
|
-
const parsed = JSON.parse(
|
|
30079
|
+
const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
|
|
30080
|
+
const enabled = parsed.enabled !== false;
|
|
29938
30081
|
return {
|
|
29939
|
-
enabled
|
|
30082
|
+
enabled,
|
|
30083
|
+
signals: normalizeSignals(enabled, parsed.signals),
|
|
29940
30084
|
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
|
|
29941
30085
|
};
|
|
29942
30086
|
} catch {
|
|
29943
|
-
return { enabled: true };
|
|
30087
|
+
return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
|
|
29944
30088
|
}
|
|
29945
30089
|
}
|
|
29946
|
-
function writeTelemetryPreference(enabled) {
|
|
30090
|
+
function writeTelemetryPreference(enabled, signals) {
|
|
30091
|
+
const current = readTelemetryPreference();
|
|
30092
|
+
const nextSignals = normalizeSignals(enabled, {
|
|
30093
|
+
...current.signals,
|
|
30094
|
+
...signals
|
|
30095
|
+
});
|
|
29947
30096
|
const preference = {
|
|
29948
30097
|
enabled,
|
|
30098
|
+
signals: enabled ? nextSignals : { ...DEFAULT_SIGNALS_OFF },
|
|
29949
30099
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29950
30100
|
};
|
|
29951
30101
|
const preferencePath = getPreferencePath();
|
|
29952
|
-
|
|
29953
|
-
|
|
30102
|
+
fs34.mkdirSync(path34.dirname(preferencePath), { recursive: true });
|
|
30103
|
+
fs34.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
|
|
29954
30104
|
return preference;
|
|
29955
30105
|
}
|
|
29956
30106
|
function isTelemetryEnabled() {
|
|
@@ -29958,23 +30108,29 @@ function isTelemetryEnabled() {
|
|
|
29958
30108
|
if (envOverride !== void 0) return envOverride;
|
|
29959
30109
|
return readTelemetryPreference().enabled;
|
|
29960
30110
|
}
|
|
30111
|
+
function getTelemetrySignals() {
|
|
30112
|
+
if (!isTelemetryEnabled()) return { ...DEFAULT_SIGNALS_OFF };
|
|
30113
|
+
return readTelemetryPreference().signals;
|
|
30114
|
+
}
|
|
29961
30115
|
function getTelemetryPreferencePath() {
|
|
29962
30116
|
return getPreferencePath();
|
|
29963
30117
|
}
|
|
29964
30118
|
var TELEMETRY_SIGNALS = [
|
|
29965
|
-
"
|
|
29966
|
-
"
|
|
30119
|
+
"Metrics (default): tokens and estimated USD cost via OTEL metrics",
|
|
30120
|
+
"Logs/events (default): session events (tool calls, api_request) without prompt bodies",
|
|
30121
|
+
"Prompts (opt-in): user/assistant text via OTEL_LOG_USER_PROMPTS \u2014 har telemetry on --prompts",
|
|
30122
|
+
"Traces (opt-in): thin span ingest when CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1",
|
|
29967
30123
|
"Fallback: har control sync harvests local Claude/Codex session files when OTEL is missing"
|
|
29968
30124
|
];
|
|
29969
30125
|
|
|
29970
30126
|
// src/core/usage-harvest/claude.ts
|
|
29971
|
-
var
|
|
30127
|
+
var fs36 = __toESM(require("fs"));
|
|
29972
30128
|
var os10 = __toESM(require("os"));
|
|
29973
|
-
var
|
|
30129
|
+
var path36 = __toESM(require("path"));
|
|
29974
30130
|
|
|
29975
30131
|
// src/core/telemetry-env.ts
|
|
29976
|
-
var
|
|
29977
|
-
var
|
|
30132
|
+
var fs35 = __toESM(require("fs"));
|
|
30133
|
+
var path35 = __toESM(require("path"));
|
|
29978
30134
|
function escapeAttrValue(value) {
|
|
29979
30135
|
return value.replace(/[,=]/g, "_");
|
|
29980
30136
|
}
|
|
@@ -30006,13 +30162,31 @@ function buildTelemetryEnvBlock(attrs, options) {
|
|
|
30006
30162
|
];
|
|
30007
30163
|
const injectOtel = isTelemetryEnabled() && options?.otelReady !== false;
|
|
30008
30164
|
if (injectOtel) {
|
|
30165
|
+
const signals = getTelemetrySignals();
|
|
30009
30166
|
lines.push(
|
|
30010
30167
|
"# HAR telemetry \u2192 Mission Control OTLP (disable: har telemetry off)",
|
|
30011
30168
|
"CLAUDE_CODE_ENABLE_TELEMETRY=1",
|
|
30012
|
-
"OTEL_METRICS_EXPORTER=otlp",
|
|
30013
30169
|
"OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
|
|
30014
30170
|
`OTEL_EXPORTER_OTLP_ENDPOINT=${apiUrl}/api/otel`
|
|
30015
30171
|
);
|
|
30172
|
+
if (signals.metrics) {
|
|
30173
|
+
lines.push("OTEL_METRICS_EXPORTER=otlp");
|
|
30174
|
+
}
|
|
30175
|
+
if (signals.logs) {
|
|
30176
|
+
lines.push("OTEL_LOGS_EXPORTER=otlp");
|
|
30177
|
+
}
|
|
30178
|
+
if (signals.prompts) {
|
|
30179
|
+
lines.push(
|
|
30180
|
+
"OTEL_LOG_USER_PROMPTS=1",
|
|
30181
|
+
"OTEL_LOG_ASSISTANT_RESPONSES=1"
|
|
30182
|
+
);
|
|
30183
|
+
}
|
|
30184
|
+
if (signals.traces) {
|
|
30185
|
+
lines.push(
|
|
30186
|
+
"OTEL_TRACES_EXPORTER=otlp",
|
|
30187
|
+
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1"
|
|
30188
|
+
);
|
|
30189
|
+
}
|
|
30016
30190
|
}
|
|
30017
30191
|
return lines.join("\n") + "\n";
|
|
30018
30192
|
}
|
|
@@ -30023,8 +30197,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs, options) {
|
|
|
30023
30197
|
${TELEMETRY_MARKER_END}
|
|
30024
30198
|
`;
|
|
30025
30199
|
let existing = "";
|
|
30026
|
-
if (
|
|
30027
|
-
existing =
|
|
30200
|
+
if (fs35.existsSync(envFilePath)) {
|
|
30201
|
+
existing = fs35.readFileSync(envFilePath, "utf8");
|
|
30028
30202
|
const start = existing.indexOf(TELEMETRY_MARKER_START);
|
|
30029
30203
|
if (start >= 0) {
|
|
30030
30204
|
const end = existing.indexOf(TELEMETRY_MARKER_END, start);
|
|
@@ -30035,9 +30209,9 @@ ${TELEMETRY_MARKER_END}
|
|
|
30035
30209
|
}
|
|
30036
30210
|
}
|
|
30037
30211
|
}
|
|
30038
|
-
|
|
30212
|
+
fs35.mkdirSync(path35.dirname(envFilePath), { recursive: true });
|
|
30039
30213
|
const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
|
|
30040
|
-
|
|
30214
|
+
fs35.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
|
|
30041
30215
|
}
|
|
30042
30216
|
function buildCodexOtelSnippet(attrs) {
|
|
30043
30217
|
const apiUrl = getControlApiUrl().replace(/\/$/, "");
|
|
@@ -30058,21 +30232,21 @@ metrics_exporter = "otlp-http"
|
|
|
30058
30232
|
|
|
30059
30233
|
// src/core/usage-harvest/claude.ts
|
|
30060
30234
|
function pathsMatch(candidate, targets) {
|
|
30061
|
-
const norm =
|
|
30235
|
+
const norm = path36.resolve(candidate);
|
|
30062
30236
|
return targets.some((t2) => {
|
|
30063
|
-
const target =
|
|
30064
|
-
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);
|
|
30065
30239
|
});
|
|
30066
30240
|
}
|
|
30067
30241
|
function claudeProjectsRoot() {
|
|
30068
|
-
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");
|
|
30069
30243
|
}
|
|
30070
30244
|
function encodeClaudeProjectDir(cwd) {
|
|
30071
30245
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
30072
30246
|
}
|
|
30073
30247
|
function readJsonlRecords(filePath) {
|
|
30074
|
-
if (!
|
|
30075
|
-
const lines =
|
|
30248
|
+
if (!fs36.existsSync(filePath)) return [];
|
|
30249
|
+
const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
30076
30250
|
const out = [];
|
|
30077
30251
|
for (const line of lines) {
|
|
30078
30252
|
const trimmed = line.trim();
|
|
@@ -30111,27 +30285,20 @@ function extractClaudeUsageFromRecords(records) {
|
|
|
30111
30285
|
}
|
|
30112
30286
|
return { tokensInput, tokensOutput, tokensCacheRead, tokensCacheCreation, costUsd };
|
|
30113
30287
|
}
|
|
30114
|
-
function
|
|
30288
|
+
function findMatchingClaudeTranscripts(slot) {
|
|
30115
30289
|
const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
|
|
30116
|
-
if (targets.length === 0) return
|
|
30290
|
+
if (targets.length === 0) return [];
|
|
30117
30291
|
const root = claudeProjectsRoot();
|
|
30118
|
-
if (!
|
|
30119
|
-
const
|
|
30120
|
-
|
|
30121
|
-
agentId: slot.agentId,
|
|
30122
|
-
suffix: slot.suffix,
|
|
30123
|
-
createdAt: slot.sessionCreatedAt
|
|
30124
|
-
});
|
|
30125
|
-
let best = null;
|
|
30126
|
-
let bestMtime = 0;
|
|
30127
|
-
for (const entry of fs35.readdirSync(root, { withFileTypes: true })) {
|
|
30292
|
+
if (!fs36.existsSync(root)) return [];
|
|
30293
|
+
const matches = [];
|
|
30294
|
+
for (const entry of fs36.readdirSync(root, { withFileTypes: true })) {
|
|
30128
30295
|
if (!entry.isDirectory()) continue;
|
|
30129
|
-
const projectDir =
|
|
30296
|
+
const projectDir = path36.join(root, entry.name);
|
|
30130
30297
|
const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
|
|
30131
|
-
for (const file of
|
|
30298
|
+
for (const file of fs36.readdirSync(projectDir)) {
|
|
30132
30299
|
if (!file.endsWith(".jsonl")) continue;
|
|
30133
|
-
const filePath =
|
|
30134
|
-
const stat =
|
|
30300
|
+
const filePath = path36.join(projectDir, file);
|
|
30301
|
+
const stat = fs36.statSync(filePath);
|
|
30135
30302
|
const records = readJsonlRecords(filePath);
|
|
30136
30303
|
let cwdHit = encodedHit;
|
|
30137
30304
|
for (const record2 of records) {
|
|
@@ -30143,16 +30310,70 @@ function harvestClaudeUsage(slot) {
|
|
|
30143
30310
|
}
|
|
30144
30311
|
}
|
|
30145
30312
|
if (!cwdHit) continue;
|
|
30146
|
-
|
|
30147
|
-
if (usage2.tokensInput + usage2.tokensOutput + usage2.tokensCacheRead === 0 && (usage2.costUsd == null || usage2.costUsd === 0)) {
|
|
30148
|
-
continue;
|
|
30149
|
-
}
|
|
30150
|
-
if (stat.mtimeMs >= bestMtime) {
|
|
30151
|
-
bestMtime = stat.mtimeMs;
|
|
30152
|
-
best = usage2;
|
|
30153
|
-
}
|
|
30313
|
+
matches.push({ filePath, records, mtimeMs: stat.mtimeMs });
|
|
30154
30314
|
}
|
|
30155
30315
|
}
|
|
30316
|
+
return matches.sort((a2, b2) => b2.mtimeMs - a2.mtimeMs);
|
|
30317
|
+
}
|
|
30318
|
+
function extractPromptEvents(records, sessionKey, slot) {
|
|
30319
|
+
const events = [];
|
|
30320
|
+
let sequence = 0;
|
|
30321
|
+
for (const record2 of records) {
|
|
30322
|
+
if (!record2 || typeof record2 !== "object") continue;
|
|
30323
|
+
const payload = record2;
|
|
30324
|
+
const typ = String(payload.type ?? "");
|
|
30325
|
+
const message = payload.message;
|
|
30326
|
+
const role = String(message?.role ?? payload.role ?? "");
|
|
30327
|
+
let text = null;
|
|
30328
|
+
const content = message?.content ?? payload.content;
|
|
30329
|
+
if (typeof content === "string") text = content;
|
|
30330
|
+
else if (Array.isArray(content)) {
|
|
30331
|
+
text = content.map((part) => {
|
|
30332
|
+
if (typeof part === "string") return part;
|
|
30333
|
+
if (part && typeof part === "object" && typeof part.text === "string") {
|
|
30334
|
+
return part.text;
|
|
30335
|
+
}
|
|
30336
|
+
return "";
|
|
30337
|
+
}).filter(Boolean).join("\n");
|
|
30338
|
+
}
|
|
30339
|
+
if (!text || text.trim().length === 0) continue;
|
|
30340
|
+
const isUser = typ === "user" || role === "user" || typ === "human" || Boolean(payload.userType);
|
|
30341
|
+
const isAssistant = typ === "assistant" || role === "assistant";
|
|
30342
|
+
if (!isUser && !isAssistant) continue;
|
|
30343
|
+
sequence += 1;
|
|
30344
|
+
const timestamp = typeof payload.timestamp === "string" ? payload.timestamp : typeof payload.ts === "string" ? payload.ts : (/* @__PURE__ */ new Date()).toISOString();
|
|
30345
|
+
events.push({
|
|
30346
|
+
sessionKey,
|
|
30347
|
+
agentId: slot.agentId,
|
|
30348
|
+
agentTool: "claude_code",
|
|
30349
|
+
eventName: isUser ? "claude_code.user_prompt" : "claude_code.assistant_response",
|
|
30350
|
+
sequence,
|
|
30351
|
+
timestamp,
|
|
30352
|
+
promptText: isUser ? text.slice(0, 8e3) : null,
|
|
30353
|
+
responseText: isAssistant ? text.slice(0, 8e3) : null,
|
|
30354
|
+
rawTruncated: text.slice(0, 4e3),
|
|
30355
|
+
source: "harvest"
|
|
30356
|
+
});
|
|
30357
|
+
}
|
|
30358
|
+
return events;
|
|
30359
|
+
}
|
|
30360
|
+
function harvestClaudeUsage(slot) {
|
|
30361
|
+
const matches = findMatchingClaudeTranscripts(slot);
|
|
30362
|
+
const sessionKey = buildSessionKey({
|
|
30363
|
+
branch: slot.branch,
|
|
30364
|
+
agentId: slot.agentId,
|
|
30365
|
+
suffix: slot.suffix,
|
|
30366
|
+
createdAt: slot.sessionCreatedAt
|
|
30367
|
+
});
|
|
30368
|
+
let best = null;
|
|
30369
|
+
for (const match of matches) {
|
|
30370
|
+
const usage2 = extractClaudeUsageFromRecords(match.records);
|
|
30371
|
+
if (usage2.tokensInput + usage2.tokensOutput + usage2.tokensCacheRead === 0 && (usage2.costUsd == null || usage2.costUsd === 0)) {
|
|
30372
|
+
continue;
|
|
30373
|
+
}
|
|
30374
|
+
best = usage2;
|
|
30375
|
+
break;
|
|
30376
|
+
}
|
|
30156
30377
|
if (!best) return null;
|
|
30157
30378
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30158
30379
|
const tokensTotal = best.tokensInput + best.tokensOutput + best.tokensCacheRead + best.tokensCacheCreation;
|
|
@@ -30174,32 +30395,44 @@ function harvestClaudeUsage(slot) {
|
|
|
30174
30395
|
lastSeenAt: now
|
|
30175
30396
|
};
|
|
30176
30397
|
}
|
|
30398
|
+
function harvestClaudeEvents(slot) {
|
|
30399
|
+
if (!getTelemetrySignals().prompts) return [];
|
|
30400
|
+
const matches = findMatchingClaudeTranscripts(slot);
|
|
30401
|
+
if (matches.length === 0) return [];
|
|
30402
|
+
const sessionKey = buildSessionKey({
|
|
30403
|
+
branch: slot.branch,
|
|
30404
|
+
agentId: slot.agentId,
|
|
30405
|
+
suffix: slot.suffix,
|
|
30406
|
+
createdAt: slot.sessionCreatedAt
|
|
30407
|
+
});
|
|
30408
|
+
return extractPromptEvents(matches[0].records, sessionKey, slot).slice(-40);
|
|
30409
|
+
}
|
|
30177
30410
|
|
|
30178
30411
|
// src/core/usage-harvest/codex.ts
|
|
30179
|
-
var
|
|
30412
|
+
var fs37 = __toESM(require("fs"));
|
|
30180
30413
|
var os11 = __toESM(require("os"));
|
|
30181
|
-
var
|
|
30414
|
+
var path37 = __toESM(require("path"));
|
|
30182
30415
|
function pathsMatch2(candidate, targets) {
|
|
30183
|
-
const norm =
|
|
30416
|
+
const norm = path37.resolve(candidate);
|
|
30184
30417
|
return targets.some((t2) => {
|
|
30185
|
-
const target =
|
|
30186
|
-
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);
|
|
30187
30420
|
});
|
|
30188
30421
|
}
|
|
30189
30422
|
function codexSessionsRoot() {
|
|
30190
|
-
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");
|
|
30191
30424
|
}
|
|
30192
30425
|
function walkJsonlFiles(dir, out = []) {
|
|
30193
|
-
if (!
|
|
30194
|
-
for (const entry of
|
|
30195
|
-
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);
|
|
30196
30429
|
if (entry.isDirectory()) walkJsonlFiles(full, out);
|
|
30197
30430
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
|
|
30198
30431
|
}
|
|
30199
30432
|
return out;
|
|
30200
30433
|
}
|
|
30201
30434
|
function readJsonl(filePath) {
|
|
30202
|
-
const lines =
|
|
30435
|
+
const lines = fs37.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
30203
30436
|
const out = [];
|
|
30204
30437
|
for (const line of lines) {
|
|
30205
30438
|
const trimmed = line.trim();
|
|
@@ -30263,7 +30496,7 @@ function harvestCodexUsage(slot) {
|
|
|
30263
30496
|
const extracted = extractCodexTokens(records);
|
|
30264
30497
|
if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
|
|
30265
30498
|
if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
|
|
30266
|
-
const mtime =
|
|
30499
|
+
const mtime = fs37.statSync(file).mtimeMs;
|
|
30267
30500
|
if (mtime >= bestMtime) {
|
|
30268
30501
|
bestMtime = mtime;
|
|
30269
30502
|
best = extracted;
|
|
@@ -30300,6 +30533,9 @@ function harvestUsageForSlot(slot) {
|
|
|
30300
30533
|
if (codex) out.push(codex);
|
|
30301
30534
|
return out;
|
|
30302
30535
|
}
|
|
30536
|
+
function harvestEventsForSlot(slot) {
|
|
30537
|
+
return harvestClaudeEvents(slot);
|
|
30538
|
+
}
|
|
30303
30539
|
|
|
30304
30540
|
// src/core/control-sync.ts
|
|
30305
30541
|
async function postJson(url, body, dryRun) {
|
|
@@ -30342,7 +30578,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
30342
30578
|
}
|
|
30343
30579
|
const repoPaths = new Set(listRegisteredRepos());
|
|
30344
30580
|
if (options?.cwd) {
|
|
30345
|
-
const cwd =
|
|
30581
|
+
const cwd = path38.resolve(options.cwd);
|
|
30346
30582
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
30347
30583
|
}
|
|
30348
30584
|
try {
|
|
@@ -30350,7 +30586,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
30350
30586
|
if (listResponse.ok) {
|
|
30351
30587
|
const repos = await listResponse.json();
|
|
30352
30588
|
for (const repo of repos) {
|
|
30353
|
-
const resolved =
|
|
30589
|
+
const resolved = path38.resolve(repo.path);
|
|
30354
30590
|
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
30355
30591
|
}
|
|
30356
30592
|
}
|
|
@@ -30369,7 +30605,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
30369
30605
|
return { synced, failed };
|
|
30370
30606
|
}
|
|
30371
30607
|
async function registerRepoWithControl(options) {
|
|
30372
|
-
const repoPath =
|
|
30608
|
+
const repoPath = path38.resolve(options.repoPath);
|
|
30373
30609
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
30374
30610
|
const manifest = readManifest(repoPath);
|
|
30375
30611
|
const stagesRegistry = readStageRegistry(repoPath);
|
|
@@ -30382,7 +30618,7 @@ async function registerRepoWithControl(options) {
|
|
|
30382
30618
|
return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
|
|
30383
30619
|
}
|
|
30384
30620
|
async function syncRepoWithControl(options) {
|
|
30385
|
-
const repoPath =
|
|
30621
|
+
const repoPath = path38.resolve(options.repoPath);
|
|
30386
30622
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
30387
30623
|
if (options.cloud) {
|
|
30388
30624
|
const remote = createRemoteExecutor();
|
|
@@ -30410,7 +30646,7 @@ async function syncRepoWithControl(options) {
|
|
|
30410
30646
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
30411
30647
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
30412
30648
|
const repos = await listResponse.json();
|
|
30413
|
-
const existing = repos.find((r2) =>
|
|
30649
|
+
const existing = repos.find((r2) => path38.resolve(r2.path) === repoPath);
|
|
30414
30650
|
if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
|
|
30415
30651
|
await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
|
|
30416
30652
|
return;
|
|
@@ -30454,6 +30690,21 @@ async function syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun) {
|
|
|
30454
30690
|
const usageBody = SyncUsageInputSchema.parse({ usage: usage2 });
|
|
30455
30691
|
await postJson(`${apiUrl}/api/repos/${repoId}/usage`, usageBody, dryRun);
|
|
30456
30692
|
}
|
|
30693
|
+
const events = status.slots.flatMap(
|
|
30694
|
+
(slot) => harvestEventsForSlot({
|
|
30695
|
+
agentId: slot.agentId,
|
|
30696
|
+
workDir: slot.workDir,
|
|
30697
|
+
worktreePath: slot.worktreePath,
|
|
30698
|
+
branch: slot.branch,
|
|
30699
|
+
suffix: slot.suffix,
|
|
30700
|
+
sessionCreatedAt: slot.sessionCreatedAt,
|
|
30701
|
+
repoPath
|
|
30702
|
+
})
|
|
30703
|
+
);
|
|
30704
|
+
if (events.length > 0) {
|
|
30705
|
+
const eventsBody = SyncSessionEventsInputSchema.parse({ events });
|
|
30706
|
+
await postJson(`${apiUrl}/api/repos/${repoId}/events`, eventsBody, dryRun);
|
|
30707
|
+
}
|
|
30457
30708
|
} catch (err) {
|
|
30458
30709
|
const message = err instanceof Error ? err.message : String(err);
|
|
30459
30710
|
process.stderr.write(`[har control] usage harvest skipped: ${message}
|
|
@@ -30514,7 +30765,7 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
|
|
|
30514
30765
|
|
|
30515
30766
|
// src/core/control-lifecycle.ts
|
|
30516
30767
|
var import_child_process8 = require("child_process");
|
|
30517
|
-
var
|
|
30768
|
+
var path39 = __toESM(require("path"));
|
|
30518
30769
|
|
|
30519
30770
|
// src/core/control-image.ts
|
|
30520
30771
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
@@ -30537,10 +30788,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
|
|
|
30537
30788
|
var CONTROL_CONTAINER_PORT = 3847;
|
|
30538
30789
|
var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
|
|
30539
30790
|
function resolveControlDir() {
|
|
30540
|
-
return
|
|
30791
|
+
return path39.resolve(__dirname, "..", "control");
|
|
30541
30792
|
}
|
|
30542
30793
|
function resolveControlBuildContext() {
|
|
30543
|
-
return
|
|
30794
|
+
return path39.resolve(resolveControlDir(), "..");
|
|
30544
30795
|
}
|
|
30545
30796
|
function buildDockerRunArgs(options) {
|
|
30546
30797
|
const name = options.containerName ?? CONTROL_CONTAINER_NAME;
|
|
@@ -30596,7 +30847,7 @@ async function startMissionControl(options) {
|
|
|
30596
30847
|
"-t",
|
|
30597
30848
|
imageRef,
|
|
30598
30849
|
"-f",
|
|
30599
|
-
|
|
30850
|
+
path39.join(resolveControlDir(), "Dockerfile"),
|
|
30600
30851
|
resolveControlBuildContext()
|
|
30601
30852
|
]);
|
|
30602
30853
|
if (buildCode !== 0) {
|
|
@@ -30738,7 +30989,7 @@ async function ensureTelemetryInfrastructure(options) {
|
|
|
30738
30989
|
}
|
|
30739
30990
|
|
|
30740
30991
|
// src/core/run-service.ts
|
|
30741
|
-
var
|
|
30992
|
+
var path40 = __toESM(require("path"));
|
|
30742
30993
|
function extractShellOutput(result) {
|
|
30743
30994
|
const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
|
|
30744
30995
|
const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
|
|
@@ -30902,14 +31153,14 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
|
|
|
30902
31153
|
suffix: session.suffix,
|
|
30903
31154
|
createdAt: session.createdAt
|
|
30904
31155
|
});
|
|
30905
|
-
const envFile =
|
|
31156
|
+
const envFile = path40.join(session.workDir, `.env.agent.${options.agentId}`);
|
|
30906
31157
|
try {
|
|
30907
31158
|
appendTelemetryEnvToFile(
|
|
30908
31159
|
envFile,
|
|
30909
31160
|
{
|
|
30910
31161
|
sessionKey,
|
|
30911
31162
|
agentId: options.agentId,
|
|
30912
|
-
repoPath:
|
|
31163
|
+
repoPath: path40.resolve(options.repoPath),
|
|
30913
31164
|
workDir: session.workDir,
|
|
30914
31165
|
branch: session.branch,
|
|
30915
31166
|
suffix: session.suffix,
|
|
@@ -31275,7 +31526,7 @@ var envCommand = {
|
|
|
31275
31526
|
}
|
|
31276
31527
|
};
|
|
31277
31528
|
async function handleInit(argv) {
|
|
31278
|
-
const repoPath =
|
|
31529
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31279
31530
|
header("har env init");
|
|
31280
31531
|
info(`Repository: ${repoPath}`);
|
|
31281
31532
|
try {
|
|
@@ -31333,7 +31584,7 @@ async function handleInit(argv) {
|
|
|
31333
31584
|
}
|
|
31334
31585
|
}
|
|
31335
31586
|
async function handleMaintain(argv) {
|
|
31336
|
-
const repoPath =
|
|
31587
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31337
31588
|
header("har env maintain");
|
|
31338
31589
|
info(`Repository: ${repoPath}`);
|
|
31339
31590
|
try {
|
|
@@ -31431,7 +31682,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
31431
31682
|
if (autoYes) {
|
|
31432
31683
|
const proposal = readAgentMdProposal(repoPath);
|
|
31433
31684
|
if (proposal) {
|
|
31434
|
-
writeFileSafe(
|
|
31685
|
+
writeFileSafe(path41.join(repoPath, "AGENT.md"), proposal.content);
|
|
31435
31686
|
clearAgentMdProposal(repoPath);
|
|
31436
31687
|
info("Applied AGENT.md proposal (--yes)");
|
|
31437
31688
|
}
|
|
@@ -31440,7 +31691,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
31440
31691
|
await promptApplyAgentMdProposal(repoPath);
|
|
31441
31692
|
}
|
|
31442
31693
|
async function handleAddStage(argv) {
|
|
31443
|
-
const repoPath =
|
|
31694
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31444
31695
|
const available = listStageTemplateIds();
|
|
31445
31696
|
if (argv.list) {
|
|
31446
31697
|
for (const id of available) {
|
|
@@ -31517,7 +31768,7 @@ async function handleAddStage(argv) {
|
|
|
31517
31768
|
}
|
|
31518
31769
|
}
|
|
31519
31770
|
async function handlePreflight(argv) {
|
|
31520
|
-
const repo =
|
|
31771
|
+
const repo = path41.resolve(argv.repo);
|
|
31521
31772
|
const agentId = validateAgentId(argv.id, repo);
|
|
31522
31773
|
const result = await preflightEnvironment({
|
|
31523
31774
|
repoPath: repo,
|
|
@@ -31535,7 +31786,7 @@ async function handlePreflight(argv) {
|
|
|
31535
31786
|
process.exit(result.code);
|
|
31536
31787
|
}
|
|
31537
31788
|
async function handleLaunch(argv) {
|
|
31538
|
-
const repo =
|
|
31789
|
+
const repo = path41.resolve(argv.repo);
|
|
31539
31790
|
const agentId = validateAgentId(argv.id, repo);
|
|
31540
31791
|
let confirmReplace = argv.replace;
|
|
31541
31792
|
let force = argv.force;
|
|
@@ -31619,7 +31870,7 @@ async function handleRecover(argv) {
|
|
|
31619
31870
|
});
|
|
31620
31871
|
}
|
|
31621
31872
|
async function handleVerify(argv) {
|
|
31622
|
-
const repo =
|
|
31873
|
+
const repo = path41.resolve(argv.repo);
|
|
31623
31874
|
const agentId = validateAgentId(argv.id, repo);
|
|
31624
31875
|
const result = await runVerification({
|
|
31625
31876
|
repoPath: repo,
|
|
@@ -31631,7 +31882,7 @@ async function handleVerify(argv) {
|
|
|
31631
31882
|
process.exit(result.code);
|
|
31632
31883
|
}
|
|
31633
31884
|
async function handleTeardown(argv) {
|
|
31634
|
-
const repo =
|
|
31885
|
+
const repo = path41.resolve(argv.repo);
|
|
31635
31886
|
const agentId = validateAgentId(argv.id, repo);
|
|
31636
31887
|
const result = await teardownEnvironment({
|
|
31637
31888
|
repoPath: repo,
|
|
@@ -31642,7 +31893,7 @@ async function handleTeardown(argv) {
|
|
|
31642
31893
|
process.exit(result.code);
|
|
31643
31894
|
}
|
|
31644
31895
|
async function handleComplete(argv) {
|
|
31645
|
-
const repo =
|
|
31896
|
+
const repo = path41.resolve(argv.repo);
|
|
31646
31897
|
const agentId = validateAgentId(argv.id, repo);
|
|
31647
31898
|
const result = await completeEnvironment({
|
|
31648
31899
|
repoPath: repo,
|
|
@@ -31663,7 +31914,7 @@ async function handleComplete(argv) {
|
|
|
31663
31914
|
process.exit(result.code);
|
|
31664
31915
|
}
|
|
31665
31916
|
async function handleStatus(argv) {
|
|
31666
|
-
const repoPath =
|
|
31917
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31667
31918
|
if (argv.json) {
|
|
31668
31919
|
const status = collectEnvironmentStatus(repoPath);
|
|
31669
31920
|
const output = EnvironmentStatusSchema.parse(status);
|
|
@@ -31676,7 +31927,7 @@ async function handleStatus(argv) {
|
|
|
31676
31927
|
});
|
|
31677
31928
|
}
|
|
31678
31929
|
async function handleRunsList(argv) {
|
|
31679
|
-
const repoPath =
|
|
31930
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31680
31931
|
const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
|
|
31681
31932
|
if (argv.json) {
|
|
31682
31933
|
process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
|
|
@@ -31693,7 +31944,7 @@ async function handleRunsList(argv) {
|
|
|
31693
31944
|
}
|
|
31694
31945
|
}
|
|
31695
31946
|
async function handleRunsGet(argv) {
|
|
31696
|
-
const run2 = getRun(
|
|
31947
|
+
const run2 = getRun(path41.resolve(argv.repo), argv.runId);
|
|
31697
31948
|
if (!run2) {
|
|
31698
31949
|
error(`Run not found: ${argv.runId}`);
|
|
31699
31950
|
process.exit(1);
|
|
@@ -31734,7 +31985,13 @@ function printDrift(drift) {
|
|
|
31734
31985
|
);
|
|
31735
31986
|
warn(" See maintain/templates/harness.env in the maintenance bundle.");
|
|
31736
31987
|
}
|
|
31737
|
-
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) {
|
|
31738
31995
|
success(" Harness matches bundled templates");
|
|
31739
31996
|
}
|
|
31740
31997
|
}
|
|
@@ -31766,7 +32023,7 @@ function printNextSteps(auto2) {
|
|
|
31766
32023
|
}
|
|
31767
32024
|
|
|
31768
32025
|
// src/cli/commands/control.ts
|
|
31769
|
-
var
|
|
32026
|
+
var path42 = __toESM(require("path"));
|
|
31770
32027
|
var controlCommand = {
|
|
31771
32028
|
command: "control <subcommand>",
|
|
31772
32029
|
describe: "Mission Control dashboard (local)",
|
|
@@ -31869,7 +32126,7 @@ async function handleDown() {
|
|
|
31869
32126
|
process.exit(code);
|
|
31870
32127
|
}
|
|
31871
32128
|
async function handleRegister(argv) {
|
|
31872
|
-
const repoPath =
|
|
32129
|
+
const repoPath = path42.resolve(argv.repo);
|
|
31873
32130
|
header("har control register");
|
|
31874
32131
|
info(`Repository: ${repoPath}`);
|
|
31875
32132
|
try {
|
|
@@ -31889,7 +32146,7 @@ async function handleRegister(argv) {
|
|
|
31889
32146
|
}
|
|
31890
32147
|
}
|
|
31891
32148
|
async function handleSync(argv) {
|
|
31892
|
-
const repoPath =
|
|
32149
|
+
const repoPath = path42.resolve(argv.repo);
|
|
31893
32150
|
try {
|
|
31894
32151
|
await syncRepoWithControl({
|
|
31895
32152
|
repoPath,
|
|
@@ -31928,7 +32185,7 @@ async function handleWatch(argv) {
|
|
|
31928
32185
|
};
|
|
31929
32186
|
const tick = async () => {
|
|
31930
32187
|
if (argv.repo) {
|
|
31931
|
-
await syncOne(
|
|
32188
|
+
await syncOne(path42.resolve(argv.repo));
|
|
31932
32189
|
return;
|
|
31933
32190
|
}
|
|
31934
32191
|
try {
|
|
@@ -31959,12 +32216,12 @@ async function handleLogin(argv) {
|
|
|
31959
32216
|
}
|
|
31960
32217
|
|
|
31961
32218
|
// src/cli/commands/hooks.ts
|
|
31962
|
-
var
|
|
32219
|
+
var path45 = __toESM(require("path"));
|
|
31963
32220
|
|
|
31964
32221
|
// src/core/hooks.ts
|
|
31965
|
-
var
|
|
32222
|
+
var fs38 = __toESM(require("fs"));
|
|
31966
32223
|
var os12 = __toESM(require("os"));
|
|
31967
|
-
var
|
|
32224
|
+
var path43 = __toESM(require("path"));
|
|
31968
32225
|
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
31969
32226
|
var MARKER_END = "# <<< har commit gate <<<";
|
|
31970
32227
|
var PRE_COMMIT_BLOCK = [
|
|
@@ -31989,7 +32246,7 @@ function resolveCheckoutRoot(cwd) {
|
|
|
31989
32246
|
function resolveHooksDir(checkoutDir) {
|
|
31990
32247
|
const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
|
|
31991
32248
|
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
31992
|
-
return
|
|
32249
|
+
return path43.resolve(checkoutDir, hooksPath);
|
|
31993
32250
|
}
|
|
31994
32251
|
function getConfiguredHooksPath(checkoutDir) {
|
|
31995
32252
|
return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
|
|
@@ -31997,9 +32254,9 @@ function getConfiguredHooksPath(checkoutDir) {
|
|
|
31997
32254
|
function defaultHarInvocation() {
|
|
31998
32255
|
const entry = process.argv[1];
|
|
31999
32256
|
if (entry && entry.endsWith(".js")) {
|
|
32000
|
-
return `"${process.execPath}" "${
|
|
32257
|
+
return `"${process.execPath}" "${path43.resolve(entry)}"`;
|
|
32001
32258
|
}
|
|
32002
|
-
if (entry) return `"${
|
|
32259
|
+
if (entry) return `"${path43.resolve(entry)}"`;
|
|
32003
32260
|
return "har";
|
|
32004
32261
|
}
|
|
32005
32262
|
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
@@ -32020,40 +32277,40 @@ exit 0
|
|
|
32020
32277
|
`;
|
|
32021
32278
|
}
|
|
32022
32279
|
function upsertMarkedBlock(filePath, block) {
|
|
32023
|
-
if (!
|
|
32024
|
-
|
|
32280
|
+
if (!fs38.existsSync(filePath)) {
|
|
32281
|
+
fs38.writeFileSync(filePath, `#!/bin/sh
|
|
32025
32282
|
${block}
|
|
32026
32283
|
`, { mode: 493 });
|
|
32027
32284
|
return "created";
|
|
32028
32285
|
}
|
|
32029
|
-
const content =
|
|
32286
|
+
const content = fs38.readFileSync(filePath, "utf8");
|
|
32030
32287
|
if (content.includes(MARKER_START)) {
|
|
32031
32288
|
const pattern = new RegExp(
|
|
32032
32289
|
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
32033
32290
|
);
|
|
32034
|
-
|
|
32035
|
-
|
|
32291
|
+
fs38.writeFileSync(filePath, content.replace(pattern, block));
|
|
32292
|
+
fs38.chmodSync(filePath, 493);
|
|
32036
32293
|
return "updated";
|
|
32037
32294
|
}
|
|
32038
32295
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
32039
|
-
|
|
32296
|
+
fs38.writeFileSync(filePath, `${content}${suffix}
|
|
32040
32297
|
${block}
|
|
32041
32298
|
`);
|
|
32042
|
-
|
|
32299
|
+
fs38.chmodSync(filePath, 493);
|
|
32043
32300
|
return "appended";
|
|
32044
32301
|
}
|
|
32045
32302
|
function removeMarkedBlock(filePath) {
|
|
32046
|
-
if (!
|
|
32047
|
-
const content =
|
|
32303
|
+
if (!fs38.existsSync(filePath)) return false;
|
|
32304
|
+
const content = fs38.readFileSync(filePath, "utf8");
|
|
32048
32305
|
if (!content.includes(MARKER_START)) return false;
|
|
32049
32306
|
const pattern = new RegExp(
|
|
32050
32307
|
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
32051
32308
|
);
|
|
32052
32309
|
const stripped = content.replace(pattern, "\n");
|
|
32053
32310
|
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
32054
|
-
|
|
32311
|
+
fs38.rmSync(filePath);
|
|
32055
32312
|
} else {
|
|
32056
|
-
|
|
32313
|
+
fs38.writeFileSync(filePath, stripped);
|
|
32057
32314
|
}
|
|
32058
32315
|
return true;
|
|
32059
32316
|
}
|
|
@@ -32075,20 +32332,20 @@ Or re-run with --force to write into that directory anyway.`
|
|
|
32075
32332
|
);
|
|
32076
32333
|
}
|
|
32077
32334
|
const hooksDir = resolveHooksDir(checkout);
|
|
32078
|
-
|
|
32335
|
+
fs38.mkdirSync(hooksDir, { recursive: true });
|
|
32079
32336
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
32080
|
-
|
|
32081
|
-
|
|
32337
|
+
fs38.writeFileSync(
|
|
32338
|
+
path43.join(hooksDir, "har-pre-commit"),
|
|
32082
32339
|
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
32083
32340
|
{ mode: 493 }
|
|
32084
32341
|
);
|
|
32085
|
-
|
|
32086
|
-
|
|
32342
|
+
fs38.writeFileSync(
|
|
32343
|
+
path43.join(hooksDir, "har-post-commit"),
|
|
32087
32344
|
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
32088
32345
|
{ mode: 493 }
|
|
32089
32346
|
);
|
|
32090
|
-
const preCommit = upsertMarkedBlock(
|
|
32091
|
-
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);
|
|
32092
32349
|
ensureValidationsIgnored(checkout);
|
|
32093
32350
|
return { hooksDir, preCommit, postCommit };
|
|
32094
32351
|
}
|
|
@@ -32097,12 +32354,12 @@ function uninstallHooks(repoPath) {
|
|
|
32097
32354
|
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
32098
32355
|
const hooksDir = resolveHooksDir(checkout);
|
|
32099
32356
|
let removed = false;
|
|
32100
|
-
removed = removeMarkedBlock(
|
|
32101
|
-
removed = removeMarkedBlock(
|
|
32357
|
+
removed = removeMarkedBlock(path43.join(hooksDir, "pre-commit")) || removed;
|
|
32358
|
+
removed = removeMarkedBlock(path43.join(hooksDir, "post-commit")) || removed;
|
|
32102
32359
|
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
32103
|
-
const full =
|
|
32104
|
-
if (
|
|
32105
|
-
|
|
32360
|
+
const full = path43.join(hooksDir, file);
|
|
32361
|
+
if (fs38.existsSync(full)) {
|
|
32362
|
+
fs38.rmSync(full);
|
|
32106
32363
|
removed = true;
|
|
32107
32364
|
}
|
|
32108
32365
|
}
|
|
@@ -32119,9 +32376,9 @@ function getCommitGateConfig(checkoutDir) {
|
|
|
32119
32376
|
function isAgentWorktree(checkoutDir) {
|
|
32120
32377
|
const branch = getCurrentBranch(checkoutDir);
|
|
32121
32378
|
if (branch && /^har-agent-\d+$/.test(branch)) return true;
|
|
32122
|
-
const worktreesRoot =
|
|
32123
|
-
const resolved =
|
|
32124
|
-
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));
|
|
32125
32382
|
}
|
|
32126
32383
|
function resolveEffectiveMode(checkoutDir, gate) {
|
|
32127
32384
|
if (!gate.enabled) return "off";
|
|
@@ -32134,15 +32391,15 @@ function getHooksStatus(repoPath) {
|
|
|
32134
32391
|
const hooksDir = resolveHooksDir(checkout);
|
|
32135
32392
|
const gate = getCommitGateConfig(checkout);
|
|
32136
32393
|
const hasBlock = (name) => {
|
|
32137
|
-
const file =
|
|
32138
|
-
return
|
|
32394
|
+
const file = path43.join(hooksDir, name);
|
|
32395
|
+
return fs38.existsSync(file) && fs38.readFileSync(file, "utf8").includes(MARKER_START);
|
|
32139
32396
|
};
|
|
32140
32397
|
return {
|
|
32141
32398
|
checkout,
|
|
32142
32399
|
hooksDir,
|
|
32143
32400
|
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
32144
|
-
preCommitInstalled: hasBlock("pre-commit") &&
|
|
32145
|
-
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")),
|
|
32146
32403
|
gate,
|
|
32147
32404
|
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
32148
32405
|
};
|
|
@@ -32153,7 +32410,7 @@ function checkCommitGate(cwd) {
|
|
|
32153
32410
|
}
|
|
32154
32411
|
const checkout = resolveCheckoutRoot(cwd);
|
|
32155
32412
|
if (!checkout) return { exitCode: 0, messages: [] };
|
|
32156
|
-
if (!
|
|
32413
|
+
if (!fs38.existsSync(path43.join(checkout, ".har", "stages.json"))) {
|
|
32157
32414
|
return { exitCode: 0, messages: [] };
|
|
32158
32415
|
}
|
|
32159
32416
|
const gate = getCommitGateConfig(checkout);
|
|
@@ -32224,8 +32481,8 @@ async function recordCommitAssociation(cwd) {
|
|
|
32224
32481
|
}
|
|
32225
32482
|
|
|
32226
32483
|
// src/core/claude-hooks.ts
|
|
32227
|
-
var
|
|
32228
|
-
var
|
|
32484
|
+
var fs39 = __toESM(require("fs"));
|
|
32485
|
+
var path44 = __toESM(require("path"));
|
|
32229
32486
|
var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
|
|
32230
32487
|
var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
32231
32488
|
var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
|
|
@@ -32236,8 +32493,8 @@ function isHarGuardEntry(entry) {
|
|
|
32236
32493
|
);
|
|
32237
32494
|
}
|
|
32238
32495
|
function readSettings(settingsPath) {
|
|
32239
|
-
if (!
|
|
32240
|
-
const raw =
|
|
32496
|
+
if (!fs39.existsSync(settingsPath)) return {};
|
|
32497
|
+
const raw = fs39.readFileSync(settingsPath, "utf8").trim();
|
|
32241
32498
|
if (raw === "") return {};
|
|
32242
32499
|
const parsed = JSON.parse(raw);
|
|
32243
32500
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -32246,19 +32503,19 @@ function readSettings(settingsPath) {
|
|
|
32246
32503
|
return parsed;
|
|
32247
32504
|
}
|
|
32248
32505
|
function writeSettings(settingsPath, settings) {
|
|
32249
|
-
|
|
32250
|
-
|
|
32506
|
+
fs39.mkdirSync(path44.dirname(settingsPath), { recursive: true });
|
|
32507
|
+
fs39.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
32251
32508
|
}
|
|
32252
32509
|
function installClaudeGuard(repoPath) {
|
|
32253
32510
|
const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
|
|
32254
32511
|
if (!templatePath) {
|
|
32255
32512
|
throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
|
|
32256
32513
|
}
|
|
32257
|
-
const guardScript =
|
|
32258
|
-
|
|
32259
|
-
|
|
32260
|
-
|
|
32261
|
-
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);
|
|
32262
32519
|
const settings = readSettings(settingsPath);
|
|
32263
32520
|
const hooks = settings.hooks ?? {};
|
|
32264
32521
|
const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
|
|
@@ -32272,15 +32529,15 @@ function installClaudeGuard(repoPath) {
|
|
|
32272
32529
|
}
|
|
32273
32530
|
function uninstallClaudeGuard(repoPath) {
|
|
32274
32531
|
let removed = false;
|
|
32275
|
-
const guardScript =
|
|
32276
|
-
if (
|
|
32277
|
-
|
|
32532
|
+
const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
32533
|
+
if (fs39.existsSync(guardScript)) {
|
|
32534
|
+
fs39.rmSync(guardScript);
|
|
32278
32535
|
removed = true;
|
|
32279
|
-
const parent =
|
|
32280
|
-
if (
|
|
32536
|
+
const parent = path44.dirname(guardScript);
|
|
32537
|
+
if (fs39.existsSync(parent) && fs39.readdirSync(parent).length === 0) fs39.rmdirSync(parent);
|
|
32281
32538
|
}
|
|
32282
|
-
const settingsPath =
|
|
32283
|
-
if (
|
|
32539
|
+
const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32540
|
+
if (fs39.existsSync(settingsPath)) {
|
|
32284
32541
|
const settings = readSettings(settingsPath);
|
|
32285
32542
|
const preToolUse = settings.hooks?.PreToolUse;
|
|
32286
32543
|
if (preToolUse) {
|
|
@@ -32305,8 +32562,8 @@ function uninstallClaudeGuard(repoPath) {
|
|
|
32305
32562
|
return { removed };
|
|
32306
32563
|
}
|
|
32307
32564
|
function claudeGuardInstalled(repoPath) {
|
|
32308
|
-
const settingsPath =
|
|
32309
|
-
if (!
|
|
32565
|
+
const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32566
|
+
if (!fs39.existsSync(settingsPath)) return false;
|
|
32310
32567
|
try {
|
|
32311
32568
|
const settings = readSettings(settingsPath);
|
|
32312
32569
|
return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
|
|
@@ -32322,7 +32579,7 @@ function repoOption(y2) {
|
|
|
32322
32579
|
function handleInstall2(argv) {
|
|
32323
32580
|
try {
|
|
32324
32581
|
if (argv.claude) {
|
|
32325
|
-
const result2 = installClaudeGuard(
|
|
32582
|
+
const result2 = installClaudeGuard(path45.resolve(argv.repo));
|
|
32326
32583
|
success("Claude Code worktree guard installed.");
|
|
32327
32584
|
info(`Guard script: ${result2.guardScript}`);
|
|
32328
32585
|
info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
|
|
@@ -32330,7 +32587,7 @@ function handleInstall2(argv) {
|
|
|
32330
32587
|
info("Commit both files so the guard travels with the repo.");
|
|
32331
32588
|
return;
|
|
32332
32589
|
}
|
|
32333
|
-
const result = installHooks({ repoPath:
|
|
32590
|
+
const result = installHooks({ repoPath: path45.resolve(argv.repo), force: argv.force });
|
|
32334
32591
|
success(`Commit gate installed in ${result.hooksDir}`);
|
|
32335
32592
|
info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
|
|
32336
32593
|
info("Commits of unverified change batches will be blocked in agent worktrees.");
|
|
@@ -32343,7 +32600,7 @@ function handleInstall2(argv) {
|
|
|
32343
32600
|
function handleUninstall(argv) {
|
|
32344
32601
|
try {
|
|
32345
32602
|
if (argv.claude) {
|
|
32346
|
-
const result2 = uninstallClaudeGuard(
|
|
32603
|
+
const result2 = uninstallClaudeGuard(path45.resolve(argv.repo));
|
|
32347
32604
|
if (result2.removed) {
|
|
32348
32605
|
success("Claude Code worktree guard removed.");
|
|
32349
32606
|
} else {
|
|
@@ -32351,7 +32608,7 @@ function handleUninstall(argv) {
|
|
|
32351
32608
|
}
|
|
32352
32609
|
return;
|
|
32353
32610
|
}
|
|
32354
|
-
const result = uninstallHooks(
|
|
32611
|
+
const result = uninstallHooks(path45.resolve(argv.repo));
|
|
32355
32612
|
if (result.removed) {
|
|
32356
32613
|
success(`Commit gate removed from ${result.hooksDir}`);
|
|
32357
32614
|
} else {
|
|
@@ -32364,7 +32621,7 @@ function handleUninstall(argv) {
|
|
|
32364
32621
|
}
|
|
32365
32622
|
function handleStatus2(argv) {
|
|
32366
32623
|
try {
|
|
32367
|
-
const status = getHooksStatus(
|
|
32624
|
+
const status = getHooksStatus(path45.resolve(argv.repo));
|
|
32368
32625
|
if (argv.json) {
|
|
32369
32626
|
process.stdout.write(`${JSON.stringify(status, null, 2)}
|
|
32370
32627
|
`);
|
|
@@ -32382,7 +32639,7 @@ function handleStatus2(argv) {
|
|
|
32382
32639
|
);
|
|
32383
32640
|
info(`Effective: ${status.effectiveMode} (in this checkout)`);
|
|
32384
32641
|
info(
|
|
32385
|
-
`Claude guard: ${claudeGuardInstalled(
|
|
32642
|
+
`Claude guard: ${claudeGuardInstalled(path45.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
|
|
32386
32643
|
);
|
|
32387
32644
|
} catch (err) {
|
|
32388
32645
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -32643,10 +32900,10 @@ function assignProp(target, prop, value) {
|
|
|
32643
32900
|
configurable: true
|
|
32644
32901
|
});
|
|
32645
32902
|
}
|
|
32646
|
-
function getElementAtPath(obj,
|
|
32647
|
-
if (!
|
|
32903
|
+
function getElementAtPath(obj, path48) {
|
|
32904
|
+
if (!path48)
|
|
32648
32905
|
return obj;
|
|
32649
|
-
return
|
|
32906
|
+
return path48.reduce((acc, key) => acc?.[key], obj);
|
|
32650
32907
|
}
|
|
32651
32908
|
function promiseAllObject(promisesObj) {
|
|
32652
32909
|
const keys = Object.keys(promisesObj);
|
|
@@ -32966,11 +33223,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
32966
33223
|
}
|
|
32967
33224
|
return false;
|
|
32968
33225
|
}
|
|
32969
|
-
function prefixIssues(
|
|
33226
|
+
function prefixIssues(path48, issues) {
|
|
32970
33227
|
return issues.map((iss) => {
|
|
32971
33228
|
var _a3;
|
|
32972
33229
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
32973
|
-
iss.path.unshift(
|
|
33230
|
+
iss.path.unshift(path48);
|
|
32974
33231
|
return iss;
|
|
32975
33232
|
});
|
|
32976
33233
|
}
|
|
@@ -39444,7 +39701,7 @@ var Server = class extends Protocol {
|
|
|
39444
39701
|
};
|
|
39445
39702
|
|
|
39446
39703
|
// src/mcp/server.ts
|
|
39447
|
-
var
|
|
39704
|
+
var path46 = __toESM(require("path"));
|
|
39448
39705
|
|
|
39449
39706
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
39450
39707
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
@@ -40076,7 +40333,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
40076
40333
|
const input = ControlUpInputSchema.parse({ ...args, repo });
|
|
40077
40334
|
const result = await startControlAndSync({
|
|
40078
40335
|
detach: input.detach,
|
|
40079
|
-
cwd:
|
|
40336
|
+
cwd: path46.resolve(input.repo)
|
|
40080
40337
|
});
|
|
40081
40338
|
if (result.code !== 0) {
|
|
40082
40339
|
return {
|
|
@@ -40139,8 +40396,8 @@ var mcpCommand = {
|
|
|
40139
40396
|
};
|
|
40140
40397
|
|
|
40141
40398
|
// src/cli/commands/telemetry.ts
|
|
40142
|
-
var
|
|
40143
|
-
var
|
|
40399
|
+
var fs40 = __toESM(require("fs"));
|
|
40400
|
+
var path47 = __toESM(require("path"));
|
|
40144
40401
|
function printSignals() {
|
|
40145
40402
|
info("Collected when enabled (local Mission Control only):");
|
|
40146
40403
|
for (const signal of TELEMETRY_SIGNALS) {
|
|
@@ -40151,18 +40408,20 @@ function printSignals() {
|
|
|
40151
40408
|
async function handleStatus3(argv) {
|
|
40152
40409
|
const preference = readTelemetryPreference();
|
|
40153
40410
|
const enabled = isTelemetryEnabled();
|
|
40411
|
+
const signals = getTelemetrySignals();
|
|
40154
40412
|
const apiUrl = getControlApiUrl();
|
|
40155
40413
|
const reachable = enabled ? await isControlApiReachable(apiUrl) : false;
|
|
40156
40414
|
const payload = {
|
|
40157
40415
|
enabled,
|
|
40158
40416
|
preferenceEnabled: preference.enabled,
|
|
40417
|
+
signals,
|
|
40159
40418
|
preferencePath: getTelemetryPreferencePath(),
|
|
40160
40419
|
updatedAt: preference.updatedAt ?? null,
|
|
40161
40420
|
envOverride: process.env.HAR_TELEMETRY ?? null,
|
|
40162
40421
|
controlApiUrl: apiUrl,
|
|
40163
40422
|
controlReachable: reachable,
|
|
40164
40423
|
otelEndpoint: `${apiUrl.replace(/\/$/, "")}/api/otel`,
|
|
40165
|
-
|
|
40424
|
+
signalDescriptions: [...TELEMETRY_SIGNALS]
|
|
40166
40425
|
};
|
|
40167
40426
|
if (argv.json) {
|
|
40168
40427
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
@@ -40172,21 +40431,33 @@ async function handleStatus3(argv) {
|
|
|
40172
40431
|
header("har telemetry status");
|
|
40173
40432
|
info(`Enabled: ${enabled ? "yes" : "no"}${process.env.HAR_TELEMETRY ? " (HAR_TELEMETRY override)" : ""}`);
|
|
40174
40433
|
info(`Preference: ${getTelemetryPreferencePath()}${preference.updatedAt ? ` (updated ${preference.updatedAt})` : " (default on)"}`);
|
|
40434
|
+
info(
|
|
40435
|
+
`Signals: metrics=${signals.metrics} logs=${signals.logs} prompts=${signals.prompts} traces=${signals.traces}`
|
|
40436
|
+
);
|
|
40175
40437
|
info(`Mission Control: ${apiUrl} (${reachable ? "reachable" : "not reachable"})`);
|
|
40176
40438
|
info(`OTLP endpoint: ${payload.otelEndpoint}`);
|
|
40177
40439
|
printSignals();
|
|
40178
40440
|
}
|
|
40179
|
-
async function handleOn() {
|
|
40441
|
+
async function handleOn(argv) {
|
|
40180
40442
|
header("har telemetry on");
|
|
40181
|
-
writeTelemetryPreference(true
|
|
40443
|
+
const preference = writeTelemetryPreference(true, {
|
|
40444
|
+
prompts: argv.prompts === true ? true : void 0,
|
|
40445
|
+
traces: argv.traces === true ? true : void 0
|
|
40446
|
+
});
|
|
40182
40447
|
success("Telemetry enabled (opt-out). Agent usage will be stored in local Mission Control.");
|
|
40448
|
+
info(
|
|
40449
|
+
`Signals: metrics=${preference.signals.metrics} logs=${preference.signals.logs} prompts=${preference.signals.prompts} traces=${preference.signals.traces}`
|
|
40450
|
+
);
|
|
40451
|
+
if (preference.signals.prompts) {
|
|
40452
|
+
warn("Prompt/response text will leave the agent machine for local Mission Control storage.");
|
|
40453
|
+
}
|
|
40183
40454
|
printSignals();
|
|
40184
40455
|
const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
|
|
40185
40456
|
if (result.message) success(result.message);
|
|
40186
40457
|
if (result.warning) warn(result.warning);
|
|
40187
40458
|
if (result.otelReady) {
|
|
40188
40459
|
info(`OTLP ingest: ${result.apiUrl.replace(/\/$/, "")}/api/otel`);
|
|
40189
|
-
info("Usage appears under Mission Control \u2192 Worktrees. Disable: har telemetry off");
|
|
40460
|
+
info("Usage appears under Mission Control \u2192 Worktrees / Usage. Disable: har telemetry off");
|
|
40190
40461
|
}
|
|
40191
40462
|
}
|
|
40192
40463
|
async function handleOff() {
|
|
@@ -40197,7 +40468,7 @@ async function handleOff() {
|
|
|
40197
40468
|
info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
|
|
40198
40469
|
}
|
|
40199
40470
|
async function handleWriteEnv(argv) {
|
|
40200
|
-
const repoPath =
|
|
40471
|
+
const repoPath = path47.resolve(argv.repo);
|
|
40201
40472
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
40202
40473
|
const session = readSlotRegistry(harnessRoot, argv.agentId);
|
|
40203
40474
|
const workDir = argv.workDir ?? session?.workDir ?? repoPath;
|
|
@@ -40210,7 +40481,7 @@ async function handleWriteEnv(argv) {
|
|
|
40210
40481
|
suffix,
|
|
40211
40482
|
createdAt: session?.createdAt
|
|
40212
40483
|
});
|
|
40213
|
-
const envFile = argv.envFile ??
|
|
40484
|
+
const envFile = argv.envFile ?? path47.join(workDir, `.env.agent.${argv.agentId}`);
|
|
40214
40485
|
let otelReady = argv.otelReady;
|
|
40215
40486
|
if (otelReady === void 0 && isTelemetryEnabled()) {
|
|
40216
40487
|
const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
|
|
@@ -40229,7 +40500,7 @@ async function handleWriteEnv(argv) {
|
|
|
40229
40500
|
success(`Wrote telemetry env to ${envFile}`);
|
|
40230
40501
|
}
|
|
40231
40502
|
function handlePrintEnv(argv) {
|
|
40232
|
-
const repoPath =
|
|
40503
|
+
const repoPath = path47.resolve(argv.repo);
|
|
40233
40504
|
const workDir = argv.workDir ?? repoPath;
|
|
40234
40505
|
const sessionKey = buildSessionKey({
|
|
40235
40506
|
branch: argv.branch,
|
|
@@ -40251,7 +40522,7 @@ function handlePrintEnv(argv) {
|
|
|
40251
40522
|
);
|
|
40252
40523
|
}
|
|
40253
40524
|
function handleCodexSnippet(argv) {
|
|
40254
|
-
const repoPath =
|
|
40525
|
+
const repoPath = path47.resolve(argv.repo);
|
|
40255
40526
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
40256
40527
|
const session = readSlotRegistry(harnessRoot, argv.agentId);
|
|
40257
40528
|
const workDir = argv.workDir ?? session?.workDir ?? repoPath;
|
|
@@ -40273,10 +40544,10 @@ function handleCodexSnippet(argv) {
|
|
|
40273
40544
|
purpose: session?.purpose
|
|
40274
40545
|
});
|
|
40275
40546
|
if (argv.write) {
|
|
40276
|
-
const outDir =
|
|
40277
|
-
|
|
40278
|
-
const outPath =
|
|
40279
|
-
|
|
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);
|
|
40280
40551
|
success(`Wrote ${outPath}`);
|
|
40281
40552
|
info("Merge the [otel] table into ~/.codex/config.toml manually.");
|
|
40282
40553
|
return;
|
|
@@ -40296,10 +40567,20 @@ var telemetryCommand = {
|
|
|
40296
40567
|
).command(
|
|
40297
40568
|
"on",
|
|
40298
40569
|
"Enable telemetry (default) and ensure Mission Control is running",
|
|
40299
|
-
() => {
|
|
40300
|
-
|
|
40301
|
-
|
|
40302
|
-
|
|
40570
|
+
(y2) => y2.option("prompts", {
|
|
40571
|
+
type: "boolean",
|
|
40572
|
+
default: false,
|
|
40573
|
+
describe: "Opt in to shipping user/assistant prompt text to Mission Control"
|
|
40574
|
+
}).option("traces", {
|
|
40575
|
+
type: "boolean",
|
|
40576
|
+
default: false,
|
|
40577
|
+
describe: "Opt in to thin OTEL traces ingest (Claude beta)"
|
|
40578
|
+
}),
|
|
40579
|
+
(argv) => {
|
|
40580
|
+
void handleOn({
|
|
40581
|
+
prompts: argv.prompts,
|
|
40582
|
+
traces: argv.traces
|
|
40583
|
+
});
|
|
40303
40584
|
}
|
|
40304
40585
|
).command(
|
|
40305
40586
|
"off",
|