@osfactory/har 0.54.1 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1055 -945
- package/dist/templates/har-boilerplate/agent-slot.sh +44 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +44 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +43 -0
- package/dist/templates/har-boilerplate-ios/launch.sh +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4248,7 +4248,7 @@ var init_zod = __esm({
|
|
|
4248
4248
|
});
|
|
4249
4249
|
|
|
4250
4250
|
// packages/schemas/src/schema.ts
|
|
4251
|
-
var HAR_STAGE_KINDS, HAR_AGENT_SLOT_MIN, AgentSkillTargetSchema, ScaffoldedAgentFileSchema, HarnessManifestSchema, HarnessStageKindSchema, HarnessArtifactKindSchema, HarnessArtifactSchema, HarnessStageIdSchema, HarnessStageSchema, HarnessAgentSlotsSchema, CommitGateConfigSchema, OnboardingPreferencesSchema, HarnessStageRegistrySchema, HarnessVerificationStepSchema, ShellRunOutputSchema, HarnessVerificationResultSchema, HarnessStageRunStatusSchema, HarnessStageLogSchema, HarnessStageUrlSchema, HarnessStageRunResultSchema, StageKindSchema, VerificationResultSchema, StageResultSchema, RunRecordTriggerSchema, WorkUnitIdSchema, WorkAttemptIdSchema, WorkUnitMetadataSchema, WorkUnitOutcomeSchema, WorkUnitRecordSchema, WorkAttemptRecordSchema, RunRecordSchema, SlotRegistryEntrySchema, AgentSlotHarnessUsageSchema, PreflightBlockerSchema, SlotReadinessSchema, AgentSlotStatusSchema, EnvironmentStatusSchema, RegisterRepoInputSchema, UnregisterRepoInputSchema, UnregisterWorktreeResultSchema, UnregisterRepoResultSchema, ResetMissionControlInputSchema, HarnessScrubResultSchema, ResetMissionControlResultSchema, DeleteWorktreesInputSchema, DeleteWorktreeResultSchema, DeleteWorktreesResultSchema, SyncRunsInputSchema, SyncSlotsInputSchema, SyncWorkUnitsInputSchema, ChangedFileStatusSchema, ChangedFileSchema, ValidationRecordSchema, SyncValidationsInputSchema, ValidationBindingRecordSchema, SyncValidationBindingsInputSchema, AgentToolSchema, UsageSourceSchema, AgentSessionUsageSchema, SyncUsageInputSchema, AgentSessionEventSchema, SyncSessionEventsInputSchema;
|
|
4251
|
+
var HAR_STAGE_KINDS, HAR_AGENT_SLOT_MIN, AgentSkillTargetSchema, ScaffoldedAgentFileSchema, HarnessManifestSchema, HarnessStageKindSchema, HarnessArtifactKindSchema, HarnessArtifactSchema, HarnessStageIdSchema, HarnessStageSchema, HarnessAgentSlotsSchema, CommitGateConfigSchema, OnboardingPreferencesSchema, HarnessStageRegistrySchema, HarnessVerificationStepSchema, ShellRunOutputSchema, HarnessVerificationResultSchema, HarnessStageRunStatusSchema, HarnessStageLogSchema, HarnessStageUrlSchema, HarnessStageRunResultSchema, StageKindSchema, VerificationResultSchema, StageResultSchema, RunRecordTriggerSchema, WorkUnitIdSchema, WorkAttemptIdSchema, WorkUnitMetadataSchema, WorkUnitOutcomeSchema, WorkUnitRecordSchema, WorkAttemptRecordSchema, RunRecordSchema, SlotRegistryEntrySchema, AgentSlotHarnessUsageSchema, PreflightBlockerSchema, SlotReadinessSchema, AgentSlotStatusSchema, EnvironmentStatusSchema, RegisterRepoInputSchema, UnregisterRepoInputSchema, UnregisterWorktreeResultSchema, UnregisterRepoResultSchema, ResetMissionControlInputSchema, HarnessScrubResultSchema, ResetMissionControlResultSchema, DeleteWorktreesInputSchema, DeleteWorktreeResultSchema, DeleteWorktreesResultSchema, SyncRunsInputSchema, SyncSlotsInputSchema, SyncWorkUnitsInputSchema, ChangedFileStatusSchema, ChangedFileSchema, ValidationRecordSchema, SyncValidationsInputSchema, ValidationBindingRecordSchema, SyncValidationBindingsInputSchema, AgentToolSchema, UsageSourceSchema, AgentTrajectorySourceSchema, AgentTrajectoryContentDisclosureSchema, AgentTrajectoryRecordV1Schema, AgentSessionUsageSchema, SyncUsageInputSchema, AgentSessionEventSchema, SyncSessionEventsInputSchema;
|
|
4252
4252
|
var init_schema = __esm({
|
|
4253
4253
|
"packages/schemas/src/schema.ts"() {
|
|
4254
4254
|
"use strict";
|
|
@@ -4517,6 +4517,8 @@ var init_schema = __esm({
|
|
|
4517
4517
|
remediations: external_exports.array(external_exports.string()),
|
|
4518
4518
|
ports: external_exports.record(external_exports.number()).optional(),
|
|
4519
4519
|
allocatedPorts: external_exports.boolean().optional(),
|
|
4520
|
+
/** A warning already states why this port was chosen — suppresses the generic note. */
|
|
4521
|
+
portChoiceExplained: external_exports.boolean().optional(),
|
|
4520
4522
|
/** Non-blocking notices (e.g. har control up holds the default port but an alternate was picked). */
|
|
4521
4523
|
warnings: external_exports.array(external_exports.string()).optional()
|
|
4522
4524
|
});
|
|
@@ -4691,6 +4693,39 @@ var init_schema = __esm({
|
|
|
4691
4693
|
});
|
|
4692
4694
|
AgentToolSchema = external_exports.enum(["claude_code", "codex", "cursor"]);
|
|
4693
4695
|
UsageSourceSchema = external_exports.enum(["otel", "harvest"]);
|
|
4696
|
+
AgentTrajectorySourceSchema = external_exports.enum(["otel", "harvest", "har"]);
|
|
4697
|
+
AgentTrajectoryContentDisclosureSchema = external_exports.enum([
|
|
4698
|
+
"full",
|
|
4699
|
+
"redacted",
|
|
4700
|
+
"masked",
|
|
4701
|
+
"truncated",
|
|
4702
|
+
"withheld",
|
|
4703
|
+
"metadata_only"
|
|
4704
|
+
]);
|
|
4705
|
+
AgentTrajectoryRecordV1Schema = external_exports.object({
|
|
4706
|
+
version: external_exports.literal(1),
|
|
4707
|
+
source: AgentTrajectorySourceSchema,
|
|
4708
|
+
sourceEventId: external_exports.string().min(1),
|
|
4709
|
+
contentKey: external_exports.string().min(1),
|
|
4710
|
+
sessionKey: external_exports.string().min(1),
|
|
4711
|
+
agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
|
|
4712
|
+
agentTool: AgentToolSchema,
|
|
4713
|
+
eventType: external_exports.string().min(1),
|
|
4714
|
+
sequence: external_exports.number().int().nonnegative(),
|
|
4715
|
+
timestamp: external_exports.string(),
|
|
4716
|
+
payload: external_exports.record(external_exports.unknown()),
|
|
4717
|
+
contentKind: external_exports.string().min(1),
|
|
4718
|
+
contentDisclosure: AgentTrajectoryContentDisclosureSchema,
|
|
4719
|
+
contentLabel: external_exports.string().optional(),
|
|
4720
|
+
traceId: external_exports.string().optional(),
|
|
4721
|
+
spanId: external_exports.string().optional(),
|
|
4722
|
+
parentSpanId: external_exports.string().optional(),
|
|
4723
|
+
generationId: external_exports.string().optional(),
|
|
4724
|
+
toolCallId: external_exports.string().optional(),
|
|
4725
|
+
correlationId: external_exports.string().optional(),
|
|
4726
|
+
workUnitId: WorkUnitIdSchema.optional(),
|
|
4727
|
+
attemptId: WorkAttemptIdSchema.optional()
|
|
4728
|
+
}).passthrough();
|
|
4694
4729
|
AgentSessionUsageSchema = external_exports.object({
|
|
4695
4730
|
sessionKey: external_exports.string().min(1),
|
|
4696
4731
|
agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
|
|
@@ -29790,9 +29825,546 @@ var init_slot_launch_guard_occupied = __esm({
|
|
|
29790
29825
|
}
|
|
29791
29826
|
});
|
|
29792
29827
|
|
|
29828
|
+
// src/core/change-batch.ts
|
|
29829
|
+
function git(checkoutDir, args, env3) {
|
|
29830
|
+
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
29831
|
+
if (result.code !== 0) {
|
|
29832
|
+
throw new Error(`git ${args} failed (${result.code}): ${result.stderr.trim()}`);
|
|
29833
|
+
}
|
|
29834
|
+
return result.stdout.trim();
|
|
29835
|
+
}
|
|
29836
|
+
function tryGit2(checkoutDir, args) {
|
|
29837
|
+
const result = run(`git ${args}`, { cwd: checkoutDir });
|
|
29838
|
+
return result.code === 0 ? result.stdout.trim() : void 0;
|
|
29839
|
+
}
|
|
29840
|
+
function isCheckoutRoot(checkoutDir) {
|
|
29841
|
+
const toplevel = tryGit2(checkoutDir, "rev-parse --show-toplevel");
|
|
29842
|
+
if (!toplevel) return false;
|
|
29843
|
+
try {
|
|
29844
|
+
return fs16.realpathSync(toplevel) === fs16.realpathSync(checkoutDir);
|
|
29845
|
+
} catch {
|
|
29846
|
+
return false;
|
|
29847
|
+
}
|
|
29848
|
+
}
|
|
29849
|
+
function getHeadTree(checkoutDir) {
|
|
29850
|
+
return tryGit2(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
|
|
29851
|
+
}
|
|
29852
|
+
function getHeadSha(checkoutDir) {
|
|
29853
|
+
return tryGit2(checkoutDir, "rev-parse -q --verify HEAD");
|
|
29854
|
+
}
|
|
29855
|
+
function getCurrentBranch(checkoutDir) {
|
|
29856
|
+
const branch = tryGit2(checkoutDir, "rev-parse --abbrev-ref HEAD");
|
|
29857
|
+
return branch && branch !== "HEAD" ? branch : void 0;
|
|
29858
|
+
}
|
|
29859
|
+
function getEmptyTree(checkoutDir) {
|
|
29860
|
+
return git(checkoutDir, "hash-object -t tree /dev/null");
|
|
29861
|
+
}
|
|
29862
|
+
function isMergeOrRebaseInProgress(checkoutDir) {
|
|
29863
|
+
if (tryGit2(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
29864
|
+
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
29865
|
+
const gitPath = tryGit2(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
29866
|
+
if (gitPath && fs16.existsSync(path16.resolve(checkoutDir, gitPath))) return true;
|
|
29867
|
+
}
|
|
29868
|
+
return false;
|
|
29869
|
+
}
|
|
29870
|
+
function computeStagedTree(checkoutDir) {
|
|
29871
|
+
return git(checkoutDir, "write-tree");
|
|
29872
|
+
}
|
|
29873
|
+
function parseNameStatus(output) {
|
|
29874
|
+
const files = [];
|
|
29875
|
+
for (const line of output.split("\n")) {
|
|
29876
|
+
if (!line.trim()) continue;
|
|
29877
|
+
const parts = line.split(" ");
|
|
29878
|
+
const status = parts[0]?.charAt(0);
|
|
29879
|
+
if (!status) continue;
|
|
29880
|
+
if ((status === "R" || status === "C") && parts.length >= 3) {
|
|
29881
|
+
files.push({ path: parts[2], status, oldPath: parts[1] });
|
|
29882
|
+
} else if (parts.length >= 2) {
|
|
29883
|
+
files.push({ path: parts[1], status });
|
|
29884
|
+
}
|
|
29885
|
+
}
|
|
29886
|
+
return files;
|
|
29887
|
+
}
|
|
29888
|
+
function computeWorktreeSnapshot(checkoutDir) {
|
|
29889
|
+
const tmpDir = fs16.mkdtempSync(path16.join(os6.tmpdir(), "har-idx-"));
|
|
29890
|
+
const tmpIndex = path16.join(tmpDir, "index");
|
|
29891
|
+
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
29892
|
+
try {
|
|
29893
|
+
const headTree = getHeadTree(checkoutDir);
|
|
29894
|
+
if (headTree) {
|
|
29895
|
+
git(checkoutDir, "read-tree HEAD", env3);
|
|
29896
|
+
} else {
|
|
29897
|
+
git(checkoutDir, "read-tree --empty", env3);
|
|
29898
|
+
}
|
|
29899
|
+
git(checkoutDir, "add -A .", env3);
|
|
29900
|
+
const treeHash = git(checkoutDir, "write-tree", env3);
|
|
29901
|
+
const baseTree = headTree ?? getEmptyTree(checkoutDir);
|
|
29902
|
+
const diffOutput = baseTree === treeHash ? "" : git(
|
|
29903
|
+
checkoutDir,
|
|
29904
|
+
`diff-tree -r -M --name-status --ignore-submodules=all ${baseTree} ${treeHash}`
|
|
29905
|
+
);
|
|
29906
|
+
return {
|
|
29907
|
+
treeHash,
|
|
29908
|
+
headSha: getHeadSha(checkoutDir),
|
|
29909
|
+
branch: getCurrentBranch(checkoutDir),
|
|
29910
|
+
changedFiles: parseNameStatus(diffOutput)
|
|
29911
|
+
};
|
|
29912
|
+
} finally {
|
|
29913
|
+
fs16.rmSync(tmpDir, { recursive: true, force: true });
|
|
29914
|
+
}
|
|
29915
|
+
}
|
|
29916
|
+
var fs16, os6, path16;
|
|
29917
|
+
var init_change_batch = __esm({
|
|
29918
|
+
"src/core/change-batch.ts"() {
|
|
29919
|
+
"use strict";
|
|
29920
|
+
fs16 = __toESM(require("fs"));
|
|
29921
|
+
os6 = __toESM(require("os"));
|
|
29922
|
+
path16 = __toESM(require("path"));
|
|
29923
|
+
init_shell();
|
|
29924
|
+
}
|
|
29925
|
+
});
|
|
29926
|
+
|
|
29927
|
+
// src/core/validations.ts
|
|
29928
|
+
function resolveValidationCheckoutDir(input) {
|
|
29929
|
+
const candidates = [input.worktreePath, input.workDir, input.harnessRoot].filter(
|
|
29930
|
+
(value) => Boolean(value)
|
|
29931
|
+
);
|
|
29932
|
+
return candidates.find(isCheckoutRoot) ?? candidates[0] ?? input.harnessRoot;
|
|
29933
|
+
}
|
|
29934
|
+
function getValidationsDir(checkoutDir) {
|
|
29935
|
+
return path17.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
29936
|
+
}
|
|
29937
|
+
function validationPath(checkoutDir, treeHash) {
|
|
29938
|
+
return path17.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
29939
|
+
}
|
|
29940
|
+
function writeRecord(checkoutDir, record2) {
|
|
29941
|
+
const dir = getValidationsDir(checkoutDir);
|
|
29942
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
29943
|
+
fs17.writeFileSync(
|
|
29944
|
+
validationPath(checkoutDir, record2.treeHash),
|
|
29945
|
+
`${JSON.stringify(record2, null, 2)}
|
|
29946
|
+
`
|
|
29947
|
+
);
|
|
29948
|
+
}
|
|
29949
|
+
function ensureValidationsIgnored(checkoutDir) {
|
|
29950
|
+
const harDir = path17.join(checkoutDir, ".har");
|
|
29951
|
+
if (!fs17.existsSync(harDir)) return;
|
|
29952
|
+
const gitignorePath = path17.join(harDir, ".gitignore");
|
|
29953
|
+
const content = fs17.existsSync(gitignorePath) ? fs17.readFileSync(gitignorePath, "utf8") : "";
|
|
29954
|
+
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
29955
|
+
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
29956
|
+
fs17.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
29957
|
+
`);
|
|
29958
|
+
}
|
|
29959
|
+
function recordValidation(input) {
|
|
29960
|
+
if (!isCheckoutRoot(input.checkoutDir)) {
|
|
29961
|
+
throw new Error(`Not the root of a git checkout: ${input.checkoutDir}`);
|
|
29962
|
+
}
|
|
29963
|
+
ensureValidationsIgnored(input.checkoutDir);
|
|
29964
|
+
ensureValidationsIgnored(input.harnessRoot);
|
|
29965
|
+
const snapshot = computeWorktreeSnapshot(input.checkoutDir);
|
|
29966
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
29967
|
+
const existing = findValidation(input.checkoutDir, snapshot.treeHash);
|
|
29968
|
+
const record2 = ValidationRecordSchema.parse({
|
|
29969
|
+
validationId: existing?.validationId ?? crypto3.randomUUID(),
|
|
29970
|
+
treeHash: snapshot.treeHash,
|
|
29971
|
+
headSha: snapshot.headSha,
|
|
29972
|
+
branch: snapshot.branch,
|
|
29973
|
+
workDir: path17.resolve(input.checkoutDir),
|
|
29974
|
+
harnessRoot: path17.resolve(input.harnessRoot),
|
|
29975
|
+
agentId: input.agentId,
|
|
29976
|
+
status: input.status,
|
|
29977
|
+
full: input.full,
|
|
29978
|
+
runId: input.runId,
|
|
29979
|
+
changedFiles: snapshot.changedFiles,
|
|
29980
|
+
createdAt: existing?.createdAt ?? now,
|
|
29981
|
+
updatedAt: now,
|
|
29982
|
+
commitSha: existing?.commitSha,
|
|
29983
|
+
committedAt: existing?.committedAt
|
|
29984
|
+
});
|
|
29985
|
+
writeRecord(input.checkoutDir, record2);
|
|
29986
|
+
if (path17.resolve(input.harnessRoot) !== path17.resolve(input.checkoutDir)) {
|
|
29987
|
+
writeRecord(input.harnessRoot, record2);
|
|
29988
|
+
}
|
|
29989
|
+
markDirty(input.checkoutDir);
|
|
29990
|
+
return record2;
|
|
29991
|
+
}
|
|
29992
|
+
function findValidation(checkoutDir, treeHash) {
|
|
29993
|
+
const file = validationPath(checkoutDir, treeHash);
|
|
29994
|
+
if (!fs17.existsSync(file)) return void 0;
|
|
29995
|
+
try {
|
|
29996
|
+
return ValidationRecordSchema.parse(JSON.parse(fs17.readFileSync(file, "utf8")));
|
|
29997
|
+
} catch {
|
|
29998
|
+
return void 0;
|
|
29999
|
+
}
|
|
30000
|
+
}
|
|
30001
|
+
function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
30002
|
+
const record2 = findValidation(checkoutDir, treeHash);
|
|
30003
|
+
if (!record2) return void 0;
|
|
30004
|
+
const updated = {
|
|
30005
|
+
...record2,
|
|
30006
|
+
commitSha,
|
|
30007
|
+
committedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30008
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30009
|
+
};
|
|
30010
|
+
writeRecord(checkoutDir, updated);
|
|
30011
|
+
if (record2.harnessRoot && path17.resolve(record2.harnessRoot) !== path17.resolve(checkoutDir) && fs17.existsSync(path17.join(record2.harnessRoot, ".har"))) {
|
|
30012
|
+
writeRecord(record2.harnessRoot, updated);
|
|
30013
|
+
}
|
|
30014
|
+
return updated;
|
|
30015
|
+
}
|
|
30016
|
+
function listValidations(harnessRoot) {
|
|
30017
|
+
const dir = getValidationsDir(harnessRoot);
|
|
30018
|
+
if (!fs17.existsSync(dir)) return [];
|
|
30019
|
+
const records = [];
|
|
30020
|
+
for (const entry of fs17.readdirSync(dir)) {
|
|
30021
|
+
if (!entry.endsWith(".json")) continue;
|
|
30022
|
+
try {
|
|
30023
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs17.readFileSync(path17.join(dir, entry), "utf8"))));
|
|
30024
|
+
} catch {
|
|
30025
|
+
}
|
|
30026
|
+
}
|
|
30027
|
+
return records.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
|
|
30028
|
+
}
|
|
30029
|
+
var crypto3, fs17, path17, VALIDATIONS_DIR;
|
|
30030
|
+
var init_validations = __esm({
|
|
30031
|
+
"src/core/validations.ts"() {
|
|
30032
|
+
"use strict";
|
|
30033
|
+
crypto3 = __toESM(require("crypto"));
|
|
30034
|
+
fs17 = __toESM(require("fs"));
|
|
30035
|
+
path17 = __toESM(require("path"));
|
|
30036
|
+
init_schema2();
|
|
30037
|
+
init_change_batch();
|
|
30038
|
+
init_sync_context();
|
|
30039
|
+
VALIDATIONS_DIR = "validations";
|
|
30040
|
+
}
|
|
30041
|
+
});
|
|
30042
|
+
|
|
30043
|
+
// src/core/hooks.ts
|
|
30044
|
+
function tryGit3(cwd, args) {
|
|
30045
|
+
const result = run(`git ${args}`, { cwd });
|
|
30046
|
+
return result.code === 0 ? result.stdout.trim() : void 0;
|
|
30047
|
+
}
|
|
30048
|
+
function resolveCheckoutRoot(cwd) {
|
|
30049
|
+
return tryGit3(cwd, "rev-parse --show-toplevel");
|
|
30050
|
+
}
|
|
30051
|
+
function resolveHooksDir(checkoutDir) {
|
|
30052
|
+
const hooksPath = tryGit3(checkoutDir, "rev-parse --git-path hooks");
|
|
30053
|
+
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
30054
|
+
return path18.resolve(checkoutDir, hooksPath);
|
|
30055
|
+
}
|
|
30056
|
+
function getConfiguredHooksPath(checkoutDir) {
|
|
30057
|
+
return tryGit3(checkoutDir, "config core.hooksPath") || void 0;
|
|
30058
|
+
}
|
|
30059
|
+
function defaultHarInvocation() {
|
|
30060
|
+
const entry = process.argv[1];
|
|
30061
|
+
if (entry && entry.endsWith(".js")) {
|
|
30062
|
+
return `"${process.execPath}" "${path18.resolve(entry)}"`;
|
|
30063
|
+
}
|
|
30064
|
+
if (entry) return `"${path18.resolve(entry)}"`;
|
|
30065
|
+
return "har";
|
|
30066
|
+
}
|
|
30067
|
+
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
30068
|
+
return `#!/bin/sh
|
|
30069
|
+
# Managed by \`har hooks\` \u2014 do not edit; reinstall with \`har hooks install\`.
|
|
30070
|
+
if ${harInvocation} --version >/dev/null 2>&1; then
|
|
30071
|
+
exec ${harInvocation} hooks ${subcommand}
|
|
30072
|
+
fi
|
|
30073
|
+
if command -v har >/dev/null 2>&1; then
|
|
30074
|
+
exec har hooks ${subcommand}
|
|
30075
|
+
fi
|
|
30076
|
+
toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
30077
|
+
if [ -n "$toplevel" ] && [ -x "$toplevel/node_modules/.bin/har" ]; then
|
|
30078
|
+
exec "$toplevel/node_modules/.bin/har" hooks ${subcommand}
|
|
30079
|
+
fi
|
|
30080
|
+
echo "${failOpenNotice}" >&2
|
|
30081
|
+
exit 0
|
|
30082
|
+
`;
|
|
30083
|
+
}
|
|
30084
|
+
function upsertMarkedBlock(filePath, block) {
|
|
30085
|
+
if (!fs18.existsSync(filePath)) {
|
|
30086
|
+
fs18.writeFileSync(filePath, `#!/bin/sh
|
|
30087
|
+
${block}
|
|
30088
|
+
`, { mode: 493 });
|
|
30089
|
+
return "created";
|
|
30090
|
+
}
|
|
30091
|
+
const content = fs18.readFileSync(filePath, "utf8");
|
|
30092
|
+
if (content.includes(MARKER_START)) {
|
|
30093
|
+
const pattern = new RegExp(
|
|
30094
|
+
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
30095
|
+
);
|
|
30096
|
+
fs18.writeFileSync(filePath, content.replace(pattern, block));
|
|
30097
|
+
fs18.chmodSync(filePath, 493);
|
|
30098
|
+
return "updated";
|
|
30099
|
+
}
|
|
30100
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
30101
|
+
fs18.writeFileSync(filePath, `${content}${suffix}
|
|
30102
|
+
${block}
|
|
30103
|
+
`);
|
|
30104
|
+
fs18.chmodSync(filePath, 493);
|
|
30105
|
+
return "appended";
|
|
30106
|
+
}
|
|
30107
|
+
function removeMarkedBlock(filePath) {
|
|
30108
|
+
if (!fs18.existsSync(filePath)) return false;
|
|
30109
|
+
const content = fs18.readFileSync(filePath, "utf8");
|
|
30110
|
+
if (!content.includes(MARKER_START)) return false;
|
|
30111
|
+
const pattern = new RegExp(
|
|
30112
|
+
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
30113
|
+
);
|
|
30114
|
+
const stripped = content.replace(pattern, "\n");
|
|
30115
|
+
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
30116
|
+
fs18.rmSync(filePath);
|
|
30117
|
+
} else {
|
|
30118
|
+
fs18.writeFileSync(filePath, stripped);
|
|
30119
|
+
}
|
|
30120
|
+
return true;
|
|
30121
|
+
}
|
|
30122
|
+
function escapeRegExp(value) {
|
|
30123
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30124
|
+
}
|
|
30125
|
+
function installHooks(options) {
|
|
30126
|
+
const checkout = resolveCheckoutRoot(options.repoPath);
|
|
30127
|
+
if (!checkout) throw new Error(`Not a git repository: ${options.repoPath}`);
|
|
30128
|
+
const configuredHooksPath = getConfiguredHooksPath(checkout);
|
|
30129
|
+
if (configuredHooksPath && !options.force) {
|
|
30130
|
+
throw new Error(
|
|
30131
|
+
`core.hooksPath is set to "${configuredHooksPath}" (managed hooks, e.g. husky).
|
|
30132
|
+
Add this to your managed pre-commit hook instead:
|
|
30133
|
+
|
|
30134
|
+
${PRE_COMMIT_BLOCK.split("\n").join("\n ")}
|
|
30135
|
+
|
|
30136
|
+
Or re-run with --force to write into that directory anyway.`
|
|
30137
|
+
);
|
|
30138
|
+
}
|
|
30139
|
+
const hooksDir = resolveHooksDir(checkout);
|
|
30140
|
+
fs18.mkdirSync(hooksDir, { recursive: true });
|
|
30141
|
+
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
30142
|
+
fs18.writeFileSync(
|
|
30143
|
+
path18.join(hooksDir, "har-pre-commit"),
|
|
30144
|
+
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
30145
|
+
{ mode: 493 }
|
|
30146
|
+
);
|
|
30147
|
+
fs18.writeFileSync(
|
|
30148
|
+
path18.join(hooksDir, "har-post-commit"),
|
|
30149
|
+
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
30150
|
+
{ mode: 493 }
|
|
30151
|
+
);
|
|
30152
|
+
const preCommit = upsertMarkedBlock(path18.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
30153
|
+
const postCommit = upsertMarkedBlock(path18.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
30154
|
+
ensureValidationsIgnored(checkout);
|
|
30155
|
+
return { hooksDir, preCommit, postCommit };
|
|
30156
|
+
}
|
|
30157
|
+
function uninstallHooks(repoPath) {
|
|
30158
|
+
const checkout = resolveCheckoutRoot(repoPath);
|
|
30159
|
+
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
30160
|
+
const hooksDir = resolveHooksDir(checkout);
|
|
30161
|
+
let removed = false;
|
|
30162
|
+
removed = removeMarkedBlock(path18.join(hooksDir, "pre-commit")) || removed;
|
|
30163
|
+
removed = removeMarkedBlock(path18.join(hooksDir, "post-commit")) || removed;
|
|
30164
|
+
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
30165
|
+
const full = path18.join(hooksDir, file);
|
|
30166
|
+
if (fs18.existsSync(full)) {
|
|
30167
|
+
fs18.rmSync(full);
|
|
30168
|
+
removed = true;
|
|
30169
|
+
}
|
|
30170
|
+
}
|
|
30171
|
+
return { hooksDir, removed };
|
|
30172
|
+
}
|
|
30173
|
+
function getCommitGateConfig(checkoutDir) {
|
|
30174
|
+
try {
|
|
30175
|
+
const registry2 = readStageRegistry(checkoutDir);
|
|
30176
|
+
return CommitGateConfigSchema.parse(registry2.commitGate ?? {});
|
|
30177
|
+
} catch {
|
|
30178
|
+
return CommitGateConfigSchema.parse({});
|
|
30179
|
+
}
|
|
30180
|
+
}
|
|
30181
|
+
function isAgentWorktree(checkoutDir) {
|
|
30182
|
+
const branch = getCurrentBranch(checkoutDir);
|
|
30183
|
+
if (branch && (/^har-agent-\d+$/.test(branch) || /-har-agent-\d+-[a-z0-9]+$/.test(branch))) {
|
|
30184
|
+
return true;
|
|
30185
|
+
}
|
|
30186
|
+
const worktreesRoot = path18.join(os7.homedir(), "worktrees");
|
|
30187
|
+
const resolved = path18.resolve(checkoutDir);
|
|
30188
|
+
const name = path18.basename(resolved);
|
|
30189
|
+
return resolved.startsWith(`${worktreesRoot}${path18.sep}`) && (/-agent-\d+$/.test(name) || /-har-agent-\d+-[a-z0-9]+$/.test(name));
|
|
30190
|
+
}
|
|
30191
|
+
function resolveEffectiveMode(checkoutDir, gate) {
|
|
30192
|
+
if (!gate.enabled) return "off";
|
|
30193
|
+
if (gate.scope === "all") return gate.mode;
|
|
30194
|
+
return isAgentWorktree(checkoutDir) ? gate.mode : "warn";
|
|
30195
|
+
}
|
|
30196
|
+
function getHooksStatus(repoPath) {
|
|
30197
|
+
const checkout = resolveCheckoutRoot(repoPath);
|
|
30198
|
+
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
30199
|
+
const hooksDir = resolveHooksDir(checkout);
|
|
30200
|
+
const gate = getCommitGateConfig(checkout);
|
|
30201
|
+
const hasBlock = (name) => {
|
|
30202
|
+
const file = path18.join(hooksDir, name);
|
|
30203
|
+
return fs18.existsSync(file) && fs18.readFileSync(file, "utf8").includes(MARKER_START);
|
|
30204
|
+
};
|
|
30205
|
+
return {
|
|
30206
|
+
checkout,
|
|
30207
|
+
hooksDir,
|
|
30208
|
+
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
30209
|
+
preCommitInstalled: hasBlock("pre-commit") && fs18.existsSync(path18.join(hooksDir, "har-pre-commit")),
|
|
30210
|
+
postCommitInstalled: hasBlock("post-commit") && fs18.existsSync(path18.join(hooksDir, "har-post-commit")),
|
|
30211
|
+
gate,
|
|
30212
|
+
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
30213
|
+
};
|
|
30214
|
+
}
|
|
30215
|
+
function checkCommitGate(cwd) {
|
|
30216
|
+
if (process.env.HAR_SKIP_GATE === "1") {
|
|
30217
|
+
return { exitCode: 0, messages: ["har: commit gate skipped (HAR_SKIP_GATE=1)"] };
|
|
30218
|
+
}
|
|
30219
|
+
const checkout = resolveCheckoutRoot(cwd);
|
|
30220
|
+
if (!checkout) return { exitCode: 0, messages: [] };
|
|
30221
|
+
if (!fs18.existsSync(path18.join(checkout, ".har", "stages.json"))) {
|
|
30222
|
+
return { exitCode: 0, messages: [] };
|
|
30223
|
+
}
|
|
30224
|
+
const gate = getCommitGateConfig(checkout);
|
|
30225
|
+
const mode = resolveEffectiveMode(checkout, gate);
|
|
30226
|
+
if (mode === "off") return { exitCode: 0, messages: [] };
|
|
30227
|
+
if (!getHeadSha(checkout)) {
|
|
30228
|
+
return { exitCode: 0, messages: ["har: commit gate skipped (initial commit)"] };
|
|
30229
|
+
}
|
|
30230
|
+
if (isMergeOrRebaseInProgress(checkout)) {
|
|
30231
|
+
return { exitCode: 0, messages: ["har: commit gate skipped (merge/rebase in progress)"] };
|
|
30232
|
+
}
|
|
30233
|
+
let stagedTree;
|
|
30234
|
+
try {
|
|
30235
|
+
stagedTree = computeStagedTree(checkout);
|
|
30236
|
+
} catch {
|
|
30237
|
+
return { exitCode: 0, messages: ["har: commit gate skipped (index has unmerged entries)"] };
|
|
30238
|
+
}
|
|
30239
|
+
if (stagedTree === getHeadTree(checkout)) {
|
|
30240
|
+
return { exitCode: 0, messages: [] };
|
|
30241
|
+
}
|
|
30242
|
+
const record2 = findValidation(checkout, stagedTree);
|
|
30243
|
+
if (record2 && record2.status === "pass" && record2.full) {
|
|
30244
|
+
return {
|
|
30245
|
+
exitCode: 0,
|
|
30246
|
+
messages: [
|
|
30247
|
+
`har: batch ${stagedTree.slice(0, 8)} verified${record2.runId ? ` by run ${record2.runId.slice(0, 8)}` : ""} \u2713`
|
|
30248
|
+
]
|
|
30249
|
+
};
|
|
30250
|
+
}
|
|
30251
|
+
const messages = [];
|
|
30252
|
+
const verb = mode === "block" ? "commit blocked" : "warning";
|
|
30253
|
+
messages.push(
|
|
30254
|
+
`har: ${verb} \u2014 no passing verification for this exact change batch (tree ${stagedTree.slice(0, 8)}).`
|
|
30255
|
+
);
|
|
30256
|
+
if (record2 && record2.status === "fail") {
|
|
30257
|
+
messages.push(
|
|
30258
|
+
` This exact batch was verified and FAILED${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}. Fix and re-run.`
|
|
30259
|
+
);
|
|
30260
|
+
} else if (record2 && !record2.full) {
|
|
30261
|
+
messages.push(
|
|
30262
|
+
` This batch only passed a partial verify${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}; the gate requires --full.`
|
|
30263
|
+
);
|
|
30264
|
+
}
|
|
30265
|
+
messages.push(" Run the full pipeline: har env verify <agentId> --full");
|
|
30266
|
+
messages.push(
|
|
30267
|
+
" Note: verify hashes the WORKING TREE \u2014 stage exactly what you verified (git add -A)."
|
|
30268
|
+
);
|
|
30269
|
+
messages.push(" Bypass (humans only): HAR_SKIP_GATE=1 git commit \u2026 or git commit --no-verify");
|
|
30270
|
+
messages.push(" Configure via commitGate { mode: block|warn, scope: worktrees|all } in .har/stages.json");
|
|
30271
|
+
return { exitCode: mode === "block" ? 1 : 0, messages };
|
|
30272
|
+
}
|
|
30273
|
+
async function recordCommitAssociation(cwd) {
|
|
30274
|
+
try {
|
|
30275
|
+
const checkout = resolveCheckoutRoot(cwd);
|
|
30276
|
+
if (!checkout) return { attached: false };
|
|
30277
|
+
const commitSha = getHeadSha(checkout);
|
|
30278
|
+
const tree = tryGit3(checkout, "rev-parse HEAD^{tree}");
|
|
30279
|
+
if (!commitSha || !tree) return { attached: false };
|
|
30280
|
+
const updated = attachCommit(checkout, tree, commitSha);
|
|
30281
|
+
if (updated) {
|
|
30282
|
+
markDirty(updated.harnessRoot);
|
|
30283
|
+
return { attached: true, commitSha };
|
|
30284
|
+
}
|
|
30285
|
+
return { attached: false, commitSha };
|
|
30286
|
+
} catch {
|
|
30287
|
+
return { attached: false };
|
|
30288
|
+
}
|
|
30289
|
+
}
|
|
30290
|
+
var fs18, os7, path18, MARKER_START, MARKER_END, PRE_COMMIT_BLOCK, POST_COMMIT_BLOCK;
|
|
30291
|
+
var init_hooks = __esm({
|
|
30292
|
+
"src/core/hooks.ts"() {
|
|
30293
|
+
"use strict";
|
|
30294
|
+
fs18 = __toESM(require("fs"));
|
|
30295
|
+
os7 = __toESM(require("os"));
|
|
30296
|
+
path18 = __toESM(require("path"));
|
|
30297
|
+
init_shell();
|
|
30298
|
+
init_schema2();
|
|
30299
|
+
init_stages();
|
|
30300
|
+
init_change_batch();
|
|
30301
|
+
init_validations();
|
|
30302
|
+
init_sync_context();
|
|
30303
|
+
MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
30304
|
+
MARKER_END = "# <<< har commit gate <<<";
|
|
30305
|
+
PRE_COMMIT_BLOCK = [
|
|
30306
|
+
MARKER_START,
|
|
30307
|
+
'if [ "$HAR_SKIP_GATE" != "1" ]; then',
|
|
30308
|
+
' "$(git rev-parse --git-path hooks)/har-pre-commit" || exit 1',
|
|
30309
|
+
"fi",
|
|
30310
|
+
MARKER_END
|
|
30311
|
+
].join("\n");
|
|
30312
|
+
POST_COMMIT_BLOCK = [
|
|
30313
|
+
MARKER_START,
|
|
30314
|
+
'"$(git rev-parse --git-path hooks)/har-post-commit" || true',
|
|
30315
|
+
MARKER_END
|
|
30316
|
+
].join("\n");
|
|
30317
|
+
}
|
|
30318
|
+
});
|
|
30319
|
+
|
|
30320
|
+
// src/core/worktree-untracked.ts
|
|
30321
|
+
function listUntrackedAbsentFromWorktree(repoPath) {
|
|
30322
|
+
const toplevel = resolveCheckoutRoot(repoPath);
|
|
30323
|
+
if (!toplevel) return [];
|
|
30324
|
+
try {
|
|
30325
|
+
const raw = (0, import_child_process6.execSync)(
|
|
30326
|
+
"git --literal-pathspecs ls-files --others --exclude-standard --directory -z",
|
|
30327
|
+
{
|
|
30328
|
+
cwd: toplevel,
|
|
30329
|
+
encoding: "utf8",
|
|
30330
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
30331
|
+
timeout: 1e4,
|
|
30332
|
+
maxBuffer: 8 * 1024 * 1024
|
|
30333
|
+
}
|
|
30334
|
+
);
|
|
30335
|
+
return raw.split("\0").filter(Boolean).sort();
|
|
30336
|
+
} catch {
|
|
30337
|
+
return [];
|
|
30338
|
+
}
|
|
30339
|
+
}
|
|
30340
|
+
function formatUntrackedWorktreeWarning(paths, maxListed = MAX_LISTED) {
|
|
30341
|
+
if (paths.length === 0) return void 0;
|
|
30342
|
+
const shown = paths.slice(0, maxListed);
|
|
30343
|
+
const omitted = paths.length - shown.length;
|
|
30344
|
+
const listed = shown.join(", ") + (omitted > 0 ? ` (+${omitted} more)` : "");
|
|
30345
|
+
const n = paths.length;
|
|
30346
|
+
const noun = n === 1 ? "path" : "paths";
|
|
30347
|
+
const them = n === 1 ? "It is" : "They are";
|
|
30348
|
+
return `${n} untracked ${noun} will not appear in the session worktree: ${listed}. ${them} only in the main checkout \u2014 track them, or launch with --no-worktree.`;
|
|
30349
|
+
}
|
|
30350
|
+
function worktreeCheckEnabled(env3, worktree) {
|
|
30351
|
+
if (worktree === false) return false;
|
|
30352
|
+
if (worktree === true) return true;
|
|
30353
|
+
return (env3.HARNESS_USE_WORKTREE ?? "true").toLowerCase() !== "false";
|
|
30354
|
+
}
|
|
30355
|
+
var import_child_process6, MAX_LISTED;
|
|
30356
|
+
var init_worktree_untracked = __esm({
|
|
30357
|
+
"src/core/worktree-untracked.ts"() {
|
|
30358
|
+
"use strict";
|
|
30359
|
+
import_child_process6 = require("child_process");
|
|
30360
|
+
init_hooks();
|
|
30361
|
+
MAX_LISTED = 8;
|
|
30362
|
+
}
|
|
30363
|
+
});
|
|
30364
|
+
|
|
29793
30365
|
// src/utils/package-runner.ts
|
|
29794
30366
|
function commandExists(command2) {
|
|
29795
|
-
const result = (0,
|
|
30367
|
+
const result = (0, import_child_process7.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
|
|
29796
30368
|
encoding: "utf8"
|
|
29797
30369
|
});
|
|
29798
30370
|
return result.status === 0 && Boolean(result.stdout?.trim());
|
|
@@ -29803,11 +30375,11 @@ function packageRunner() {
|
|
|
29803
30375
|
cached = available ?? RUNNERS[0];
|
|
29804
30376
|
return cached;
|
|
29805
30377
|
}
|
|
29806
|
-
var
|
|
30378
|
+
var import_child_process7, RUNNERS, cached;
|
|
29807
30379
|
var init_package_runner = __esm({
|
|
29808
30380
|
"src/utils/package-runner.ts"() {
|
|
29809
30381
|
"use strict";
|
|
29810
|
-
|
|
30382
|
+
import_child_process7 = require("child_process");
|
|
29811
30383
|
RUNNERS = ["npx --yes", "bunx", "pnpm dlx", "yarn dlx"];
|
|
29812
30384
|
}
|
|
29813
30385
|
});
|
|
@@ -29815,7 +30387,7 @@ var init_package_runner = __esm({
|
|
|
29815
30387
|
// src/core/slot-preflight.ts
|
|
29816
30388
|
function listPm2Processes() {
|
|
29817
30389
|
try {
|
|
29818
|
-
const raw = (0,
|
|
30390
|
+
const raw = (0, import_child_process8.execSync)(`${packageRunner()} pm2 jlist`, {
|
|
29819
30391
|
encoding: "utf8",
|
|
29820
30392
|
stdio: ["pipe", "pipe", "ignore"],
|
|
29821
30393
|
timeout: 3e3
|
|
@@ -29860,10 +30432,14 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
|
29860
30432
|
}
|
|
29861
30433
|
return { port };
|
|
29862
30434
|
}
|
|
30435
|
+
function scanUntrackedWorktreePaths(repoPath, env3) {
|
|
30436
|
+
if (!worktreeCheckEnabled(env3, void 0)) return [];
|
|
30437
|
+
return listUntrackedAbsentFromWorktree(repoPath);
|
|
30438
|
+
}
|
|
29863
30439
|
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
29864
30440
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29865
30441
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29866
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
30442
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path19.basename(harnessRoot);
|
|
29867
30443
|
const usesPm2 = harnessUsesPm2(repoPath);
|
|
29868
30444
|
const blockers = [];
|
|
29869
30445
|
const remediations = [];
|
|
@@ -29917,6 +30493,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
29917
30493
|
}
|
|
29918
30494
|
}
|
|
29919
30495
|
let allocatedPorts = false;
|
|
30496
|
+
let portChoiceExplained = false;
|
|
29920
30497
|
if (usesPm2 && (options.allocatePorts ?? true)) {
|
|
29921
30498
|
const alloc = allocateAppPorts(repoPath, agentId);
|
|
29922
30499
|
if ("error" in alloc) {
|
|
@@ -29936,7 +30513,9 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
29936
30513
|
agentId,
|
|
29937
30514
|
portStep(env3)
|
|
29938
30515
|
);
|
|
29939
|
-
|
|
30516
|
+
const portWarnings = controlDefaultPortWarnings(containers, feDefault, alloc.frontend);
|
|
30517
|
+
portChoiceExplained = portWarnings.length > 0;
|
|
30518
|
+
warnings.push(...portWarnings);
|
|
29940
30519
|
for (const [label, port] of Object.entries({ frontend: alloc.frontend, api: alloc.api })) {
|
|
29941
30520
|
const control = controlContainerOnPort(containers, port);
|
|
29942
30521
|
if (control) {
|
|
@@ -29988,6 +30567,11 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
29988
30567
|
}
|
|
29989
30568
|
}
|
|
29990
30569
|
}
|
|
30570
|
+
if (worktreeCheckEnabled(env3, options.worktree)) {
|
|
30571
|
+
const untracked = options.untrackedPaths ?? scanUntrackedWorktreePaths(repoPath, env3);
|
|
30572
|
+
const untrackedWarning = formatUntrackedWorktreeWarning(untracked);
|
|
30573
|
+
if (untrackedWarning) warnings.push(untrackedWarning);
|
|
30574
|
+
}
|
|
29991
30575
|
const canLaunch = blockers.length === 0;
|
|
29992
30576
|
return {
|
|
29993
30577
|
canLaunch,
|
|
@@ -29996,6 +30580,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
29996
30580
|
remediations: [...new Set(remediations)],
|
|
29997
30581
|
ports: Object.keys(ports).length > 0 ? ports : void 0,
|
|
29998
30582
|
allocatedPorts: allocatedPorts || void 0,
|
|
30583
|
+
portChoiceExplained: portChoiceExplained || void 0,
|
|
29999
30584
|
warnings: warnings.length > 0 ? warnings : void 0
|
|
30000
30585
|
};
|
|
30001
30586
|
}
|
|
@@ -30012,7 +30597,7 @@ function formatPreflightReport(agentId, readiness) {
|
|
|
30012
30597
|
p.db !== void 0 ? `db=${p.db}` : void 0
|
|
30013
30598
|
].filter(Boolean);
|
|
30014
30599
|
if (parts.length) lines2.push(` Ports: ${parts.join(" ")}`);
|
|
30015
|
-
if (readiness.allocatedPorts && !readiness.
|
|
30600
|
+
if (readiness.allocatedPorts && !readiness.portChoiceExplained) {
|
|
30016
30601
|
lines2.push(" (alternate ports selected \u2014 defaults were busy)");
|
|
30017
30602
|
}
|
|
30018
30603
|
}
|
|
@@ -30028,20 +30613,24 @@ function formatPreflightReport(agentId, readiness) {
|
|
|
30028
30613
|
lines2.push(` [${b2.code}] ${b2.message}`);
|
|
30029
30614
|
if (b2.remediation) lines2.push(` \u2192 ${b2.remediation}`);
|
|
30030
30615
|
}
|
|
30616
|
+
for (const w2 of readiness.warnings ?? []) {
|
|
30617
|
+
lines2.push(` WARN: ${w2}`);
|
|
30618
|
+
}
|
|
30031
30619
|
return lines2.join("\n");
|
|
30032
30620
|
}
|
|
30033
|
-
var
|
|
30621
|
+
var path19, import_child_process8;
|
|
30034
30622
|
var init_slot_preflight = __esm({
|
|
30035
30623
|
"src/core/slot-preflight.ts"() {
|
|
30036
30624
|
"use strict";
|
|
30037
|
-
|
|
30625
|
+
path19 = __toESM(require("path"));
|
|
30038
30626
|
init_env();
|
|
30039
30627
|
init_manifest();
|
|
30040
30628
|
init_control_port();
|
|
30041
30629
|
init_slot_launch_guard_occupied();
|
|
30042
30630
|
init_slot_ports();
|
|
30043
30631
|
init_slot_registry();
|
|
30044
|
-
|
|
30632
|
+
init_worktree_untracked();
|
|
30633
|
+
import_child_process8 = require("child_process");
|
|
30045
30634
|
init_package_runner();
|
|
30046
30635
|
}
|
|
30047
30636
|
});
|
|
@@ -30049,7 +30638,7 @@ var init_slot_preflight = __esm({
|
|
|
30049
30638
|
// src/core/slot-status.ts
|
|
30050
30639
|
function readGitRemote(repoPath) {
|
|
30051
30640
|
try {
|
|
30052
|
-
const remote = (0,
|
|
30641
|
+
const remote = (0, import_child_process9.execSync)("git remote get-url origin", {
|
|
30053
30642
|
cwd: repoPath,
|
|
30054
30643
|
encoding: "utf8",
|
|
30055
30644
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -30061,7 +30650,7 @@ function readGitRemote(repoPath) {
|
|
|
30061
30650
|
}
|
|
30062
30651
|
function runGit2(cwd, args) {
|
|
30063
30652
|
try {
|
|
30064
|
-
return (0,
|
|
30653
|
+
return (0, import_child_process9.execSync)(`git ${args}`, {
|
|
30065
30654
|
cwd,
|
|
30066
30655
|
encoding: "utf8",
|
|
30067
30656
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -30075,7 +30664,7 @@ function readWorktreeBranch(worktreePath) {
|
|
|
30075
30664
|
}
|
|
30076
30665
|
function gitCommonDir3(cwd) {
|
|
30077
30666
|
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
30078
|
-
return out ?
|
|
30667
|
+
return out ? path20.resolve(cwd, out) : void 0;
|
|
30079
30668
|
}
|
|
30080
30669
|
function sameGitCheckout3(a, b2) {
|
|
30081
30670
|
const left2 = gitCommonDir3(a);
|
|
@@ -30087,12 +30676,12 @@ function gitPrefix2(cwd) {
|
|
|
30087
30676
|
return out ?? "";
|
|
30088
30677
|
}
|
|
30089
30678
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
30090
|
-
const worktreesRoot =
|
|
30091
|
-
if (!
|
|
30679
|
+
const worktreesRoot = path20.join(os8.homedir(), "worktrees");
|
|
30680
|
+
if (!fs19.existsSync(worktreesRoot)) return void 0;
|
|
30092
30681
|
const suffix = `-har-agent-${agentId}-`;
|
|
30093
30682
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
30094
|
-
const matches =
|
|
30095
|
-
(candidate) => sameGitCheckout3(harnessRoot, candidate) &&
|
|
30683
|
+
const matches = fs19.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path20.join(worktreesRoot, entry.name)).filter(
|
|
30684
|
+
(candidate) => sameGitCheckout3(harnessRoot, candidate) && fs19.existsSync(path20.join(candidate, relPrefix, `.env.agent.${agentId}`))
|
|
30096
30685
|
);
|
|
30097
30686
|
return matches.sort()[0];
|
|
30098
30687
|
}
|
|
@@ -30137,7 +30726,7 @@ function lastBuildPass(run2) {
|
|
|
30137
30726
|
}
|
|
30138
30727
|
function listPm2Processes2() {
|
|
30139
30728
|
try {
|
|
30140
|
-
const raw = (0,
|
|
30729
|
+
const raw = (0, import_child_process9.execSync)(`${packageRunner()} pm2 jlist`, {
|
|
30141
30730
|
encoding: "utf8",
|
|
30142
30731
|
stdio: ["pipe", "pipe", "ignore"],
|
|
30143
30732
|
timeout: 3e3
|
|
@@ -30165,20 +30754,20 @@ function detectPm2Issue(projectName, agentId, session, procs) {
|
|
|
30165
30754
|
}
|
|
30166
30755
|
return void 0;
|
|
30167
30756
|
}
|
|
30168
|
-
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
30757
|
+
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs, untrackedPaths) {
|
|
30169
30758
|
const env3 = readHarnessEnv(harnessRoot);
|
|
30170
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
30759
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path20.basename(harnessRoot);
|
|
30171
30760
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
30172
|
-
const legacyWorktreePath =
|
|
30173
|
-
|
|
30761
|
+
const legacyWorktreePath = path20.join(
|
|
30762
|
+
os8.homedir(),
|
|
30174
30763
|
"worktrees",
|
|
30175
30764
|
`${projectName}-agent-${agentId}`
|
|
30176
30765
|
);
|
|
30177
|
-
const worktreePath = session?.worktreePath &&
|
|
30766
|
+
const worktreePath = session?.worktreePath && fs19.existsSync(session.worktreePath) ? session.worktreePath : fs19.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
30178
30767
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
30179
|
-
const envInWorkDir = workDir ?
|
|
30180
|
-
const envInRoot =
|
|
30181
|
-
const envInWorktree = worktreePath ?
|
|
30768
|
+
const envInWorkDir = workDir ? fs19.existsSync(path20.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
30769
|
+
const envInRoot = fs19.existsSync(path20.join(harnessRoot, `.env.agent.${agentId}`));
|
|
30770
|
+
const envInWorktree = worktreePath ? fs19.existsSync(path20.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
30182
30771
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
30183
30772
|
const latest = latestRunForAgent(runs, agentId);
|
|
30184
30773
|
const verifyRun = runs.find((r) => r.agentId === agentId && r.stageId === "verify");
|
|
@@ -30194,7 +30783,8 @@ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
|
30194
30783
|
const pm2Issue = detectPm2Issue(projectName, agentId, session, pm2Procs);
|
|
30195
30784
|
const readiness = inspectSlotReadiness(harnessRoot, agentId, {
|
|
30196
30785
|
allocatePorts: true,
|
|
30197
|
-
occupied: { active, dirty: drift.dirty }
|
|
30786
|
+
occupied: { active, dirty: drift.dirty },
|
|
30787
|
+
untrackedPaths: untrackedPaths ?? scanUntrackedWorktreePaths(harnessRoot, env3)
|
|
30198
30788
|
});
|
|
30199
30789
|
const resumeHint = session?.status === "failed" || session?.status === "starting" ? `har env launch ${agentId} --resume (alias: har env recover ${agentId})` : void 0;
|
|
30200
30790
|
return {
|
|
@@ -30231,23 +30821,24 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
30231
30821
|
const manifest = readManifest(harnessRoot);
|
|
30232
30822
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
30233
30823
|
const pm2Procs = listPm2Processes2();
|
|
30824
|
+
const untrackedPaths = scanUntrackedWorktreePaths(harnessRoot, readHarnessEnv(harnessRoot));
|
|
30234
30825
|
return {
|
|
30235
|
-
repoPath:
|
|
30826
|
+
repoPath: path20.resolve(repoPath),
|
|
30236
30827
|
harnessRoot,
|
|
30237
30828
|
gitRemote: readGitRemote(harnessRoot),
|
|
30238
30829
|
profile: manifest?.profile,
|
|
30239
|
-
slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs)),
|
|
30830
|
+
slots: slotIds.map((id) => collectSlotStatus(harnessRoot, id, runs, pm2Procs, untrackedPaths)),
|
|
30240
30831
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30241
30832
|
};
|
|
30242
30833
|
}
|
|
30243
|
-
var
|
|
30834
|
+
var fs19, os8, path20, import_child_process9, BYPASS_WARNING_MS;
|
|
30244
30835
|
var init_slot_status = __esm({
|
|
30245
30836
|
"src/core/slot-status.ts"() {
|
|
30246
30837
|
"use strict";
|
|
30247
|
-
|
|
30248
|
-
|
|
30249
|
-
|
|
30250
|
-
|
|
30838
|
+
fs19 = __toESM(require("fs"));
|
|
30839
|
+
os8 = __toESM(require("os"));
|
|
30840
|
+
path20 = __toESM(require("path"));
|
|
30841
|
+
import_child_process9 = require("child_process");
|
|
30251
30842
|
init_env();
|
|
30252
30843
|
init_manifest();
|
|
30253
30844
|
init_stages();
|
|
@@ -30260,256 +30851,41 @@ var init_slot_status = __esm({
|
|
|
30260
30851
|
}
|
|
30261
30852
|
});
|
|
30262
30853
|
|
|
30263
|
-
// src/core/change-batch.ts
|
|
30264
|
-
function git(checkoutDir, args, env3) {
|
|
30265
|
-
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
30266
|
-
if (result.code !== 0) {
|
|
30267
|
-
throw new Error(`git ${args} failed (${result.code}): ${result.stderr.trim()}`);
|
|
30268
|
-
}
|
|
30269
|
-
return result.stdout.trim();
|
|
30270
|
-
}
|
|
30271
|
-
function tryGit2(checkoutDir, args) {
|
|
30272
|
-
const result = run(`git ${args}`, { cwd: checkoutDir });
|
|
30273
|
-
return result.code === 0 ? result.stdout.trim() : void 0;
|
|
30274
|
-
}
|
|
30275
|
-
function isCheckoutRoot(checkoutDir) {
|
|
30276
|
-
const toplevel = tryGit2(checkoutDir, "rev-parse --show-toplevel");
|
|
30277
|
-
if (!toplevel) return false;
|
|
30278
|
-
try {
|
|
30279
|
-
return fs17.realpathSync(toplevel) === fs17.realpathSync(checkoutDir);
|
|
30280
|
-
} catch {
|
|
30281
|
-
return false;
|
|
30282
|
-
}
|
|
30283
|
-
}
|
|
30284
|
-
function getHeadTree(checkoutDir) {
|
|
30285
|
-
return tryGit2(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
|
|
30286
|
-
}
|
|
30287
|
-
function getHeadSha(checkoutDir) {
|
|
30288
|
-
return tryGit2(checkoutDir, "rev-parse -q --verify HEAD");
|
|
30289
|
-
}
|
|
30290
|
-
function getCurrentBranch(checkoutDir) {
|
|
30291
|
-
const branch = tryGit2(checkoutDir, "rev-parse --abbrev-ref HEAD");
|
|
30292
|
-
return branch && branch !== "HEAD" ? branch : void 0;
|
|
30293
|
-
}
|
|
30294
|
-
function getEmptyTree(checkoutDir) {
|
|
30295
|
-
return git(checkoutDir, "hash-object -t tree /dev/null");
|
|
30296
|
-
}
|
|
30297
|
-
function isMergeOrRebaseInProgress(checkoutDir) {
|
|
30298
|
-
if (tryGit2(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
30299
|
-
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
30300
|
-
const gitPath = tryGit2(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
30301
|
-
if (gitPath && fs17.existsSync(path18.resolve(checkoutDir, gitPath))) return true;
|
|
30302
|
-
}
|
|
30303
|
-
return false;
|
|
30304
|
-
}
|
|
30305
|
-
function computeStagedTree(checkoutDir) {
|
|
30306
|
-
return git(checkoutDir, "write-tree");
|
|
30307
|
-
}
|
|
30308
|
-
function parseNameStatus(output) {
|
|
30309
|
-
const files = [];
|
|
30310
|
-
for (const line of output.split("\n")) {
|
|
30311
|
-
if (!line.trim()) continue;
|
|
30312
|
-
const parts = line.split(" ");
|
|
30313
|
-
const status = parts[0]?.charAt(0);
|
|
30314
|
-
if (!status) continue;
|
|
30315
|
-
if ((status === "R" || status === "C") && parts.length >= 3) {
|
|
30316
|
-
files.push({ path: parts[2], status, oldPath: parts[1] });
|
|
30317
|
-
} else if (parts.length >= 2) {
|
|
30318
|
-
files.push({ path: parts[1], status });
|
|
30319
|
-
}
|
|
30320
|
-
}
|
|
30321
|
-
return files;
|
|
30322
|
-
}
|
|
30323
|
-
function computeWorktreeSnapshot(checkoutDir) {
|
|
30324
|
-
const tmpDir = fs17.mkdtempSync(path18.join(os7.tmpdir(), "har-idx-"));
|
|
30325
|
-
const tmpIndex = path18.join(tmpDir, "index");
|
|
30326
|
-
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
30327
|
-
try {
|
|
30328
|
-
const headTree = getHeadTree(checkoutDir);
|
|
30329
|
-
if (headTree) {
|
|
30330
|
-
git(checkoutDir, "read-tree HEAD", env3);
|
|
30331
|
-
} else {
|
|
30332
|
-
git(checkoutDir, "read-tree --empty", env3);
|
|
30333
|
-
}
|
|
30334
|
-
git(checkoutDir, "add -A .", env3);
|
|
30335
|
-
const treeHash = git(checkoutDir, "write-tree", env3);
|
|
30336
|
-
const baseTree = headTree ?? getEmptyTree(checkoutDir);
|
|
30337
|
-
const diffOutput = baseTree === treeHash ? "" : git(
|
|
30338
|
-
checkoutDir,
|
|
30339
|
-
`diff-tree -r -M --name-status --ignore-submodules=all ${baseTree} ${treeHash}`
|
|
30340
|
-
);
|
|
30341
|
-
return {
|
|
30342
|
-
treeHash,
|
|
30343
|
-
headSha: getHeadSha(checkoutDir),
|
|
30344
|
-
branch: getCurrentBranch(checkoutDir),
|
|
30345
|
-
changedFiles: parseNameStatus(diffOutput)
|
|
30346
|
-
};
|
|
30347
|
-
} finally {
|
|
30348
|
-
fs17.rmSync(tmpDir, { recursive: true, force: true });
|
|
30349
|
-
}
|
|
30350
|
-
}
|
|
30351
|
-
var fs17, os7, path18;
|
|
30352
|
-
var init_change_batch = __esm({
|
|
30353
|
-
"src/core/change-batch.ts"() {
|
|
30354
|
-
"use strict";
|
|
30355
|
-
fs17 = __toESM(require("fs"));
|
|
30356
|
-
os7 = __toESM(require("os"));
|
|
30357
|
-
path18 = __toESM(require("path"));
|
|
30358
|
-
init_shell();
|
|
30359
|
-
}
|
|
30360
|
-
});
|
|
30361
|
-
|
|
30362
|
-
// src/core/validations.ts
|
|
30363
|
-
function resolveValidationCheckoutDir(input) {
|
|
30364
|
-
const candidates = [input.worktreePath, input.workDir, input.harnessRoot].filter(
|
|
30365
|
-
(value) => Boolean(value)
|
|
30366
|
-
);
|
|
30367
|
-
return candidates.find(isCheckoutRoot) ?? candidates[0] ?? input.harnessRoot;
|
|
30368
|
-
}
|
|
30369
|
-
function getValidationsDir(checkoutDir) {
|
|
30370
|
-
return path19.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
30371
|
-
}
|
|
30372
|
-
function validationPath(checkoutDir, treeHash) {
|
|
30373
|
-
return path19.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
30374
|
-
}
|
|
30375
|
-
function writeRecord(checkoutDir, record2) {
|
|
30376
|
-
const dir = getValidationsDir(checkoutDir);
|
|
30377
|
-
fs18.mkdirSync(dir, { recursive: true });
|
|
30378
|
-
fs18.writeFileSync(
|
|
30379
|
-
validationPath(checkoutDir, record2.treeHash),
|
|
30380
|
-
`${JSON.stringify(record2, null, 2)}
|
|
30381
|
-
`
|
|
30382
|
-
);
|
|
30383
|
-
}
|
|
30384
|
-
function ensureValidationsIgnored(checkoutDir) {
|
|
30385
|
-
const harDir = path19.join(checkoutDir, ".har");
|
|
30386
|
-
if (!fs18.existsSync(harDir)) return;
|
|
30387
|
-
const gitignorePath = path19.join(harDir, ".gitignore");
|
|
30388
|
-
const content = fs18.existsSync(gitignorePath) ? fs18.readFileSync(gitignorePath, "utf8") : "";
|
|
30389
|
-
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
30390
|
-
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
30391
|
-
fs18.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
30392
|
-
`);
|
|
30393
|
-
}
|
|
30394
|
-
function recordValidation(input) {
|
|
30395
|
-
if (!isCheckoutRoot(input.checkoutDir)) {
|
|
30396
|
-
throw new Error(`Not the root of a git checkout: ${input.checkoutDir}`);
|
|
30397
|
-
}
|
|
30398
|
-
ensureValidationsIgnored(input.checkoutDir);
|
|
30399
|
-
ensureValidationsIgnored(input.harnessRoot);
|
|
30400
|
-
const snapshot = computeWorktreeSnapshot(input.checkoutDir);
|
|
30401
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30402
|
-
const existing = findValidation(input.checkoutDir, snapshot.treeHash);
|
|
30403
|
-
const record2 = ValidationRecordSchema.parse({
|
|
30404
|
-
validationId: existing?.validationId ?? crypto3.randomUUID(),
|
|
30405
|
-
treeHash: snapshot.treeHash,
|
|
30406
|
-
headSha: snapshot.headSha,
|
|
30407
|
-
branch: snapshot.branch,
|
|
30408
|
-
workDir: path19.resolve(input.checkoutDir),
|
|
30409
|
-
harnessRoot: path19.resolve(input.harnessRoot),
|
|
30410
|
-
agentId: input.agentId,
|
|
30411
|
-
status: input.status,
|
|
30412
|
-
full: input.full,
|
|
30413
|
-
runId: input.runId,
|
|
30414
|
-
changedFiles: snapshot.changedFiles,
|
|
30415
|
-
createdAt: existing?.createdAt ?? now,
|
|
30416
|
-
updatedAt: now,
|
|
30417
|
-
commitSha: existing?.commitSha,
|
|
30418
|
-
committedAt: existing?.committedAt
|
|
30419
|
-
});
|
|
30420
|
-
writeRecord(input.checkoutDir, record2);
|
|
30421
|
-
if (path19.resolve(input.harnessRoot) !== path19.resolve(input.checkoutDir)) {
|
|
30422
|
-
writeRecord(input.harnessRoot, record2);
|
|
30423
|
-
}
|
|
30424
|
-
markDirty(input.checkoutDir);
|
|
30425
|
-
return record2;
|
|
30426
|
-
}
|
|
30427
|
-
function findValidation(checkoutDir, treeHash) {
|
|
30428
|
-
const file = validationPath(checkoutDir, treeHash);
|
|
30429
|
-
if (!fs18.existsSync(file)) return void 0;
|
|
30430
|
-
try {
|
|
30431
|
-
return ValidationRecordSchema.parse(JSON.parse(fs18.readFileSync(file, "utf8")));
|
|
30432
|
-
} catch {
|
|
30433
|
-
return void 0;
|
|
30434
|
-
}
|
|
30435
|
-
}
|
|
30436
|
-
function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
30437
|
-
const record2 = findValidation(checkoutDir, treeHash);
|
|
30438
|
-
if (!record2) return void 0;
|
|
30439
|
-
const updated = {
|
|
30440
|
-
...record2,
|
|
30441
|
-
commitSha,
|
|
30442
|
-
committedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30443
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30444
|
-
};
|
|
30445
|
-
writeRecord(checkoutDir, updated);
|
|
30446
|
-
if (record2.harnessRoot && path19.resolve(record2.harnessRoot) !== path19.resolve(checkoutDir) && fs18.existsSync(path19.join(record2.harnessRoot, ".har"))) {
|
|
30447
|
-
writeRecord(record2.harnessRoot, updated);
|
|
30448
|
-
}
|
|
30449
|
-
return updated;
|
|
30450
|
-
}
|
|
30451
|
-
function listValidations(harnessRoot) {
|
|
30452
|
-
const dir = getValidationsDir(harnessRoot);
|
|
30453
|
-
if (!fs18.existsSync(dir)) return [];
|
|
30454
|
-
const records = [];
|
|
30455
|
-
for (const entry of fs18.readdirSync(dir)) {
|
|
30456
|
-
if (!entry.endsWith(".json")) continue;
|
|
30457
|
-
try {
|
|
30458
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(fs18.readFileSync(path19.join(dir, entry), "utf8"))));
|
|
30459
|
-
} catch {
|
|
30460
|
-
}
|
|
30461
|
-
}
|
|
30462
|
-
return records.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
|
|
30463
|
-
}
|
|
30464
|
-
var crypto3, fs18, path19, VALIDATIONS_DIR;
|
|
30465
|
-
var init_validations = __esm({
|
|
30466
|
-
"src/core/validations.ts"() {
|
|
30467
|
-
"use strict";
|
|
30468
|
-
crypto3 = __toESM(require("crypto"));
|
|
30469
|
-
fs18 = __toESM(require("fs"));
|
|
30470
|
-
path19 = __toESM(require("path"));
|
|
30471
|
-
init_schema2();
|
|
30472
|
-
init_change_batch();
|
|
30473
|
-
init_sync_context();
|
|
30474
|
-
VALIDATIONS_DIR = "validations";
|
|
30475
|
-
}
|
|
30476
|
-
});
|
|
30477
|
-
|
|
30478
30854
|
// src/core/work-units.ts
|
|
30479
30855
|
function harEvidenceDir(harnessRoot, name) {
|
|
30480
|
-
return
|
|
30856
|
+
return path21.join(harnessRoot, ".har", name);
|
|
30481
30857
|
}
|
|
30482
30858
|
function hashedRecordPath(harnessRoot, dir, id) {
|
|
30483
30859
|
const digest = crypto4.createHash("sha256").update(id).digest("hex");
|
|
30484
|
-
return
|
|
30860
|
+
return path21.join(harEvidenceDir(harnessRoot, dir), `${digest}.json`);
|
|
30485
30861
|
}
|
|
30486
30862
|
function writeRecord2(file, value) {
|
|
30487
|
-
|
|
30863
|
+
fs20.mkdirSync(path21.dirname(file), { recursive: true });
|
|
30488
30864
|
const temp = `${file}.${process.pid}.tmp`;
|
|
30489
|
-
|
|
30865
|
+
fs20.writeFileSync(temp, `${JSON.stringify(value, null, 2)}
|
|
30490
30866
|
`);
|
|
30491
|
-
|
|
30867
|
+
fs20.renameSync(temp, file);
|
|
30492
30868
|
}
|
|
30493
30869
|
function ensureWorkEvidenceIgnored(harnessRoot) {
|
|
30494
|
-
const harDir =
|
|
30495
|
-
if (!
|
|
30496
|
-
const gitignorePath =
|
|
30497
|
-
const content =
|
|
30870
|
+
const harDir = path21.join(harnessRoot, ".har");
|
|
30871
|
+
if (!fs20.existsSync(harDir)) return;
|
|
30872
|
+
const gitignorePath = path21.join(harDir, ".gitignore");
|
|
30873
|
+
const content = fs20.existsSync(gitignorePath) ? fs20.readFileSync(gitignorePath, "utf8") : "";
|
|
30498
30874
|
const lines2 = new Set(content.split("\n").map((line) => line.trim()));
|
|
30499
30875
|
const missing = EVIDENCE_DIRS.map((dir) => `${dir}/`).filter((dir) => !lines2.has(dir));
|
|
30500
30876
|
if (missing.length === 0) return;
|
|
30501
30877
|
const separator = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
30502
|
-
|
|
30878
|
+
fs20.writeFileSync(gitignorePath, `${content}${separator}${missing.join("\n")}
|
|
30503
30879
|
`);
|
|
30504
30880
|
}
|
|
30505
30881
|
function listRecords(harnessRoot, dir, parse4) {
|
|
30506
30882
|
const recordDir = harEvidenceDir(harnessRoot, dir);
|
|
30507
|
-
if (!
|
|
30883
|
+
if (!fs20.existsSync(recordDir)) return [];
|
|
30508
30884
|
const records = [];
|
|
30509
|
-
for (const name of
|
|
30885
|
+
for (const name of fs20.readdirSync(recordDir)) {
|
|
30510
30886
|
if (!name.endsWith(".json")) continue;
|
|
30511
30887
|
try {
|
|
30512
|
-
records.push(parse4(JSON.parse(
|
|
30888
|
+
records.push(parse4(JSON.parse(fs20.readFileSync(path21.join(recordDir, name), "utf8"))));
|
|
30513
30889
|
} catch {
|
|
30514
30890
|
}
|
|
30515
30891
|
}
|
|
@@ -30518,9 +30894,9 @@ function listRecords(harnessRoot, dir, parse4) {
|
|
|
30518
30894
|
function findWorkUnit(harnessRoot, workUnitId) {
|
|
30519
30895
|
const metadata = WorkUnitMetadataSchema.parse({ workUnitId });
|
|
30520
30896
|
const file = hashedRecordPath(harnessRoot, WORK_UNITS_DIR, metadata.workUnitId);
|
|
30521
|
-
if (!
|
|
30897
|
+
if (!fs20.existsSync(file)) return void 0;
|
|
30522
30898
|
try {
|
|
30523
|
-
return WorkUnitRecordSchema.parse(JSON.parse(
|
|
30899
|
+
return WorkUnitRecordSchema.parse(JSON.parse(fs20.readFileSync(file, "utf8")));
|
|
30524
30900
|
} catch {
|
|
30525
30901
|
return void 0;
|
|
30526
30902
|
}
|
|
@@ -30598,10 +30974,10 @@ function bindValidationToAttempt(harnessRoot, input) {
|
|
|
30598
30974
|
VALIDATION_BINDINGS_DIR,
|
|
30599
30975
|
stableKey
|
|
30600
30976
|
);
|
|
30601
|
-
if (
|
|
30977
|
+
if (fs20.existsSync(existingFile)) {
|
|
30602
30978
|
try {
|
|
30603
30979
|
return ValidationBindingRecordSchema.parse(
|
|
30604
|
-
JSON.parse(
|
|
30980
|
+
JSON.parse(fs20.readFileSync(existingFile, "utf8"))
|
|
30605
30981
|
);
|
|
30606
30982
|
} catch {
|
|
30607
30983
|
}
|
|
@@ -30636,13 +31012,13 @@ function decideWorkUnitOutcome(harnessRoot, workUnitId, outcome) {
|
|
|
30636
31012
|
markDirty(harnessRoot);
|
|
30637
31013
|
return record2;
|
|
30638
31014
|
}
|
|
30639
|
-
var crypto4,
|
|
31015
|
+
var crypto4, fs20, path21, WORK_UNITS_DIR, WORK_ATTEMPTS_DIR, VALIDATION_BINDINGS_DIR, EVIDENCE_DIRS;
|
|
30640
31016
|
var init_work_units = __esm({
|
|
30641
31017
|
"src/core/work-units.ts"() {
|
|
30642
31018
|
"use strict";
|
|
30643
31019
|
crypto4 = __toESM(require("crypto"));
|
|
30644
|
-
|
|
30645
|
-
|
|
31020
|
+
fs20 = __toESM(require("fs"));
|
|
31021
|
+
path21 = __toESM(require("path"));
|
|
30646
31022
|
init_schema2();
|
|
30647
31023
|
init_sync_context();
|
|
30648
31024
|
WORK_UNITS_DIR = "work-units";
|
|
@@ -31265,8 +31641,8 @@ function appendTelemetryEnvToFile(envFilePath, attrs) {
|
|
|
31265
31641
|
${TELEMETRY_MARKER_END}
|
|
31266
31642
|
`;
|
|
31267
31643
|
let existing = "";
|
|
31268
|
-
if (
|
|
31269
|
-
existing =
|
|
31644
|
+
if (fs21.existsSync(envFilePath)) {
|
|
31645
|
+
existing = fs21.readFileSync(envFilePath, "utf8");
|
|
31270
31646
|
const start = existing.indexOf(TELEMETRY_MARKER_START);
|
|
31271
31647
|
if (start >= 0) {
|
|
31272
31648
|
const end = existing.indexOf(TELEMETRY_MARKER_END, start);
|
|
@@ -31277,16 +31653,16 @@ ${TELEMETRY_MARKER_END}
|
|
|
31277
31653
|
}
|
|
31278
31654
|
}
|
|
31279
31655
|
}
|
|
31280
|
-
|
|
31656
|
+
fs21.mkdirSync(path22.dirname(envFilePath), { recursive: true });
|
|
31281
31657
|
const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
|
|
31282
|
-
|
|
31658
|
+
fs21.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
|
|
31283
31659
|
}
|
|
31284
|
-
var
|
|
31660
|
+
var fs21, path22, TELEMETRY_MARKER_START, TELEMETRY_MARKER_END;
|
|
31285
31661
|
var init_telemetry_env = __esm({
|
|
31286
31662
|
"src/core/telemetry-env.ts"() {
|
|
31287
31663
|
"use strict";
|
|
31288
|
-
|
|
31289
|
-
|
|
31664
|
+
fs21 = __toESM(require("fs"));
|
|
31665
|
+
path22 = __toESM(require("path"));
|
|
31290
31666
|
init_control_config();
|
|
31291
31667
|
init_shell();
|
|
31292
31668
|
TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
|
|
@@ -31299,32 +31675,32 @@ function stripTrailingSeparators(value) {
|
|
|
31299
31675
|
return value.replace(/[/\\]+$/, "");
|
|
31300
31676
|
}
|
|
31301
31677
|
function isWorkspaceUnderPath(workspace, basePath) {
|
|
31302
|
-
const workspaceNorm = stripTrailingSeparators(
|
|
31303
|
-
const baseNorm = stripTrailingSeparators(
|
|
31678
|
+
const workspaceNorm = stripTrailingSeparators(path23.resolve(workspace));
|
|
31679
|
+
const baseNorm = stripTrailingSeparators(path23.resolve(basePath));
|
|
31304
31680
|
if (!workspaceNorm || !baseNorm) return false;
|
|
31305
|
-
return workspaceNorm === baseNorm || workspaceNorm.startsWith(`${baseNorm}${
|
|
31681
|
+
return workspaceNorm === baseNorm || workspaceNorm.startsWith(`${baseNorm}${path23.sep}`);
|
|
31306
31682
|
}
|
|
31307
31683
|
function workspaceMatchesTarget(workspace, targets) {
|
|
31308
31684
|
return targets.some((target) => isWorkspaceUnderPath(workspace, target));
|
|
31309
31685
|
}
|
|
31310
|
-
var
|
|
31686
|
+
var path23;
|
|
31311
31687
|
var init_workspace_path_match = __esm({
|
|
31312
31688
|
"src/core/workspace-path-match.ts"() {
|
|
31313
31689
|
"use strict";
|
|
31314
|
-
|
|
31690
|
+
path23 = __toESM(require("path"));
|
|
31315
31691
|
}
|
|
31316
31692
|
});
|
|
31317
31693
|
|
|
31318
31694
|
// src/core/usage-harvest/claude.ts
|
|
31319
31695
|
function claudeProjectsRoot() {
|
|
31320
|
-
return process.env.HAR_CLAUDE_PROJECTS_DIR ?
|
|
31696
|
+
return process.env.HAR_CLAUDE_PROJECTS_DIR ? path24.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path24.join(os10.homedir(), ".claude", "projects");
|
|
31321
31697
|
}
|
|
31322
31698
|
function encodeClaudeProjectDir(cwd) {
|
|
31323
31699
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
31324
31700
|
}
|
|
31325
31701
|
function readJsonlRecords(filePath) {
|
|
31326
|
-
if (!
|
|
31327
|
-
const lines2 =
|
|
31702
|
+
if (!fs22.existsSync(filePath)) return [];
|
|
31703
|
+
const lines2 = fs22.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
31328
31704
|
const out = [];
|
|
31329
31705
|
for (const line of lines2) {
|
|
31330
31706
|
const trimmed = line.trim();
|
|
@@ -31389,18 +31765,18 @@ function findMatchingClaudeTranscripts(slot) {
|
|
|
31389
31765
|
const fallbackTargets = slot.includeRepoPathFallback && slot.repoPath && !primaryTargets.includes(slot.repoPath) ? [slot.repoPath] : [];
|
|
31390
31766
|
if (primaryTargets.length === 0 && fallbackTargets.length === 0) return [];
|
|
31391
31767
|
const root = claudeProjectsRoot();
|
|
31392
|
-
if (!
|
|
31768
|
+
if (!fs22.existsSync(root)) return [];
|
|
31393
31769
|
const primary = [];
|
|
31394
31770
|
const fallback = [];
|
|
31395
|
-
for (const entry of
|
|
31771
|
+
for (const entry of fs22.readdirSync(root, { withFileTypes: true })) {
|
|
31396
31772
|
if (!entry.isDirectory()) continue;
|
|
31397
|
-
const projectDir =
|
|
31773
|
+
const projectDir = path24.join(root, entry.name);
|
|
31398
31774
|
const primaryEncoded = primaryTargets.some((t) => entry.name === encodeClaudeProjectDir(t));
|
|
31399
31775
|
const fallbackEncoded = fallbackTargets.some((t) => entry.name === encodeClaudeProjectDir(t));
|
|
31400
|
-
for (const file of
|
|
31776
|
+
for (const file of fs22.readdirSync(projectDir)) {
|
|
31401
31777
|
if (!file.endsWith(".jsonl")) continue;
|
|
31402
|
-
const filePath =
|
|
31403
|
-
const stat =
|
|
31778
|
+
const filePath = path24.join(projectDir, file);
|
|
31779
|
+
const stat = fs22.statSync(filePath);
|
|
31404
31780
|
const records = readJsonlRecords(filePath);
|
|
31405
31781
|
let primaryHit = primaryEncoded;
|
|
31406
31782
|
let fallbackHit = fallbackEncoded;
|
|
@@ -31519,13 +31895,13 @@ function harvestClaudeEvents(slot) {
|
|
|
31519
31895
|
});
|
|
31520
31896
|
return extractPromptEvents(matches[0].records, sessionKey, slot).slice(-40);
|
|
31521
31897
|
}
|
|
31522
|
-
var
|
|
31898
|
+
var fs22, os10, path24;
|
|
31523
31899
|
var init_claude = __esm({
|
|
31524
31900
|
"src/core/usage-harvest/claude.ts"() {
|
|
31525
31901
|
"use strict";
|
|
31526
|
-
|
|
31527
|
-
|
|
31528
|
-
|
|
31902
|
+
fs22 = __toESM(require("fs"));
|
|
31903
|
+
os10 = __toESM(require("os"));
|
|
31904
|
+
path24 = __toESM(require("path"));
|
|
31529
31905
|
init_telemetry_config();
|
|
31530
31906
|
init_telemetry_env();
|
|
31531
31907
|
init_workspace_path_match();
|
|
@@ -31534,19 +31910,19 @@ var init_claude = __esm({
|
|
|
31534
31910
|
|
|
31535
31911
|
// src/core/usage-harvest/codex.ts
|
|
31536
31912
|
function codexSessionsRoot() {
|
|
31537
|
-
return process.env.HAR_CODEX_SESSIONS_DIR ?
|
|
31913
|
+
return process.env.HAR_CODEX_SESSIONS_DIR ? path25.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path25.join(os11.homedir(), ".codex", "sessions");
|
|
31538
31914
|
}
|
|
31539
31915
|
function walkJsonlFiles(dir, out = []) {
|
|
31540
|
-
if (!
|
|
31541
|
-
for (const entry of
|
|
31542
|
-
const full =
|
|
31916
|
+
if (!fs23.existsSync(dir)) return out;
|
|
31917
|
+
for (const entry of fs23.readdirSync(dir, { withFileTypes: true })) {
|
|
31918
|
+
const full = path25.join(dir, entry.name);
|
|
31543
31919
|
if (entry.isDirectory()) walkJsonlFiles(full, out);
|
|
31544
31920
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
|
|
31545
31921
|
}
|
|
31546
31922
|
return out;
|
|
31547
31923
|
}
|
|
31548
31924
|
function readJsonl(filePath) {
|
|
31549
|
-
const lines2 =
|
|
31925
|
+
const lines2 = fs23.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
31550
31926
|
const out = [];
|
|
31551
31927
|
for (const line of lines2) {
|
|
31552
31928
|
const trimmed = line.trim();
|
|
@@ -31613,7 +31989,7 @@ function harvestCodexUsage(slot) {
|
|
|
31613
31989
|
const extracted = extractCodexTokens(records);
|
|
31614
31990
|
if (!extracted.cwd) continue;
|
|
31615
31991
|
if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
|
|
31616
|
-
const mtime =
|
|
31992
|
+
const mtime = fs23.statSync(file).mtimeMs;
|
|
31617
31993
|
if (workspaceMatchesTarget(extracted.cwd, primaryTargets)) {
|
|
31618
31994
|
if (mtime >= bestPrimaryMtime) {
|
|
31619
31995
|
bestPrimaryMtime = mtime;
|
|
@@ -31648,13 +32024,13 @@ function harvestCodexUsage(slot) {
|
|
|
31648
32024
|
lastSeenAt: now
|
|
31649
32025
|
};
|
|
31650
32026
|
}
|
|
31651
|
-
var
|
|
32027
|
+
var fs23, os11, path25;
|
|
31652
32028
|
var init_codex = __esm({
|
|
31653
32029
|
"src/core/usage-harvest/codex.ts"() {
|
|
31654
32030
|
"use strict";
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
32031
|
+
fs23 = __toESM(require("fs"));
|
|
32032
|
+
os11 = __toESM(require("os"));
|
|
32033
|
+
path25 = __toESM(require("path"));
|
|
31658
32034
|
init_telemetry_env();
|
|
31659
32035
|
init_workspace_path_match();
|
|
31660
32036
|
}
|
|
@@ -31702,15 +32078,15 @@ var init_usage_harvest = __esm({
|
|
|
31702
32078
|
// src/core/portal-watermark.ts
|
|
31703
32079
|
function getStatePath() {
|
|
31704
32080
|
if (process.env.HAR_PORTAL_SYNC_STATE_PATH) {
|
|
31705
|
-
return
|
|
32081
|
+
return path26.resolve(process.env.HAR_PORTAL_SYNC_STATE_PATH);
|
|
31706
32082
|
}
|
|
31707
|
-
return
|
|
32083
|
+
return path26.join(os12.homedir(), ".har", "portal-sync-state.json");
|
|
31708
32084
|
}
|
|
31709
32085
|
function readState() {
|
|
31710
32086
|
const statePath = getStatePath();
|
|
31711
32087
|
try {
|
|
31712
|
-
if (!
|
|
31713
|
-
const parsed = JSON.parse(
|
|
32088
|
+
if (!fs24.existsSync(statePath)) return { states: [] };
|
|
32089
|
+
const parsed = JSON.parse(fs24.readFileSync(statePath, "utf8"));
|
|
31714
32090
|
return Array.isArray(parsed.states) ? parsed : { states: [] };
|
|
31715
32091
|
} catch {
|
|
31716
32092
|
return { states: [] };
|
|
@@ -31718,8 +32094,8 @@ function readState() {
|
|
|
31718
32094
|
}
|
|
31719
32095
|
function writeState(state) {
|
|
31720
32096
|
const statePath = getStatePath();
|
|
31721
|
-
|
|
31722
|
-
|
|
32097
|
+
fs24.mkdirSync(path26.dirname(statePath), { recursive: true });
|
|
32098
|
+
fs24.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
|
|
31723
32099
|
}
|
|
31724
32100
|
function readPortalWatermark(repoPath, portalUrl) {
|
|
31725
32101
|
const canonical = canonicalizeControlRepoPath(repoPath);
|
|
@@ -31773,13 +32149,13 @@ function selectSince(rows, since, getTimestamp) {
|
|
|
31773
32149
|
}
|
|
31774
32150
|
return { selected, maxSyncedAt };
|
|
31775
32151
|
}
|
|
31776
|
-
var
|
|
32152
|
+
var fs24, os12, path26;
|
|
31777
32153
|
var init_portal_watermark = __esm({
|
|
31778
32154
|
"src/core/portal-watermark.ts"() {
|
|
31779
32155
|
"use strict";
|
|
31780
|
-
|
|
31781
|
-
|
|
31782
|
-
|
|
32156
|
+
fs24 = __toESM(require("fs"));
|
|
32157
|
+
os12 = __toESM(require("os"));
|
|
32158
|
+
path26 = __toESM(require("path"));
|
|
31783
32159
|
init_control_repo_path();
|
|
31784
32160
|
}
|
|
31785
32161
|
});
|
|
@@ -31800,8 +32176,8 @@ async function getJson(url) {
|
|
|
31800
32176
|
async function resolveControlRepoId(apiUrl, repoPath) {
|
|
31801
32177
|
const repos = await getJson(`${apiUrl}/api/repos`);
|
|
31802
32178
|
if (!Array.isArray(repos)) return null;
|
|
31803
|
-
const target =
|
|
31804
|
-
return repos.find((repo) =>
|
|
32179
|
+
const target = path27.resolve(repoPath);
|
|
32180
|
+
return repos.find((repo) => path27.resolve(repo.path) === target)?.id ?? null;
|
|
31805
32181
|
}
|
|
31806
32182
|
function optionalString(value) {
|
|
31807
32183
|
return value == null ? void 0 : value;
|
|
@@ -31870,11 +32246,11 @@ async function fetchPersistedPortalTelemetry(repoPath, apiUrl, options) {
|
|
|
31870
32246
|
maxSyncedAt: maxTimestamp(usage2.maxSyncedAt, events.maxSyncedAt)
|
|
31871
32247
|
};
|
|
31872
32248
|
}
|
|
31873
|
-
var
|
|
32249
|
+
var path27, FETCH_TIMEOUT_MS;
|
|
31874
32250
|
var init_control_persisted_usage = __esm({
|
|
31875
32251
|
"src/core/control-persisted-usage.ts"() {
|
|
31876
32252
|
"use strict";
|
|
31877
|
-
|
|
32253
|
+
path27 = __toESM(require("path"));
|
|
31878
32254
|
init_portal_watermark();
|
|
31879
32255
|
FETCH_TIMEOUT_MS = 3e3;
|
|
31880
32256
|
}
|
|
@@ -32206,7 +32582,7 @@ async function buildPortalPayload(repoPath, controlApiUrl, since, full = false)
|
|
|
32206
32582
|
);
|
|
32207
32583
|
if (full && isTelemetryEnabled() && usage2.length === 0 && runs.some((r) => r.agentId != null)) {
|
|
32208
32584
|
warn(
|
|
32209
|
-
`${
|
|
32585
|
+
`${path28.basename(repoPath)}: agent runs found but no usage harvested \u2014 attribution will be missing. This happens when agent sessions ran outside the har worktree slot (e.g. in the main checkout). Run agents inside \`har env launch\`.`
|
|
32210
32586
|
);
|
|
32211
32587
|
}
|
|
32212
32588
|
const syncBody = {
|
|
@@ -32333,7 +32709,7 @@ async function syncRepoWithLocalControl(options) {
|
|
|
32333
32709
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
32334
32710
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
32335
32711
|
const repos = await listResponse.json();
|
|
32336
|
-
const existing = repos.find((r) =>
|
|
32712
|
+
const existing = repos.find((r) => path28.resolve(r.path) === repoPath);
|
|
32337
32713
|
if (!existing) {
|
|
32338
32714
|
return;
|
|
32339
32715
|
}
|
|
@@ -32546,7 +32922,7 @@ async function syncRunWithLocalControl(repoPath, run2, apiUrl) {
|
|
|
32546
32922
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
32547
32923
|
if (!listResponse.ok) return;
|
|
32548
32924
|
const repos = await listResponse.json();
|
|
32549
|
-
repoId = repos.find((r) =>
|
|
32925
|
+
repoId = repos.find((r) => path28.resolve(r.path) === repoPath)?.id;
|
|
32550
32926
|
}
|
|
32551
32927
|
if (!repoId) return;
|
|
32552
32928
|
const runsBody = SyncRunsInputSchema.parse({ runs: [run2] });
|
|
@@ -32576,11 +32952,11 @@ async function syncRunWithControlAsync(repoPath, run2) {
|
|
|
32576
32952
|
`);
|
|
32577
32953
|
}
|
|
32578
32954
|
}
|
|
32579
|
-
var
|
|
32955
|
+
var path28, DEFAULT_MAX_BATCH_BYTES, DEFAULT_SYNC_OVERLAP_MS, MAX_FETCH_ATTEMPTS, SYNC_TIMEOUT_MS, EXPLICIT_SYNC_TIMEOUT_MS;
|
|
32580
32956
|
var init_control_sync = __esm({
|
|
32581
32957
|
"src/core/control-sync.ts"() {
|
|
32582
32958
|
"use strict";
|
|
32583
|
-
|
|
32959
|
+
path28 = __toESM(require("path"));
|
|
32584
32960
|
init_manifest();
|
|
32585
32961
|
init_stages();
|
|
32586
32962
|
init_schema2();
|
|
@@ -32612,7 +32988,7 @@ var init_control_sync = __esm({
|
|
|
32612
32988
|
// src/core/sync-context.ts
|
|
32613
32989
|
function markDirty(repoPath) {
|
|
32614
32990
|
if (!repoPath) return;
|
|
32615
|
-
dirtyRepos.add(
|
|
32991
|
+
dirtyRepos.add(path29.resolve(repoPath));
|
|
32616
32992
|
}
|
|
32617
32993
|
function markAllRegisteredDirty() {
|
|
32618
32994
|
syncAllRegistered = true;
|
|
@@ -32637,11 +33013,11 @@ async function syncDirtyRepos() {
|
|
|
32637
33013
|
} catch {
|
|
32638
33014
|
}
|
|
32639
33015
|
}
|
|
32640
|
-
var
|
|
33016
|
+
var path29, dirtyRepos, syncAllRegistered;
|
|
32641
33017
|
var init_sync_context = __esm({
|
|
32642
33018
|
"src/core/sync-context.ts"() {
|
|
32643
33019
|
"use strict";
|
|
32644
|
-
|
|
33020
|
+
path29 = __toESM(require("path"));
|
|
32645
33021
|
dirtyRepos = /* @__PURE__ */ new Set();
|
|
32646
33022
|
syncAllRegistered = false;
|
|
32647
33023
|
}
|
|
@@ -53412,32 +53788,32 @@ __export(otel_hooks_exports, {
|
|
|
53412
53788
|
});
|
|
53413
53789
|
function getOtelHooksHome() {
|
|
53414
53790
|
if (process.env.HAR_OTEL_HOOKS_HOME) {
|
|
53415
|
-
return
|
|
53791
|
+
return path48.resolve(process.env.HAR_OTEL_HOOKS_HOME);
|
|
53416
53792
|
}
|
|
53417
|
-
return
|
|
53793
|
+
return path48.join(os16.homedir(), ".har", "otel-hooks");
|
|
53418
53794
|
}
|
|
53419
53795
|
function getOtelHooksConfigPath(hooksHome = getOtelHooksHome()) {
|
|
53420
|
-
return
|
|
53796
|
+
return path48.join(hooksHome, "otel_config.json");
|
|
53421
53797
|
}
|
|
53422
53798
|
function getOtelHooksWrapperPath(hooksHome = getOtelHooksHome()) {
|
|
53423
|
-
return
|
|
53799
|
+
return path48.join(hooksHome, "run-otel-hook.sh");
|
|
53424
53800
|
}
|
|
53425
53801
|
function getOtelHooksStateDir(hooksHome = getOtelHooksHome()) {
|
|
53426
|
-
return
|
|
53802
|
+
return path48.join(hooksHome, "state");
|
|
53427
53803
|
}
|
|
53428
53804
|
function localOtelHookBin(hooksHome) {
|
|
53429
|
-
return
|
|
53805
|
+
return path48.join(hooksHome, "node_modules", ".bin", "otel-hook");
|
|
53430
53806
|
}
|
|
53431
53807
|
function installedPackageVersion(hooksHome) {
|
|
53432
53808
|
try {
|
|
53433
|
-
const pkgPath =
|
|
53809
|
+
const pkgPath = path48.join(
|
|
53434
53810
|
hooksHome,
|
|
53435
53811
|
"node_modules",
|
|
53436
53812
|
"@osfactory",
|
|
53437
53813
|
"otel-hook",
|
|
53438
53814
|
"package.json"
|
|
53439
53815
|
);
|
|
53440
|
-
const pkg = JSON.parse(
|
|
53816
|
+
const pkg = JSON.parse(fs42.readFileSync(pkgPath, "utf8"));
|
|
53441
53817
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
53442
53818
|
} catch {
|
|
53443
53819
|
return null;
|
|
@@ -53488,15 +53864,15 @@ function buildOtelHooksConfig(options) {
|
|
|
53488
53864
|
};
|
|
53489
53865
|
}
|
|
53490
53866
|
function writeOtelHooksConfig(config2, hooksHome = getOtelHooksHome()) {
|
|
53491
|
-
|
|
53867
|
+
fs42.mkdirSync(hooksHome, { recursive: true });
|
|
53492
53868
|
const configPath = getOtelHooksConfigPath(hooksHome);
|
|
53493
|
-
|
|
53869
|
+
fs42.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
|
|
53494
53870
|
`);
|
|
53495
53871
|
return configPath;
|
|
53496
53872
|
}
|
|
53497
53873
|
function writeOtelHooksWrapper(otelHookCommand, hooksHome = getOtelHooksHome()) {
|
|
53498
|
-
|
|
53499
|
-
|
|
53874
|
+
fs42.mkdirSync(hooksHome, { recursive: true });
|
|
53875
|
+
fs42.mkdirSync(getOtelHooksStateDir(hooksHome), { recursive: true });
|
|
53500
53876
|
const wrapperPath = getOtelHooksWrapperPath(hooksHome);
|
|
53501
53877
|
const configPath = getOtelHooksConfigPath(hooksHome);
|
|
53502
53878
|
const stateDir = getOtelHooksStateDir(hooksHome);
|
|
@@ -53508,22 +53884,22 @@ exec "${otelHookCommand}" run \\
|
|
|
53508
53884
|
--state-dir "${stateDir}" \\
|
|
53509
53885
|
"$@"
|
|
53510
53886
|
`;
|
|
53511
|
-
|
|
53887
|
+
fs42.writeFileSync(wrapperPath, script, { mode: 493 });
|
|
53512
53888
|
try {
|
|
53513
|
-
|
|
53889
|
+
fs42.chmodSync(wrapperPath, 493);
|
|
53514
53890
|
} catch {
|
|
53515
53891
|
}
|
|
53516
53892
|
return wrapperPath;
|
|
53517
53893
|
}
|
|
53518
53894
|
function commandExists3(command2) {
|
|
53519
|
-
const result = (0,
|
|
53895
|
+
const result = (0, import_child_process12.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
|
|
53520
53896
|
encoding: "utf8"
|
|
53521
53897
|
});
|
|
53522
53898
|
return result.status === 0 && Boolean(result.stdout?.trim());
|
|
53523
53899
|
}
|
|
53524
53900
|
function resolveOtelHookCommand(hooksHome) {
|
|
53525
53901
|
const local = localOtelHookBin(hooksHome);
|
|
53526
|
-
if (
|
|
53902
|
+
if (fs42.existsSync(local)) return local;
|
|
53527
53903
|
return null;
|
|
53528
53904
|
}
|
|
53529
53905
|
function resolveHooksInstaller(hooksHome) {
|
|
@@ -53538,7 +53914,7 @@ function resolveHooksInstaller(hooksHome) {
|
|
|
53538
53914
|
return candidates.find((candidate) => commandExists3(candidate.command)) ?? null;
|
|
53539
53915
|
}
|
|
53540
53916
|
function tryInstallPackage(hooksHome) {
|
|
53541
|
-
|
|
53917
|
+
fs42.mkdirSync(hooksHome, { recursive: true });
|
|
53542
53918
|
const installer = resolveHooksInstaller(hooksHome);
|
|
53543
53919
|
if (!installer) {
|
|
53544
53920
|
return {
|
|
@@ -53547,11 +53923,11 @@ function tryInstallPackage(hooksHome) {
|
|
|
53547
53923
|
};
|
|
53548
53924
|
}
|
|
53549
53925
|
const described = `${installer.command} ${installer.args.join(" ")}`;
|
|
53550
|
-
const result = (0,
|
|
53926
|
+
const result = (0, import_child_process12.spawnSync)(installer.command, installer.args, {
|
|
53551
53927
|
encoding: "utf8",
|
|
53552
53928
|
timeout: 18e4
|
|
53553
53929
|
});
|
|
53554
|
-
if (result.status === 0 &&
|
|
53930
|
+
if (result.status === 0 && fs42.existsSync(localOtelHookBin(hooksHome))) {
|
|
53555
53931
|
return { ok: true, detail: described };
|
|
53556
53932
|
}
|
|
53557
53933
|
return {
|
|
@@ -53569,7 +53945,7 @@ function uninstallLegacyPythonHooks(hooksHome) {
|
|
|
53569
53945
|
for (const [cmd, args] of attempts) {
|
|
53570
53946
|
if (!commandExists3(cmd)) continue;
|
|
53571
53947
|
try {
|
|
53572
|
-
(0,
|
|
53948
|
+
(0, import_child_process12.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
|
|
53573
53949
|
} catch {
|
|
53574
53950
|
}
|
|
53575
53951
|
}
|
|
@@ -53577,14 +53953,14 @@ function uninstallLegacyPythonHooks(hooksHome) {
|
|
|
53577
53953
|
}
|
|
53578
53954
|
function removeLegacyPythonArtifacts(hooksHome) {
|
|
53579
53955
|
const legacyPaths = [
|
|
53580
|
-
|
|
53581
|
-
|
|
53582
|
-
|
|
53956
|
+
path48.join(hooksHome, ".venv"),
|
|
53957
|
+
path48.join(hooksHome, "otel_config.yaml"),
|
|
53958
|
+
path48.join(hooksHome, "venv")
|
|
53583
53959
|
];
|
|
53584
53960
|
for (const target of legacyPaths) {
|
|
53585
53961
|
try {
|
|
53586
|
-
if (
|
|
53587
|
-
|
|
53962
|
+
if (fs42.existsSync(target)) {
|
|
53963
|
+
fs42.rmSync(target, { recursive: true, force: true });
|
|
53588
53964
|
}
|
|
53589
53965
|
} catch {
|
|
53590
53966
|
}
|
|
@@ -53601,7 +53977,7 @@ function normalizeLegacyProviderFlagsInCommand(command2) {
|
|
|
53601
53977
|
}
|
|
53602
53978
|
function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
|
|
53603
53979
|
const hookCommand = `${wrapperPath} --provider ${providerId}`;
|
|
53604
|
-
const result = (0,
|
|
53980
|
+
const result = (0, import_child_process12.spawnSync)(
|
|
53605
53981
|
otelHookCommand,
|
|
53606
53982
|
[
|
|
53607
53983
|
"setup",
|
|
@@ -53625,10 +54001,10 @@ function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
|
|
|
53625
54001
|
};
|
|
53626
54002
|
}
|
|
53627
54003
|
function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
|
|
53628
|
-
if (!
|
|
54004
|
+
if (!fs42.existsSync(filePath)) return false;
|
|
53629
54005
|
let raw;
|
|
53630
54006
|
try {
|
|
53631
|
-
raw =
|
|
54007
|
+
raw = fs42.readFileSync(filePath, "utf8");
|
|
53632
54008
|
} catch {
|
|
53633
54009
|
return false;
|
|
53634
54010
|
}
|
|
@@ -53675,15 +54051,15 @@ function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
|
|
|
53675
54051
|
return changed;
|
|
53676
54052
|
};
|
|
53677
54053
|
if (!walk(parsed)) return false;
|
|
53678
|
-
|
|
54054
|
+
fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
|
|
53679
54055
|
`);
|
|
53680
54056
|
return true;
|
|
53681
54057
|
}
|
|
53682
54058
|
function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
|
|
53683
|
-
if (!
|
|
54059
|
+
if (!fs42.existsSync(filePath)) return false;
|
|
53684
54060
|
let raw;
|
|
53685
54061
|
try {
|
|
53686
|
-
raw =
|
|
54062
|
+
raw = fs42.readFileSync(filePath, "utf8");
|
|
53687
54063
|
} catch {
|
|
53688
54064
|
return false;
|
|
53689
54065
|
}
|
|
@@ -53718,21 +54094,21 @@ function pruneLegacyCursorHookEvents(filePath, wrapperPath) {
|
|
|
53718
54094
|
}
|
|
53719
54095
|
}
|
|
53720
54096
|
if (!changed) return false;
|
|
53721
|
-
|
|
54097
|
+
fs42.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
|
|
53722
54098
|
`);
|
|
53723
54099
|
return true;
|
|
53724
54100
|
}
|
|
53725
54101
|
function rewriteKnownHookRegistrations(wrapperPath) {
|
|
53726
|
-
const home =
|
|
54102
|
+
const home = os16.homedir();
|
|
53727
54103
|
const candidates = [
|
|
53728
|
-
|
|
53729
|
-
|
|
53730
|
-
|
|
54104
|
+
path48.join(home, ".cursor", "hooks.json"),
|
|
54105
|
+
path48.join(home, ".claude", "settings.json"),
|
|
54106
|
+
path48.join(home, ".codex", "hooks.json")
|
|
53731
54107
|
];
|
|
53732
54108
|
for (const filePath of candidates) {
|
|
53733
54109
|
try {
|
|
53734
54110
|
rewriteHookCommandsToWrapper(filePath, wrapperPath);
|
|
53735
|
-
if (filePath.endsWith(`${
|
|
54111
|
+
if (filePath.endsWith(`${path48.sep}.cursor${path48.sep}hooks.json`)) {
|
|
53736
54112
|
pruneLegacyCursorHookEvents(filePath, wrapperPath);
|
|
53737
54113
|
}
|
|
53738
54114
|
} catch {
|
|
@@ -53748,7 +54124,7 @@ function ensureOtelHooks(options) {
|
|
|
53748
54124
|
const hooksHome = getOtelHooksHome();
|
|
53749
54125
|
const errors = [];
|
|
53750
54126
|
const providersSetup = [];
|
|
53751
|
-
|
|
54127
|
+
fs42.mkdirSync(hooksHome, { recursive: true });
|
|
53752
54128
|
let otelHookCommand = resolveOtelHookCommand(hooksHome);
|
|
53753
54129
|
const needsInstall = !otelHookCommand || installedPackageVersion(hooksHome) !== pinnedVersion();
|
|
53754
54130
|
if (needsInstall) {
|
|
@@ -53842,14 +54218,14 @@ function ensureOtelHooks(options) {
|
|
|
53842
54218
|
function disableOtelHooksExport() {
|
|
53843
54219
|
return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
|
|
53844
54220
|
}
|
|
53845
|
-
var
|
|
54221
|
+
var import_child_process12, fs42, os16, path48, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
|
|
53846
54222
|
var init_otel_hooks = __esm({
|
|
53847
54223
|
"src/core/otel-hooks.ts"() {
|
|
53848
54224
|
"use strict";
|
|
53849
|
-
|
|
53850
|
-
|
|
53851
|
-
|
|
53852
|
-
|
|
54225
|
+
import_child_process12 = require("child_process");
|
|
54226
|
+
fs42 = __toESM(require("fs"));
|
|
54227
|
+
os16 = __toESM(require("os"));
|
|
54228
|
+
path48 = __toESM(require("path"));
|
|
53853
54229
|
init_control_config();
|
|
53854
54230
|
init_telemetry_config();
|
|
53855
54231
|
OTEL_HOOKS_PACKAGE = "@osfactory/otel-hook@0.1.2";
|
|
@@ -65603,34 +65979,34 @@ init_telemetry_config();
|
|
|
65603
65979
|
init_sync_context();
|
|
65604
65980
|
|
|
65605
65981
|
// src/cli/commands/agents.ts
|
|
65606
|
-
var
|
|
65982
|
+
var path32 = __toESM(require("path"));
|
|
65607
65983
|
|
|
65608
65984
|
// src/harness/agent-skills.ts
|
|
65609
|
-
var
|
|
65610
|
-
var
|
|
65611
|
-
var
|
|
65985
|
+
var fs26 = __toESM(require("fs"));
|
|
65986
|
+
var os13 = __toESM(require("os"));
|
|
65987
|
+
var path31 = __toESM(require("path"));
|
|
65612
65988
|
var readline = __toESM(require("readline"));
|
|
65613
65989
|
init_file_ops();
|
|
65614
65990
|
init_logging();
|
|
65615
65991
|
|
|
65616
65992
|
// src/utils/paths.ts
|
|
65617
|
-
var
|
|
65618
|
-
var
|
|
65993
|
+
var fs25 = __toESM(require("fs"));
|
|
65994
|
+
var path30 = __toESM(require("path"));
|
|
65619
65995
|
function resolveTemplatesDir() {
|
|
65620
65996
|
const candidates = [
|
|
65621
|
-
|
|
65622
|
-
|
|
65623
|
-
|
|
65997
|
+
path30.join(__dirname, "templates"),
|
|
65998
|
+
path30.join(__dirname, "..", "templates"),
|
|
65999
|
+
path30.join(__dirname, "..", "harness", "templates")
|
|
65624
66000
|
];
|
|
65625
66001
|
for (const dir of candidates) {
|
|
65626
|
-
if (
|
|
66002
|
+
if (fs25.existsSync(path30.join(dir, "har-boilerplate"))) return dir;
|
|
65627
66003
|
}
|
|
65628
66004
|
throw new Error("Templates directory not found. Run npm run build.");
|
|
65629
66005
|
}
|
|
65630
66006
|
function resolveTemplateFile(name) {
|
|
65631
66007
|
const dir = resolveTemplatesDir();
|
|
65632
|
-
const filePath =
|
|
65633
|
-
return
|
|
66008
|
+
const filePath = path30.join(dir, name);
|
|
66009
|
+
return fs25.existsSync(filePath) ? filePath : null;
|
|
65634
66010
|
}
|
|
65635
66011
|
|
|
65636
66012
|
// src/harness/agent-skills.ts
|
|
@@ -65638,18 +66014,18 @@ init_manifest();
|
|
|
65638
66014
|
var AGENT_SKILL_TARGETS = ["claude", "cursor", "codex"];
|
|
65639
66015
|
var MANAGED_HEADER = "<!-- managed by har \u2014 regenerated by `har agents install` / `har env maintain`; edit at your own risk -->";
|
|
65640
66016
|
function agentSkillsTemplateDir() {
|
|
65641
|
-
const dir =
|
|
65642
|
-
if (!
|
|
66017
|
+
const dir = path31.join(resolveTemplatesDir(), "agent-skills");
|
|
66018
|
+
if (!fs26.existsSync(dir)) {
|
|
65643
66019
|
throw new Error("Agent skills templates not found (templates/agent-skills). Run npm run build.");
|
|
65644
66020
|
}
|
|
65645
66021
|
return dir;
|
|
65646
66022
|
}
|
|
65647
66023
|
function readSkillsManifest() {
|
|
65648
|
-
const manifestPath =
|
|
65649
|
-
return JSON.parse(
|
|
66024
|
+
const manifestPath = path31.join(agentSkillsTemplateDir(), "skills.manifest.json");
|
|
66025
|
+
return JSON.parse(fs26.readFileSync(manifestPath, "utf8"));
|
|
65650
66026
|
}
|
|
65651
66027
|
function readSkillBody(entry) {
|
|
65652
|
-
return
|
|
66028
|
+
return fs26.readFileSync(path31.join(agentSkillsTemplateDir(), entry.body), "utf8");
|
|
65653
66029
|
}
|
|
65654
66030
|
function renderYamlValue(value) {
|
|
65655
66031
|
if (typeof value === "string") return JSON.stringify(value);
|
|
@@ -65681,7 +66057,7 @@ function renderSkillFiles(agents) {
|
|
|
65681
66057
|
agent: "claude",
|
|
65682
66058
|
skill: entry.id,
|
|
65683
66059
|
scope: "repo",
|
|
65684
|
-
relPath:
|
|
66060
|
+
relPath: path31.join(".claude", "skills", entry.id, "SKILL.md"),
|
|
65685
66061
|
content: renderClaudeSkill(entry, body)
|
|
65686
66062
|
});
|
|
65687
66063
|
}
|
|
@@ -65690,7 +66066,7 @@ function renderSkillFiles(agents) {
|
|
|
65690
66066
|
agent: "cursor",
|
|
65691
66067
|
skill: entry.id,
|
|
65692
66068
|
scope: "repo",
|
|
65693
|
-
relPath:
|
|
66069
|
+
relPath: path31.join(".cursor", "commands", `${entry.id}.md`),
|
|
65694
66070
|
content: renderPlainSkill(body)
|
|
65695
66071
|
});
|
|
65696
66072
|
}
|
|
@@ -65699,7 +66075,7 @@ function renderSkillFiles(agents) {
|
|
|
65699
66075
|
agent: "codex",
|
|
65700
66076
|
skill: entry.id,
|
|
65701
66077
|
scope: "global",
|
|
65702
|
-
relPath:
|
|
66078
|
+
relPath: path31.posix.join("~", ".codex", "prompts", `${entry.codex.promptName}.md`),
|
|
65703
66079
|
content: renderPlainSkill(body)
|
|
65704
66080
|
});
|
|
65705
66081
|
}
|
|
@@ -65708,12 +66084,12 @@ function renderSkillFiles(agents) {
|
|
|
65708
66084
|
}
|
|
65709
66085
|
function resolveScaffoldPath(repoPath, file) {
|
|
65710
66086
|
if (file.scope === "global") {
|
|
65711
|
-
return
|
|
66087
|
+
return path31.join(os13.homedir(), file.relPath.replace(/^~\//, ""));
|
|
65712
66088
|
}
|
|
65713
|
-
return
|
|
66089
|
+
return path31.join(repoPath, file.relPath);
|
|
65714
66090
|
}
|
|
65715
66091
|
function isHarManaged(filePath) {
|
|
65716
|
-
return
|
|
66092
|
+
return fs26.readFileSync(filePath, "utf8").includes(MANAGED_HEADER);
|
|
65717
66093
|
}
|
|
65718
66094
|
function scaffoldAgentSkills(repoPath, agents, options = {}) {
|
|
65719
66095
|
const written = [];
|
|
@@ -65721,7 +66097,7 @@ function scaffoldAgentSkills(repoPath, agents, options = {}) {
|
|
|
65721
66097
|
const records = [];
|
|
65722
66098
|
for (const file of renderSkillFiles(agents)) {
|
|
65723
66099
|
const destPath = resolveScaffoldPath(repoPath, file);
|
|
65724
|
-
if (
|
|
66100
|
+
if (fs26.existsSync(destPath) && !options.force && !isHarManaged(destPath)) {
|
|
65725
66101
|
warn(`Skipped ${file.relPath} \u2014 exists with user modifications (use --force to overwrite)`);
|
|
65726
66102
|
skipped.push(file.relPath);
|
|
65727
66103
|
continue;
|
|
@@ -65755,16 +66131,16 @@ function removeAgentSkills(repoPath, agents) {
|
|
|
65755
66131
|
const removed = [];
|
|
65756
66132
|
for (const file of renderSkillFiles(agents)) {
|
|
65757
66133
|
const destPath = resolveScaffoldPath(repoPath, file);
|
|
65758
|
-
if (!
|
|
66134
|
+
if (!fs26.existsSync(destPath)) continue;
|
|
65759
66135
|
if (!isHarManaged(destPath)) {
|
|
65760
66136
|
warn(`Kept ${file.relPath} \u2014 has user modifications`);
|
|
65761
66137
|
continue;
|
|
65762
66138
|
}
|
|
65763
|
-
|
|
66139
|
+
fs26.rmSync(destPath);
|
|
65764
66140
|
removed.push(file.relPath);
|
|
65765
|
-
const parent =
|
|
65766
|
-
if (
|
|
65767
|
-
|
|
66141
|
+
const parent = path31.dirname(destPath);
|
|
66142
|
+
if (fs26.existsSync(parent) && fs26.readdirSync(parent).length === 0) {
|
|
66143
|
+
fs26.rmdirSync(parent);
|
|
65768
66144
|
}
|
|
65769
66145
|
}
|
|
65770
66146
|
const manifest = readManifest(repoPath);
|
|
@@ -65783,9 +66159,9 @@ function detectAgentTargets(repoPath) {
|
|
|
65783
66159
|
const manifest = readManifest(repoPath);
|
|
65784
66160
|
const scaffolded = new Set((manifest?.scaffoldedAgentFiles ?? []).map((entry) => entry.agent));
|
|
65785
66161
|
const detected = new Set(scaffolded);
|
|
65786
|
-
if (
|
|
65787
|
-
if (
|
|
65788
|
-
if (
|
|
66162
|
+
if (fs26.existsSync(path31.join(repoPath, ".claude"))) detected.add("claude");
|
|
66163
|
+
if (fs26.existsSync(path31.join(repoPath, ".cursor"))) detected.add("cursor");
|
|
66164
|
+
if (fs26.existsSync(path31.join(os13.homedir(), ".codex"))) detected.add("codex");
|
|
65789
66165
|
return AGENT_SKILL_TARGETS.filter((agent) => detected.has(agent));
|
|
65790
66166
|
}
|
|
65791
66167
|
function parseAgentTargets(raw) {
|
|
@@ -65872,7 +66248,7 @@ var agentsCommand = {
|
|
|
65872
66248
|
}
|
|
65873
66249
|
};
|
|
65874
66250
|
function handleInstall(argv) {
|
|
65875
|
-
const repoPath =
|
|
66251
|
+
const repoPath = path32.resolve(argv.repo);
|
|
65876
66252
|
header("har agents install");
|
|
65877
66253
|
try {
|
|
65878
66254
|
const targets = resolveTargets(argv, repoPath);
|
|
@@ -65898,7 +66274,7 @@ function handleInstall(argv) {
|
|
|
65898
66274
|
}
|
|
65899
66275
|
}
|
|
65900
66276
|
function handleRemove(argv) {
|
|
65901
|
-
const repoPath =
|
|
66277
|
+
const repoPath = path32.resolve(argv.repo);
|
|
65902
66278
|
header("har agents remove");
|
|
65903
66279
|
try {
|
|
65904
66280
|
const targets = resolveTargets(argv, repoPath);
|
|
@@ -65928,25 +66304,25 @@ async function finishCommand(code) {
|
|
|
65928
66304
|
}
|
|
65929
66305
|
|
|
65930
66306
|
// src/core/harness.ts
|
|
65931
|
-
var
|
|
65932
|
-
var
|
|
66307
|
+
var fs38 = __toESM(require("fs"));
|
|
66308
|
+
var path43 = __toESM(require("path"));
|
|
65933
66309
|
|
|
65934
66310
|
// src/harness/generator.ts
|
|
65935
|
-
var
|
|
65936
|
-
var
|
|
66311
|
+
var fs29 = __toESM(require("fs"));
|
|
66312
|
+
var path35 = __toESM(require("path"));
|
|
65937
66313
|
init_file_ops();
|
|
65938
66314
|
init_logging();
|
|
65939
66315
|
|
|
65940
66316
|
// src/core/gitignore.ts
|
|
65941
|
-
var
|
|
65942
|
-
var
|
|
66317
|
+
var fs27 = __toESM(require("fs"));
|
|
66318
|
+
var path33 = __toESM(require("path"));
|
|
65943
66319
|
var HARNESS_ROOT_GITIGNORE_PATTERNS = [
|
|
65944
66320
|
".env.agent.*",
|
|
65945
66321
|
"ecosystem.agent.*.config.cjs"
|
|
65946
66322
|
];
|
|
65947
66323
|
function ensureRootGitignorePatterns(checkoutDir) {
|
|
65948
|
-
const gitignorePath =
|
|
65949
|
-
const content =
|
|
66324
|
+
const gitignorePath = path33.join(checkoutDir, ".gitignore");
|
|
66325
|
+
const content = fs27.existsSync(gitignorePath) ? fs27.readFileSync(gitignorePath, "utf8") : "";
|
|
65950
66326
|
const lines2 = content.split("\n").map((line) => line.trim());
|
|
65951
66327
|
const missing = HARNESS_ROOT_GITIGNORE_PATTERNS.filter((pattern) => !lines2.includes(pattern));
|
|
65952
66328
|
if (missing.length === 0) return;
|
|
@@ -65955,12 +66331,12 @@ function ensureRootGitignorePatterns(checkoutDir) {
|
|
|
65955
66331
|
const block = lines2.some((line) => line.includes("HAR agent slot artifacts")) || header2.length === 0 ? `${missing.join("\n")}
|
|
65956
66332
|
` : `${header2}${missing.join("\n")}
|
|
65957
66333
|
`;
|
|
65958
|
-
|
|
66334
|
+
fs27.writeFileSync(gitignorePath, `${content}${suffix}${block}`);
|
|
65959
66335
|
}
|
|
65960
66336
|
|
|
65961
66337
|
// src/harness/gitignore-template.ts
|
|
65962
|
-
var
|
|
65963
|
-
var
|
|
66338
|
+
var fs28 = __toESM(require("fs"));
|
|
66339
|
+
var path34 = __toESM(require("path"));
|
|
65964
66340
|
var HARNESS_GITIGNORE_TEMPLATE = "gitignore.template";
|
|
65965
66341
|
var HARNESS_GITIGNORE_FILE = ".gitignore";
|
|
65966
66342
|
function harnessFileForTemplate(templateFile) {
|
|
@@ -65970,16 +66346,16 @@ function templateFileForHarness(harnessFile) {
|
|
|
65970
66346
|
return harnessFile === HARNESS_GITIGNORE_FILE ? HARNESS_GITIGNORE_TEMPLATE : harnessFile;
|
|
65971
66347
|
}
|
|
65972
66348
|
function resolveGitignoreTemplateSource(boilerplateDir) {
|
|
65973
|
-
const primary =
|
|
65974
|
-
if (
|
|
65975
|
-
const legacy =
|
|
65976
|
-
if (
|
|
66349
|
+
const primary = path34.join(boilerplateDir, HARNESS_GITIGNORE_TEMPLATE);
|
|
66350
|
+
if (fs28.existsSync(primary)) return primary;
|
|
66351
|
+
const legacy = path34.join(boilerplateDir, HARNESS_GITIGNORE_FILE);
|
|
66352
|
+
if (fs28.existsSync(legacy)) return legacy;
|
|
65977
66353
|
return null;
|
|
65978
66354
|
}
|
|
65979
66355
|
function writeHarnessGitignore(harnessDir, boilerplateDir) {
|
|
65980
66356
|
const src = resolveGitignoreTemplateSource(boilerplateDir);
|
|
65981
66357
|
if (!src) return;
|
|
65982
|
-
|
|
66358
|
+
fs28.writeFileSync(path34.join(harnessDir, HARNESS_GITIGNORE_FILE), fs28.readFileSync(src, "utf8"));
|
|
65983
66359
|
}
|
|
65984
66360
|
function isExpectedHarnessOnlyFile(harnessFile, templateFiles) {
|
|
65985
66361
|
return harnessFile === HARNESS_GITIGNORE_FILE && templateFiles.includes(HARNESS_GITIGNORE_TEMPLATE);
|
|
@@ -66000,27 +66376,27 @@ var CLI_PRUNE_FILES = [
|
|
|
66000
66376
|
];
|
|
66001
66377
|
function pruneCliProfile(harnessDir) {
|
|
66002
66378
|
for (const file of CLI_PRUNE_FILES) {
|
|
66003
|
-
const filePath =
|
|
66004
|
-
if (
|
|
66005
|
-
|
|
66379
|
+
const filePath = path35.join(harnessDir, file);
|
|
66380
|
+
if (fs29.existsSync(filePath)) {
|
|
66381
|
+
fs29.unlinkSync(filePath);
|
|
66006
66382
|
}
|
|
66007
66383
|
}
|
|
66008
66384
|
}
|
|
66009
66385
|
function scaffoldHarnessBoilerplate(repoPath, options = {}) {
|
|
66010
|
-
const harnessDir =
|
|
66011
|
-
const projectName =
|
|
66386
|
+
const harnessDir = path35.join(repoPath, DEFAULT_HAR_DIR);
|
|
66387
|
+
const projectName = path35.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
66012
66388
|
const profile = options.profile ?? "default";
|
|
66013
|
-
const boilerplateDir =
|
|
66014
|
-
if (
|
|
66389
|
+
const boilerplateDir = path35.join(resolveTemplatesDir(), PROFILE_DIRS[profile]);
|
|
66390
|
+
if (fs29.existsSync(harnessDir) && !options.force) {
|
|
66015
66391
|
throw new Error(
|
|
66016
66392
|
'.har/ already exists. Use --force to overwrite or run "har env maintain" to update in place.'
|
|
66017
66393
|
);
|
|
66018
66394
|
}
|
|
66019
|
-
if (!
|
|
66395
|
+
if (!fs29.existsSync(boilerplateDir)) {
|
|
66020
66396
|
throw new Error(`Boilerplate template not found at ${boilerplateDir}`);
|
|
66021
66397
|
}
|
|
66022
|
-
if (options.force &&
|
|
66023
|
-
|
|
66398
|
+
if (options.force && fs29.existsSync(harnessDir)) {
|
|
66399
|
+
fs29.rmSync(harnessDir, { recursive: true, force: true });
|
|
66024
66400
|
}
|
|
66025
66401
|
copyDirRecursive(boilerplateDir, harnessDir);
|
|
66026
66402
|
writeHarnessGitignore(harnessDir, boilerplateDir);
|
|
@@ -66028,11 +66404,11 @@ function scaffoldHarnessBoilerplate(repoPath, options = {}) {
|
|
|
66028
66404
|
pruneCliProfile(harnessDir);
|
|
66029
66405
|
}
|
|
66030
66406
|
syncAgentSlotsToHarnessEnv(repoPath);
|
|
66031
|
-
const harnessEnvPath =
|
|
66032
|
-
if (
|
|
66033
|
-
let content =
|
|
66407
|
+
const harnessEnvPath = path35.join(harnessDir, "harness.env");
|
|
66408
|
+
if (fs29.existsSync(harnessEnvPath)) {
|
|
66409
|
+
let content = fs29.readFileSync(harnessEnvPath, "utf8");
|
|
66034
66410
|
content = content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
66035
|
-
|
|
66411
|
+
fs29.writeFileSync(harnessEnvPath, content);
|
|
66036
66412
|
}
|
|
66037
66413
|
const manifest = createManifest(
|
|
66038
66414
|
repoPath,
|
|
@@ -66054,16 +66430,16 @@ function finalizeHarness(repoPath, adaptationSummary, stack) {
|
|
|
66054
66430
|
}
|
|
66055
66431
|
|
|
66056
66432
|
// src/harness/agent-md.ts
|
|
66057
|
-
var
|
|
66058
|
-
var
|
|
66433
|
+
var fs31 = __toESM(require("fs"));
|
|
66434
|
+
var path37 = __toESM(require("path"));
|
|
66059
66435
|
init_file_ops();
|
|
66060
66436
|
init_logging();
|
|
66061
66437
|
init_manifest();
|
|
66062
66438
|
|
|
66063
66439
|
// src/harness/instruction-files.ts
|
|
66064
|
-
var
|
|
66065
|
-
var
|
|
66066
|
-
var
|
|
66440
|
+
var fs30 = __toESM(require("fs"));
|
|
66441
|
+
var os14 = __toESM(require("os"));
|
|
66442
|
+
var path36 = __toESM(require("path"));
|
|
66067
66443
|
|
|
66068
66444
|
// node_modules/@inquirer/core/dist/esm/lib/key.mjs
|
|
66069
66445
|
var isUpKey = (key) => (
|
|
@@ -68396,7 +68772,7 @@ var AgentsMdShrinkError = class extends Error {
|
|
|
68396
68772
|
this.name = "AgentsMdShrinkError";
|
|
68397
68773
|
}
|
|
68398
68774
|
};
|
|
68399
|
-
function
|
|
68775
|
+
function escapeRegExp2(value) {
|
|
68400
68776
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68401
68777
|
}
|
|
68402
68778
|
function countNonEmptyLines(text) {
|
|
@@ -68405,7 +68781,7 @@ function countNonEmptyLines(text) {
|
|
|
68405
68781
|
function stripClaudePointerFromAgentsMd(content) {
|
|
68406
68782
|
if (!content.includes(CLAUDE_HAR_SECTION_START)) return content;
|
|
68407
68783
|
const pattern = new RegExp(
|
|
68408
|
-
`${
|
|
68784
|
+
`${escapeRegExp2(CLAUDE_HAR_SECTION_START)}[\\s\\S]*?${escapeRegExp2(CLAUDE_HAR_SECTION_END)}\\n?`,
|
|
68409
68785
|
"g"
|
|
68410
68786
|
);
|
|
68411
68787
|
return content.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
|
|
@@ -68457,12 +68833,12 @@ function outsideContentShrinkMessage(beforeLines, afterLines) {
|
|
|
68457
68833
|
}
|
|
68458
68834
|
function detectInstructionFiles(repoPath) {
|
|
68459
68835
|
return {
|
|
68460
|
-
agentsMd:
|
|
68461
|
-
agentMd:
|
|
68462
|
-
claudeMd:
|
|
68463
|
-
cursorDir:
|
|
68464
|
-
claudeDir:
|
|
68465
|
-
codexHome:
|
|
68836
|
+
agentsMd: fs30.existsSync(path36.join(repoPath, AGENTS_MD)),
|
|
68837
|
+
agentMd: fs30.existsSync(path36.join(repoPath, LEGACY_AGENT_MD)),
|
|
68838
|
+
claudeMd: fs30.existsSync(path36.join(repoPath, CLAUDE_MD)),
|
|
68839
|
+
cursorDir: fs30.existsSync(path36.join(repoPath, ".cursor")),
|
|
68840
|
+
claudeDir: fs30.existsSync(path36.join(repoPath, ".claude")),
|
|
68841
|
+
codexHome: fs30.existsSync(path36.join(os14.homedir(), ".codex"))
|
|
68466
68842
|
};
|
|
68467
68843
|
}
|
|
68468
68844
|
function formatDetectionReport(detection) {
|
|
@@ -68517,7 +68893,7 @@ function loadHarAgentsSectionFromTemplate() {
|
|
|
68517
68893
|
if (!templatePath) {
|
|
68518
68894
|
throw new Error("AGENTS.md.template not found. Run npm run build.");
|
|
68519
68895
|
}
|
|
68520
|
-
const content =
|
|
68896
|
+
const content = fs30.readFileSync(templatePath, "utf8");
|
|
68521
68897
|
const start = content.indexOf(HAR_SECTION_START);
|
|
68522
68898
|
const end = content.indexOf(HAR_SECTION_END);
|
|
68523
68899
|
if (start === -1 || end === -1 || end < start) {
|
|
@@ -68530,9 +68906,9 @@ function loadAgentsMdTemplate() {
|
|
|
68530
68906
|
if (!templatePath) {
|
|
68531
68907
|
throw new Error("AGENTS.md.template not found. Run npm run build.");
|
|
68532
68908
|
}
|
|
68533
|
-
return
|
|
68909
|
+
return fs30.readFileSync(templatePath, "utf8");
|
|
68534
68910
|
}
|
|
68535
|
-
function
|
|
68911
|
+
function upsertMarkedBlock2(content, block, startMarker, endMarker) {
|
|
68536
68912
|
const existingBlock = findLastCompleteMarkedBlock(content, startMarker, endMarker);
|
|
68537
68913
|
if (existingBlock) {
|
|
68538
68914
|
const newContent = content.slice(0, existingBlock.start) + block + content.slice(existingBlock.end);
|
|
@@ -68554,7 +68930,7 @@ function mergeAgentsMdContent(existing, incoming) {
|
|
|
68554
68930
|
harSection = sanitizedIncoming.slice(start, end + HAR_SECTION_END.length);
|
|
68555
68931
|
}
|
|
68556
68932
|
}
|
|
68557
|
-
const { content } =
|
|
68933
|
+
const { content } = upsertMarkedBlock2(
|
|
68558
68934
|
sanitizedExisting,
|
|
68559
68935
|
harSection,
|
|
68560
68936
|
HAR_SECTION_START,
|
|
@@ -68563,15 +68939,15 @@ function mergeAgentsMdContent(existing, incoming) {
|
|
|
68563
68939
|
return content;
|
|
68564
68940
|
}
|
|
68565
68941
|
function upsertAgentsMdHarSection(repoPath, options = {}) {
|
|
68566
|
-
const dest =
|
|
68942
|
+
const dest = path36.join(repoPath, AGENTS_MD);
|
|
68567
68943
|
const section = loadHarAgentsSectionFromTemplate();
|
|
68568
|
-
if (!
|
|
68944
|
+
if (!fs30.existsSync(dest)) {
|
|
68569
68945
|
writeFileSafe(dest, loadAgentsMdTemplate());
|
|
68570
68946
|
return "created";
|
|
68571
68947
|
}
|
|
68572
|
-
const existing =
|
|
68948
|
+
const existing = fs30.readFileSync(dest, "utf8");
|
|
68573
68949
|
const sanitized = stripClaudePointerFromAgentsMd(existing);
|
|
68574
|
-
const { content, action } =
|
|
68950
|
+
const { content, action } = upsertMarkedBlock2(
|
|
68575
68951
|
sanitized,
|
|
68576
68952
|
section,
|
|
68577
68953
|
HAR_SECTION_START,
|
|
@@ -68594,15 +68970,15 @@ function upsertAgentsMdHarSection(repoPath, options = {}) {
|
|
|
68594
68970
|
return action;
|
|
68595
68971
|
}
|
|
68596
68972
|
function migrateLegacyAgentMd(repoPath) {
|
|
68597
|
-
const legacyPath =
|
|
68598
|
-
const agentsPath =
|
|
68599
|
-
if (!
|
|
68600
|
-
const legacy =
|
|
68601
|
-
if (!
|
|
68973
|
+
const legacyPath = path36.join(repoPath, LEGACY_AGENT_MD);
|
|
68974
|
+
const agentsPath = path36.join(repoPath, AGENTS_MD);
|
|
68975
|
+
if (!fs30.existsSync(legacyPath)) return false;
|
|
68976
|
+
const legacy = fs30.readFileSync(legacyPath, "utf8");
|
|
68977
|
+
if (!fs30.existsSync(agentsPath)) {
|
|
68602
68978
|
writeFileSafe(agentsPath, legacy);
|
|
68603
68979
|
upsertAgentsMdHarSection(repoPath);
|
|
68604
68980
|
} else {
|
|
68605
|
-
const agents =
|
|
68981
|
+
const agents = fs30.readFileSync(agentsPath, "utf8");
|
|
68606
68982
|
const legacyTrimmed = legacy.trim();
|
|
68607
68983
|
if (legacyTrimmed.length > 0 && !agents.includes(legacyTrimmed)) {
|
|
68608
68984
|
const suffix = agents.endsWith("\n") ? "" : "\n";
|
|
@@ -68612,7 +68988,7 @@ ${legacyTrimmed}
|
|
|
68612
68988
|
}
|
|
68613
68989
|
upsertAgentsMdHarSection(repoPath);
|
|
68614
68990
|
}
|
|
68615
|
-
|
|
68991
|
+
fs30.unlinkSync(legacyPath);
|
|
68616
68992
|
info(`Migrated ${LEGACY_AGENT_MD} \u2192 ${AGENTS_MD} (removed legacy file)`);
|
|
68617
68993
|
return true;
|
|
68618
68994
|
}
|
|
@@ -68622,7 +68998,7 @@ function loadClaudeMdTemplate(projectName) {
|
|
|
68622
68998
|
throw new Error("CLAUDE.md.template not found. Run npm run build.");
|
|
68623
68999
|
}
|
|
68624
69000
|
const displayName = projectName.replace(/_/g, " ");
|
|
68625
|
-
return
|
|
69001
|
+
return fs30.readFileSync(templatePath, "utf8").replace(/__PROJECT_DISPLAY_NAME__/g, displayName);
|
|
68626
69002
|
}
|
|
68627
69003
|
function isThinClaudePointer(content) {
|
|
68628
69004
|
const trimmed = content.trim();
|
|
@@ -68636,21 +69012,21 @@ This repository uses a \`.har/\` harness. Read [AGENTS.md](./AGENTS.md) and
|
|
|
68636
69012
|
[\`.har/README.md\`](./.har/README.md) before making changes.
|
|
68637
69013
|
${CLAUDE_HAR_SECTION_END}`;
|
|
68638
69014
|
function ensureClaudeMdPointer(repoPath, options = {}) {
|
|
68639
|
-
const dest =
|
|
68640
|
-
const projectName =
|
|
69015
|
+
const dest = path36.join(repoPath, CLAUDE_MD);
|
|
69016
|
+
const projectName = path36.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
68641
69017
|
const thin = loadClaudeMdTemplate(projectName);
|
|
68642
|
-
if (!
|
|
69018
|
+
if (!fs30.existsSync(dest)) {
|
|
68643
69019
|
writeFileSafe(dest, thin);
|
|
68644
69020
|
return "created";
|
|
68645
69021
|
}
|
|
68646
|
-
const existing =
|
|
69022
|
+
const existing = fs30.readFileSync(dest, "utf8");
|
|
68647
69023
|
if (isThinClaudePointer(existing) || options.force) {
|
|
68648
69024
|
writeFileSafe(dest, thin);
|
|
68649
69025
|
return "updated";
|
|
68650
69026
|
}
|
|
68651
69027
|
if (existing.includes(CLAUDE_HAR_SECTION_START) || /\[AGENTS\.md\]/.test(existing)) {
|
|
68652
69028
|
if (existing.includes(CLAUDE_HAR_SECTION_START)) {
|
|
68653
|
-
const { content: content2, action: action2 } =
|
|
69029
|
+
const { content: content2, action: action2 } = upsertMarkedBlock2(
|
|
68654
69030
|
existing,
|
|
68655
69031
|
CLAUDE_POINTER_BLOCK,
|
|
68656
69032
|
CLAUDE_HAR_SECTION_START,
|
|
@@ -68661,7 +69037,7 @@ function ensureClaudeMdPointer(repoPath, options = {}) {
|
|
|
68661
69037
|
}
|
|
68662
69038
|
return "skipped";
|
|
68663
69039
|
}
|
|
68664
|
-
const { content, action } =
|
|
69040
|
+
const { content, action } = upsertMarkedBlock2(
|
|
68665
69041
|
existing,
|
|
68666
69042
|
CLAUDE_POINTER_BLOCK,
|
|
68667
69043
|
CLAUDE_HAR_SECTION_START,
|
|
@@ -68792,19 +69168,19 @@ var AGENT_MD_PROPOSAL = "AGENT.md.proposed";
|
|
|
68792
69168
|
var AGENT_MD_PROPOSAL_META = "AGENT.md.proposed.meta.json";
|
|
68793
69169
|
function readAgentMdProposal(repoPath) {
|
|
68794
69170
|
const harnessDir = getHarnessDir(repoPath);
|
|
68795
|
-
const proposalPath =
|
|
68796
|
-
const metaPath =
|
|
68797
|
-
const legacyProposalPath =
|
|
68798
|
-
const legacyMetaPath =
|
|
68799
|
-
const resolvedProposal =
|
|
69171
|
+
const proposalPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL);
|
|
69172
|
+
const metaPath = path37.join(harnessDir, AGENTS_MD_PROPOSAL_META);
|
|
69173
|
+
const legacyProposalPath = path37.join(harnessDir, AGENT_MD_PROPOSAL);
|
|
69174
|
+
const legacyMetaPath = path37.join(harnessDir, AGENT_MD_PROPOSAL_META);
|
|
69175
|
+
const resolvedProposal = fs31.existsSync(proposalPath) ? proposalPath : fs31.existsSync(legacyProposalPath) ? legacyProposalPath : null;
|
|
68800
69176
|
if (!resolvedProposal) return null;
|
|
68801
69177
|
const resolvedMeta = resolvedProposal === proposalPath ? metaPath : legacyMetaPath;
|
|
68802
|
-
const content =
|
|
69178
|
+
const content = fs31.readFileSync(resolvedProposal, "utf8");
|
|
68803
69179
|
let rationale = "";
|
|
68804
69180
|
let createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
68805
|
-
if (
|
|
69181
|
+
if (fs31.existsSync(resolvedMeta)) {
|
|
68806
69182
|
try {
|
|
68807
|
-
const meta = JSON.parse(
|
|
69183
|
+
const meta = JSON.parse(fs31.readFileSync(resolvedMeta, "utf8"));
|
|
68808
69184
|
rationale = meta.rationale ?? "";
|
|
68809
69185
|
createdAt = meta.createdAt ?? createdAt;
|
|
68810
69186
|
} catch {
|
|
@@ -68821,8 +69197,8 @@ function clearAgentMdProposal(repoPath) {
|
|
|
68821
69197
|
AGENT_MD_PROPOSAL,
|
|
68822
69198
|
AGENT_MD_PROPOSAL_META
|
|
68823
69199
|
]) {
|
|
68824
|
-
const p =
|
|
68825
|
-
if (
|
|
69200
|
+
const p = path37.join(harnessDir, file);
|
|
69201
|
+
if (fs31.existsSync(p)) fs31.unlinkSync(p);
|
|
68826
69202
|
}
|
|
68827
69203
|
}
|
|
68828
69204
|
function wouldProposalShrinkExisting(existing, proposal) {
|
|
@@ -68835,9 +69211,9 @@ function wouldProposalShrinkExisting(existing, proposal) {
|
|
|
68835
69211
|
function applyAgentMdProposalMerge(repoPath, options = {}) {
|
|
68836
69212
|
const proposal = readAgentMdProposal(repoPath);
|
|
68837
69213
|
if (!proposal) return false;
|
|
68838
|
-
const agentsMdPath =
|
|
68839
|
-
if (
|
|
68840
|
-
const existing =
|
|
69214
|
+
const agentsMdPath = path37.join(repoPath, AGENTS_MD);
|
|
69215
|
+
if (fs31.existsSync(agentsMdPath)) {
|
|
69216
|
+
const existing = fs31.readFileSync(agentsMdPath, "utf8");
|
|
68841
69217
|
if (wouldProposalShrinkExisting(existing, proposal.content)) {
|
|
68842
69218
|
const message = `${AGENTS_MD} proposal would drop project-specific content outside har:agent-environment markers \u2014 merge custom sections from .har/${AGENTS_MD_PROPOSAL} manually, then delete the proposal.`;
|
|
68843
69219
|
if (options.rejectSignificantShrink) {
|
|
@@ -68860,8 +69236,8 @@ function finalizeAgentsMdInstructionFiles(repoPath) {
|
|
|
68860
69236
|
}
|
|
68861
69237
|
|
|
68862
69238
|
// src/harness/validator.ts
|
|
68863
|
-
var
|
|
68864
|
-
var
|
|
69239
|
+
var fs32 = __toESM(require("fs"));
|
|
69240
|
+
var path38 = __toESM(require("path"));
|
|
68865
69241
|
init_shell();
|
|
68866
69242
|
init_manifest();
|
|
68867
69243
|
init_stages();
|
|
@@ -68916,22 +69292,22 @@ var SHELL_SCRIPTS = [
|
|
|
68916
69292
|
function validateHarness(repoPath) {
|
|
68917
69293
|
const harnessDir = getHarnessDir(repoPath);
|
|
68918
69294
|
const issues = [];
|
|
68919
|
-
if (!
|
|
69295
|
+
if (!fs32.existsSync(harnessDir)) {
|
|
68920
69296
|
return {
|
|
68921
69297
|
pass: false,
|
|
68922
69298
|
issues: [{ file: ".har", message: "Harness directory not found", severity: "error" }]
|
|
68923
69299
|
};
|
|
68924
69300
|
}
|
|
68925
69301
|
for (const file of getRequiredFiles(repoPath)) {
|
|
68926
|
-
const filePath =
|
|
68927
|
-
if (!
|
|
69302
|
+
const filePath = path38.join(harnessDir, file);
|
|
69303
|
+
if (!fs32.existsSync(filePath)) {
|
|
68928
69304
|
issues.push({ file, message: "Required file missing", severity: "error" });
|
|
68929
69305
|
}
|
|
68930
69306
|
}
|
|
68931
69307
|
for (const script of SHELL_SCRIPTS) {
|
|
68932
|
-
const scriptPath =
|
|
68933
|
-
if (!
|
|
68934
|
-
const stat =
|
|
69308
|
+
const scriptPath = path38.join(harnessDir, script);
|
|
69309
|
+
if (!fs32.existsSync(scriptPath)) continue;
|
|
69310
|
+
const stat = fs32.statSync(scriptPath);
|
|
68935
69311
|
if (!(stat.mode & 73)) {
|
|
68936
69312
|
issues.push({ file: script, message: "Script is not executable", severity: "warning" });
|
|
68937
69313
|
}
|
|
@@ -68946,9 +69322,9 @@ function validateHarness(repoPath) {
|
|
|
68946
69322
|
}
|
|
68947
69323
|
const manifest = readManifest(repoPath);
|
|
68948
69324
|
const profile = manifest?.profile ?? "default";
|
|
68949
|
-
const ecosystemPath =
|
|
68950
|
-
if (profile !== "cli" &&
|
|
68951
|
-
const content =
|
|
69325
|
+
const ecosystemPath = path38.join(harnessDir, "ecosystem.agent.template.cjs");
|
|
69326
|
+
if (profile !== "cli" && fs32.existsSync(ecosystemPath)) {
|
|
69327
|
+
const content = fs32.readFileSync(ecosystemPath, "utf8");
|
|
68952
69328
|
if (!content.includes("module.exports")) {
|
|
68953
69329
|
issues.push({
|
|
68954
69330
|
file: "ecosystem.agent.template.cjs",
|
|
@@ -68957,9 +69333,9 @@ function validateHarness(repoPath) {
|
|
|
68957
69333
|
});
|
|
68958
69334
|
}
|
|
68959
69335
|
}
|
|
68960
|
-
const harnessEnvPath =
|
|
68961
|
-
if (
|
|
68962
|
-
const content =
|
|
69336
|
+
const harnessEnvPath = path38.join(harnessDir, "harness.env");
|
|
69337
|
+
if (fs32.existsSync(harnessEnvPath)) {
|
|
69338
|
+
const content = fs32.readFileSync(harnessEnvPath, "utf8");
|
|
68963
69339
|
if (content.includes("TODO: set migrate command")) {
|
|
68964
69340
|
issues.push({ file: "harness.env", message: "Migrate command still has TODO", severity: "warning" });
|
|
68965
69341
|
}
|
|
@@ -68967,17 +69343,17 @@ function validateHarness(repoPath) {
|
|
|
68967
69343
|
issues.push({ file: "harness.env", message: "Seed command still has TODO", severity: "warning" });
|
|
68968
69344
|
}
|
|
68969
69345
|
}
|
|
68970
|
-
const verifyPath =
|
|
68971
|
-
if (
|
|
68972
|
-
const content =
|
|
69346
|
+
const verifyPath = path38.join(harnessDir, "verify.sh");
|
|
69347
|
+
if (fs32.existsSync(verifyPath)) {
|
|
69348
|
+
const content = fs32.readFileSync(verifyPath, "utf8");
|
|
68973
69349
|
if (content.includes("echo 'TODO:")) {
|
|
68974
69350
|
issues.push({ file: "verify.sh", message: "Verification steps still have TODO placeholders", severity: "warning" });
|
|
68975
69351
|
}
|
|
68976
69352
|
}
|
|
68977
|
-
const stagesPath =
|
|
68978
|
-
if (
|
|
69353
|
+
const stagesPath = path38.join(harnessDir, "stages.json");
|
|
69354
|
+
if (fs32.existsSync(stagesPath)) {
|
|
68979
69355
|
try {
|
|
68980
|
-
JSON.parse(
|
|
69356
|
+
JSON.parse(fs32.readFileSync(stagesPath, "utf8"));
|
|
68981
69357
|
const registry2 = readStageRegistry(repoPath);
|
|
68982
69358
|
if (registry2.stages.length === 0) {
|
|
68983
69359
|
issues.push({ file: "stages.json", message: "No harness stages declared", severity: "warning" });
|
|
@@ -68992,9 +69368,9 @@ function validateHarness(repoPath) {
|
|
|
68992
69368
|
} else {
|
|
68993
69369
|
issues.push({ file: "stages.json", message: "Stage registry missing", severity: "warning" });
|
|
68994
69370
|
}
|
|
68995
|
-
const readmePath =
|
|
68996
|
-
if (
|
|
68997
|
-
const content =
|
|
69371
|
+
const readmePath = path38.join(harnessDir, "README.md");
|
|
69372
|
+
if (fs32.existsSync(readmePath)) {
|
|
69373
|
+
const content = fs32.readFileSync(readmePath, "utf8");
|
|
68998
69374
|
if (!content.includes(".har")) {
|
|
68999
69375
|
issues.push({ file: "README.md", message: "README should document .har/ paths", severity: "warning" });
|
|
69000
69376
|
}
|
|
@@ -69008,8 +69384,8 @@ function validateHarness(repoPath) {
|
|
|
69008
69384
|
async function smokeTestHarness(repoPath) {
|
|
69009
69385
|
const harnessDir = getHarnessDir(repoPath);
|
|
69010
69386
|
const issues = [];
|
|
69011
|
-
const setupScript =
|
|
69012
|
-
if (
|
|
69387
|
+
const setupScript = path38.join(harnessDir, "setup-infra.sh");
|
|
69388
|
+
if (fs32.existsSync(setupScript)) {
|
|
69013
69389
|
const result = run(`bash "${setupScript}"`, { cwd: repoPath });
|
|
69014
69390
|
if (result.code !== 0) {
|
|
69015
69391
|
issues.push({
|
|
@@ -69024,8 +69400,8 @@ async function smokeTestHarness(repoPath) {
|
|
|
69024
69400
|
}
|
|
69025
69401
|
|
|
69026
69402
|
// src/harness/drift.ts
|
|
69027
|
-
var
|
|
69028
|
-
var
|
|
69403
|
+
var fs33 = __toESM(require("fs"));
|
|
69404
|
+
var path39 = __toESM(require("path"));
|
|
69029
69405
|
init_env();
|
|
69030
69406
|
init_manifest();
|
|
69031
69407
|
init_stages();
|
|
@@ -69096,16 +69472,16 @@ function substituteProjectName(content, projectName) {
|
|
|
69096
69472
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
69097
69473
|
}
|
|
69098
69474
|
function listBoilerplateFiles(boilerplateDir) {
|
|
69099
|
-
if (!
|
|
69100
|
-
return
|
|
69475
|
+
if (!fs33.existsSync(boilerplateDir)) return [];
|
|
69476
|
+
return fs33.readdirSync(boilerplateDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
|
|
69101
69477
|
}
|
|
69102
69478
|
function compareHarnessToTemplate(repoPath) {
|
|
69103
|
-
const resolved =
|
|
69479
|
+
const resolved = path39.resolve(repoPath);
|
|
69104
69480
|
const manifest = readManifest(resolved);
|
|
69105
69481
|
const profile = manifest?.profile ?? "default";
|
|
69106
69482
|
const harnessDir = getHarnessDir(resolved);
|
|
69107
|
-
const projectName =
|
|
69108
|
-
const boilerplateDir =
|
|
69483
|
+
const projectName = path39.basename(resolved).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
69484
|
+
const boilerplateDir = path39.join(resolveTemplatesDir(), PROFILE_DIRS2[profile]);
|
|
69109
69485
|
const templateFiles = listBoilerplateFiles(boilerplateDir);
|
|
69110
69486
|
const missing = [];
|
|
69111
69487
|
const checksumMismatch = [];
|
|
@@ -69113,17 +69489,17 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
69113
69489
|
const unchanged = [];
|
|
69114
69490
|
for (const file of templateFiles) {
|
|
69115
69491
|
const harnessFile = harnessFileForTemplate(file);
|
|
69116
|
-
const templatePath =
|
|
69117
|
-
const harnessPath =
|
|
69118
|
-
let templateContent =
|
|
69492
|
+
const templatePath = path39.join(boilerplateDir, file);
|
|
69493
|
+
const harnessPath = path39.join(harnessDir, harnessFile);
|
|
69494
|
+
let templateContent = fs33.readFileSync(templatePath, "utf8");
|
|
69119
69495
|
if (file === "harness.env") {
|
|
69120
69496
|
templateContent = substituteProjectName(templateContent, projectName);
|
|
69121
69497
|
}
|
|
69122
|
-
if (!
|
|
69498
|
+
if (!fs33.existsSync(harnessPath)) {
|
|
69123
69499
|
missing.push(harnessFile);
|
|
69124
69500
|
continue;
|
|
69125
69501
|
}
|
|
69126
|
-
const harnessChecksum = computeFileChecksum(
|
|
69502
|
+
const harnessChecksum = computeFileChecksum(fs33.readFileSync(harnessPath, "utf8"));
|
|
69127
69503
|
const templateChecksum = computeFileChecksum(templateContent);
|
|
69128
69504
|
if (harnessChecksum === templateChecksum) {
|
|
69129
69505
|
unchanged.push(harnessFile);
|
|
@@ -69131,10 +69507,10 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
69131
69507
|
checksumMismatch.push(harnessFile);
|
|
69132
69508
|
}
|
|
69133
69509
|
}
|
|
69134
|
-
if (
|
|
69135
|
-
for (const file of
|
|
69136
|
-
const full =
|
|
69137
|
-
if (!
|
|
69510
|
+
if (fs33.existsSync(harnessDir)) {
|
|
69511
|
+
for (const file of fs33.readdirSync(harnessDir)) {
|
|
69512
|
+
const full = path39.join(harnessDir, file);
|
|
69513
|
+
if (!fs33.statSync(full).isFile()) continue;
|
|
69138
69514
|
if (file === "manifest.json" || file.startsWith("ADAPT-PROMPT")) continue;
|
|
69139
69515
|
if (profile === "cli" && CLI_EXPECTED_ABSENT.has(file)) {
|
|
69140
69516
|
extra.push(file);
|
|
@@ -69165,29 +69541,29 @@ function compareHarnessToTemplate(repoPath) {
|
|
|
69165
69541
|
}
|
|
69166
69542
|
|
|
69167
69543
|
// src/harness/maintain-bundle.ts
|
|
69168
|
-
var
|
|
69169
|
-
var
|
|
69170
|
-
var
|
|
69544
|
+
var fs37 = __toESM(require("fs"));
|
|
69545
|
+
var os15 = __toESM(require("os"));
|
|
69546
|
+
var path42 = __toESM(require("path"));
|
|
69171
69547
|
init_file_ops();
|
|
69172
69548
|
init_shell();
|
|
69173
69549
|
init_manifest();
|
|
69174
69550
|
|
|
69175
69551
|
// src/harness/plugin-drift.ts
|
|
69176
|
-
var
|
|
69177
|
-
var
|
|
69552
|
+
var fs36 = __toESM(require("fs"));
|
|
69553
|
+
var path41 = __toESM(require("path"));
|
|
69178
69554
|
init_manifest();
|
|
69179
69555
|
|
|
69180
69556
|
// src/harness/plugins.ts
|
|
69181
|
-
var
|
|
69182
|
-
var
|
|
69557
|
+
var fs35 = __toESM(require("fs"));
|
|
69558
|
+
var path40 = __toESM(require("path"));
|
|
69183
69559
|
init_zod();
|
|
69184
69560
|
init_logging();
|
|
69185
69561
|
|
|
69186
69562
|
// src/harness/parser.ts
|
|
69187
|
-
var
|
|
69563
|
+
var fs34 = __toESM(require("fs"));
|
|
69188
69564
|
init_manifest();
|
|
69189
69565
|
function harnessExists(repoPath) {
|
|
69190
|
-
return
|
|
69566
|
+
return fs34.existsSync(`${repoPath}/.har/setup-infra.sh`);
|
|
69191
69567
|
}
|
|
69192
69568
|
|
|
69193
69569
|
// src/harness/plugins.ts
|
|
@@ -69212,16 +69588,16 @@ var PluginManifestSchema = external_exports.object({
|
|
|
69212
69588
|
docsPath: external_exports.string().min(1)
|
|
69213
69589
|
});
|
|
69214
69590
|
function resolvePluginDir(pluginId) {
|
|
69215
|
-
const dir =
|
|
69216
|
-
if (!
|
|
69591
|
+
const dir = path40.join(resolveTemplatesDir(), "plugins", pluginId);
|
|
69592
|
+
if (!fs35.existsSync(dir)) {
|
|
69217
69593
|
throw new Error(`Plugin not found: ${pluginId}. Run npm run build.`);
|
|
69218
69594
|
}
|
|
69219
69595
|
return dir;
|
|
69220
69596
|
}
|
|
69221
69597
|
function readPluginManifest(pluginId) {
|
|
69222
|
-
const manifestPath =
|
|
69598
|
+
const manifestPath = path40.join(resolvePluginDir(pluginId), "template.manifest.json");
|
|
69223
69599
|
const parsed = PluginManifestSchema.safeParse(
|
|
69224
|
-
JSON.parse(
|
|
69600
|
+
JSON.parse(fs35.readFileSync(manifestPath, "utf8"))
|
|
69225
69601
|
);
|
|
69226
69602
|
if (!parsed.success) {
|
|
69227
69603
|
throw new Error(`Invalid plugin manifest for ${pluginId}: ${parsed.error.message}`);
|
|
@@ -69232,40 +69608,40 @@ function readPluginManifest(pluginId) {
|
|
|
69232
69608
|
return parsed.data;
|
|
69233
69609
|
}
|
|
69234
69610
|
function ensureParentDir(filePath) {
|
|
69235
|
-
const parent =
|
|
69236
|
-
if (!
|
|
69237
|
-
|
|
69611
|
+
const parent = path40.dirname(filePath);
|
|
69612
|
+
if (!fs35.existsSync(parent)) {
|
|
69613
|
+
fs35.mkdirSync(parent, { recursive: true });
|
|
69238
69614
|
}
|
|
69239
69615
|
}
|
|
69240
69616
|
function copyPluginFile(pluginDir, file, repoPath, force) {
|
|
69241
|
-
const srcPath =
|
|
69242
|
-
const destPath =
|
|
69243
|
-
if (!
|
|
69617
|
+
const srcPath = path40.join(pluginDir, file.src);
|
|
69618
|
+
const destPath = path40.join(repoPath, file.dest);
|
|
69619
|
+
if (!fs35.existsSync(srcPath)) {
|
|
69244
69620
|
throw new Error(`Plugin file missing: ${file.src}`);
|
|
69245
69621
|
}
|
|
69246
|
-
if (
|
|
69622
|
+
if (fs35.existsSync(destPath) && !force) {
|
|
69247
69623
|
throw new Error(
|
|
69248
69624
|
`File already exists: ${file.dest}. Use --force to overwrite or remove it first.`
|
|
69249
69625
|
);
|
|
69250
69626
|
}
|
|
69251
69627
|
ensureParentDir(destPath);
|
|
69252
|
-
|
|
69628
|
+
fs35.copyFileSync(srcPath, destPath);
|
|
69253
69629
|
if (file.executable) {
|
|
69254
|
-
|
|
69630
|
+
fs35.chmodSync(destPath, 493);
|
|
69255
69631
|
}
|
|
69256
69632
|
return { written: true, path: file.dest };
|
|
69257
69633
|
}
|
|
69258
69634
|
function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
|
|
69259
|
-
const packagePath =
|
|
69260
|
-
if (!
|
|
69635
|
+
const packagePath = path40.join(repoPath, "package.json");
|
|
69636
|
+
if (!fs35.existsSync(packagePath)) {
|
|
69261
69637
|
throw new Error("No package.json in repo root. Add one before applying this plugin.");
|
|
69262
69638
|
}
|
|
69263
|
-
const fragmentPath =
|
|
69264
|
-
if (!
|
|
69639
|
+
const fragmentPath = path40.join(pluginDir, fragmentRelPath);
|
|
69640
|
+
if (!fs35.existsSync(fragmentPath)) {
|
|
69265
69641
|
throw new Error(`Package fragment missing: ${fragmentRelPath}`);
|
|
69266
69642
|
}
|
|
69267
|
-
const pkg = JSON.parse(
|
|
69268
|
-
const fragment = JSON.parse(
|
|
69643
|
+
const pkg = JSON.parse(fs35.readFileSync(packagePath, "utf8"));
|
|
69644
|
+
const fragment = JSON.parse(fs35.readFileSync(fragmentPath, "utf8"));
|
|
69269
69645
|
for (const section of ["scripts", "devDependencies"]) {
|
|
69270
69646
|
const existing = pkg[section] ?? {};
|
|
69271
69647
|
const incoming = fragment[section] ?? {};
|
|
@@ -69278,7 +69654,7 @@ function mergePackageJson(repoPath, pluginDir, fragmentRelPath, warnings) {
|
|
|
69278
69654
|
}
|
|
69279
69655
|
pkg[section] = existing;
|
|
69280
69656
|
}
|
|
69281
|
-
|
|
69657
|
+
fs35.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
69282
69658
|
}
|
|
69283
69659
|
function patchStageRegistry(repoPath, manifest, force) {
|
|
69284
69660
|
const registry2 = readStageRegistry(repoPath);
|
|
@@ -69318,8 +69694,8 @@ function assertHarnessPresent(repoPath) {
|
|
|
69318
69694
|
}
|
|
69319
69695
|
function assertStageNotPresent(repoPath, stageId, force) {
|
|
69320
69696
|
if (force) return;
|
|
69321
|
-
const scriptPath =
|
|
69322
|
-
if (
|
|
69697
|
+
const scriptPath = path40.join(repoPath, ".har", "stages", `${stageId}.sh`);
|
|
69698
|
+
if (fs35.existsSync(scriptPath)) {
|
|
69323
69699
|
throw new Error(
|
|
69324
69700
|
`Stage script already exists: .har/stages/${stageId}.sh. Use --force to overwrite.`
|
|
69325
69701
|
);
|
|
@@ -69332,7 +69708,7 @@ function assertStageNotPresent(repoPath, stageId, force) {
|
|
|
69332
69708
|
}
|
|
69333
69709
|
}
|
|
69334
69710
|
function applyPlugin(repoPath, pluginId, options = {}) {
|
|
69335
|
-
const resolved =
|
|
69711
|
+
const resolved = path40.resolve(repoPath);
|
|
69336
69712
|
const force = options.force ?? false;
|
|
69337
69713
|
const warnings = [];
|
|
69338
69714
|
const filesWritten = [];
|
|
@@ -69351,7 +69727,7 @@ function applyPlugin(repoPath, pluginId, options = {}) {
|
|
|
69351
69727
|
if (file.skipFlag === "skipCi" && options.skipCi) {
|
|
69352
69728
|
continue;
|
|
69353
69729
|
}
|
|
69354
|
-
if (
|
|
69730
|
+
if (fs35.existsSync(path40.join(resolved, file.dest)) && !force) {
|
|
69355
69731
|
warnings.push(`Skipped optional file (exists): ${file.dest}`);
|
|
69356
69732
|
continue;
|
|
69357
69733
|
}
|
|
@@ -69386,18 +69762,18 @@ function applyPlugin(repoPath, pluginId, options = {}) {
|
|
|
69386
69762
|
};
|
|
69387
69763
|
}
|
|
69388
69764
|
function listPluginIds() {
|
|
69389
|
-
const root =
|
|
69390
|
-
if (!
|
|
69391
|
-
return
|
|
69765
|
+
const root = path40.join(resolveTemplatesDir(), "plugins");
|
|
69766
|
+
if (!fs35.existsSync(root)) return [...PLUGIN_IDS];
|
|
69767
|
+
return fs35.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => PLUGIN_IDS.includes(name));
|
|
69392
69768
|
}
|
|
69393
69769
|
|
|
69394
69770
|
// src/harness/plugin-drift.ts
|
|
69395
69771
|
init_stages();
|
|
69396
69772
|
var PACKAGE_SECTIONS = ["scripts", "devDependencies"];
|
|
69397
69773
|
function detectInstalledPlugins(repoPath) {
|
|
69398
|
-
const resolved =
|
|
69399
|
-
const registryPath =
|
|
69400
|
-
if (!
|
|
69774
|
+
const resolved = path41.resolve(repoPath);
|
|
69775
|
+
const registryPath = path41.join(resolved, ".har", "stages.json");
|
|
69776
|
+
if (!fs36.existsSync(registryPath)) return [];
|
|
69401
69777
|
const registry2 = readStageRegistry(resolved);
|
|
69402
69778
|
const stageIds = new Set(registry2.stages.map((stage) => stage.id));
|
|
69403
69779
|
const installed = [];
|
|
@@ -69410,17 +69786,17 @@ function detectInstalledPlugins(repoPath) {
|
|
|
69410
69786
|
return installed;
|
|
69411
69787
|
}
|
|
69412
69788
|
function pluginTemplatePath(pluginId, srcRel) {
|
|
69413
|
-
return
|
|
69789
|
+
return path41.join(resolveTemplatesDir(), "plugins", pluginId, srcRel);
|
|
69414
69790
|
}
|
|
69415
69791
|
function readBundledPluginFile(pluginId, srcRel) {
|
|
69416
|
-
return
|
|
69792
|
+
return fs36.readFileSync(pluginTemplatePath(pluginId, srcRel), "utf8");
|
|
69417
69793
|
}
|
|
69418
69794
|
function listPluginDestPaths(pluginId, repoPath) {
|
|
69419
69795
|
const manifest = readPluginManifest(pluginId);
|
|
69420
69796
|
const paths = manifest.files.map((file) => file.dest);
|
|
69421
69797
|
if (manifest.optionalFiles) {
|
|
69422
69798
|
for (const file of manifest.optionalFiles) {
|
|
69423
|
-
if (
|
|
69799
|
+
if (fs36.existsSync(path41.join(repoPath, file.dest))) {
|
|
69424
69800
|
paths.push(file.dest);
|
|
69425
69801
|
}
|
|
69426
69802
|
}
|
|
@@ -69479,9 +69855,9 @@ function readTemplatePluginFile(pluginId, dest) {
|
|
|
69479
69855
|
return null;
|
|
69480
69856
|
}
|
|
69481
69857
|
function readInstalledPluginFile(repoPath, pluginId, dest) {
|
|
69482
|
-
const installedPath =
|
|
69483
|
-
if (!
|
|
69484
|
-
const content =
|
|
69858
|
+
const installedPath = path41.join(path41.resolve(repoPath), dest);
|
|
69859
|
+
if (!fs36.existsSync(installedPath)) return null;
|
|
69860
|
+
const content = fs36.readFileSync(installedPath, "utf8");
|
|
69485
69861
|
const manifest = readPluginManifest(pluginId);
|
|
69486
69862
|
const fragmentRel = manifest.merge?.[dest];
|
|
69487
69863
|
if (fragmentRel) {
|
|
@@ -69500,7 +69876,7 @@ function pluginActionHint(pluginId, dest, kind) {
|
|
|
69500
69876
|
}
|
|
69501
69877
|
function comparePluginToTemplate(repoPath, pluginId) {
|
|
69502
69878
|
const manifest = readPluginManifest(pluginId);
|
|
69503
|
-
const resolved =
|
|
69879
|
+
const resolved = path41.resolve(repoPath);
|
|
69504
69880
|
const destPaths = listPluginDestPaths(pluginId, resolved);
|
|
69505
69881
|
const missing = [];
|
|
69506
69882
|
const checksumMismatch = [];
|
|
@@ -69515,8 +69891,8 @@ function comparePluginToTemplate(repoPath, pluginId) {
|
|
|
69515
69891
|
}
|
|
69516
69892
|
if (dest === "package.json") {
|
|
69517
69893
|
const fragmentRel = manifest.merge?.[dest];
|
|
69518
|
-
const installedPath =
|
|
69519
|
-
const installedRaw =
|
|
69894
|
+
const installedPath = path41.join(resolved, dest);
|
|
69895
|
+
const installedRaw = fs36.readFileSync(installedPath, "utf8");
|
|
69520
69896
|
if (fragmentRel && pluginKeysMatch(installedRaw, readBundledPluginFile(pluginId, fragmentRel))) {
|
|
69521
69897
|
unchanged.push(dest);
|
|
69522
69898
|
} else if (computeFileChecksum(installedContent) === computeFileChecksum(templateContent)) {
|
|
@@ -69585,14 +69961,14 @@ function substituteProjectName2(content, projectName) {
|
|
|
69585
69961
|
return content.replace(/__PROJECT_NAME__/g, projectName).replace(/template___PROJECT_NAME__/g, `template_${projectName}`);
|
|
69586
69962
|
}
|
|
69587
69963
|
function projectNameFromRepo(repoPath) {
|
|
69588
|
-
return
|
|
69964
|
+
return path42.basename(repoPath).toLowerCase().replace(/[^a-z0-9]/g, "_");
|
|
69589
69965
|
}
|
|
69590
69966
|
function boilerplateDirForProfile(profile) {
|
|
69591
|
-
return
|
|
69967
|
+
return path42.join(resolveTemplatesDir(), PROFILE_DIRS3[profile]);
|
|
69592
69968
|
}
|
|
69593
69969
|
function readBundledTemplateContent(repoPath, profile, file) {
|
|
69594
|
-
const templatePath =
|
|
69595
|
-
let content =
|
|
69970
|
+
const templatePath = path42.join(boilerplateDirForProfile(profile), templateFileForHarness(file));
|
|
69971
|
+
let content = fs37.readFileSync(templatePath, "utf8");
|
|
69596
69972
|
if (file === "harness.env") {
|
|
69597
69973
|
content = substituteProjectName2(content, projectNameFromRepo(repoPath));
|
|
69598
69974
|
}
|
|
@@ -69623,12 +69999,12 @@ function actionHint(file, kind) {
|
|
|
69623
69999
|
return `Read maintain/diffs/${file}.diff and merge into .har/${file}.`;
|
|
69624
70000
|
}
|
|
69625
70001
|
function createUnifiedDiff(installedContent, templateContent, installedLabel, templateLabel) {
|
|
69626
|
-
const tmpDir =
|
|
70002
|
+
const tmpDir = fs37.mkdtempSync(path42.join(os15.tmpdir(), "har-maintain-diff-"));
|
|
69627
70003
|
try {
|
|
69628
|
-
const installedPath =
|
|
69629
|
-
const templatePath =
|
|
69630
|
-
|
|
69631
|
-
|
|
70004
|
+
const installedPath = path42.join(tmpDir, "installed");
|
|
70005
|
+
const templatePath = path42.join(tmpDir, "template");
|
|
70006
|
+
fs37.writeFileSync(installedPath, installedContent);
|
|
70007
|
+
fs37.writeFileSync(templatePath, templateContent);
|
|
69632
70008
|
const result = run(`diff -u "${installedPath}" "${templatePath}"`);
|
|
69633
70009
|
const body = result.stdout.trim();
|
|
69634
70010
|
if (!body) return "";
|
|
@@ -69637,7 +70013,7 @@ function createUnifiedDiff(installedContent, templateContent, installedLabel, te
|
|
|
69637
70013
|
${body.split("\n").slice(2).join("\n")}
|
|
69638
70014
|
`;
|
|
69639
70015
|
} finally {
|
|
69640
|
-
|
|
70016
|
+
fs37.rmSync(tmpDir, { recursive: true, force: true });
|
|
69641
70017
|
}
|
|
69642
70018
|
}
|
|
69643
70019
|
function buildActions(repoPath, profile, drift) {
|
|
@@ -69652,7 +70028,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
69652
70028
|
});
|
|
69653
70029
|
}
|
|
69654
70030
|
for (const file of drift.checksumMismatch) {
|
|
69655
|
-
const installedPath =
|
|
70031
|
+
const installedPath = path42.join(harnessDir, file);
|
|
69656
70032
|
const installedRel = `maintain/installed/${file}`;
|
|
69657
70033
|
const templateRel = `maintain/templates/${file}`;
|
|
69658
70034
|
const diffRel = `maintain/diffs/${file}.diff`;
|
|
@@ -69660,7 +70036,7 @@ function buildActions(repoPath, profile, drift) {
|
|
|
69660
70036
|
file,
|
|
69661
70037
|
kind: "drift",
|
|
69662
70038
|
template: templateRel,
|
|
69663
|
-
installed:
|
|
70039
|
+
installed: fs37.existsSync(installedPath) ? installedRel : void 0,
|
|
69664
70040
|
diff: diffRel,
|
|
69665
70041
|
hint: actionHint(file, "drift")
|
|
69666
70042
|
});
|
|
@@ -69812,28 +70188,28 @@ function buildReadme(report) {
|
|
|
69812
70188
|
}
|
|
69813
70189
|
function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
69814
70190
|
const harnessDir = getHarnessDir(repoPath);
|
|
69815
|
-
const bundleDir =
|
|
69816
|
-
if (
|
|
69817
|
-
|
|
70191
|
+
const bundleDir = path42.join(harnessDir, MAINTAIN_DIR);
|
|
70192
|
+
if (fs37.existsSync(bundleDir)) {
|
|
70193
|
+
fs37.rmSync(bundleDir, { recursive: true, force: true });
|
|
69818
70194
|
}
|
|
69819
70195
|
const dirs = [
|
|
69820
70196
|
bundleDir,
|
|
69821
|
-
|
|
69822
|
-
|
|
69823
|
-
|
|
69824
|
-
|
|
70197
|
+
path42.join(bundleDir, "templates"),
|
|
70198
|
+
path42.join(bundleDir, "installed"),
|
|
70199
|
+
path42.join(bundleDir, "diffs"),
|
|
70200
|
+
path42.join(bundleDir, "stale")
|
|
69825
70201
|
];
|
|
69826
70202
|
for (const dir of dirs) {
|
|
69827
|
-
|
|
70203
|
+
fs37.mkdirSync(dir, { recursive: true });
|
|
69828
70204
|
}
|
|
69829
70205
|
const affectedFiles = [...drift.missing, ...drift.checksumMismatch];
|
|
69830
70206
|
for (const file of affectedFiles) {
|
|
69831
70207
|
const templateContent = readBundledTemplateContent(repoPath, profile, file);
|
|
69832
|
-
writeFileSafe(
|
|
69833
|
-
const harnessPath =
|
|
69834
|
-
if (
|
|
69835
|
-
const installedContent =
|
|
69836
|
-
writeFileSafe(
|
|
70208
|
+
writeFileSafe(path42.join(bundleDir, "templates", file), templateContent);
|
|
70209
|
+
const harnessPath = path42.join(harnessDir, file);
|
|
70210
|
+
if (fs37.existsSync(harnessPath)) {
|
|
70211
|
+
const installedContent = fs37.readFileSync(harnessPath, "utf8");
|
|
70212
|
+
writeFileSafe(path42.join(bundleDir, "installed", file), installedContent);
|
|
69837
70213
|
const diff = createUnifiedDiff(
|
|
69838
70214
|
installedContent,
|
|
69839
70215
|
templateContent,
|
|
@@ -69841,14 +70217,14 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
69841
70217
|
`templates/${file}`
|
|
69842
70218
|
);
|
|
69843
70219
|
if (diff) {
|
|
69844
|
-
writeFileSafe(
|
|
70220
|
+
writeFileSafe(path42.join(bundleDir, "diffs", `${file}.diff`), diff);
|
|
69845
70221
|
}
|
|
69846
70222
|
}
|
|
69847
70223
|
}
|
|
69848
|
-
writeFileSafe(
|
|
69849
|
-
writeFileSafe(
|
|
70224
|
+
writeFileSafe(path42.join(bundleDir, "README.md"), buildReadme(report));
|
|
70225
|
+
writeFileSafe(path42.join(bundleDir, "drift-report.json"), JSON.stringify(report, null, 2) + "\n");
|
|
69850
70226
|
writeFileSafe(
|
|
69851
|
-
|
|
70227
|
+
path42.join(bundleDir, "validation.json"),
|
|
69852
70228
|
JSON.stringify(report.validation, null, 2) + "\n"
|
|
69853
70229
|
);
|
|
69854
70230
|
if (report.stale.length > 0) {
|
|
@@ -69860,34 +70236,34 @@ function writeBundleArtifacts(repoPath, profile, drift, report) {
|
|
|
69860
70236
|
...report.stale.map((s) => `- **${s.file}** \u2014 ${s.hint}`),
|
|
69861
70237
|
""
|
|
69862
70238
|
];
|
|
69863
|
-
writeFileSafe(
|
|
70239
|
+
writeFileSafe(path42.join(bundleDir, "stale", "MANIFEST.md"), staleLines.join("\n"));
|
|
69864
70240
|
}
|
|
69865
70241
|
return bundleDir;
|
|
69866
70242
|
}
|
|
69867
70243
|
function ensureParentDir2(filePath) {
|
|
69868
|
-
const parent =
|
|
69869
|
-
if (!
|
|
69870
|
-
|
|
70244
|
+
const parent = path42.dirname(filePath);
|
|
70245
|
+
if (!fs37.existsSync(parent)) {
|
|
70246
|
+
fs37.mkdirSync(parent, { recursive: true });
|
|
69871
70247
|
}
|
|
69872
70248
|
}
|
|
69873
70249
|
function writePluginBundleArtifacts(repoPath, bundleDir, report) {
|
|
69874
70250
|
if (report.pluginActions.length === 0) return;
|
|
69875
70251
|
for (const action of report.pluginActions) {
|
|
69876
|
-
const pluginRoot =
|
|
69877
|
-
const templatesDir =
|
|
69878
|
-
const installedDir =
|
|
69879
|
-
const diffsDir =
|
|
70252
|
+
const pluginRoot = path42.join(bundleDir, "plugins", action.pluginId);
|
|
70253
|
+
const templatesDir = path42.join(pluginRoot, "templates");
|
|
70254
|
+
const installedDir = path42.join(pluginRoot, "installed");
|
|
70255
|
+
const diffsDir = path42.join(pluginRoot, "diffs");
|
|
69880
70256
|
for (const dir of [templatesDir, installedDir, diffsDir]) {
|
|
69881
|
-
|
|
70257
|
+
fs37.mkdirSync(dir, { recursive: true });
|
|
69882
70258
|
}
|
|
69883
70259
|
const templateContent = readTemplatePluginFile(action.pluginId, action.file);
|
|
69884
70260
|
if (templateContent === null) continue;
|
|
69885
|
-
const templatePath =
|
|
70261
|
+
const templatePath = path42.join(templatesDir, action.file);
|
|
69886
70262
|
ensureParentDir2(templatePath);
|
|
69887
70263
|
writeFileSafe(templatePath, templateContent);
|
|
69888
70264
|
const installedContent = readInstalledPluginFile(repoPath, action.pluginId, action.file);
|
|
69889
70265
|
if (installedContent !== null) {
|
|
69890
|
-
const installedPath =
|
|
70266
|
+
const installedPath = path42.join(installedDir, action.file);
|
|
69891
70267
|
ensureParentDir2(installedPath);
|
|
69892
70268
|
writeFileSafe(installedPath, installedContent);
|
|
69893
70269
|
const diff = createUnifiedDiff(
|
|
@@ -69897,7 +70273,7 @@ function writePluginBundleArtifacts(repoPath, bundleDir, report) {
|
|
|
69897
70273
|
`templates/${action.file}`
|
|
69898
70274
|
);
|
|
69899
70275
|
if (diff) {
|
|
69900
|
-
const diffPath =
|
|
70276
|
+
const diffPath = path42.join(diffsDir, `${action.file}.diff`);
|
|
69901
70277
|
ensureParentDir2(diffPath);
|
|
69902
70278
|
writeFileSafe(diffPath, diff);
|
|
69903
70279
|
}
|
|
@@ -69933,9 +70309,9 @@ function buildMaintainBundle(repoPath, validation2, drift) {
|
|
|
69933
70309
|
return { bundleDir, report };
|
|
69934
70310
|
}
|
|
69935
70311
|
function removeMaintainBundle(repoPath) {
|
|
69936
|
-
const bundleDir =
|
|
69937
|
-
if (
|
|
69938
|
-
|
|
70312
|
+
const bundleDir = path42.join(getHarnessDir(repoPath), MAINTAIN_DIR);
|
|
70313
|
+
if (fs37.existsSync(bundleDir)) {
|
|
70314
|
+
fs37.rmSync(bundleDir, { recursive: true, force: true });
|
|
69939
70315
|
}
|
|
69940
70316
|
}
|
|
69941
70317
|
function formatMaintainBundlePromptSection(report) {
|
|
@@ -70024,11 +70400,11 @@ init_manifest();
|
|
|
70024
70400
|
init_stages();
|
|
70025
70401
|
function listHarnessScripts(repoPath) {
|
|
70026
70402
|
const harnessDir = getHarnessDir(repoPath);
|
|
70027
|
-
if (!
|
|
70028
|
-
return
|
|
70403
|
+
if (!fs38.existsSync(harnessDir)) return [];
|
|
70404
|
+
return fs38.readdirSync(harnessDir).filter((name) => name.endsWith(".sh")).sort();
|
|
70029
70405
|
}
|
|
70030
70406
|
function describeProject(repoPath) {
|
|
70031
|
-
const resolved =
|
|
70407
|
+
const resolved = path43.resolve(repoPath);
|
|
70032
70408
|
const manifest = readManifest(resolved);
|
|
70033
70409
|
const present = harnessExists(resolved);
|
|
70034
70410
|
return {
|
|
@@ -70048,8 +70424,8 @@ function describeProject(repoPath) {
|
|
|
70048
70424
|
};
|
|
70049
70425
|
}
|
|
70050
70426
|
async function initHarness(options) {
|
|
70051
|
-
const repoPath =
|
|
70052
|
-
if (!
|
|
70427
|
+
const repoPath = path43.resolve(options.repoPath);
|
|
70428
|
+
if (!fs38.existsSync(repoPath)) {
|
|
70053
70429
|
throw new Error(`Path not found: ${repoPath}`);
|
|
70054
70430
|
}
|
|
70055
70431
|
const scaffold = scaffoldHarnessBoilerplate(repoPath, {
|
|
@@ -70069,9 +70445,9 @@ async function initHarness(options) {
|
|
|
70069
70445
|
};
|
|
70070
70446
|
}
|
|
70071
70447
|
async function maintainHarness(options) {
|
|
70072
|
-
const repoPath =
|
|
70448
|
+
const repoPath = path43.resolve(options.repoPath);
|
|
70073
70449
|
const harnessDir = getHarnessDir(repoPath);
|
|
70074
|
-
if (!
|
|
70450
|
+
if (!fs38.existsSync(harnessDir)) {
|
|
70075
70451
|
throw new Error('No .har/ found. Run "har env init" first.');
|
|
70076
70452
|
}
|
|
70077
70453
|
const drift = compareHarnessToTemplate(repoPath);
|
|
@@ -70106,13 +70482,13 @@ function addPlugin(repoPath, pluginId, options = {}) {
|
|
|
70106
70482
|
}
|
|
70107
70483
|
|
|
70108
70484
|
// src/harness/custom-stage.ts
|
|
70109
|
-
var
|
|
70110
|
-
var
|
|
70485
|
+
var fs39 = __toESM(require("fs"));
|
|
70486
|
+
var path44 = __toESM(require("path"));
|
|
70111
70487
|
init_schema2();
|
|
70112
70488
|
init_stages();
|
|
70113
70489
|
var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
70114
70490
|
function addCustomStage(repoPath, options) {
|
|
70115
|
-
const resolved =
|
|
70491
|
+
const resolved = path44.resolve(repoPath);
|
|
70116
70492
|
if (!harnessExists(resolved)) {
|
|
70117
70493
|
throw new Error('No .har/ harness found. Run "har env init" first.');
|
|
70118
70494
|
}
|
|
@@ -70149,20 +70525,20 @@ function addCustomStage(repoPath, options) {
|
|
|
70149
70525
|
if (options.command) {
|
|
70150
70526
|
stage.command = options.command;
|
|
70151
70527
|
} else {
|
|
70152
|
-
const scriptRel =
|
|
70153
|
-
const scriptAbs =
|
|
70154
|
-
if (
|
|
70528
|
+
const scriptRel = path44.join("stages", `${id}.sh`);
|
|
70529
|
+
const scriptAbs = path44.join(resolved, ".har", scriptRel);
|
|
70530
|
+
if (fs39.existsSync(scriptAbs) && !options.force) {
|
|
70155
70531
|
throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
|
|
70156
70532
|
}
|
|
70157
|
-
const skeletonPath =
|
|
70533
|
+
const skeletonPath = path44.join(
|
|
70158
70534
|
resolveTemplatesDir(),
|
|
70159
70535
|
"plugins",
|
|
70160
70536
|
"custom-stage-skeleton.sh"
|
|
70161
70537
|
);
|
|
70162
|
-
const skeleton =
|
|
70163
|
-
|
|
70164
|
-
|
|
70165
|
-
|
|
70538
|
+
const skeleton = fs39.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind).replace(/__STAGE_DESCRIPTION__/g, stage.description);
|
|
70539
|
+
fs39.mkdirSync(path44.dirname(scriptAbs), { recursive: true });
|
|
70540
|
+
fs39.writeFileSync(scriptAbs, skeleton);
|
|
70541
|
+
fs39.chmodSync(scriptAbs, 493);
|
|
70166
70542
|
filesWritten.push(`.har/${scriptRel}`);
|
|
70167
70543
|
stage.script = scriptRel;
|
|
70168
70544
|
stage.artifacts = [
|
|
@@ -70210,18 +70586,18 @@ function addCustomStage(repoPath, options) {
|
|
|
70210
70586
|
}
|
|
70211
70587
|
|
|
70212
70588
|
// src/harness/adaptation-prompt.ts
|
|
70213
|
-
var
|
|
70214
|
-
var
|
|
70589
|
+
var fs40 = __toESM(require("fs"));
|
|
70590
|
+
var path45 = __toESM(require("path"));
|
|
70215
70591
|
var readline5 = __toESM(require("readline"));
|
|
70216
70592
|
|
|
70217
70593
|
// src/utils/clipboard.ts
|
|
70218
|
-
var
|
|
70594
|
+
var import_child_process10 = require("child_process");
|
|
70219
70595
|
function commandExists2(command2) {
|
|
70220
|
-
const probe = process.platform === "win32" ? (0,
|
|
70596
|
+
const probe = process.platform === "win32" ? (0, import_child_process10.spawnSync)("where", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }) : (0, import_child_process10.spawnSync)("which", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] });
|
|
70221
70597
|
return probe.status === 0;
|
|
70222
70598
|
}
|
|
70223
70599
|
function tryPipeCommand(command2, args, text) {
|
|
70224
|
-
const result = (0,
|
|
70600
|
+
const result = (0, import_child_process10.spawnSync)(command2, args, {
|
|
70225
70601
|
input: text,
|
|
70226
70602
|
encoding: "utf8",
|
|
70227
70603
|
stdio: ["pipe", "ignore", "ignore"]
|
|
@@ -70241,7 +70617,7 @@ function copyViaPlatformTools(text) {
|
|
|
70241
70617
|
}
|
|
70242
70618
|
if (process.platform === "win32") {
|
|
70243
70619
|
if (commandExists2("powershell")) {
|
|
70244
|
-
const result = (0,
|
|
70620
|
+
const result = (0, import_child_process10.spawnSync)(
|
|
70245
70621
|
"powershell",
|
|
70246
70622
|
[
|
|
70247
70623
|
"-NoProfile",
|
|
@@ -70301,7 +70677,7 @@ function loadTemplate(name) {
|
|
|
70301
70677
|
if (!filePath) {
|
|
70302
70678
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
70303
70679
|
}
|
|
70304
|
-
return
|
|
70680
|
+
return fs40.readFileSync(filePath, "utf8");
|
|
70305
70681
|
}
|
|
70306
70682
|
function applyProfilePlaceholders(content, profile) {
|
|
70307
70683
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -70323,7 +70699,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
|
70323
70699
|
}
|
|
70324
70700
|
function writeAdaptationPrompt(repoPath, content) {
|
|
70325
70701
|
const harnessDir = getHarnessDir(repoPath);
|
|
70326
|
-
const filePath =
|
|
70702
|
+
const filePath = path45.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
70327
70703
|
writeFileSafe(filePath, content);
|
|
70328
70704
|
return filePath;
|
|
70329
70705
|
}
|
|
@@ -70378,27 +70754,27 @@ async function offerAdaptationPromptClipboard(content, options = {}) {
|
|
|
70378
70754
|
}
|
|
70379
70755
|
|
|
70380
70756
|
// src/harness/cursor-rule.ts
|
|
70381
|
-
var
|
|
70382
|
-
var
|
|
70757
|
+
var fs41 = __toESM(require("fs"));
|
|
70758
|
+
var path46 = __toESM(require("path"));
|
|
70383
70759
|
var readline6 = __toESM(require("readline"));
|
|
70384
70760
|
init_file_ops();
|
|
70385
70761
|
init_logging();
|
|
70386
70762
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
70387
70763
|
function getCursorRulePath(repoPath) {
|
|
70388
|
-
return
|
|
70764
|
+
return path46.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
70389
70765
|
}
|
|
70390
70766
|
function isCursorWorkspace(repoPath) {
|
|
70391
|
-
return
|
|
70767
|
+
return fs41.existsSync(path46.join(repoPath, ".cursor"));
|
|
70392
70768
|
}
|
|
70393
70769
|
function cursorRuleExists(repoPath) {
|
|
70394
|
-
return
|
|
70770
|
+
return fs41.existsSync(getCursorRulePath(repoPath));
|
|
70395
70771
|
}
|
|
70396
70772
|
function scaffoldCursorRule(repoPath) {
|
|
70397
70773
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
70398
70774
|
if (!templatePath) {
|
|
70399
70775
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
70400
70776
|
}
|
|
70401
|
-
const content =
|
|
70777
|
+
const content = fs41.readFileSync(templatePath, "utf8");
|
|
70402
70778
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
70403
70779
|
}
|
|
70404
70780
|
async function handleCursorRule(options) {
|
|
@@ -70487,8 +70863,8 @@ init_validation();
|
|
|
70487
70863
|
init_control_config();
|
|
70488
70864
|
|
|
70489
70865
|
// src/core/control-lifecycle.ts
|
|
70490
|
-
var
|
|
70491
|
-
var
|
|
70866
|
+
var import_child_process11 = require("child_process");
|
|
70867
|
+
var path47 = __toESM(require("path"));
|
|
70492
70868
|
|
|
70493
70869
|
// src/core/control-image.ts
|
|
70494
70870
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
@@ -70514,10 +70890,10 @@ var CONTROL_DATA_VOLUME = "har_control_data";
|
|
|
70514
70890
|
var CONTROL_CONTAINER_PORT = 3847;
|
|
70515
70891
|
var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
|
|
70516
70892
|
function resolveControlDir() {
|
|
70517
|
-
return
|
|
70893
|
+
return path47.resolve(__dirname, "..", "control");
|
|
70518
70894
|
}
|
|
70519
70895
|
function resolveControlBuildContext() {
|
|
70520
|
-
return
|
|
70896
|
+
return path47.resolve(resolveControlDir(), "..");
|
|
70521
70897
|
}
|
|
70522
70898
|
function buildDockerRunArgs(options) {
|
|
70523
70899
|
const name = options.containerName ?? CONTROL_CONTAINER_NAME;
|
|
@@ -70540,7 +70916,7 @@ function buildDockerRunArgs(options) {
|
|
|
70540
70916
|
return args;
|
|
70541
70917
|
}
|
|
70542
70918
|
function runDocker(args) {
|
|
70543
|
-
const result = (0,
|
|
70919
|
+
const result = (0, import_child_process11.spawnSync)("docker", args, {
|
|
70544
70920
|
stdio: "inherit",
|
|
70545
70921
|
env: process.env
|
|
70546
70922
|
});
|
|
@@ -70550,7 +70926,7 @@ function runDocker(args) {
|
|
|
70550
70926
|
return result.status ?? 1;
|
|
70551
70927
|
}
|
|
70552
70928
|
function controlContainerExists(name = CONTROL_CONTAINER_NAME) {
|
|
70553
|
-
const result = (0,
|
|
70929
|
+
const result = (0, import_child_process11.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
|
|
70554
70930
|
encoding: "utf8",
|
|
70555
70931
|
stdio: ["pipe", "pipe", "ignore"]
|
|
70556
70932
|
});
|
|
@@ -70573,7 +70949,7 @@ async function startMissionControl(options) {
|
|
|
70573
70949
|
"-t",
|
|
70574
70950
|
imageRef,
|
|
70575
70951
|
"-f",
|
|
70576
|
-
|
|
70952
|
+
path47.join(resolveControlDir(), "Dockerfile"),
|
|
70577
70953
|
resolveControlBuildContext()
|
|
70578
70954
|
]);
|
|
70579
70955
|
if (buildCode !== 0) {
|
|
@@ -70720,7 +71096,7 @@ async function ensureTelemetryInfrastructure(options) {
|
|
|
70720
71096
|
// src/core/run-service.ts
|
|
70721
71097
|
init_telemetry_config();
|
|
70722
71098
|
init_telemetry_env();
|
|
70723
|
-
var
|
|
71099
|
+
var path49 = __toESM(require("path"));
|
|
70724
71100
|
init_local_executor();
|
|
70725
71101
|
function extractShellOutput(result) {
|
|
70726
71102
|
const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
|
|
@@ -70819,7 +71195,8 @@ var RunService = class {
|
|
|
70819
71195
|
}
|
|
70820
71196
|
async launchEnvironment(options) {
|
|
70821
71197
|
const guard = checkLaunchGuard2(options.repoPath, options.agentId, {
|
|
70822
|
-
resume: options.resume
|
|
71198
|
+
resume: options.resume,
|
|
71199
|
+
worktree: options.worktree
|
|
70823
71200
|
});
|
|
70824
71201
|
if (!guard.allowed) {
|
|
70825
71202
|
const slot = guard.slot;
|
|
@@ -70838,27 +71215,28 @@ var RunService = class {
|
|
|
70838
71215
|
} : void 0
|
|
70839
71216
|
};
|
|
70840
71217
|
}
|
|
70841
|
-
let
|
|
71218
|
+
let launchBanner = (guard.readiness?.warnings ?? []).map((warning) => `WARN: ${warning}
|
|
71219
|
+
`).join("");
|
|
70842
71220
|
if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
|
|
70843
71221
|
const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
|
|
70844
71222
|
if (ensured.message) {
|
|
70845
|
-
|
|
71223
|
+
launchBanner += `${ensured.message}
|
|
70846
71224
|
Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Worktrees. Disable: har telemetry off
|
|
70847
71225
|
`;
|
|
70848
71226
|
}
|
|
70849
71227
|
if (ensured.warning) {
|
|
70850
|
-
|
|
71228
|
+
launchBanner += `${ensured.warning}
|
|
70851
71229
|
`;
|
|
70852
71230
|
}
|
|
70853
71231
|
try {
|
|
70854
71232
|
const { ensureOtelHooks: ensureOtelHooks2 } = await Promise.resolve().then(() => (init_otel_hooks(), otel_hooks_exports));
|
|
70855
71233
|
const hooks = ensureOtelHooks2({ setupAgents: true });
|
|
70856
|
-
if (hooks.message)
|
|
71234
|
+
if (hooks.message) launchBanner += `${hooks.message}
|
|
70857
71235
|
`;
|
|
70858
|
-
if (hooks.warning)
|
|
71236
|
+
if (hooks.warning) launchBanner += `${hooks.warning}
|
|
70859
71237
|
`;
|
|
70860
71238
|
} catch (err) {
|
|
70861
|
-
|
|
71239
|
+
launchBanner += `@osfactory/otel-hook setup skipped: ${err instanceof Error ? err.message : String(err)}
|
|
70862
71240
|
`;
|
|
70863
71241
|
}
|
|
70864
71242
|
}
|
|
@@ -70923,12 +71301,12 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
|
|
|
70923
71301
|
suffix: session.suffix,
|
|
70924
71302
|
createdAt: session.createdAt
|
|
70925
71303
|
});
|
|
70926
|
-
const envFile =
|
|
71304
|
+
const envFile = path49.join(session.workDir, `.env.agent.${options.agentId}`);
|
|
70927
71305
|
try {
|
|
70928
71306
|
appendTelemetryEnvToFile(envFile, {
|
|
70929
71307
|
sessionKey,
|
|
70930
71308
|
agentId: options.agentId,
|
|
70931
|
-
repoPath:
|
|
71309
|
+
repoPath: path49.resolve(options.repoPath),
|
|
70932
71310
|
workDir: session.workDir,
|
|
70933
71311
|
branch: session.branch,
|
|
70934
71312
|
suffix: session.suffix,
|
|
@@ -70951,8 +71329,8 @@ Usage from Cursor / Claude / Codex (@osfactory/otel-hook) will appear under Work
|
|
|
70951
71329
|
});
|
|
70952
71330
|
}
|
|
70953
71331
|
}
|
|
70954
|
-
if (
|
|
70955
|
-
envResult.stderr = `${
|
|
71332
|
+
if (launchBanner) {
|
|
71333
|
+
envResult.stderr = `${launchBanner}${envResult.stderr ?? ""}`;
|
|
70956
71334
|
}
|
|
70957
71335
|
}
|
|
70958
71336
|
return envResult;
|
|
@@ -71164,279 +71542,7 @@ init_slot_status();
|
|
|
71164
71542
|
|
|
71165
71543
|
// src/core/commit-gate-onboarding.ts
|
|
71166
71544
|
var readline7 = __toESM(require("readline"));
|
|
71167
|
-
|
|
71168
|
-
// src/core/hooks.ts
|
|
71169
|
-
var fs42 = __toESM(require("fs"));
|
|
71170
|
-
var os16 = __toESM(require("os"));
|
|
71171
|
-
var path49 = __toESM(require("path"));
|
|
71172
|
-
init_shell();
|
|
71173
|
-
init_schema2();
|
|
71174
|
-
init_stages();
|
|
71175
|
-
init_change_batch();
|
|
71176
|
-
init_validations();
|
|
71177
|
-
init_sync_context();
|
|
71178
|
-
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
71179
|
-
var MARKER_END = "# <<< har commit gate <<<";
|
|
71180
|
-
var PRE_COMMIT_BLOCK = [
|
|
71181
|
-
MARKER_START,
|
|
71182
|
-
'if [ "$HAR_SKIP_GATE" != "1" ]; then',
|
|
71183
|
-
' "$(git rev-parse --git-path hooks)/har-pre-commit" || exit 1',
|
|
71184
|
-
"fi",
|
|
71185
|
-
MARKER_END
|
|
71186
|
-
].join("\n");
|
|
71187
|
-
var POST_COMMIT_BLOCK = [
|
|
71188
|
-
MARKER_START,
|
|
71189
|
-
'"$(git rev-parse --git-path hooks)/har-post-commit" || true',
|
|
71190
|
-
MARKER_END
|
|
71191
|
-
].join("\n");
|
|
71192
|
-
function tryGit3(cwd, args) {
|
|
71193
|
-
const result = run(`git ${args}`, { cwd });
|
|
71194
|
-
return result.code === 0 ? result.stdout.trim() : void 0;
|
|
71195
|
-
}
|
|
71196
|
-
function resolveCheckoutRoot(cwd) {
|
|
71197
|
-
return tryGit3(cwd, "rev-parse --show-toplevel");
|
|
71198
|
-
}
|
|
71199
|
-
function resolveHooksDir(checkoutDir) {
|
|
71200
|
-
const hooksPath = tryGit3(checkoutDir, "rev-parse --git-path hooks");
|
|
71201
|
-
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
71202
|
-
return path49.resolve(checkoutDir, hooksPath);
|
|
71203
|
-
}
|
|
71204
|
-
function getConfiguredHooksPath(checkoutDir) {
|
|
71205
|
-
return tryGit3(checkoutDir, "config core.hooksPath") || void 0;
|
|
71206
|
-
}
|
|
71207
|
-
function defaultHarInvocation() {
|
|
71208
|
-
const entry = process.argv[1];
|
|
71209
|
-
if (entry && entry.endsWith(".js")) {
|
|
71210
|
-
return `"${process.execPath}" "${path49.resolve(entry)}"`;
|
|
71211
|
-
}
|
|
71212
|
-
if (entry) return `"${path49.resolve(entry)}"`;
|
|
71213
|
-
return "har";
|
|
71214
|
-
}
|
|
71215
|
-
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
71216
|
-
return `#!/bin/sh
|
|
71217
|
-
# Managed by \`har hooks\` \u2014 do not edit; reinstall with \`har hooks install\`.
|
|
71218
|
-
if ${harInvocation} --version >/dev/null 2>&1; then
|
|
71219
|
-
exec ${harInvocation} hooks ${subcommand}
|
|
71220
|
-
fi
|
|
71221
|
-
if command -v har >/dev/null 2>&1; then
|
|
71222
|
-
exec har hooks ${subcommand}
|
|
71223
|
-
fi
|
|
71224
|
-
toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
71225
|
-
if [ -n "$toplevel" ] && [ -x "$toplevel/node_modules/.bin/har" ]; then
|
|
71226
|
-
exec "$toplevel/node_modules/.bin/har" hooks ${subcommand}
|
|
71227
|
-
fi
|
|
71228
|
-
echo "${failOpenNotice}" >&2
|
|
71229
|
-
exit 0
|
|
71230
|
-
`;
|
|
71231
|
-
}
|
|
71232
|
-
function upsertMarkedBlock2(filePath, block) {
|
|
71233
|
-
if (!fs42.existsSync(filePath)) {
|
|
71234
|
-
fs42.writeFileSync(filePath, `#!/bin/sh
|
|
71235
|
-
${block}
|
|
71236
|
-
`, { mode: 493 });
|
|
71237
|
-
return "created";
|
|
71238
|
-
}
|
|
71239
|
-
const content = fs42.readFileSync(filePath, "utf8");
|
|
71240
|
-
if (content.includes(MARKER_START)) {
|
|
71241
|
-
const pattern = new RegExp(
|
|
71242
|
-
`${escapeRegExp2(MARKER_START)}[\\s\\S]*?${escapeRegExp2(MARKER_END)}`
|
|
71243
|
-
);
|
|
71244
|
-
fs42.writeFileSync(filePath, content.replace(pattern, block));
|
|
71245
|
-
fs42.chmodSync(filePath, 493);
|
|
71246
|
-
return "updated";
|
|
71247
|
-
}
|
|
71248
|
-
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
71249
|
-
fs42.writeFileSync(filePath, `${content}${suffix}
|
|
71250
|
-
${block}
|
|
71251
|
-
`);
|
|
71252
|
-
fs42.chmodSync(filePath, 493);
|
|
71253
|
-
return "appended";
|
|
71254
|
-
}
|
|
71255
|
-
function removeMarkedBlock(filePath) {
|
|
71256
|
-
if (!fs42.existsSync(filePath)) return false;
|
|
71257
|
-
const content = fs42.readFileSync(filePath, "utf8");
|
|
71258
|
-
if (!content.includes(MARKER_START)) return false;
|
|
71259
|
-
const pattern = new RegExp(
|
|
71260
|
-
`\\n?${escapeRegExp2(MARKER_START)}[\\s\\S]*?${escapeRegExp2(MARKER_END)}\\n?`
|
|
71261
|
-
);
|
|
71262
|
-
const stripped = content.replace(pattern, "\n");
|
|
71263
|
-
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
71264
|
-
fs42.rmSync(filePath);
|
|
71265
|
-
} else {
|
|
71266
|
-
fs42.writeFileSync(filePath, stripped);
|
|
71267
|
-
}
|
|
71268
|
-
return true;
|
|
71269
|
-
}
|
|
71270
|
-
function escapeRegExp2(value) {
|
|
71271
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
71272
|
-
}
|
|
71273
|
-
function installHooks(options) {
|
|
71274
|
-
const checkout = resolveCheckoutRoot(options.repoPath);
|
|
71275
|
-
if (!checkout) throw new Error(`Not a git repository: ${options.repoPath}`);
|
|
71276
|
-
const configuredHooksPath = getConfiguredHooksPath(checkout);
|
|
71277
|
-
if (configuredHooksPath && !options.force) {
|
|
71278
|
-
throw new Error(
|
|
71279
|
-
`core.hooksPath is set to "${configuredHooksPath}" (managed hooks, e.g. husky).
|
|
71280
|
-
Add this to your managed pre-commit hook instead:
|
|
71281
|
-
|
|
71282
|
-
${PRE_COMMIT_BLOCK.split("\n").join("\n ")}
|
|
71283
|
-
|
|
71284
|
-
Or re-run with --force to write into that directory anyway.`
|
|
71285
|
-
);
|
|
71286
|
-
}
|
|
71287
|
-
const hooksDir = resolveHooksDir(checkout);
|
|
71288
|
-
fs42.mkdirSync(hooksDir, { recursive: true });
|
|
71289
|
-
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
71290
|
-
fs42.writeFileSync(
|
|
71291
|
-
path49.join(hooksDir, "har-pre-commit"),
|
|
71292
|
-
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
71293
|
-
{ mode: 493 }
|
|
71294
|
-
);
|
|
71295
|
-
fs42.writeFileSync(
|
|
71296
|
-
path49.join(hooksDir, "har-post-commit"),
|
|
71297
|
-
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
71298
|
-
{ mode: 493 }
|
|
71299
|
-
);
|
|
71300
|
-
const preCommit = upsertMarkedBlock2(path49.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
71301
|
-
const postCommit = upsertMarkedBlock2(path49.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
71302
|
-
ensureValidationsIgnored(checkout);
|
|
71303
|
-
return { hooksDir, preCommit, postCommit };
|
|
71304
|
-
}
|
|
71305
|
-
function uninstallHooks(repoPath) {
|
|
71306
|
-
const checkout = resolveCheckoutRoot(repoPath);
|
|
71307
|
-
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
71308
|
-
const hooksDir = resolveHooksDir(checkout);
|
|
71309
|
-
let removed = false;
|
|
71310
|
-
removed = removeMarkedBlock(path49.join(hooksDir, "pre-commit")) || removed;
|
|
71311
|
-
removed = removeMarkedBlock(path49.join(hooksDir, "post-commit")) || removed;
|
|
71312
|
-
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
71313
|
-
const full = path49.join(hooksDir, file);
|
|
71314
|
-
if (fs42.existsSync(full)) {
|
|
71315
|
-
fs42.rmSync(full);
|
|
71316
|
-
removed = true;
|
|
71317
|
-
}
|
|
71318
|
-
}
|
|
71319
|
-
return { hooksDir, removed };
|
|
71320
|
-
}
|
|
71321
|
-
function getCommitGateConfig(checkoutDir) {
|
|
71322
|
-
try {
|
|
71323
|
-
const registry2 = readStageRegistry(checkoutDir);
|
|
71324
|
-
return CommitGateConfigSchema.parse(registry2.commitGate ?? {});
|
|
71325
|
-
} catch {
|
|
71326
|
-
return CommitGateConfigSchema.parse({});
|
|
71327
|
-
}
|
|
71328
|
-
}
|
|
71329
|
-
function isAgentWorktree(checkoutDir) {
|
|
71330
|
-
const branch = getCurrentBranch(checkoutDir);
|
|
71331
|
-
if (branch && (/^har-agent-\d+$/.test(branch) || /-har-agent-\d+-[a-z0-9]+$/.test(branch))) {
|
|
71332
|
-
return true;
|
|
71333
|
-
}
|
|
71334
|
-
const worktreesRoot = path49.join(os16.homedir(), "worktrees");
|
|
71335
|
-
const resolved = path49.resolve(checkoutDir);
|
|
71336
|
-
const name = path49.basename(resolved);
|
|
71337
|
-
return resolved.startsWith(`${worktreesRoot}${path49.sep}`) && (/-agent-\d+$/.test(name) || /-har-agent-\d+-[a-z0-9]+$/.test(name));
|
|
71338
|
-
}
|
|
71339
|
-
function resolveEffectiveMode(checkoutDir, gate) {
|
|
71340
|
-
if (!gate.enabled) return "off";
|
|
71341
|
-
if (gate.scope === "all") return gate.mode;
|
|
71342
|
-
return isAgentWorktree(checkoutDir) ? gate.mode : "warn";
|
|
71343
|
-
}
|
|
71344
|
-
function getHooksStatus(repoPath) {
|
|
71345
|
-
const checkout = resolveCheckoutRoot(repoPath);
|
|
71346
|
-
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
71347
|
-
const hooksDir = resolveHooksDir(checkout);
|
|
71348
|
-
const gate = getCommitGateConfig(checkout);
|
|
71349
|
-
const hasBlock = (name) => {
|
|
71350
|
-
const file = path49.join(hooksDir, name);
|
|
71351
|
-
return fs42.existsSync(file) && fs42.readFileSync(file, "utf8").includes(MARKER_START);
|
|
71352
|
-
};
|
|
71353
|
-
return {
|
|
71354
|
-
checkout,
|
|
71355
|
-
hooksDir,
|
|
71356
|
-
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
71357
|
-
preCommitInstalled: hasBlock("pre-commit") && fs42.existsSync(path49.join(hooksDir, "har-pre-commit")),
|
|
71358
|
-
postCommitInstalled: hasBlock("post-commit") && fs42.existsSync(path49.join(hooksDir, "har-post-commit")),
|
|
71359
|
-
gate,
|
|
71360
|
-
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
71361
|
-
};
|
|
71362
|
-
}
|
|
71363
|
-
function checkCommitGate(cwd) {
|
|
71364
|
-
if (process.env.HAR_SKIP_GATE === "1") {
|
|
71365
|
-
return { exitCode: 0, messages: ["har: commit gate skipped (HAR_SKIP_GATE=1)"] };
|
|
71366
|
-
}
|
|
71367
|
-
const checkout = resolveCheckoutRoot(cwd);
|
|
71368
|
-
if (!checkout) return { exitCode: 0, messages: [] };
|
|
71369
|
-
if (!fs42.existsSync(path49.join(checkout, ".har", "stages.json"))) {
|
|
71370
|
-
return { exitCode: 0, messages: [] };
|
|
71371
|
-
}
|
|
71372
|
-
const gate = getCommitGateConfig(checkout);
|
|
71373
|
-
const mode = resolveEffectiveMode(checkout, gate);
|
|
71374
|
-
if (mode === "off") return { exitCode: 0, messages: [] };
|
|
71375
|
-
if (!getHeadSha(checkout)) {
|
|
71376
|
-
return { exitCode: 0, messages: ["har: commit gate skipped (initial commit)"] };
|
|
71377
|
-
}
|
|
71378
|
-
if (isMergeOrRebaseInProgress(checkout)) {
|
|
71379
|
-
return { exitCode: 0, messages: ["har: commit gate skipped (merge/rebase in progress)"] };
|
|
71380
|
-
}
|
|
71381
|
-
let stagedTree;
|
|
71382
|
-
try {
|
|
71383
|
-
stagedTree = computeStagedTree(checkout);
|
|
71384
|
-
} catch {
|
|
71385
|
-
return { exitCode: 0, messages: ["har: commit gate skipped (index has unmerged entries)"] };
|
|
71386
|
-
}
|
|
71387
|
-
if (stagedTree === getHeadTree(checkout)) {
|
|
71388
|
-
return { exitCode: 0, messages: [] };
|
|
71389
|
-
}
|
|
71390
|
-
const record2 = findValidation(checkout, stagedTree);
|
|
71391
|
-
if (record2 && record2.status === "pass" && record2.full) {
|
|
71392
|
-
return {
|
|
71393
|
-
exitCode: 0,
|
|
71394
|
-
messages: [
|
|
71395
|
-
`har: batch ${stagedTree.slice(0, 8)} verified${record2.runId ? ` by run ${record2.runId.slice(0, 8)}` : ""} \u2713`
|
|
71396
|
-
]
|
|
71397
|
-
};
|
|
71398
|
-
}
|
|
71399
|
-
const messages = [];
|
|
71400
|
-
const verb = mode === "block" ? "commit blocked" : "warning";
|
|
71401
|
-
messages.push(
|
|
71402
|
-
`har: ${verb} \u2014 no passing verification for this exact change batch (tree ${stagedTree.slice(0, 8)}).`
|
|
71403
|
-
);
|
|
71404
|
-
if (record2 && record2.status === "fail") {
|
|
71405
|
-
messages.push(
|
|
71406
|
-
` This exact batch was verified and FAILED${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}. Fix and re-run.`
|
|
71407
|
-
);
|
|
71408
|
-
} else if (record2 && !record2.full) {
|
|
71409
|
-
messages.push(
|
|
71410
|
-
` This batch only passed a partial verify${record2.runId ? ` (run ${record2.runId.slice(0, 8)})` : ""}; the gate requires --full.`
|
|
71411
|
-
);
|
|
71412
|
-
}
|
|
71413
|
-
messages.push(" Run the full pipeline: har env verify <agentId> --full");
|
|
71414
|
-
messages.push(
|
|
71415
|
-
" Note: verify hashes the WORKING TREE \u2014 stage exactly what you verified (git add -A)."
|
|
71416
|
-
);
|
|
71417
|
-
messages.push(" Bypass (humans only): HAR_SKIP_GATE=1 git commit \u2026 or git commit --no-verify");
|
|
71418
|
-
messages.push(" Configure via commitGate { mode: block|warn, scope: worktrees|all } in .har/stages.json");
|
|
71419
|
-
return { exitCode: mode === "block" ? 1 : 0, messages };
|
|
71420
|
-
}
|
|
71421
|
-
async function recordCommitAssociation(cwd) {
|
|
71422
|
-
try {
|
|
71423
|
-
const checkout = resolveCheckoutRoot(cwd);
|
|
71424
|
-
if (!checkout) return { attached: false };
|
|
71425
|
-
const commitSha = getHeadSha(checkout);
|
|
71426
|
-
const tree = tryGit3(checkout, "rev-parse HEAD^{tree}");
|
|
71427
|
-
if (!commitSha || !tree) return { attached: false };
|
|
71428
|
-
const updated = attachCommit(checkout, tree, commitSha);
|
|
71429
|
-
if (updated) {
|
|
71430
|
-
markDirty(updated.harnessRoot);
|
|
71431
|
-
return { attached: true, commitSha };
|
|
71432
|
-
}
|
|
71433
|
-
return { attached: false, commitSha };
|
|
71434
|
-
} catch {
|
|
71435
|
-
return { attached: false };
|
|
71436
|
-
}
|
|
71437
|
-
}
|
|
71438
|
-
|
|
71439
|
-
// src/core/commit-gate-onboarding.ts
|
|
71545
|
+
init_hooks();
|
|
71440
71546
|
init_stages();
|
|
71441
71547
|
init_logging();
|
|
71442
71548
|
function askYesNo4(question) {
|
|
@@ -71564,7 +71670,7 @@ Occupied slots always block a new launch:
|
|
|
71564
71670
|
For a new unrelated task: checkout main (or your intended base) on the main
|
|
71565
71671
|
repo checkout, free the slot (complete/teardown), then launch.`;
|
|
71566
71672
|
var HAR_ENV_EPILOG = `Environment lifecycle:
|
|
71567
|
-
preflight <id> dry-run: ports, PM2, Docker, occupied-slot gates
|
|
71673
|
+
preflight <id> dry-run: ports, PM2, Docker, occupied-slot gates, untracked paths
|
|
71568
71674
|
launch <id> fresh session worktree from the main checkout's current HEAD
|
|
71569
71675
|
recover <id> alias for launch --resume (failed/starting only)
|
|
71570
71676
|
verify <id> run checks (--full before declaring done)
|
|
@@ -71760,7 +71866,7 @@ var envCommand = {
|
|
|
71760
71866
|
handleRecover
|
|
71761
71867
|
).command(
|
|
71762
71868
|
"preflight <id>",
|
|
71763
|
-
"Check whether a slot can launch now (ports, PM2, Docker, occupied slot)",
|
|
71869
|
+
"Check whether a slot can launch now (ports, PM2, Docker, occupied slot, untracked worktree paths)",
|
|
71764
71870
|
(y) => y.positional("id", { type: "number", describe: "Agent slot id (see .har/stages.json agentSlots)" }).option("repo", { type: "string", default: "." }).option("json", { type: "boolean", default: false, describe: "Structured JSON output" }),
|
|
71765
71871
|
handlePreflight
|
|
71766
71872
|
).command(
|
|
@@ -72119,11 +72225,14 @@ async function handleLaunch(argv) {
|
|
|
72119
72225
|
const repo = path51.resolve(argv.repo);
|
|
72120
72226
|
const agentId = validateAgentId(argv.id, repo);
|
|
72121
72227
|
if (!argv.resume) {
|
|
72122
|
-
const guard = checkLaunchGuard2(repo, agentId, {});
|
|
72228
|
+
const guard = checkLaunchGuard2(repo, agentId, { worktree: argv.worktree });
|
|
72123
72229
|
if (!guard.allowed && guard.blocked) {
|
|
72124
72230
|
error(guard.reason ?? `Slot ${agentId} is occupied.`);
|
|
72125
72231
|
return finishCommand(2);
|
|
72126
72232
|
}
|
|
72233
|
+
for (const warning of guard.readiness?.warnings ?? []) {
|
|
72234
|
+
warn(warning);
|
|
72235
|
+
}
|
|
72127
72236
|
}
|
|
72128
72237
|
const result = await launchEnvironment({
|
|
72129
72238
|
repoPath: repo,
|
|
@@ -72390,13 +72499,13 @@ init_portal_credentials();
|
|
|
72390
72499
|
// src/core/portal-login.ts
|
|
72391
72500
|
var http = __toESM(require("http"));
|
|
72392
72501
|
var import_crypto = require("crypto");
|
|
72393
|
-
var
|
|
72502
|
+
var import_child_process13 = require("child_process");
|
|
72394
72503
|
var CALLBACK_PATH = "/callback";
|
|
72395
72504
|
var LOGIN_TIMEOUT_MS = 3 * 6e4;
|
|
72396
72505
|
function openBrowser(url) {
|
|
72397
72506
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
72398
72507
|
try {
|
|
72399
|
-
const child = (0,
|
|
72508
|
+
const child = (0, import_child_process13.spawn)(cmd, args, { stdio: "ignore", detached: true });
|
|
72400
72509
|
child.on("error", () => {
|
|
72401
72510
|
});
|
|
72402
72511
|
child.unref();
|
|
@@ -73256,6 +73365,7 @@ async function handleLogin(argv) {
|
|
|
73256
73365
|
|
|
73257
73366
|
// src/cli/commands/hooks.ts
|
|
73258
73367
|
var path57 = __toESM(require("path"));
|
|
73368
|
+
init_hooks();
|
|
73259
73369
|
|
|
73260
73370
|
// src/core/claude-hooks.ts
|
|
73261
73371
|
var fs47 = __toESM(require("fs"));
|
|
@@ -80825,7 +80935,7 @@ var HAR_MCP_TOOLS = [
|
|
|
80825
80935
|
},
|
|
80826
80936
|
{
|
|
80827
80937
|
name: "har_preflight_environment",
|
|
80828
|
-
description: "Readiness gate before launch: checks ports, foreign PM2, Docker conflicts, and
|
|
80938
|
+
description: "Readiness gate before launch: checks ports, foreign PM2, Docker conflicts, occupied slot, and untracked paths that will be missing from a session worktree. Returns canLaunch with actionable blockers and warnings. Call before har_launch_environment.",
|
|
80829
80939
|
inputSchema: objectJsonSchema(
|
|
80830
80940
|
{
|
|
80831
80941
|
repo: repoJsonProperty,
|