hunter-harness 0.2.28 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -26
- package/dist/bin.js +556 -181
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1216,8 +1216,8 @@ async function assertNoSymlinks(root, relativePath) {
|
|
|
1216
1216
|
for (const segment of normalized.split("/")) {
|
|
1217
1217
|
current = join(current, segment);
|
|
1218
1218
|
try {
|
|
1219
|
-
const
|
|
1220
|
-
if (
|
|
1219
|
+
const stat8 = await lstat(current);
|
|
1220
|
+
if (stat8.isSymbolicLink()) {
|
|
1221
1221
|
throw new UnsafePathError("symbolic links are not managed");
|
|
1222
1222
|
}
|
|
1223
1223
|
} catch (error) {
|
|
@@ -1233,7 +1233,7 @@ async function assertNoSymlinks(root, relativePath) {
|
|
|
1233
1233
|
async function withRetry(action, options) {
|
|
1234
1234
|
const attempts = options.attempts ?? 3;
|
|
1235
1235
|
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
1236
|
-
await new Promise((
|
|
1236
|
+
await new Promise((resolve11) => setTimeout(resolve11, milliseconds));
|
|
1237
1237
|
});
|
|
1238
1238
|
let lastError;
|
|
1239
1239
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
@@ -2263,6 +2263,12 @@ var MANAGED_NAMES = /* @__PURE__ */ new Set([
|
|
|
2263
2263
|
"harness-profile-java.md",
|
|
2264
2264
|
"harness-profile-java.mdc"
|
|
2265
2265
|
]);
|
|
2266
|
+
var AGENT_RULE_ROOTS = [
|
|
2267
|
+
".claude/rules",
|
|
2268
|
+
".cursor/rules",
|
|
2269
|
+
".codebuddy/.rules",
|
|
2270
|
+
".codebuddy/rules"
|
|
2271
|
+
];
|
|
2266
2272
|
function sha256(content) {
|
|
2267
2273
|
return createHash3("sha256").update(content).digest("hex");
|
|
2268
2274
|
}
|
|
@@ -2293,6 +2299,72 @@ async function markdownFiles(root) {
|
|
|
2293
2299
|
throw error;
|
|
2294
2300
|
}
|
|
2295
2301
|
}
|
|
2302
|
+
function portable(path) {
|
|
2303
|
+
return path.replaceAll("\\", "/");
|
|
2304
|
+
}
|
|
2305
|
+
function normalizeRuleContent(content) {
|
|
2306
|
+
return content.replace(/\r\n/g, "\n").trimEnd() + "\n";
|
|
2307
|
+
}
|
|
2308
|
+
function canonicalImportContent(content) {
|
|
2309
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
2310
|
+
if (!normalized.startsWith("---\n"))
|
|
2311
|
+
return normalizeRuleContent(normalized);
|
|
2312
|
+
const closing = normalized.indexOf("\n---\n", 4);
|
|
2313
|
+
if (closing < 0)
|
|
2314
|
+
return null;
|
|
2315
|
+
const frontmatter = normalized.slice(4, closing);
|
|
2316
|
+
if (/^\s*(?:globs?|paths?)\s*:/im.test(frontmatter) || /^\s*alwaysApply\s*:\s*false\s*$/im.test(frontmatter)) {
|
|
2317
|
+
return null;
|
|
2318
|
+
}
|
|
2319
|
+
return normalizeRuleContent(normalized.slice(closing + 5));
|
|
2320
|
+
}
|
|
2321
|
+
async function collectImportCandidates(root, previous, result) {
|
|
2322
|
+
const candidates = [];
|
|
2323
|
+
for (const ruleRoot of AGENT_RULE_ROOTS) {
|
|
2324
|
+
for (const name of await markdownFiles(join4(root, ...ruleRoot.split("/")))) {
|
|
2325
|
+
const source = portable(`${ruleRoot}/${name}`);
|
|
2326
|
+
const content = await readFile3(join4(root, ...source.split("/")), "utf8");
|
|
2327
|
+
if (previous.targets[source] === sha256(content))
|
|
2328
|
+
continue;
|
|
2329
|
+
const canonical = canonicalImportContent(content);
|
|
2330
|
+
if (canonical === null) {
|
|
2331
|
+
result.agent_specific.push(source);
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
candidates.push({
|
|
2335
|
+
source,
|
|
2336
|
+
destination: `${RULES_ROOT}/${basename(name, extname(name))}.md`,
|
|
2337
|
+
content: canonical
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return candidates;
|
|
2342
|
+
}
|
|
2343
|
+
async function importAgentRules(root, canonicalRoot, previous, result) {
|
|
2344
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2345
|
+
for (const candidate of await collectImportCandidates(root, previous, result)) {
|
|
2346
|
+
const values = grouped.get(candidate.destination) ?? [];
|
|
2347
|
+
values.push(candidate);
|
|
2348
|
+
grouped.set(candidate.destination, values);
|
|
2349
|
+
}
|
|
2350
|
+
for (const [destination, candidates] of [...grouped].sort(([a], [b]) => a.localeCompare(b))) {
|
|
2351
|
+
const destinationPath = join4(canonicalRoot, basename(destination));
|
|
2352
|
+
const current = await optionalText(destinationPath);
|
|
2353
|
+
const distinct = new Set(candidates.map((candidate) => candidate.content));
|
|
2354
|
+
const representative = candidates.at(0);
|
|
2355
|
+
if (representative === void 0)
|
|
2356
|
+
continue;
|
|
2357
|
+
if (current === null && distinct.size === 1) {
|
|
2358
|
+
await atomicWrite(destinationPath, representative.content);
|
|
2359
|
+
result.migrated.push(destination);
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
if (current !== null && distinct.size === 1 && normalizeRuleContent(current) === representative.content) {
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
result.conflicts.push(...candidates.map((candidate) => candidate.source));
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2296
2368
|
function targetsFor(name, agents, surface) {
|
|
2297
2369
|
const stem = basename(name, extname(name));
|
|
2298
2370
|
const targets = [];
|
|
@@ -2319,23 +2391,15 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
|
|
|
2319
2391
|
const canonicalRoot = join4(root, RULES_ROOT);
|
|
2320
2392
|
const result = {
|
|
2321
2393
|
migrated: [],
|
|
2394
|
+
agent_specific: [],
|
|
2322
2395
|
written: [],
|
|
2323
2396
|
removed: [],
|
|
2324
2397
|
unchanged: [],
|
|
2325
2398
|
conflicts: []
|
|
2326
2399
|
};
|
|
2327
2400
|
await mkdir4(canonicalRoot, { recursive: true });
|
|
2328
|
-
if ((await markdownFiles(canonicalRoot)).length === 0) {
|
|
2329
|
-
for (const name of await markdownFiles(join4(root, ".claude", "rules"))) {
|
|
2330
|
-
const destination = join4(canonicalRoot, `${basename(name, extname(name))}.md`);
|
|
2331
|
-
if (await optionalText(destination) !== null)
|
|
2332
|
-
continue;
|
|
2333
|
-
const content = await readFile3(join4(root, ".claude", "rules", name), "utf8");
|
|
2334
|
-
await atomicWrite(destination, content);
|
|
2335
|
-
result.migrated.push(`${RULES_ROOT}/${basename(destination)}`);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
2401
|
const previous = await readReceipt(root);
|
|
2402
|
+
await importAgentRules(root, canonicalRoot, previous, result);
|
|
2339
2403
|
const next = { schema_version: 1, source_hashes: {}, targets: {} };
|
|
2340
2404
|
const desired = /* @__PURE__ */ new Map();
|
|
2341
2405
|
for (const name of await markdownFiles(canonicalRoot)) {
|
|
@@ -2349,9 +2413,10 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
|
|
|
2349
2413
|
const path = join4(root, target);
|
|
2350
2414
|
const current = await optionalText(path);
|
|
2351
2415
|
const incomingHash = sha256(content);
|
|
2352
|
-
|
|
2416
|
+
const canonicalCurrent = current === null ? null : canonicalImportContent(current);
|
|
2417
|
+
if (current === content || canonicalCurrent === content) {
|
|
2353
2418
|
result.unchanged.push(target);
|
|
2354
|
-
next.targets[target] = incomingHash;
|
|
2419
|
+
next.targets[target] = current === null ? incomingHash : sha256(current);
|
|
2355
2420
|
} else if (current === null || previous.targets[target] === sha256(current)) {
|
|
2356
2421
|
await atomicWrite(path, content);
|
|
2357
2422
|
result.written.push(target);
|
|
@@ -2411,6 +2476,7 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
|
|
|
2411
2476
|
}
|
|
2412
2477
|
await atomicWrite(join4(root, RECEIPT_PATH), JSON.stringify(next, null, 2) + "\n");
|
|
2413
2478
|
result.migrated.sort();
|
|
2479
|
+
result.agent_specific.sort();
|
|
2414
2480
|
result.written.sort();
|
|
2415
2481
|
result.removed.sort();
|
|
2416
2482
|
result.unchanged.sort();
|
|
@@ -2918,16 +2984,237 @@ async function enrichContextIndexWithVerification(root, agents, profile, resourc
|
|
|
2918
2984
|
await writeFile4(contextPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
2919
2985
|
}
|
|
2920
2986
|
|
|
2921
|
-
// ../core/dist/project/
|
|
2987
|
+
// ../core/dist/project/rule-candidates.js
|
|
2922
2988
|
import { createHash as createHash6 } from "node:crypto";
|
|
2923
|
-
import { readFile as readFile6, readdir as readdir4,
|
|
2924
|
-
import { dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
|
|
2989
|
+
import { mkdir as mkdir5, readFile as readFile6, readdir as readdir4, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
2990
|
+
import { basename as basename3, dirname as dirname4, join as join7, relative, resolve as resolve6 } from "node:path";
|
|
2991
|
+
var ARCHIVE_ROOT = ".harness/archive";
|
|
2992
|
+
var CANDIDATE_PATH = ".harness/knowledge/rule-candidates.json";
|
|
2993
|
+
var MAX_EVIDENCE_BYTES = 2 * 1024 * 1024;
|
|
2994
|
+
var EVIDENCE_NAMES = [
|
|
2995
|
+
/^review-findings.*\.json$/i,
|
|
2996
|
+
/^test-(?:report|results?|failures?).*\.json$/i,
|
|
2997
|
+
/^summary-data\.json$/i
|
|
2998
|
+
];
|
|
2999
|
+
function sha2562(content) {
|
|
3000
|
+
return createHash6("sha256").update(content).digest("hex");
|
|
3001
|
+
}
|
|
3002
|
+
function portable2(path) {
|
|
3003
|
+
return path.replaceAll("\\", "/");
|
|
3004
|
+
}
|
|
3005
|
+
function normalizeText(value) {
|
|
3006
|
+
return value.replace(/\s+/g, " ").trim();
|
|
3007
|
+
}
|
|
3008
|
+
function safeText(value, limit = 500) {
|
|
3009
|
+
if (typeof value !== "string")
|
|
3010
|
+
return null;
|
|
3011
|
+
const normalized = normalizeText(value).slice(0, limit);
|
|
3012
|
+
if (normalized.length < 8)
|
|
3013
|
+
return null;
|
|
3014
|
+
if (/(?:ignore|disregard)\s+(?:all\s+)?previous|system\s+prompt|developer\s+message/i.test(normalized)) {
|
|
3015
|
+
return null;
|
|
3016
|
+
}
|
|
3017
|
+
if (/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|gh[pousr]_[A-Za-z0-9]{20,}|(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\//i.test(normalized)) {
|
|
3018
|
+
return null;
|
|
3019
|
+
}
|
|
3020
|
+
return normalized;
|
|
3021
|
+
}
|
|
3022
|
+
function stringField(record, names) {
|
|
3023
|
+
for (const name of names) {
|
|
3024
|
+
const value = safeText(record[name]);
|
|
3025
|
+
if (value !== null)
|
|
3026
|
+
return value;
|
|
3027
|
+
}
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
function severityOf(record) {
|
|
3031
|
+
const value = record.severity ?? record.level ?? record.priority ?? "unknown";
|
|
3032
|
+
return typeof value === "string" ? value.toLowerCase() : "unknown";
|
|
3033
|
+
}
|
|
3034
|
+
function highSeverity(severity) {
|
|
3035
|
+
return /^(?:red|critical|high|error|blocker|p0|p1)$/.test(severity);
|
|
3036
|
+
}
|
|
3037
|
+
function evidenceKind(path) {
|
|
3038
|
+
const name = basename3(path).toLowerCase();
|
|
3039
|
+
if (name.startsWith("review-"))
|
|
3040
|
+
return "review";
|
|
3041
|
+
if (name.startsWith("test-"))
|
|
3042
|
+
return "test";
|
|
3043
|
+
return "validation";
|
|
3044
|
+
}
|
|
3045
|
+
function observationFrom(record, path, archive) {
|
|
3046
|
+
const severity = severityOf(record);
|
|
3047
|
+
const suggestion = stringField(record, [
|
|
3048
|
+
"proposed_rule",
|
|
3049
|
+
"proposedRule",
|
|
3050
|
+
"suggestion",
|
|
3051
|
+
"recommendation",
|
|
3052
|
+
"remediation"
|
|
3053
|
+
]);
|
|
3054
|
+
const issue = stringField(record, ["issue", "message", "error", "failure"]);
|
|
3055
|
+
const title = stringField(record, ["title", "name", "id", "code"]) ?? issue;
|
|
3056
|
+
let proposedRule = suggestion;
|
|
3057
|
+
if (proposedRule === null && issue !== null && highSeverity(severity)) {
|
|
3058
|
+
proposedRule = `\u5FC5\u987B\u589E\u52A0\u53EF\u91CD\u590D\u9A8C\u8BC1\uFF0C\u9632\u6B62\u4EE5\u4E0B\u95EE\u9898\u518D\u6B21\u51FA\u73B0\uFF1A${issue}`;
|
|
3059
|
+
}
|
|
3060
|
+
if (proposedRule === null || title === null)
|
|
3061
|
+
return null;
|
|
3062
|
+
const recordId = record.id ?? record.code ?? record.name ?? null;
|
|
3063
|
+
return {
|
|
3064
|
+
title,
|
|
3065
|
+
proposedRule,
|
|
3066
|
+
severity,
|
|
3067
|
+
evidence: {
|
|
3068
|
+
archive,
|
|
3069
|
+
path,
|
|
3070
|
+
kind: evidenceKind(path),
|
|
3071
|
+
record_id: typeof recordId === "string" ? recordId.slice(0, 120) : null
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
function collectObservations(value, path, archive, output, rejected) {
|
|
3076
|
+
if (Array.isArray(value)) {
|
|
3077
|
+
for (const item2 of value)
|
|
3078
|
+
collectObservations(item2, path, archive, output, rejected);
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
if (value === null || typeof value !== "object")
|
|
3082
|
+
return;
|
|
3083
|
+
const record = value;
|
|
3084
|
+
const candidate = observationFrom(record, path, archive);
|
|
3085
|
+
if (candidate !== null)
|
|
3086
|
+
output.push(candidate);
|
|
3087
|
+
else if (Object.keys(record).some((key) => ["suggestion", "recommendation", "proposed_rule", "proposedRule"].includes(key)))
|
|
3088
|
+
rejected.count += 1;
|
|
3089
|
+
for (const nested of Object.values(record)) {
|
|
3090
|
+
if (nested !== null && typeof nested === "object") {
|
|
3091
|
+
collectObservations(nested, path, archive, output, rejected);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
async function evidenceFiles(root) {
|
|
3096
|
+
const archiveRoot = join7(root, ...ARCHIVE_ROOT.split("/"));
|
|
3097
|
+
const output = [];
|
|
3098
|
+
async function walk(directory) {
|
|
3099
|
+
let entries;
|
|
3100
|
+
try {
|
|
3101
|
+
entries = await readdir4(directory, { withFileTypes: true });
|
|
3102
|
+
} catch (error) {
|
|
3103
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
3104
|
+
return;
|
|
3105
|
+
throw error;
|
|
3106
|
+
}
|
|
3107
|
+
for (const entry of entries) {
|
|
3108
|
+
const path = join7(directory, entry.name);
|
|
3109
|
+
if (entry.isDirectory())
|
|
3110
|
+
await walk(path);
|
|
3111
|
+
else if (entry.isFile() && EVIDENCE_NAMES.some((pattern) => pattern.test(entry.name))) {
|
|
3112
|
+
if ((await stat3(path)).size <= MAX_EVIDENCE_BYTES)
|
|
3113
|
+
output.push(path);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
await walk(archiveRoot);
|
|
3118
|
+
return output.sort();
|
|
3119
|
+
}
|
|
3120
|
+
function candidateKey(rule) {
|
|
3121
|
+
return normalizeText(rule).toLocaleLowerCase();
|
|
3122
|
+
}
|
|
3123
|
+
function buildCandidates(observations) {
|
|
3124
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3125
|
+
for (const observation of observations) {
|
|
3126
|
+
const key = candidateKey(observation.proposedRule);
|
|
3127
|
+
const values = grouped.get(key) ?? [];
|
|
3128
|
+
values.push(observation);
|
|
3129
|
+
grouped.set(key, values);
|
|
3130
|
+
}
|
|
3131
|
+
const candidates = [];
|
|
3132
|
+
for (const [key, values] of grouped) {
|
|
3133
|
+
const archives = new Set(values.map((value) => value.evidence.archive));
|
|
3134
|
+
const highest = values.find((value) => highSeverity(value.severity));
|
|
3135
|
+
if (archives.size < 2 && highest === void 0)
|
|
3136
|
+
continue;
|
|
3137
|
+
const representative = highest ?? values.at(0);
|
|
3138
|
+
if (representative === void 0)
|
|
3139
|
+
continue;
|
|
3140
|
+
const evidence = [...new Map(values.map((value) => [
|
|
3141
|
+
`${value.evidence.path}\0${value.evidence.record_id ?? ""}`,
|
|
3142
|
+
value.evidence
|
|
3143
|
+
])).values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
3144
|
+
candidates.push({
|
|
3145
|
+
id: `rule_${sha2562(key).slice(0, 16)}`,
|
|
3146
|
+
status: "candidate",
|
|
3147
|
+
title: representative.title,
|
|
3148
|
+
proposed_rule: representative.proposedRule,
|
|
3149
|
+
confidence: archives.size >= 2 && highest !== void 0 ? "high" : "medium",
|
|
3150
|
+
severity: representative.severity,
|
|
3151
|
+
occurrences: evidence.length,
|
|
3152
|
+
evidence
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
return candidates.sort((a, b) => a.id.localeCompare(b.id));
|
|
3156
|
+
}
|
|
3157
|
+
async function atomicWrite2(path, content) {
|
|
3158
|
+
await mkdir5(dirname4(path), { recursive: true });
|
|
3159
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
3160
|
+
await writeFile5(temporary, content, "utf8");
|
|
3161
|
+
await rename4(temporary, path);
|
|
3162
|
+
}
|
|
3163
|
+
async function synchronizeRuleCandidates(projectRoot, options = {}) {
|
|
3164
|
+
const root = resolve6(projectRoot);
|
|
3165
|
+
const files = await evidenceFiles(root);
|
|
3166
|
+
const sourceHashes = {};
|
|
3167
|
+
const observations = [];
|
|
3168
|
+
const rejected = { count: 0 };
|
|
3169
|
+
for (const path of files) {
|
|
3170
|
+
const relativePath = portable2(relative(root, path));
|
|
3171
|
+
const content2 = await readFile6(path, "utf8");
|
|
3172
|
+
sourceHashes[relativePath] = sha2562(content2);
|
|
3173
|
+
let parsed;
|
|
3174
|
+
try {
|
|
3175
|
+
parsed = JSON.parse(content2);
|
|
3176
|
+
} catch {
|
|
3177
|
+
continue;
|
|
3178
|
+
}
|
|
3179
|
+
const archive = relativePath.split("/")[2] ?? "unknown";
|
|
3180
|
+
collectObservations(parsed, relativePath, archive, observations, rejected);
|
|
3181
|
+
}
|
|
3182
|
+
const manifest = {
|
|
3183
|
+
schema_version: 1,
|
|
3184
|
+
source_hashes: sourceHashes,
|
|
3185
|
+
candidates: buildCandidates(observations)
|
|
3186
|
+
};
|
|
3187
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
3188
|
+
const destination = join7(root, ...CANDIDATE_PATH.split("/"));
|
|
3189
|
+
let current = null;
|
|
3190
|
+
try {
|
|
3191
|
+
current = await readFile6(destination, "utf8");
|
|
3192
|
+
} catch (error) {
|
|
3193
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
3194
|
+
throw error;
|
|
3195
|
+
}
|
|
3196
|
+
const changed = current !== content;
|
|
3197
|
+
if (changed && options.dryRun !== true)
|
|
3198
|
+
await atomicWrite2(destination, content);
|
|
3199
|
+
return {
|
|
3200
|
+
path: CANDIDATE_PATH,
|
|
3201
|
+
scanned: files.length,
|
|
3202
|
+
candidates: manifest.candidates.length,
|
|
3203
|
+
changed,
|
|
3204
|
+
rejected_untrusted: rejected.count
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
// ../core/dist/project/refresh.js
|
|
3209
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
3210
|
+
import { readFile as readFile7, readdir as readdir5, rmdir } from "node:fs/promises";
|
|
3211
|
+
import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
|
|
2925
3212
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
2926
3213
|
var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
2927
3214
|
var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
|
|
2928
3215
|
async function fileHex2(path) {
|
|
2929
3216
|
try {
|
|
2930
|
-
return
|
|
3217
|
+
return createHash7("sha256").update(await readFile7(path)).digest("hex");
|
|
2931
3218
|
} catch (error) {
|
|
2932
3219
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2933
3220
|
return null;
|
|
@@ -2937,7 +3224,7 @@ async function fileHex2(path) {
|
|
|
2937
3224
|
}
|
|
2938
3225
|
async function readOptionalText(path) {
|
|
2939
3226
|
try {
|
|
2940
|
-
return await
|
|
3227
|
+
return await readFile7(path, "utf8");
|
|
2941
3228
|
} catch (error) {
|
|
2942
3229
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2943
3230
|
return "";
|
|
@@ -2946,7 +3233,7 @@ async function readOptionalText(path) {
|
|
|
2946
3233
|
}
|
|
2947
3234
|
}
|
|
2948
3235
|
async function readInstalledState(root) {
|
|
2949
|
-
const content = await readOptionalText(
|
|
3236
|
+
const content = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
|
|
2950
3237
|
if (content === "") {
|
|
2951
3238
|
return {
|
|
2952
3239
|
profile: null,
|
|
@@ -3016,7 +3303,7 @@ async function readInstalledState(root) {
|
|
|
3016
3303
|
};
|
|
3017
3304
|
}
|
|
3018
3305
|
async function readInstalledAgentConfiguration(projectRoot) {
|
|
3019
|
-
const installed = await readInstalledState(
|
|
3306
|
+
const installed = await readInstalledState(resolve7(projectRoot));
|
|
3020
3307
|
return {
|
|
3021
3308
|
agents: installed.adapters,
|
|
3022
3309
|
profiles: Object.fromEntries(installed.adapters.map((agent) => [
|
|
@@ -3026,7 +3313,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
|
|
|
3026
3313
|
};
|
|
3027
3314
|
}
|
|
3028
3315
|
async function readContextIndexBundleHash(root) {
|
|
3029
|
-
const content = await readOptionalText(
|
|
3316
|
+
const content = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
|
|
3030
3317
|
if (content === "")
|
|
3031
3318
|
return null;
|
|
3032
3319
|
try {
|
|
@@ -3038,13 +3325,13 @@ async function readContextIndexBundleHash(root) {
|
|
|
3038
3325
|
}
|
|
3039
3326
|
}
|
|
3040
3327
|
async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
3041
|
-
const boundaries = new Set(boundaryPaths.map((path) =>
|
|
3328
|
+
const boundaries = new Set(boundaryPaths.map((path) => join8(root, path)));
|
|
3042
3329
|
for (const deleted of deletedPaths) {
|
|
3043
|
-
let dir =
|
|
3330
|
+
let dir = dirname5(join8(root, deleted));
|
|
3044
3331
|
while (dir.startsWith(root) && !boundaries.has(dir)) {
|
|
3045
3332
|
let entries;
|
|
3046
3333
|
try {
|
|
3047
|
-
entries = await
|
|
3334
|
+
entries = await readdir5(dir);
|
|
3048
3335
|
} catch {
|
|
3049
3336
|
break;
|
|
3050
3337
|
}
|
|
@@ -3055,7 +3342,7 @@ async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
|
3055
3342
|
} catch {
|
|
3056
3343
|
break;
|
|
3057
3344
|
}
|
|
3058
|
-
dir =
|
|
3345
|
+
dir = dirname5(dir);
|
|
3059
3346
|
}
|
|
3060
3347
|
}
|
|
3061
3348
|
}
|
|
@@ -3082,7 +3369,7 @@ function sortByTarget(items) {
|
|
|
3082
3369
|
return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
3083
3370
|
}
|
|
3084
3371
|
async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
|
|
3085
|
-
const existing = await readOptionalText(
|
|
3372
|
+
const existing = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
|
|
3086
3373
|
let codebase = {
|
|
3087
3374
|
map: ".harness/codebase/map",
|
|
3088
3375
|
status: "missing"
|
|
@@ -3135,7 +3422,7 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
|
|
|
3135
3422
|
}
|
|
3136
3423
|
async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
|
|
3137
3424
|
const path = ".harness/project.yaml";
|
|
3138
|
-
const content = await readOptionalText(
|
|
3425
|
+
const content = await readOptionalText(join8(root, path));
|
|
3139
3426
|
if (content === "")
|
|
3140
3427
|
return null;
|
|
3141
3428
|
const project = parseYaml3(content);
|
|
@@ -3170,11 +3457,11 @@ function mergeTargets(targets) {
|
|
|
3170
3457
|
}).sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
3171
3458
|
}
|
|
3172
3459
|
async function reconcileMarkdownBlock(root, fileName, blockId, content, remove, ops, conflicts, preserved) {
|
|
3173
|
-
const original = await readOptionalText(
|
|
3460
|
+
const original = await readOptionalText(join8(root, fileName));
|
|
3174
3461
|
const synthetic = {
|
|
3175
3462
|
source_path: fileName,
|
|
3176
3463
|
target_path: fileName,
|
|
3177
|
-
sha256:
|
|
3464
|
+
sha256: createHash7("sha256").update(content).digest("hex"),
|
|
3178
3465
|
bytes: new TextEncoder().encode(content)
|
|
3179
3466
|
};
|
|
3180
3467
|
let next;
|
|
@@ -3189,7 +3476,7 @@ async function reconcileMarkdownBlock(root, fileName, blockId, content, remove,
|
|
|
3189
3476
|
next = refreshed.content;
|
|
3190
3477
|
}
|
|
3191
3478
|
} catch {
|
|
3192
|
-
const current = original === "" ? null :
|
|
3479
|
+
const current = original === "" ? null : createHash7("sha256").update(original).digest("hex");
|
|
3193
3480
|
preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3194
3481
|
conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3195
3482
|
return;
|
|
@@ -3209,7 +3496,7 @@ function stateWithoutInstalledAt(value) {
|
|
|
3209
3496
|
return copy;
|
|
3210
3497
|
}
|
|
3211
3498
|
async function refreshProject(options) {
|
|
3212
|
-
const root =
|
|
3499
|
+
const root = resolve7(options.projectRoot);
|
|
3213
3500
|
const installed = await readInstalledState(root);
|
|
3214
3501
|
const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
|
|
3215
3502
|
const selectedAgents = sortHarnessAgents(options.agents);
|
|
@@ -3293,7 +3580,7 @@ async function refreshProject(options) {
|
|
|
3293
3580
|
const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
|
|
3294
3581
|
for (const target of newManaged) {
|
|
3295
3582
|
const incoming = target.sha256;
|
|
3296
|
-
const current = await fileHex2(
|
|
3583
|
+
const current = await fileHex2(join8(root, target.target_path));
|
|
3297
3584
|
if (current === null) {
|
|
3298
3585
|
applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
|
|
3299
3586
|
ops.push({ operation: "add", path: target.target_path, content: target.bytes });
|
|
@@ -3321,7 +3608,7 @@ async function refreshProject(options) {
|
|
|
3321
3608
|
}
|
|
3322
3609
|
}
|
|
3323
3610
|
for (const target of oldOnly) {
|
|
3324
|
-
const current = await fileHex2(
|
|
3611
|
+
const current = await fileHex2(join8(root, target.target_path));
|
|
3325
3612
|
if (current === null) {
|
|
3326
3613
|
continue;
|
|
3327
3614
|
}
|
|
@@ -3364,7 +3651,7 @@ async function refreshProject(options) {
|
|
|
3364
3651
|
const verifyEntries = [];
|
|
3365
3652
|
for (const target of verifyTargets) {
|
|
3366
3653
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3367
|
-
const actual = await fileHex2(
|
|
3654
|
+
const actual = await fileHex2(join8(root, target.target_path));
|
|
3368
3655
|
verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3369
3656
|
if (actual === null) {
|
|
3370
3657
|
verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3397,19 +3684,19 @@ async function refreshProject(options) {
|
|
|
3397
3684
|
owner: "shared",
|
|
3398
3685
|
target_path: "AGENTS.md",
|
|
3399
3686
|
block_id: AGENTS_CORE_BLOCK_ID,
|
|
3400
|
-
content_sha256:
|
|
3687
|
+
content_sha256: createHash7("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3401
3688
|
},
|
|
3402
3689
|
...selectedSet.has("claude-code") ? [{
|
|
3403
3690
|
owner: "claude-code",
|
|
3404
3691
|
target_path: "CLAUDE.md",
|
|
3405
3692
|
block_id: CLAUDE_BLOCK_ID,
|
|
3406
|
-
content_sha256:
|
|
3693
|
+
content_sha256: createHash7("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3407
3694
|
}] : [],
|
|
3408
3695
|
...selectedSet.has("codebuddy") ? [{
|
|
3409
3696
|
owner: "codebuddy",
|
|
3410
3697
|
target_path: "CODEBUDDY.md",
|
|
3411
3698
|
block_id: CODEBUDDY_BLOCK_ID,
|
|
3412
|
-
content_sha256:
|
|
3699
|
+
content_sha256: createHash7("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3413
3700
|
}] : []
|
|
3414
3701
|
].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
|
|
3415
3702
|
const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
|
|
@@ -3425,7 +3712,7 @@ async function refreshProject(options) {
|
|
|
3425
3712
|
files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
|
|
3426
3713
|
managed_blocks: managedBlocks
|
|
3427
3714
|
};
|
|
3428
|
-
const existingState = await readOptionalText(
|
|
3715
|
+
const existingState = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
|
|
3429
3716
|
let existingParsed = null;
|
|
3430
3717
|
try {
|
|
3431
3718
|
existingParsed = existingState === "" ? null : JSON.parse(existingState);
|
|
@@ -3480,7 +3767,7 @@ function buildMarkerCoreHash(text) {
|
|
|
3480
3767
|
}
|
|
3481
3768
|
}
|
|
3482
3769
|
async function collectFreshness(options) {
|
|
3483
|
-
const root =
|
|
3770
|
+
const root = resolve7(options.projectRoot);
|
|
3484
3771
|
const installed = await readInstalledState(root);
|
|
3485
3772
|
const codebuddySurface2 = options.codebuddySurface ?? "both";
|
|
3486
3773
|
const agents = [];
|
|
@@ -3525,18 +3812,18 @@ async function collectFreshness(options) {
|
|
|
3525
3812
|
identity.adapterHash = sha256Bytes(canonicalJson(targets.map((target) => ({ path: target.target_path.replace(/\\/g, "/"), sha256: target.sha256 })).sort((a, b) => a.path.localeCompare(b.path))));
|
|
3526
3813
|
const installedProjection = await Promise.all(targets.map(async (target) => ({
|
|
3527
3814
|
path: target.target_path.replace(/\\/g, "/"),
|
|
3528
|
-
sha256: await fileHex2(
|
|
3815
|
+
sha256: await fileHex2(join8(root, target.target_path))
|
|
3529
3816
|
})));
|
|
3530
3817
|
identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
|
|
3531
3818
|
const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
|
|
3532
3819
|
if (markerTarget !== void 0) {
|
|
3533
|
-
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(
|
|
3820
|
+
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join8(root, markerTarget.target_path)));
|
|
3534
3821
|
}
|
|
3535
3822
|
const mismatchDetails = [];
|
|
3536
3823
|
const contentEntries = [];
|
|
3537
3824
|
for (const target of targets) {
|
|
3538
3825
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3539
|
-
const actual = await fileHex2(
|
|
3826
|
+
const actual = await fileHex2(join8(root, target.target_path));
|
|
3540
3827
|
contentEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3541
3828
|
if (actual === null) {
|
|
3542
3829
|
mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3559,7 +3846,7 @@ async function collectFreshness(options) {
|
|
|
3559
3846
|
const drifted = [];
|
|
3560
3847
|
const missing = [];
|
|
3561
3848
|
for (const target of targets) {
|
|
3562
|
-
const current = await fileHex2(
|
|
3849
|
+
const current = await fileHex2(join8(root, target.target_path));
|
|
3563
3850
|
if (current === null) {
|
|
3564
3851
|
missing.push(target.target_path);
|
|
3565
3852
|
} else if (current !== target.sha256) {
|
|
@@ -3807,11 +4094,11 @@ function rawFindings(content) {
|
|
|
3807
4094
|
if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
|
|
3808
4095
|
continue;
|
|
3809
4096
|
}
|
|
3810
|
-
const
|
|
4097
|
+
const relative4 = match[0].indexOf(value);
|
|
3811
4098
|
findings.push({
|
|
3812
4099
|
ruleId: rule.id,
|
|
3813
4100
|
severity: rule.severity,
|
|
3814
|
-
offset: match.index + Math.max(0,
|
|
4101
|
+
offset: match.index + Math.max(0, relative4),
|
|
3815
4102
|
value
|
|
3816
4103
|
});
|
|
3817
4104
|
}
|
|
@@ -3923,8 +4210,8 @@ function generateProposalPreview(input, scanOptions = {}) {
|
|
|
3923
4210
|
}
|
|
3924
4211
|
|
|
3925
4212
|
// ../core/dist/push/credentials.js
|
|
3926
|
-
import { readFile as
|
|
3927
|
-
import { join as
|
|
4213
|
+
import { readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
|
|
4214
|
+
import { join as join9 } from "node:path";
|
|
3928
4215
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
3929
4216
|
var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
|
|
3930
4217
|
var CREDENTIALS_GITIGNORE_LINES = [
|
|
@@ -3994,7 +4281,7 @@ function mergeLocalCredentials(existing, patch) {
|
|
|
3994
4281
|
}
|
|
3995
4282
|
async function readLocalCredentials(projectRoot) {
|
|
3996
4283
|
try {
|
|
3997
|
-
const raw = await
|
|
4284
|
+
const raw = await readFile8(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
|
|
3998
4285
|
return parseLocalCredentials(parseYaml4(raw));
|
|
3999
4286
|
} catch (error) {
|
|
4000
4287
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -4005,13 +4292,13 @@ async function readLocalCredentials(projectRoot) {
|
|
|
4005
4292
|
}
|
|
4006
4293
|
async function writeLocalCredentials(projectRoot, credentials) {
|
|
4007
4294
|
const normalized = validateLocalCredentials(credentials);
|
|
4008
|
-
await
|
|
4295
|
+
await writeFile6(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
|
|
4009
4296
|
}
|
|
4010
4297
|
async function ensureCredentialsGitignore(projectRoot) {
|
|
4011
|
-
const gitignorePath =
|
|
4298
|
+
const gitignorePath = join9(projectRoot, ".gitignore");
|
|
4012
4299
|
let content = "";
|
|
4013
4300
|
try {
|
|
4014
|
-
content = await
|
|
4301
|
+
content = await readFile8(gitignorePath, "utf8");
|
|
4015
4302
|
} catch (error) {
|
|
4016
4303
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
4017
4304
|
throw error;
|
|
@@ -4034,7 +4321,7 @@ async function ensureCredentialsGitignore(projectRoot) {
|
|
|
4034
4321
|
return;
|
|
4035
4322
|
}
|
|
4036
4323
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
4037
|
-
await
|
|
4324
|
+
await writeFile6(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
|
|
4038
4325
|
}
|
|
4039
4326
|
function resolvePushAuth(input) {
|
|
4040
4327
|
const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
|
|
@@ -4062,28 +4349,28 @@ function resolvePushAuth(input) {
|
|
|
4062
4349
|
}
|
|
4063
4350
|
|
|
4064
4351
|
// ../core/dist/push/push.js
|
|
4065
|
-
import { lstat as lstat3, readFile as
|
|
4066
|
-
import { join as
|
|
4352
|
+
import { lstat as lstat3, readFile as readFile12, readdir as readdir6, rm as rm5 } from "node:fs/promises";
|
|
4353
|
+
import { join as join13, relative as relative2, resolve as resolve9 } from "node:path";
|
|
4067
4354
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
4068
4355
|
|
|
4069
4356
|
// ../core/dist/state/baseline.js
|
|
4070
|
-
import { readFile as
|
|
4071
|
-
import { join as
|
|
4357
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
4358
|
+
import { join as join10 } from "node:path";
|
|
4072
4359
|
async function readBaseline(projectRoot) {
|
|
4073
|
-
const content = await
|
|
4360
|
+
const content = await readFile9(join10(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
|
|
4074
4361
|
return baselineManifestSchema.parse(JSON.parse(content));
|
|
4075
4362
|
}
|
|
4076
4363
|
|
|
4077
4364
|
// ../core/dist/state/locks.js
|
|
4078
4365
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4079
|
-
import { readFile as
|
|
4080
|
-
import { join as
|
|
4366
|
+
import { readFile as readFile10, rename as rename5, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
4367
|
+
import { join as join11 } from "node:path";
|
|
4081
4368
|
async function readLock(path) {
|
|
4082
|
-
return JSON.parse(await
|
|
4369
|
+
return JSON.parse(await readFile10(path, "utf8"));
|
|
4083
4370
|
}
|
|
4084
4371
|
async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
4085
4372
|
const layout = await ensureStateLayout(projectRoot);
|
|
4086
|
-
const lockPath =
|
|
4373
|
+
const lockPath = join11(layout.locks, "protocol.lock");
|
|
4087
4374
|
const now = options.now ?? Date.now();
|
|
4088
4375
|
const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
|
|
4089
4376
|
const record = {
|
|
@@ -4096,7 +4383,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
4096
4383
|
};
|
|
4097
4384
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4098
4385
|
try {
|
|
4099
|
-
await
|
|
4386
|
+
await writeFile7(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
4100
4387
|
return {
|
|
4101
4388
|
path: lockPath,
|
|
4102
4389
|
operation,
|
|
@@ -4118,15 +4405,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
4118
4405
|
if (now - current.heartbeat_at_ms <= staleAfterMs) {
|
|
4119
4406
|
throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
|
|
4120
4407
|
}
|
|
4121
|
-
await
|
|
4408
|
+
await rename5(lockPath, lockPath + ".stale-" + randomUUID3());
|
|
4122
4409
|
}
|
|
4123
4410
|
}
|
|
4124
4411
|
throw new Error("unable to acquire protocol lock");
|
|
4125
4412
|
}
|
|
4126
4413
|
|
|
4127
4414
|
// ../core/dist/sync/synchronize.js
|
|
4128
|
-
import { lstat as lstat2, readFile as
|
|
4129
|
-
import { join as
|
|
4415
|
+
import { lstat as lstat2, readFile as readFile11, rm as rm4 } from "node:fs/promises";
|
|
4416
|
+
import { join as join12, resolve as resolve8 } from "node:path";
|
|
4130
4417
|
|
|
4131
4418
|
// ../core/dist/update/conflicts.js
|
|
4132
4419
|
function operationTargetPath(operation) {
|
|
@@ -4408,8 +4695,8 @@ async function pathExists(path) {
|
|
|
4408
4695
|
}
|
|
4409
4696
|
}
|
|
4410
4697
|
async function optionalContent(root, path) {
|
|
4411
|
-
const full =
|
|
4412
|
-
return await pathExists(full) ?
|
|
4698
|
+
const full = join12(root, path);
|
|
4699
|
+
return await pathExists(full) ? readFile11(full, "utf8") : null;
|
|
4413
4700
|
}
|
|
4414
4701
|
function manifestPayloadHash(manifest) {
|
|
4415
4702
|
const payload = { ...manifest };
|
|
@@ -4421,10 +4708,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
|
|
|
4421
4708
|
return null;
|
|
4422
4709
|
}
|
|
4423
4710
|
const hash = operation.content_sha256;
|
|
4424
|
-
const cacheRoot =
|
|
4425
|
-
const cachePath =
|
|
4711
|
+
const cacheRoot = join12(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4712
|
+
const cachePath = join12(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
4426
4713
|
if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
|
|
4427
|
-
return
|
|
4714
|
+
return readFile11(cachePath, "utf8");
|
|
4428
4715
|
}
|
|
4429
4716
|
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
4430
4717
|
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
@@ -4529,7 +4816,7 @@ async function planSingleArtifact(root, baseline, manifest, client, requestId, d
|
|
|
4529
4816
|
});
|
|
4530
4817
|
}
|
|
4531
4818
|
async function saveConflictReport(root, requestId, manifest, plan) {
|
|
4532
|
-
const reportPath =
|
|
4819
|
+
const reportPath = join12(root, ".harness", "reports", "conflicts-" + requestId + ".json");
|
|
4533
4820
|
await atomicWriteJson(reportPath, {
|
|
4534
4821
|
schema_version: 1,
|
|
4535
4822
|
request_id: requestId,
|
|
@@ -4540,7 +4827,7 @@ async function saveConflictReport(root, requestId, manifest, plan) {
|
|
|
4540
4827
|
});
|
|
4541
4828
|
}
|
|
4542
4829
|
async function synchronizeArtifacts(options, initialBaseline) {
|
|
4543
|
-
const root =
|
|
4830
|
+
const root = resolve8(options.projectRoot);
|
|
4544
4831
|
const projectId = options.project.project.project_id;
|
|
4545
4832
|
if (projectId === null) {
|
|
4546
4833
|
throw new Error("PROJECT_NOT_BOUND");
|
|
@@ -4647,7 +4934,7 @@ async function synchronizeArtifacts(options, initialBaseline) {
|
|
|
4647
4934
|
}
|
|
4648
4935
|
continue;
|
|
4649
4936
|
}
|
|
4650
|
-
await atomicWriteJson(
|
|
4937
|
+
await atomicWriteJson(join12(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4651
4938
|
const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
|
|
4652
4939
|
try {
|
|
4653
4940
|
const nextBaseline = applyBaselineUpdates(baseline, plan);
|
|
@@ -4717,7 +5004,7 @@ function baselineEntryFromOperation(manifest, operation, localContent) {
|
|
|
4717
5004
|
};
|
|
4718
5005
|
}
|
|
4719
5006
|
async function advanceBaselineFromArtifact(options, baseline) {
|
|
4720
|
-
const root =
|
|
5007
|
+
const root = resolve8(options.projectRoot);
|
|
4721
5008
|
if (options.manifest.project_version === null) {
|
|
4722
5009
|
throw new Error("artifact manifest missing project_version");
|
|
4723
5010
|
}
|
|
@@ -4818,29 +5105,29 @@ async function walkFiles(root, current, output) {
|
|
|
4818
5105
|
if (!await exists3(current)) {
|
|
4819
5106
|
return;
|
|
4820
5107
|
}
|
|
4821
|
-
for (const item2 of await
|
|
4822
|
-
const path =
|
|
5108
|
+
for (const item2 of await readdir6(current, { withFileTypes: true })) {
|
|
5109
|
+
const path = join13(current, item2.name);
|
|
4823
5110
|
if (item2.isSymbolicLink()) {
|
|
4824
5111
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
4825
5112
|
}
|
|
4826
5113
|
if (item2.isDirectory()) {
|
|
4827
5114
|
await walkFiles(root, path, output);
|
|
4828
5115
|
} else if (item2.isFile()) {
|
|
4829
|
-
output.push(normalizeManagedPath(
|
|
5116
|
+
output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
|
|
4830
5117
|
}
|
|
4831
5118
|
}
|
|
4832
5119
|
}
|
|
4833
5120
|
async function walkArchiveSummaries(root, output) {
|
|
4834
|
-
const archiveRoot =
|
|
5121
|
+
const archiveRoot = join13(root, ".harness", "archive");
|
|
4835
5122
|
if (!await exists3(archiveRoot))
|
|
4836
5123
|
return;
|
|
4837
|
-
for (const item2 of await
|
|
5124
|
+
for (const item2 of await readdir6(archiveRoot, { withFileTypes: true })) {
|
|
4838
5125
|
if (item2.isSymbolicLink()) {
|
|
4839
5126
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
4840
5127
|
}
|
|
4841
5128
|
if (!item2.isDirectory())
|
|
4842
5129
|
continue;
|
|
4843
|
-
const summaryPath =
|
|
5130
|
+
const summaryPath = join13(archiveRoot, item2.name, "reports", "final", "summary-data.json");
|
|
4844
5131
|
try {
|
|
4845
5132
|
const stats = await lstat3(summaryPath);
|
|
4846
5133
|
if (stats.isSymbolicLink()) {
|
|
@@ -4853,7 +5140,7 @@ async function walkArchiveSummaries(root, output) {
|
|
|
4853
5140
|
continue;
|
|
4854
5141
|
throw error;
|
|
4855
5142
|
}
|
|
4856
|
-
const relativePath = normalizeManagedPath(
|
|
5143
|
+
const relativePath = normalizeManagedPath(relative2(root, summaryPath).replaceAll("\\", "/"));
|
|
4857
5144
|
if (ARCHIVE_SUMMARY_PATH.test(relativePath)) {
|
|
4858
5145
|
output.push(relativePath);
|
|
4859
5146
|
}
|
|
@@ -4868,19 +5155,19 @@ function enabledHarnessAgents(project) {
|
|
|
4868
5155
|
async function walkHarnessEntries(root, directory, output) {
|
|
4869
5156
|
if (!await exists3(directory))
|
|
4870
5157
|
return;
|
|
4871
|
-
for (const item2 of await
|
|
5158
|
+
for (const item2 of await readdir6(directory, { withFileTypes: true })) {
|
|
4872
5159
|
if (item2.name.startsWith("harness-")) {
|
|
4873
|
-
const path =
|
|
5160
|
+
const path = join13(directory, item2.name);
|
|
4874
5161
|
if (item2.isDirectory()) {
|
|
4875
5162
|
await walkFiles(root, path, output);
|
|
4876
5163
|
} else if (item2.isFile()) {
|
|
4877
|
-
output.push(normalizeManagedPath(
|
|
5164
|
+
output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
|
|
4878
5165
|
}
|
|
4879
5166
|
}
|
|
4880
5167
|
}
|
|
4881
5168
|
}
|
|
4882
5169
|
async function managedFiles(projectRoot, project) {
|
|
4883
|
-
const root =
|
|
5170
|
+
const root = resolve9(projectRoot);
|
|
4884
5171
|
const paths = [];
|
|
4885
5172
|
const adapters = getAdapters(enabledHarnessAgents(project));
|
|
4886
5173
|
const managedFiles2 = [
|
|
@@ -4889,26 +5176,26 @@ async function managedFiles(projectRoot, project) {
|
|
|
4889
5176
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
4890
5177
|
];
|
|
4891
5178
|
for (const path of managedFiles2) {
|
|
4892
|
-
if (await exists3(
|
|
5179
|
+
if (await exists3(join13(root, path))) {
|
|
4893
5180
|
paths.push(path);
|
|
4894
5181
|
}
|
|
4895
5182
|
}
|
|
4896
5183
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
4897
|
-
await walkFiles(root,
|
|
5184
|
+
await walkFiles(root, join13(root, path), paths);
|
|
4898
5185
|
}
|
|
4899
5186
|
await walkArchiveSummaries(root, paths);
|
|
4900
5187
|
for (const adapter of adapters) {
|
|
4901
5188
|
if (adapter.rulesRoot !== null) {
|
|
4902
|
-
await walkHarnessEntries(root,
|
|
5189
|
+
await walkHarnessEntries(root, join13(root, adapter.rulesRoot), paths);
|
|
4903
5190
|
}
|
|
4904
|
-
await walkHarnessEntries(root,
|
|
5191
|
+
await walkHarnessEntries(root, join13(root, adapter.skillsRoot), paths);
|
|
4905
5192
|
if (adapter.agentsRoot !== null) {
|
|
4906
|
-
await walkHarnessEntries(root,
|
|
5193
|
+
await walkHarnessEntries(root, join13(root, adapter.agentsRoot), paths);
|
|
4907
5194
|
}
|
|
4908
5195
|
}
|
|
4909
5196
|
const result = {};
|
|
4910
5197
|
for (const path of [...new Set(paths)].sort()) {
|
|
4911
|
-
result[path] = await
|
|
5198
|
+
result[path] = await readFile12(join13(root, path), "utf8");
|
|
4912
5199
|
}
|
|
4913
5200
|
return result;
|
|
4914
5201
|
}
|
|
@@ -4917,14 +5204,14 @@ function proposalBaseline(baseline) {
|
|
|
4917
5204
|
}
|
|
4918
5205
|
async function readProject(root) {
|
|
4919
5206
|
try {
|
|
4920
|
-
return projectConfigSchema.parse(parseYaml5(await
|
|
5207
|
+
return projectConfigSchema.parse(parseYaml5(await readFile12(join13(root, ".harness", "project.yaml"), "utf8")));
|
|
4921
5208
|
} catch {
|
|
4922
5209
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
4923
5210
|
}
|
|
4924
5211
|
}
|
|
4925
5212
|
async function readOptionalJson(path) {
|
|
4926
5213
|
try {
|
|
4927
|
-
return JSON.parse(await
|
|
5214
|
+
return JSON.parse(await readFile12(path, "utf8"));
|
|
4928
5215
|
} catch (error) {
|
|
4929
5216
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4930
5217
|
return null;
|
|
@@ -4936,7 +5223,7 @@ async function clientIdFor(root, explicit) {
|
|
|
4936
5223
|
if (explicit !== void 0) {
|
|
4937
5224
|
return explicit;
|
|
4938
5225
|
}
|
|
4939
|
-
const path =
|
|
5226
|
+
const path = join13(root, ".harness", "state", "local", "client.json");
|
|
4940
5227
|
const existing = await readOptionalJson(path);
|
|
4941
5228
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
4942
5229
|
return existing.client_id;
|
|
@@ -5132,7 +5419,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
5132
5419
|
return { project: nextProject, baseline: nextBaseline };
|
|
5133
5420
|
}
|
|
5134
5421
|
async function pushProject(options) {
|
|
5135
|
-
const root =
|
|
5422
|
+
const root = resolve9(options.projectRoot);
|
|
5136
5423
|
let project = await readProject(root);
|
|
5137
5424
|
let baseline = await readBaseline(root);
|
|
5138
5425
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
@@ -5198,7 +5485,7 @@ async function pushProject(options) {
|
|
|
5198
5485
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
5199
5486
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
5200
5487
|
}
|
|
5201
|
-
const workflowPath =
|
|
5488
|
+
const workflowPath = join13(root, ".harness", "state", "local", "push-workflow.json");
|
|
5202
5489
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
5203
5490
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
5204
5491
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -5353,7 +5640,7 @@ async function pushProject(options) {
|
|
|
5353
5640
|
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
5354
5641
|
}
|
|
5355
5642
|
}
|
|
5356
|
-
await atomicWriteJson(
|
|
5643
|
+
await atomicWriteJson(join13(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
5357
5644
|
schema_version: 1,
|
|
5358
5645
|
request_id: requestId,
|
|
5359
5646
|
project_id: projectId,
|
|
@@ -5398,14 +5685,14 @@ async function pushProject(options) {
|
|
|
5398
5685
|
}
|
|
5399
5686
|
|
|
5400
5687
|
// ../core/dist/state/cleanup.js
|
|
5401
|
-
import { readFile as
|
|
5402
|
-
import { join as
|
|
5688
|
+
import { readFile as readFile13, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
5689
|
+
import { join as join14 } from "node:path";
|
|
5403
5690
|
function isSafeEntryName(name) {
|
|
5404
5691
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
5405
5692
|
}
|
|
5406
5693
|
async function listDir(path) {
|
|
5407
5694
|
try {
|
|
5408
|
-
return await
|
|
5695
|
+
return await readdir7(path);
|
|
5409
5696
|
} catch (error) {
|
|
5410
5697
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5411
5698
|
return [];
|
|
@@ -5423,7 +5710,7 @@ async function cleanupProject(options) {
|
|
|
5423
5710
|
continue;
|
|
5424
5711
|
let journal;
|
|
5425
5712
|
try {
|
|
5426
|
-
journal = JSON.parse(await
|
|
5713
|
+
journal = JSON.parse(await readFile13(join14(layout.transactions, name, "journal.json"), "utf8"));
|
|
5427
5714
|
} catch {
|
|
5428
5715
|
continue;
|
|
5429
5716
|
}
|
|
@@ -5441,7 +5728,7 @@ async function cleanupProject(options) {
|
|
|
5441
5728
|
continue;
|
|
5442
5729
|
pruned.push(entry.id);
|
|
5443
5730
|
if (!options.dryRun) {
|
|
5444
|
-
await rm6(
|
|
5731
|
+
await rm6(join14(layout.transactions, entry.id), { recursive: true, force: true });
|
|
5445
5732
|
}
|
|
5446
5733
|
}
|
|
5447
5734
|
}
|
|
@@ -5450,7 +5737,7 @@ async function cleanupProject(options) {
|
|
|
5450
5737
|
continue;
|
|
5451
5738
|
removedCache.push(name);
|
|
5452
5739
|
if (!options.dryRun) {
|
|
5453
|
-
await rm6(
|
|
5740
|
+
await rm6(join14(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
5454
5741
|
}
|
|
5455
5742
|
}
|
|
5456
5743
|
return {
|
|
@@ -5510,10 +5797,10 @@ var SKILL_TARGET_AGENTS = Object.freeze([
|
|
|
5510
5797
|
]);
|
|
5511
5798
|
|
|
5512
5799
|
// ../core/dist/transaction/recovery.js
|
|
5513
|
-
import { readFile as
|
|
5514
|
-
import { join as
|
|
5800
|
+
import { readFile as readFile14, readdir as readdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
5801
|
+
import { join as join15 } from "node:path";
|
|
5515
5802
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
5516
|
-
const journal = JSON.parse(await
|
|
5803
|
+
const journal = JSON.parse(await readFile14(join15(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
5517
5804
|
if (journal.state === "committed") {
|
|
5518
5805
|
return { transactionId, status: "committed" };
|
|
5519
5806
|
}
|
|
@@ -5530,7 +5817,7 @@ async function listTransactions(projectRoot) {
|
|
|
5530
5817
|
const root = stateLayout(projectRoot).transactions;
|
|
5531
5818
|
let names;
|
|
5532
5819
|
try {
|
|
5533
|
-
names = await
|
|
5820
|
+
names = await readdir8(root);
|
|
5534
5821
|
} catch (error) {
|
|
5535
5822
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5536
5823
|
return [];
|
|
@@ -5540,7 +5827,7 @@ async function listTransactions(projectRoot) {
|
|
|
5540
5827
|
const transactions = [];
|
|
5541
5828
|
for (const transactionId of names) {
|
|
5542
5829
|
try {
|
|
5543
|
-
const journal = JSON.parse(await
|
|
5830
|
+
const journal = JSON.parse(await readFile14(join15(root, transactionId, "journal.json"), "utf8"));
|
|
5544
5831
|
transactions.push({
|
|
5545
5832
|
transactionId,
|
|
5546
5833
|
kind: journal.kind,
|
|
@@ -5557,7 +5844,7 @@ async function pendingTransactions(projectRoot) {
|
|
|
5557
5844
|
}
|
|
5558
5845
|
async function pathExists2(path) {
|
|
5559
5846
|
try {
|
|
5560
|
-
await
|
|
5847
|
+
await stat4(path);
|
|
5561
5848
|
return true;
|
|
5562
5849
|
} catch (error) {
|
|
5563
5850
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -5571,11 +5858,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5571
5858
|
if (latest === void 0) {
|
|
5572
5859
|
throw new Error("no committed update transaction is available for rollback");
|
|
5573
5860
|
}
|
|
5574
|
-
const transactionRoot =
|
|
5575
|
-
const journal = JSON.parse(await
|
|
5576
|
-
const after = JSON.parse(await
|
|
5861
|
+
const transactionRoot = join15(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
5862
|
+
const journal = JSON.parse(await readFile14(join15(transactionRoot, "journal.json"), "utf8"));
|
|
5863
|
+
const after = JSON.parse(await readFile14(join15(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
5577
5864
|
for (const entry of after) {
|
|
5578
|
-
const target =
|
|
5865
|
+
const target = join15(projectRoot, entry.path);
|
|
5579
5866
|
const exists6 = await pathExists2(target);
|
|
5580
5867
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
5581
5868
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
@@ -5588,10 +5875,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5588
5875
|
continue;
|
|
5589
5876
|
}
|
|
5590
5877
|
seen.add(snapshot.path);
|
|
5591
|
-
const target =
|
|
5878
|
+
const target = join15(projectRoot, snapshot.path);
|
|
5592
5879
|
const exists6 = await pathExists2(target);
|
|
5593
5880
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
5594
|
-
const content = await
|
|
5881
|
+
const content = await readFile14(join15(transactionRoot, "before", snapshot.snapshot_name));
|
|
5595
5882
|
operations.push({
|
|
5596
5883
|
operation: exists6 ? "modify" : "add",
|
|
5597
5884
|
path: snapshot.path,
|
|
@@ -5620,7 +5907,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5620
5907
|
if (!removable) {
|
|
5621
5908
|
continue;
|
|
5622
5909
|
}
|
|
5623
|
-
await rm7(
|
|
5910
|
+
await rm7(join15(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
5624
5911
|
recursive: true,
|
|
5625
5912
|
force: true
|
|
5626
5913
|
});
|
|
@@ -5630,8 +5917,8 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5630
5917
|
}
|
|
5631
5918
|
|
|
5632
5919
|
// ../core/dist/update/update.js
|
|
5633
|
-
import { readFile as
|
|
5634
|
-
import { join as
|
|
5920
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
5921
|
+
import { join as join16, resolve as resolve10 } from "node:path";
|
|
5635
5922
|
import { parse as parseYaml8 } from "yaml";
|
|
5636
5923
|
var UpdateWorkflowError = class extends Error {
|
|
5637
5924
|
exitCode;
|
|
@@ -5644,10 +5931,10 @@ var UpdateWorkflowError = class extends Error {
|
|
|
5644
5931
|
}
|
|
5645
5932
|
};
|
|
5646
5933
|
async function updateProject(options) {
|
|
5647
|
-
const root =
|
|
5934
|
+
const root = resolve10(options.projectRoot);
|
|
5648
5935
|
let project;
|
|
5649
5936
|
try {
|
|
5650
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
5937
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile15(join16(root, ".harness", "project.yaml"), "utf8")));
|
|
5651
5938
|
} catch {
|
|
5652
5939
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
5653
5940
|
}
|
|
@@ -5729,8 +6016,8 @@ async function updateProject(options) {
|
|
|
5729
6016
|
}
|
|
5730
6017
|
|
|
5731
6018
|
// src/config/init-config.ts
|
|
5732
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5733
|
-
import { readFile as
|
|
6019
|
+
import { isAbsolute as isAbsolute2, join as join17 } from "node:path";
|
|
6020
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
5734
6021
|
var InitConfigurationError = class extends Error {
|
|
5735
6022
|
exitCode;
|
|
5736
6023
|
code;
|
|
@@ -5813,9 +6100,9 @@ function hasOwn(record, key) {
|
|
|
5813
6100
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
5814
6101
|
let fileConfig = {};
|
|
5815
6102
|
if (flags.config !== void 0) {
|
|
5816
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
6103
|
+
const path = isAbsolute2(flags.config) ? flags.config : join17(cwd, flags.config);
|
|
5817
6104
|
try {
|
|
5818
|
-
fileConfig = JSON.parse(await
|
|
6105
|
+
fileConfig = JSON.parse(await readFile16(path, "utf8"));
|
|
5819
6106
|
} catch (error) {
|
|
5820
6107
|
throw new InitConfigurationError(
|
|
5821
6108
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -5897,8 +6184,8 @@ function serializeCliResult(result) {
|
|
|
5897
6184
|
}
|
|
5898
6185
|
|
|
5899
6186
|
// src/config/codebuddy-setup.ts
|
|
5900
|
-
import { mkdir as
|
|
5901
|
-
import { basename as
|
|
6187
|
+
import { mkdir as mkdir6, readFile as readFile17, readdir as readdir9, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
|
|
6188
|
+
import { basename as basename4, dirname as dirname6, extname as extname2, join as join18 } from "node:path";
|
|
5902
6189
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
5903
6190
|
"harness-general.md",
|
|
5904
6191
|
"harness-general.mdc",
|
|
@@ -5909,7 +6196,7 @@ var SENSITIVE_ASSIGNMENT = /(?:password|passwd|token|secret|access[_-]?key|priva
|
|
|
5909
6196
|
var PRIVATE_KEY_BLOCK = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
|
|
5910
6197
|
async function exists4(path) {
|
|
5911
6198
|
try {
|
|
5912
|
-
await
|
|
6199
|
+
await stat5(path);
|
|
5913
6200
|
return true;
|
|
5914
6201
|
} catch {
|
|
5915
6202
|
return false;
|
|
@@ -5917,7 +6204,7 @@ async function exists4(path) {
|
|
|
5917
6204
|
}
|
|
5918
6205
|
async function readJsonObject(path) {
|
|
5919
6206
|
try {
|
|
5920
|
-
const parsed = JSON.parse(await
|
|
6207
|
+
const parsed = JSON.parse(await readFile17(path, "utf8"));
|
|
5921
6208
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5922
6209
|
} catch (error) {
|
|
5923
6210
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
@@ -5925,10 +6212,10 @@ async function readJsonObject(path) {
|
|
|
5925
6212
|
}
|
|
5926
6213
|
}
|
|
5927
6214
|
async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
5928
|
-
const rulesRoot =
|
|
6215
|
+
const rulesRoot = join18(projectRoot, ".claude", "rules");
|
|
5929
6216
|
let ruleNames = [];
|
|
5930
6217
|
try {
|
|
5931
|
-
ruleNames = (await
|
|
6218
|
+
ruleNames = (await readdir9(rulesRoot, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname2(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_RULE_NAMES.has(name)).sort();
|
|
5932
6219
|
} catch (error) {
|
|
5933
6220
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
5934
6221
|
}
|
|
@@ -5936,11 +6223,11 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
|
5936
6223
|
const currentClaudeRules = [];
|
|
5937
6224
|
const conflictingClaudeRules = [];
|
|
5938
6225
|
for (const name of ruleNames) {
|
|
5939
|
-
const sourceContent = await
|
|
6226
|
+
const sourceContent = await readFile17(join18(rulesRoot, name), "utf8");
|
|
5940
6227
|
const targetContents = await Promise.all(
|
|
5941
6228
|
destinationTargets(projectRoot, surface, name).map(async (target) => {
|
|
5942
6229
|
try {
|
|
5943
|
-
return await
|
|
6230
|
+
return await readFile17(target, "utf8");
|
|
5944
6231
|
} catch (error) {
|
|
5945
6232
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
5946
6233
|
throw error;
|
|
@@ -5954,22 +6241,22 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
|
5954
6241
|
currentClaudeRules.push(name);
|
|
5955
6242
|
}
|
|
5956
6243
|
}
|
|
5957
|
-
const mcp = await readJsonObject(
|
|
6244
|
+
const mcp = await readJsonObject(join18(projectRoot, ".mcp.json"));
|
|
5958
6245
|
const servers = mcp?.mcpServers;
|
|
5959
6246
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
5960
6247
|
return {
|
|
5961
6248
|
claudeRules,
|
|
5962
6249
|
currentClaudeRules,
|
|
5963
6250
|
conflictingClaudeRules,
|
|
5964
|
-
hasCodeGraphIndex: await exists4(
|
|
6251
|
+
hasCodeGraphIndex: await exists4(join18(projectRoot, ".codegraph")),
|
|
5965
6252
|
codeGraphConfigured: configured
|
|
5966
6253
|
};
|
|
5967
6254
|
}
|
|
5968
6255
|
function destinationTargets(root, surface, name) {
|
|
5969
|
-
const stem =
|
|
6256
|
+
const stem = basename4(name, extname2(name));
|
|
5970
6257
|
const targets = [];
|
|
5971
|
-
if (surface !== "cli") targets.push(
|
|
5972
|
-
if (surface !== "ide") targets.push(
|
|
6258
|
+
if (surface !== "cli") targets.push(join18(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
6259
|
+
if (surface !== "ide") targets.push(join18(root, ".codebuddy", "rules", `${stem}.md`));
|
|
5973
6260
|
return targets;
|
|
5974
6261
|
}
|
|
5975
6262
|
async function applyCodeBuddySetup(options) {
|
|
@@ -5983,8 +6270,8 @@ async function applyCodeBuddySetup(options) {
|
|
|
5983
6270
|
if (options.syncClaudeRules) {
|
|
5984
6271
|
const plan = await inspectCodeBuddySetup(options.projectRoot, options.surface);
|
|
5985
6272
|
for (const name of plan.claudeRules) {
|
|
5986
|
-
const source =
|
|
5987
|
-
const content = await
|
|
6273
|
+
const source = join18(options.projectRoot, ".claude", "rules", name);
|
|
6274
|
+
const content = await readFile17(source, "utf8");
|
|
5988
6275
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
5989
6276
|
result.skippedSensitive.push(name);
|
|
5990
6277
|
continue;
|
|
@@ -5994,14 +6281,14 @@ async function applyCodeBuddySetup(options) {
|
|
|
5994
6281
|
result.preserved.push(target);
|
|
5995
6282
|
continue;
|
|
5996
6283
|
}
|
|
5997
|
-
await
|
|
5998
|
-
await
|
|
6284
|
+
await mkdir6(dirname6(target), { recursive: true });
|
|
6285
|
+
await writeFile8(target, content, { encoding: "utf8", flag: "wx" });
|
|
5999
6286
|
result.copied.push(target);
|
|
6000
6287
|
}
|
|
6001
6288
|
}
|
|
6002
6289
|
}
|
|
6003
6290
|
if (options.configureCodeGraph) {
|
|
6004
|
-
const path =
|
|
6291
|
+
const path = join18(options.projectRoot, ".mcp.json");
|
|
6005
6292
|
const current = await readJsonObject(path);
|
|
6006
6293
|
if (current === null) {
|
|
6007
6294
|
result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
|
|
@@ -6016,7 +6303,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
6016
6303
|
codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
|
|
6017
6304
|
}
|
|
6018
6305
|
};
|
|
6019
|
-
await
|
|
6306
|
+
await writeFile8(path, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
6020
6307
|
result.mcpUpdated = true;
|
|
6021
6308
|
}
|
|
6022
6309
|
}
|
|
@@ -6025,13 +6312,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
6025
6312
|
}
|
|
6026
6313
|
|
|
6027
6314
|
// src/commands/refresh.ts
|
|
6028
|
-
import { readFile as
|
|
6029
|
-
import { join as
|
|
6315
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
6316
|
+
import { join as join19 } from "node:path";
|
|
6030
6317
|
import { parse as parseYaml9 } from "yaml";
|
|
6031
6318
|
async function detectProject(root) {
|
|
6032
6319
|
let content;
|
|
6033
6320
|
try {
|
|
6034
|
-
content = await
|
|
6321
|
+
content = await readFile18(join19(root, ".harness", "project.yaml"), "utf8");
|
|
6035
6322
|
} catch (error) {
|
|
6036
6323
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
6037
6324
|
return { status: "absent" };
|
|
@@ -6863,12 +7150,94 @@ async function runUpdate(options, dependencies) {
|
|
|
6863
7150
|
}
|
|
6864
7151
|
}
|
|
6865
7152
|
|
|
7153
|
+
// src/commands/rules-sync.ts
|
|
7154
|
+
function configuredAgents(config) {
|
|
7155
|
+
const agents = config.adapters.enabled.flatMap((value) => {
|
|
7156
|
+
const parsed = harnessAgentSchema.safeParse(value);
|
|
7157
|
+
return parsed.success ? [parsed.data] : [];
|
|
7158
|
+
});
|
|
7159
|
+
return sortHarnessAgents(agents.length > 0 ? agents : ["claude-code"]);
|
|
7160
|
+
}
|
|
7161
|
+
function configuredSurface(config, override) {
|
|
7162
|
+
const value = override ?? config.adapter_options?.codebuddy?.surface ?? "both";
|
|
7163
|
+
if (value === "both" || value === "ide" || value === "cli") return value;
|
|
7164
|
+
throw new Error("codebuddy surface \u5FC5\u987B\u4E3A both\u3001ide \u6216 cli");
|
|
7165
|
+
}
|
|
7166
|
+
async function runRulesSync(options, dependencies) {
|
|
7167
|
+
const detection = await detectProject(dependencies.cwd);
|
|
7168
|
+
if (detection.status === "absent") {
|
|
7169
|
+
dependencies.stderr("\u5C1A\u672A\u521D\u59CB\u5316 Hunter Harness\uFF1B\u8BF7\u5148\u8FD0\u884C `hunter-harness`\u3002\n");
|
|
7170
|
+
return 3;
|
|
7171
|
+
}
|
|
7172
|
+
if (detection.status === "invalid") {
|
|
7173
|
+
dependencies.stderr("PROJECT_CONFIG_INVALID\uFF1A.harness/project.yaml \u65E0\u6548\n");
|
|
7174
|
+
return 3;
|
|
7175
|
+
}
|
|
7176
|
+
try {
|
|
7177
|
+
const agents = options.agents === void 0 ? configuredAgents(detection.config) : parseAgentsInput(options.agents);
|
|
7178
|
+
const projections = await synchronizeProjectRules(
|
|
7179
|
+
dependencies.cwd,
|
|
7180
|
+
agents,
|
|
7181
|
+
configuredSurface(detection.config, options.codebuddySurface)
|
|
7182
|
+
);
|
|
7183
|
+
const learning = options.learn === false ? null : await synchronizeRuleCandidates(dependencies.cwd);
|
|
7184
|
+
const exitCode = projections.conflicts.length > 0 ? 5 : 0;
|
|
7185
|
+
const payload = {
|
|
7186
|
+
schema_version: 1,
|
|
7187
|
+
command: "rules-sync",
|
|
7188
|
+
request_id: uuidV7(),
|
|
7189
|
+
dry_run: false,
|
|
7190
|
+
ok: exitCode === 0,
|
|
7191
|
+
exit_code: exitCode,
|
|
7192
|
+
project_id: detection.config.project.project_id,
|
|
7193
|
+
summary: {
|
|
7194
|
+
migrated: projections.migrated.length,
|
|
7195
|
+
projected: projections.written.length,
|
|
7196
|
+
removed: projections.removed.length,
|
|
7197
|
+
unchanged: projections.unchanged.length,
|
|
7198
|
+
conflicts: projections.conflicts.length,
|
|
7199
|
+
agent_specific: projections.agent_specific.length,
|
|
7200
|
+
rule_candidates: learning?.candidates ?? 0
|
|
7201
|
+
},
|
|
7202
|
+
items: [
|
|
7203
|
+
...projections.migrated.map((path) => ({ path, status: "migrated" })),
|
|
7204
|
+
...projections.written.map((path) => ({ path, status: "projected" })),
|
|
7205
|
+
...projections.agent_specific.map((path) => ({ path, status: "agent-specific" })),
|
|
7206
|
+
...learning === null ? [] : [{
|
|
7207
|
+
path: learning.path,
|
|
7208
|
+
status: learning.changed ? "updated" : "unchanged",
|
|
7209
|
+
candidates: learning.candidates,
|
|
7210
|
+
scanned: learning.scanned
|
|
7211
|
+
}]
|
|
7212
|
+
],
|
|
7213
|
+
warnings: [
|
|
7214
|
+
...projections.conflicts.map((path) => `\u89C4\u5219\u5206\u6B67\u672A\u8986\u76D6\uFF1A${path}`),
|
|
7215
|
+
...projections.agent_specific.map((path) => `\u4FDD\u7559 Agent \u4E13\u5C5E\u89C4\u5219\uFF1A${path}`)
|
|
7216
|
+
],
|
|
7217
|
+
errors: []
|
|
7218
|
+
};
|
|
7219
|
+
if (options.json === true) {
|
|
7220
|
+
dependencies.stdout(serializeCliResult(payload));
|
|
7221
|
+
} else {
|
|
7222
|
+
dependencies.stdout(
|
|
7223
|
+
`\u89C4\u5219\u540C\u6B65\uFF1A\u8FC1\u79FB ${payload.summary.migrated}\uFF0C\u6295\u5F71 ${payload.summary.projected}\uFF0C\u51B2\u7A81 ${payload.summary.conflicts}\uFF0C\u5019\u9009 ${payload.summary.rule_candidates}\u3002
|
|
7224
|
+
`
|
|
7225
|
+
);
|
|
7226
|
+
for (const warning of payload.warnings) dependencies.stderr(warning + "\n");
|
|
7227
|
+
}
|
|
7228
|
+
return exitCode;
|
|
7229
|
+
} catch (error) {
|
|
7230
|
+
dependencies.stderr((error instanceof Error ? error.message : String(error)) + "\n");
|
|
7231
|
+
return 1;
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
|
|
6866
7235
|
// src/commands/recovery.ts
|
|
6867
|
-
import { stat as
|
|
6868
|
-
import { join as
|
|
7236
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
7237
|
+
import { join as join20 } from "node:path";
|
|
6869
7238
|
async function exists5(path) {
|
|
6870
7239
|
try {
|
|
6871
|
-
await
|
|
7240
|
+
await stat6(path);
|
|
6872
7241
|
return true;
|
|
6873
7242
|
} catch {
|
|
6874
7243
|
return false;
|
|
@@ -6925,7 +7294,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
6925
7294
|
return 5;
|
|
6926
7295
|
}
|
|
6927
7296
|
}
|
|
6928
|
-
const initialized = await exists5(
|
|
7297
|
+
const initialized = await exists5(join20(dependencies.cwd, ".harness", "project.yaml"));
|
|
6929
7298
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
6930
7299
|
return null;
|
|
6931
7300
|
}
|
|
@@ -6968,8 +7337,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
6968
7337
|
}
|
|
6969
7338
|
|
|
6970
7339
|
// src/workflow-data/resolve.ts
|
|
6971
|
-
import { cp, mkdir as
|
|
6972
|
-
import { dirname as
|
|
7340
|
+
import { cp, mkdir as mkdir7, readdir as readdir10, readFile as readFile19, rm as rm8, stat as stat7 } from "node:fs/promises";
|
|
7341
|
+
import { dirname as dirname7, join as join21, relative as relative3 } from "node:path";
|
|
6973
7342
|
import { fileURLToPath } from "node:url";
|
|
6974
7343
|
var WorkflowDataResolutionError = class extends Error {
|
|
6975
7344
|
code;
|
|
@@ -6983,7 +7352,7 @@ var WorkflowDataResolutionError = class extends Error {
|
|
|
6983
7352
|
};
|
|
6984
7353
|
async function pathExists3(path) {
|
|
6985
7354
|
try {
|
|
6986
|
-
await
|
|
7355
|
+
await stat7(path);
|
|
6987
7356
|
return true;
|
|
6988
7357
|
} catch {
|
|
6989
7358
|
return false;
|
|
@@ -7003,17 +7372,17 @@ function workflowPackageName(family, env) {
|
|
|
7003
7372
|
return `${scope}/workflow-${family}`;
|
|
7004
7373
|
}
|
|
7005
7374
|
async function listFilesRecursive(root, base = root) {
|
|
7006
|
-
const entries = await
|
|
7375
|
+
const entries = await readdir10(root, { withFileTypes: true });
|
|
7007
7376
|
const files = [];
|
|
7008
7377
|
for (const entry of entries) {
|
|
7009
|
-
const absolute =
|
|
7378
|
+
const absolute = join21(root, entry.name);
|
|
7010
7379
|
if (entry.isDirectory()) {
|
|
7011
7380
|
files.push(...await listFilesRecursive(absolute, base));
|
|
7012
7381
|
continue;
|
|
7013
7382
|
}
|
|
7014
7383
|
if (!entry.isFile()) continue;
|
|
7015
|
-
const rel =
|
|
7016
|
-
files.push({ path: rel, content: await
|
|
7384
|
+
const rel = relative3(base, absolute).replaceAll("\\", "/");
|
|
7385
|
+
files.push({ path: rel, content: await readFile19(absolute, "utf8") });
|
|
7017
7386
|
}
|
|
7018
7387
|
return files;
|
|
7019
7388
|
}
|
|
@@ -7026,7 +7395,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
7026
7395
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
7027
7396
|
const expected = manifest.content_sha256;
|
|
7028
7397
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
7029
|
-
const harnessRoot =
|
|
7398
|
+
const harnessRoot = join21(resourcesRoot, "harness");
|
|
7030
7399
|
if (!await pathExists3(harnessRoot)) {
|
|
7031
7400
|
throw new WorkflowDataResolutionError(
|
|
7032
7401
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -7045,40 +7414,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
7045
7414
|
}
|
|
7046
7415
|
}
|
|
7047
7416
|
async function monorepoResourcesRoot() {
|
|
7048
|
-
const here =
|
|
7417
|
+
const here = dirname7(fileURLToPath(import.meta.url));
|
|
7049
7418
|
const candidates = [
|
|
7050
7419
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
7051
|
-
|
|
7420
|
+
join21(here, "../../../workflow-data-harness"),
|
|
7052
7421
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
7053
|
-
|
|
7422
|
+
join21(here, "../../workflow-data-harness"),
|
|
7054
7423
|
// Test/build layouts that preserve additional source directory levels.
|
|
7055
|
-
|
|
7424
|
+
join21(here, "../../../../packages/workflow-data-harness")
|
|
7056
7425
|
];
|
|
7057
7426
|
for (const candidate of candidates) {
|
|
7058
|
-
if (await pathExists3(
|
|
7427
|
+
if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
|
|
7059
7428
|
}
|
|
7060
7429
|
return null;
|
|
7061
7430
|
}
|
|
7062
7431
|
async function siblingWorkflowPackage(cwd) {
|
|
7063
|
-
const here =
|
|
7432
|
+
const here = dirname7(fileURLToPath(import.meta.url));
|
|
7064
7433
|
const candidates = [
|
|
7065
|
-
|
|
7434
|
+
join21(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
7066
7435
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
7067
7436
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
7068
|
-
|
|
7437
|
+
join21(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
7069
7438
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
7070
|
-
|
|
7439
|
+
join21(here, "..", "..", "workflow-data-harness")
|
|
7071
7440
|
];
|
|
7072
7441
|
for (const candidate of candidates) {
|
|
7073
|
-
if (await pathExists3(
|
|
7442
|
+
if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
|
|
7074
7443
|
}
|
|
7075
7444
|
return null;
|
|
7076
7445
|
}
|
|
7077
7446
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
7078
|
-
const manifestPath =
|
|
7447
|
+
const manifestPath = join21(cacheRoot, "package.json");
|
|
7079
7448
|
if (!await pathExists3(manifestPath)) return null;
|
|
7080
7449
|
try {
|
|
7081
|
-
const pkg = JSON.parse(await
|
|
7450
|
+
const pkg = JSON.parse(await readFile19(manifestPath, "utf8"));
|
|
7082
7451
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
7083
7452
|
} catch {
|
|
7084
7453
|
return null;
|
|
@@ -7100,13 +7469,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
7100
7469
|
}
|
|
7101
7470
|
}
|
|
7102
7471
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
7103
|
-
await
|
|
7104
|
-
const staging =
|
|
7472
|
+
await mkdir7(cacheRoot, { recursive: true });
|
|
7473
|
+
const staging = join21(cacheRoot, ".extract");
|
|
7105
7474
|
await rm8(staging, { recursive: true, force: true });
|
|
7106
|
-
await
|
|
7475
|
+
await mkdir7(staging, { recursive: true });
|
|
7107
7476
|
await extract(packageSpec, staging);
|
|
7108
|
-
const packageDir = await pathExists3(
|
|
7109
|
-
if (!await pathExists3(
|
|
7477
|
+
const packageDir = await pathExists3(join21(staging, "harness", "manifests")) ? staging : join21(staging, "package");
|
|
7478
|
+
if (!await pathExists3(join21(packageDir, "harness", "manifests"))) {
|
|
7110
7479
|
throw new WorkflowDataResolutionError(
|
|
7111
7480
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
7112
7481
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -7135,8 +7504,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
7135
7504
|
const packageName = workflowPackageName(family, options.env);
|
|
7136
7505
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
7137
7506
|
const cacheKey = packageSpec.replace("/", "+");
|
|
7138
|
-
const cacheRoot =
|
|
7139
|
-
if (await pathExists3(
|
|
7507
|
+
const cacheRoot = join21(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
7508
|
+
if (await pathExists3(join21(cacheRoot, "harness", "manifests"))) {
|
|
7140
7509
|
if (version === "latest") {
|
|
7141
7510
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
7142
7511
|
if (stale) {
|
|
@@ -7182,9 +7551,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
7182
7551
|
return `\u65E0\u6CD5\u83B7\u53D6\u5DE5\u4F5C\u6D41\u6570\u636E\u5305 ${packageSpec}\uFF1A\u672C\u5730\u7F13\u5B58\u4E0D\u5B58\u5728\u4E14\u83B7\u53D6\u5931\u8D25\u3002\u53EF${hintRoot}\u3002\u539F\u56E0\uFF1A${code !== "" ? code + " " : ""}${detail}`;
|
|
7183
7552
|
}
|
|
7184
7553
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
7185
|
-
const manifestPath =
|
|
7554
|
+
const manifestPath = join21(resourcesRoot, "hunter-workflow-family.json");
|
|
7186
7555
|
if (!await pathExists3(manifestPath)) return {};
|
|
7187
|
-
return JSON.parse(await
|
|
7556
|
+
return JSON.parse(await readFile19(manifestPath, "utf8"));
|
|
7188
7557
|
}
|
|
7189
7558
|
|
|
7190
7559
|
// src/bin.ts
|
|
@@ -7298,6 +7667,12 @@ async function runCli(argv, overrides = {}) {
|
|
|
7298
7667
|
dependencies
|
|
7299
7668
|
);
|
|
7300
7669
|
});
|
|
7670
|
+
program.command("rules-sync").description("\u6536\u655B\u516C\u5171\u89C4\u5219\u3001\u5237\u65B0 Agent \u6295\u5F71\u5E76\u63D0\u70BC\u5386\u53F2\u89C4\u5219\u5019\u9009").option("--agents <csv>").option("--codebuddy-surface <surface>").option("--no-learn", "\u53EA\u540C\u6B65\u89C4\u5219\uFF0C\u4E0D\u8BFB\u53D6\u5386\u53F2 review/test \u8BC1\u636E").option("--json").action(async (options) => {
|
|
7671
|
+
exitCode = await runRulesSync(
|
|
7672
|
+
{ ...program.opts(), ...options },
|
|
7673
|
+
dependencies
|
|
7674
|
+
);
|
|
7675
|
+
});
|
|
7301
7676
|
try {
|
|
7302
7677
|
await program.parseAsync(["node", "hunter-harness", ...argv]);
|
|
7303
7678
|
return exitCode;
|