hunter-harness 0.2.30 → 0.2.31
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/bin.js +401 -164
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -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((resolve13) => setTimeout(resolve13, milliseconds));
|
|
1237
1237
|
});
|
|
1238
1238
|
let lastError;
|
|
1239
1239
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
@@ -2309,18 +2309,39 @@ function portable(path) {
|
|
|
2309
2309
|
function normalizeRuleContent(content) {
|
|
2310
2310
|
return content.replace(/\r\n/g, "\n").trimEnd() + "\n";
|
|
2311
2311
|
}
|
|
2312
|
-
function
|
|
2312
|
+
function parseAgentRule(content) {
|
|
2313
2313
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
2314
|
-
if (!normalized.startsWith("---\n"))
|
|
2315
|
-
return
|
|
2314
|
+
if (!normalized.startsWith("---\n")) {
|
|
2315
|
+
return {
|
|
2316
|
+
canonical: normalizeRuleContent(normalized),
|
|
2317
|
+
globalFrontmatterPrefix: null
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2316
2320
|
const closing = normalized.indexOf("\n---\n", 4);
|
|
2317
|
-
if (closing < 0)
|
|
2318
|
-
return null;
|
|
2321
|
+
if (closing < 0) {
|
|
2322
|
+
return { canonical: null, globalFrontmatterPrefix: null };
|
|
2323
|
+
}
|
|
2319
2324
|
const frontmatter = normalized.slice(4, closing);
|
|
2320
2325
|
if (/^\s*(?:globs?|paths?)\s*:/im.test(frontmatter) || /^\s*alwaysApply\s*:\s*false\s*$/im.test(frontmatter)) {
|
|
2321
|
-
return null;
|
|
2326
|
+
return { canonical: null, globalFrontmatterPrefix: null };
|
|
2322
2327
|
}
|
|
2323
|
-
|
|
2328
|
+
const bodyStart = closing + 5;
|
|
2329
|
+
return {
|
|
2330
|
+
canonical: normalizeRuleContent(normalized.slice(bodyStart).replace(/^\n+/, "")),
|
|
2331
|
+
globalFrontmatterPrefix: normalized.slice(0, bodyStart)
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
function canonicalImportContent(content) {
|
|
2335
|
+
return parseAgentRule(content).canonical;
|
|
2336
|
+
}
|
|
2337
|
+
function projectionContent(target, current, canonical) {
|
|
2338
|
+
if (current === null || !target.toLowerCase().endsWith(".mdc"))
|
|
2339
|
+
return canonical;
|
|
2340
|
+
const parsed = parseAgentRule(current);
|
|
2341
|
+
if (parsed.globalFrontmatterPrefix === null)
|
|
2342
|
+
return canonical;
|
|
2343
|
+
return `${parsed.globalFrontmatterPrefix}
|
|
2344
|
+
${canonical}`;
|
|
2324
2345
|
}
|
|
2325
2346
|
async function collectImportCandidates(root, previous, result) {
|
|
2326
2347
|
const candidates = [];
|
|
@@ -2422,9 +2443,10 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
|
|
|
2422
2443
|
result.unchanged.push(target);
|
|
2423
2444
|
next.targets[target] = current === null ? incomingHash : sha256(current);
|
|
2424
2445
|
} else if (current === null || previous.targets[target] === sha256(current)) {
|
|
2425
|
-
|
|
2446
|
+
const projected = projectionContent(target, current, content);
|
|
2447
|
+
await atomicWrite(path, projected);
|
|
2426
2448
|
result.written.push(target);
|
|
2427
|
-
next.targets[target] =
|
|
2449
|
+
next.targets[target] = sha256(projected);
|
|
2428
2450
|
} else {
|
|
2429
2451
|
result.conflicts.push(target);
|
|
2430
2452
|
next.targets[target] = previous.targets[target] ?? sha256(current);
|
|
@@ -3209,16 +3231,157 @@ async function synchronizeRuleCandidates(projectRoot, options = {}) {
|
|
|
3209
3231
|
};
|
|
3210
3232
|
}
|
|
3211
3233
|
|
|
3212
|
-
// ../core/dist/project/
|
|
3234
|
+
// ../core/dist/project/rule-review.js
|
|
3213
3235
|
import { createHash as createHash7 } from "node:crypto";
|
|
3214
|
-
import { readFile as readFile7,
|
|
3215
|
-
import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
|
|
3236
|
+
import { mkdir as mkdir6, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
|
|
3237
|
+
import { basename as basename4, dirname as dirname5, join as join8, relative as relative2, resolve as resolve7 } from "node:path";
|
|
3238
|
+
var CANDIDATE_PATH2 = ".harness/knowledge/rule-candidates.json";
|
|
3239
|
+
var DECISION_PATH = ".harness/knowledge/rule-decisions.json";
|
|
3240
|
+
var RULES_ROOT2 = ".harness/rules";
|
|
3241
|
+
function sha2563(content) {
|
|
3242
|
+
return createHash7("sha256").update(content).digest("hex");
|
|
3243
|
+
}
|
|
3244
|
+
async function readOptional2(path) {
|
|
3245
|
+
try {
|
|
3246
|
+
return await readFile7(path, "utf8");
|
|
3247
|
+
} catch (error) {
|
|
3248
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
3249
|
+
return null;
|
|
3250
|
+
throw error;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
async function readJson(path, fallback) {
|
|
3254
|
+
const content = await readOptional2(path);
|
|
3255
|
+
if (content === null)
|
|
3256
|
+
return fallback;
|
|
3257
|
+
return JSON.parse(content);
|
|
3258
|
+
}
|
|
3259
|
+
async function atomicWrite3(path, content) {
|
|
3260
|
+
await mkdir6(dirname5(path), { recursive: true });
|
|
3261
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
3262
|
+
await writeFile6(temporary, content, "utf8");
|
|
3263
|
+
await rename5(temporary, path);
|
|
3264
|
+
}
|
|
3265
|
+
function candidateRevision(candidate) {
|
|
3266
|
+
return sha2563(JSON.stringify(candidate));
|
|
3267
|
+
}
|
|
3268
|
+
function portable3(path) {
|
|
3269
|
+
return path.replaceAll("\\", "/");
|
|
3270
|
+
}
|
|
3271
|
+
function ruleTarget2(root, input) {
|
|
3272
|
+
const portablePath = portable3(input);
|
|
3273
|
+
if (!portablePath.startsWith(`${RULES_ROOT2}/`) || !portablePath.toLowerCase().endsWith(".md") || basename4(portablePath) === "") {
|
|
3274
|
+
throw new Error(`RULE_PATCH_PATH_INVALID: ${input}`);
|
|
3275
|
+
}
|
|
3276
|
+
const target = resolve7(root, ...portablePath.split("/"));
|
|
3277
|
+
const rulesRoot = resolve7(root, ...RULES_ROOT2.split("/"));
|
|
3278
|
+
const relativePath = portable3(relative2(rulesRoot, target));
|
|
3279
|
+
if (relativePath === ".." || relativePath.startsWith("../")) {
|
|
3280
|
+
throw new Error(`RULE_PATCH_PATH_INVALID: ${input}`);
|
|
3281
|
+
}
|
|
3282
|
+
return target;
|
|
3283
|
+
}
|
|
3284
|
+
function validateDecision(decision, candidates) {
|
|
3285
|
+
const candidate = candidates.get(decision.candidate_id);
|
|
3286
|
+
if (candidate === void 0) {
|
|
3287
|
+
throw new Error(`RULE_CANDIDATE_UNKNOWN: ${decision.candidate_id}`);
|
|
3288
|
+
}
|
|
3289
|
+
if (candidateRevision(candidate) !== decision.candidate_revision) {
|
|
3290
|
+
throw new Error(`RULE_CANDIDATE_STALE: ${decision.candidate_id}`);
|
|
3291
|
+
}
|
|
3292
|
+
if (decision.dispositions.length === 0 || decision.reason.trim().length < 8) {
|
|
3293
|
+
throw new Error(`RULE_DECISION_INVALID: ${decision.candidate_id}`);
|
|
3294
|
+
}
|
|
3295
|
+
if (decision.dispositions.includes("defer") && decision.review_after === void 0) {
|
|
3296
|
+
throw new Error(`RULE_DECISION_REVIEW_AFTER_REQUIRED: ${decision.candidate_id}`);
|
|
3297
|
+
}
|
|
3298
|
+
if (decision.dispositions.includes("public-rule") !== (decision.rule_patch !== void 0)) {
|
|
3299
|
+
throw new Error(`RULE_PATCH_REQUIRED: ${decision.candidate_id}`);
|
|
3300
|
+
}
|
|
3301
|
+
return candidate;
|
|
3302
|
+
}
|
|
3303
|
+
async function readCandidates(root) {
|
|
3304
|
+
return readJson(join8(root, ...CANDIDATE_PATH2.split("/")), { schema_version: 1, source_hashes: {}, candidates: [] });
|
|
3305
|
+
}
|
|
3306
|
+
async function readDecisions(root) {
|
|
3307
|
+
return readJson(join8(root, ...DECISION_PATH.split("/")), { schema_version: 1, decisions: [] });
|
|
3308
|
+
}
|
|
3309
|
+
async function exportRuleReviewQueue(projectRoot) {
|
|
3310
|
+
const root = resolve7(projectRoot);
|
|
3311
|
+
const candidates = await readCandidates(root);
|
|
3312
|
+
const decisions = await readDecisions(root);
|
|
3313
|
+
const decided = new Set(decisions.decisions.map((decision) => `${decision.candidate_id}\0${decision.candidate_revision}`));
|
|
3314
|
+
const pending = candidates.candidates.flatMap((candidate) => {
|
|
3315
|
+
const revision = candidateRevision(candidate);
|
|
3316
|
+
return decided.has(`${candidate.id}\0${revision}`) ? [] : [{
|
|
3317
|
+
candidate_id: candidate.id,
|
|
3318
|
+
candidate_revision: revision,
|
|
3319
|
+
candidate
|
|
3320
|
+
}];
|
|
3321
|
+
});
|
|
3322
|
+
return {
|
|
3323
|
+
schema_version: 1,
|
|
3324
|
+
pending,
|
|
3325
|
+
decided: candidates.candidates.length - pending.length
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
async function applyRuleReviewDecisions(projectRoot, input) {
|
|
3329
|
+
if (input.schema_version !== 1 || !Array.isArray(input.decisions)) {
|
|
3330
|
+
throw new Error("RULE_DECISIONS_INVALID");
|
|
3331
|
+
}
|
|
3332
|
+
const root = resolve7(projectRoot);
|
|
3333
|
+
const candidateManifest = await readCandidates(root);
|
|
3334
|
+
const candidates = new Map(candidateManifest.candidates.map((candidate) => [
|
|
3335
|
+
candidate.id,
|
|
3336
|
+
candidate
|
|
3337
|
+
]));
|
|
3338
|
+
const plannedPatches = [];
|
|
3339
|
+
for (const decision of input.decisions) {
|
|
3340
|
+
validateDecision(decision, candidates);
|
|
3341
|
+
if (decision.rule_patch === void 0)
|
|
3342
|
+
continue;
|
|
3343
|
+
const target = ruleTarget2(root, decision.rule_patch.target_path);
|
|
3344
|
+
const current = await readOptional2(target);
|
|
3345
|
+
const currentHash = current === null ? null : sha2563(current);
|
|
3346
|
+
if (currentHash !== decision.rule_patch.expected_sha256) {
|
|
3347
|
+
throw new Error(`RULE_PATCH_STALE: ${decision.rule_patch.target_path}`);
|
|
3348
|
+
}
|
|
3349
|
+
plannedPatches.push({ path: target, content: decision.rule_patch.content });
|
|
3350
|
+
}
|
|
3351
|
+
const currentManifest = await readDecisions(root);
|
|
3352
|
+
const replacements = new Map(input.decisions.map((decision) => [
|
|
3353
|
+
`${decision.candidate_id}\0${decision.candidate_revision}`,
|
|
3354
|
+
decision
|
|
3355
|
+
]));
|
|
3356
|
+
for (const decision of currentManifest.decisions) {
|
|
3357
|
+
const key = `${decision.candidate_id}\0${decision.candidate_revision}`;
|
|
3358
|
+
if (!replacements.has(key))
|
|
3359
|
+
replacements.set(key, decision);
|
|
3360
|
+
}
|
|
3361
|
+
const next = {
|
|
3362
|
+
schema_version: 1,
|
|
3363
|
+
decisions: [...replacements.values()].sort((left, right) => left.candidate_id.localeCompare(right.candidate_id) || left.candidate_revision.localeCompare(right.candidate_revision))
|
|
3364
|
+
};
|
|
3365
|
+
for (const patch of plannedPatches)
|
|
3366
|
+
await atomicWrite3(patch.path, patch.content);
|
|
3367
|
+
await atomicWrite3(join8(root, ...DECISION_PATH.split("/")), JSON.stringify(next, null, 2) + "\n");
|
|
3368
|
+
return {
|
|
3369
|
+
applied: plannedPatches.length,
|
|
3370
|
+
recorded: input.decisions.length,
|
|
3371
|
+
path: DECISION_PATH
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
// ../core/dist/project/refresh.js
|
|
3376
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
3377
|
+
import { readFile as readFile8, readdir as readdir5, rmdir } from "node:fs/promises";
|
|
3378
|
+
import { dirname as dirname6, join as join9, resolve as resolve8 } from "node:path";
|
|
3216
3379
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
3217
3380
|
var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
3218
3381
|
var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
|
|
3219
3382
|
async function fileHex2(path) {
|
|
3220
3383
|
try {
|
|
3221
|
-
return
|
|
3384
|
+
return createHash8("sha256").update(await readFile8(path)).digest("hex");
|
|
3222
3385
|
} catch (error) {
|
|
3223
3386
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3224
3387
|
return null;
|
|
@@ -3228,7 +3391,7 @@ async function fileHex2(path) {
|
|
|
3228
3391
|
}
|
|
3229
3392
|
async function readOptionalText(path) {
|
|
3230
3393
|
try {
|
|
3231
|
-
return await
|
|
3394
|
+
return await readFile8(path, "utf8");
|
|
3232
3395
|
} catch (error) {
|
|
3233
3396
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3234
3397
|
return "";
|
|
@@ -3237,7 +3400,7 @@ async function readOptionalText(path) {
|
|
|
3237
3400
|
}
|
|
3238
3401
|
}
|
|
3239
3402
|
async function readInstalledState(root) {
|
|
3240
|
-
const content = await readOptionalText(
|
|
3403
|
+
const content = await readOptionalText(join9(root, INSTALLED_STATE_PATH));
|
|
3241
3404
|
if (content === "") {
|
|
3242
3405
|
return {
|
|
3243
3406
|
profile: null,
|
|
@@ -3307,7 +3470,7 @@ async function readInstalledState(root) {
|
|
|
3307
3470
|
};
|
|
3308
3471
|
}
|
|
3309
3472
|
async function readInstalledAgentConfiguration(projectRoot) {
|
|
3310
|
-
const installed = await readInstalledState(
|
|
3473
|
+
const installed = await readInstalledState(resolve8(projectRoot));
|
|
3311
3474
|
return {
|
|
3312
3475
|
agents: installed.adapters,
|
|
3313
3476
|
profiles: Object.fromEntries(installed.adapters.map((agent) => [
|
|
@@ -3317,7 +3480,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
|
|
|
3317
3480
|
};
|
|
3318
3481
|
}
|
|
3319
3482
|
async function readContextIndexBundleHash(root) {
|
|
3320
|
-
const content = await readOptionalText(
|
|
3483
|
+
const content = await readOptionalText(join9(root, CONTEXT_INDEX_PATH2));
|
|
3321
3484
|
if (content === "")
|
|
3322
3485
|
return null;
|
|
3323
3486
|
try {
|
|
@@ -3329,9 +3492,9 @@ async function readContextIndexBundleHash(root) {
|
|
|
3329
3492
|
}
|
|
3330
3493
|
}
|
|
3331
3494
|
async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
3332
|
-
const boundaries = new Set(boundaryPaths.map((path) =>
|
|
3495
|
+
const boundaries = new Set(boundaryPaths.map((path) => join9(root, path)));
|
|
3333
3496
|
for (const deleted of deletedPaths) {
|
|
3334
|
-
let dir =
|
|
3497
|
+
let dir = dirname6(join9(root, deleted));
|
|
3335
3498
|
while (dir.startsWith(root) && !boundaries.has(dir)) {
|
|
3336
3499
|
let entries;
|
|
3337
3500
|
try {
|
|
@@ -3346,7 +3509,7 @@ async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
|
3346
3509
|
} catch {
|
|
3347
3510
|
break;
|
|
3348
3511
|
}
|
|
3349
|
-
dir =
|
|
3512
|
+
dir = dirname6(dir);
|
|
3350
3513
|
}
|
|
3351
3514
|
}
|
|
3352
3515
|
}
|
|
@@ -3373,7 +3536,7 @@ function sortByTarget(items) {
|
|
|
3373
3536
|
return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
3374
3537
|
}
|
|
3375
3538
|
async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
|
|
3376
|
-
const existing = await readOptionalText(
|
|
3539
|
+
const existing = await readOptionalText(join9(root, CONTEXT_INDEX_PATH2));
|
|
3377
3540
|
let codebase = {
|
|
3378
3541
|
map: ".harness/codebase/map",
|
|
3379
3542
|
status: "missing"
|
|
@@ -3426,7 +3589,7 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
|
|
|
3426
3589
|
}
|
|
3427
3590
|
async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
|
|
3428
3591
|
const path = ".harness/project.yaml";
|
|
3429
|
-
const content = await readOptionalText(
|
|
3592
|
+
const content = await readOptionalText(join9(root, path));
|
|
3430
3593
|
if (content === "")
|
|
3431
3594
|
return null;
|
|
3432
3595
|
const project = parseYaml3(content);
|
|
@@ -3461,11 +3624,11 @@ function mergeTargets(targets) {
|
|
|
3461
3624
|
}).sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
3462
3625
|
}
|
|
3463
3626
|
async function reconcileMarkdownBlock(root, fileName, blockId, content, remove, ops, conflicts, preserved) {
|
|
3464
|
-
const original = await readOptionalText(
|
|
3627
|
+
const original = await readOptionalText(join9(root, fileName));
|
|
3465
3628
|
const synthetic = {
|
|
3466
3629
|
source_path: fileName,
|
|
3467
3630
|
target_path: fileName,
|
|
3468
|
-
sha256:
|
|
3631
|
+
sha256: createHash8("sha256").update(content).digest("hex"),
|
|
3469
3632
|
bytes: new TextEncoder().encode(content)
|
|
3470
3633
|
};
|
|
3471
3634
|
let next;
|
|
@@ -3480,7 +3643,7 @@ async function reconcileMarkdownBlock(root, fileName, blockId, content, remove,
|
|
|
3480
3643
|
next = refreshed.content;
|
|
3481
3644
|
}
|
|
3482
3645
|
} catch {
|
|
3483
|
-
const current = original === "" ? null :
|
|
3646
|
+
const current = original === "" ? null : createHash8("sha256").update(original).digest("hex");
|
|
3484
3647
|
preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3485
3648
|
conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3486
3649
|
return;
|
|
@@ -3500,7 +3663,7 @@ function stateWithoutInstalledAt(value) {
|
|
|
3500
3663
|
return copy;
|
|
3501
3664
|
}
|
|
3502
3665
|
async function refreshProject(options) {
|
|
3503
|
-
const root =
|
|
3666
|
+
const root = resolve8(options.projectRoot);
|
|
3504
3667
|
const installed = await readInstalledState(root);
|
|
3505
3668
|
const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
|
|
3506
3669
|
const selectedAgents = sortHarnessAgents(options.agents);
|
|
@@ -3584,7 +3747,7 @@ async function refreshProject(options) {
|
|
|
3584
3747
|
const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
|
|
3585
3748
|
for (const target of newManaged) {
|
|
3586
3749
|
const incoming = target.sha256;
|
|
3587
|
-
const current = await fileHex2(
|
|
3750
|
+
const current = await fileHex2(join9(root, target.target_path));
|
|
3588
3751
|
if (current === null) {
|
|
3589
3752
|
applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
|
|
3590
3753
|
ops.push({ operation: "add", path: target.target_path, content: target.bytes });
|
|
@@ -3612,7 +3775,7 @@ async function refreshProject(options) {
|
|
|
3612
3775
|
}
|
|
3613
3776
|
}
|
|
3614
3777
|
for (const target of oldOnly) {
|
|
3615
|
-
const current = await fileHex2(
|
|
3778
|
+
const current = await fileHex2(join9(root, target.target_path));
|
|
3616
3779
|
if (current === null) {
|
|
3617
3780
|
continue;
|
|
3618
3781
|
}
|
|
@@ -3655,7 +3818,7 @@ async function refreshProject(options) {
|
|
|
3655
3818
|
const verifyEntries = [];
|
|
3656
3819
|
for (const target of verifyTargets) {
|
|
3657
3820
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3658
|
-
const actual = await fileHex2(
|
|
3821
|
+
const actual = await fileHex2(join9(root, target.target_path));
|
|
3659
3822
|
verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3660
3823
|
if (actual === null) {
|
|
3661
3824
|
verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3688,19 +3851,19 @@ async function refreshProject(options) {
|
|
|
3688
3851
|
owner: "shared",
|
|
3689
3852
|
target_path: "AGENTS.md",
|
|
3690
3853
|
block_id: AGENTS_CORE_BLOCK_ID,
|
|
3691
|
-
content_sha256:
|
|
3854
|
+
content_sha256: createHash8("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3692
3855
|
},
|
|
3693
3856
|
...selectedSet.has("claude-code") ? [{
|
|
3694
3857
|
owner: "claude-code",
|
|
3695
3858
|
target_path: "CLAUDE.md",
|
|
3696
3859
|
block_id: CLAUDE_BLOCK_ID,
|
|
3697
|
-
content_sha256:
|
|
3860
|
+
content_sha256: createHash8("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3698
3861
|
}] : [],
|
|
3699
3862
|
...selectedSet.has("codebuddy") ? [{
|
|
3700
3863
|
owner: "codebuddy",
|
|
3701
3864
|
target_path: "CODEBUDDY.md",
|
|
3702
3865
|
block_id: CODEBUDDY_BLOCK_ID,
|
|
3703
|
-
content_sha256:
|
|
3866
|
+
content_sha256: createHash8("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3704
3867
|
}] : []
|
|
3705
3868
|
].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
|
|
3706
3869
|
const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
|
|
@@ -3716,7 +3879,7 @@ async function refreshProject(options) {
|
|
|
3716
3879
|
files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
|
|
3717
3880
|
managed_blocks: managedBlocks
|
|
3718
3881
|
};
|
|
3719
|
-
const existingState = await readOptionalText(
|
|
3882
|
+
const existingState = await readOptionalText(join9(root, INSTALLED_STATE_PATH));
|
|
3720
3883
|
let existingParsed = null;
|
|
3721
3884
|
try {
|
|
3722
3885
|
existingParsed = existingState === "" ? null : JSON.parse(existingState);
|
|
@@ -3771,7 +3934,7 @@ function buildMarkerCoreHash(text) {
|
|
|
3771
3934
|
}
|
|
3772
3935
|
}
|
|
3773
3936
|
async function collectFreshness(options) {
|
|
3774
|
-
const root =
|
|
3937
|
+
const root = resolve8(options.projectRoot);
|
|
3775
3938
|
const installed = await readInstalledState(root);
|
|
3776
3939
|
const codebuddySurface2 = options.codebuddySurface ?? "both";
|
|
3777
3940
|
const agents = [];
|
|
@@ -3816,18 +3979,18 @@ async function collectFreshness(options) {
|
|
|
3816
3979
|
identity.adapterHash = sha256Bytes(canonicalJson(targets.map((target) => ({ path: target.target_path.replace(/\\/g, "/"), sha256: target.sha256 })).sort((a, b) => a.path.localeCompare(b.path))));
|
|
3817
3980
|
const installedProjection = await Promise.all(targets.map(async (target) => ({
|
|
3818
3981
|
path: target.target_path.replace(/\\/g, "/"),
|
|
3819
|
-
sha256: await fileHex2(
|
|
3982
|
+
sha256: await fileHex2(join9(root, target.target_path))
|
|
3820
3983
|
})));
|
|
3821
3984
|
identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
|
|
3822
3985
|
const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
|
|
3823
3986
|
if (markerTarget !== void 0) {
|
|
3824
|
-
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(
|
|
3987
|
+
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join9(root, markerTarget.target_path)));
|
|
3825
3988
|
}
|
|
3826
3989
|
const mismatchDetails = [];
|
|
3827
3990
|
const contentEntries = [];
|
|
3828
3991
|
for (const target of targets) {
|
|
3829
3992
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3830
|
-
const actual = await fileHex2(
|
|
3993
|
+
const actual = await fileHex2(join9(root, target.target_path));
|
|
3831
3994
|
contentEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3832
3995
|
if (actual === null) {
|
|
3833
3996
|
mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3850,7 +4013,7 @@ async function collectFreshness(options) {
|
|
|
3850
4013
|
const drifted = [];
|
|
3851
4014
|
const missing = [];
|
|
3852
4015
|
for (const target of targets) {
|
|
3853
|
-
const current = await fileHex2(
|
|
4016
|
+
const current = await fileHex2(join9(root, target.target_path));
|
|
3854
4017
|
if (current === null) {
|
|
3855
4018
|
missing.push(target.target_path);
|
|
3856
4019
|
} else if (current !== target.sha256) {
|
|
@@ -4098,11 +4261,11 @@ function rawFindings(content) {
|
|
|
4098
4261
|
if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
|
|
4099
4262
|
continue;
|
|
4100
4263
|
}
|
|
4101
|
-
const
|
|
4264
|
+
const relative5 = match[0].indexOf(value);
|
|
4102
4265
|
findings.push({
|
|
4103
4266
|
ruleId: rule.id,
|
|
4104
4267
|
severity: rule.severity,
|
|
4105
|
-
offset: match.index + Math.max(0,
|
|
4268
|
+
offset: match.index + Math.max(0, relative5),
|
|
4106
4269
|
value
|
|
4107
4270
|
});
|
|
4108
4271
|
}
|
|
@@ -4214,8 +4377,8 @@ function generateProposalPreview(input, scanOptions = {}) {
|
|
|
4214
4377
|
}
|
|
4215
4378
|
|
|
4216
4379
|
// ../core/dist/push/credentials.js
|
|
4217
|
-
import { readFile as
|
|
4218
|
-
import { join as
|
|
4380
|
+
import { readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
4381
|
+
import { join as join10 } from "node:path";
|
|
4219
4382
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
4220
4383
|
var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
|
|
4221
4384
|
var CREDENTIALS_GITIGNORE_LINES = [
|
|
@@ -4285,7 +4448,7 @@ function mergeLocalCredentials(existing, patch) {
|
|
|
4285
4448
|
}
|
|
4286
4449
|
async function readLocalCredentials(projectRoot) {
|
|
4287
4450
|
try {
|
|
4288
|
-
const raw = await
|
|
4451
|
+
const raw = await readFile9(join10(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
|
|
4289
4452
|
return parseLocalCredentials(parseYaml4(raw));
|
|
4290
4453
|
} catch (error) {
|
|
4291
4454
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -4296,13 +4459,13 @@ async function readLocalCredentials(projectRoot) {
|
|
|
4296
4459
|
}
|
|
4297
4460
|
async function writeLocalCredentials(projectRoot, credentials) {
|
|
4298
4461
|
const normalized = validateLocalCredentials(credentials);
|
|
4299
|
-
await
|
|
4462
|
+
await writeFile7(join10(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
|
|
4300
4463
|
}
|
|
4301
4464
|
async function ensureCredentialsGitignore(projectRoot) {
|
|
4302
|
-
const gitignorePath =
|
|
4465
|
+
const gitignorePath = join10(projectRoot, ".gitignore");
|
|
4303
4466
|
let content = "";
|
|
4304
4467
|
try {
|
|
4305
|
-
content = await
|
|
4468
|
+
content = await readFile9(gitignorePath, "utf8");
|
|
4306
4469
|
} catch (error) {
|
|
4307
4470
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
4308
4471
|
throw error;
|
|
@@ -4325,7 +4488,7 @@ async function ensureCredentialsGitignore(projectRoot) {
|
|
|
4325
4488
|
return;
|
|
4326
4489
|
}
|
|
4327
4490
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
4328
|
-
await
|
|
4491
|
+
await writeFile7(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
|
|
4329
4492
|
}
|
|
4330
4493
|
function resolvePushAuth(input) {
|
|
4331
4494
|
const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
|
|
@@ -4353,28 +4516,28 @@ function resolvePushAuth(input) {
|
|
|
4353
4516
|
}
|
|
4354
4517
|
|
|
4355
4518
|
// ../core/dist/push/push.js
|
|
4356
|
-
import { lstat as lstat3, readFile as
|
|
4357
|
-
import { join as
|
|
4519
|
+
import { lstat as lstat3, readFile as readFile13, readdir as readdir6, rm as rm5 } from "node:fs/promises";
|
|
4520
|
+
import { join as join14, relative as relative3, resolve as resolve10 } from "node:path";
|
|
4358
4521
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
4359
4522
|
|
|
4360
4523
|
// ../core/dist/state/baseline.js
|
|
4361
|
-
import { readFile as
|
|
4362
|
-
import { join as
|
|
4524
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
4525
|
+
import { join as join11 } from "node:path";
|
|
4363
4526
|
async function readBaseline(projectRoot) {
|
|
4364
|
-
const content = await
|
|
4527
|
+
const content = await readFile10(join11(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
|
|
4365
4528
|
return baselineManifestSchema.parse(JSON.parse(content));
|
|
4366
4529
|
}
|
|
4367
4530
|
|
|
4368
4531
|
// ../core/dist/state/locks.js
|
|
4369
4532
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4370
|
-
import { readFile as
|
|
4371
|
-
import { join as
|
|
4533
|
+
import { readFile as readFile11, rename as rename6, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
|
|
4534
|
+
import { join as join12 } from "node:path";
|
|
4372
4535
|
async function readLock(path) {
|
|
4373
|
-
return JSON.parse(await
|
|
4536
|
+
return JSON.parse(await readFile11(path, "utf8"));
|
|
4374
4537
|
}
|
|
4375
4538
|
async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
4376
4539
|
const layout = await ensureStateLayout(projectRoot);
|
|
4377
|
-
const lockPath =
|
|
4540
|
+
const lockPath = join12(layout.locks, "protocol.lock");
|
|
4378
4541
|
const now = options.now ?? Date.now();
|
|
4379
4542
|
const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
|
|
4380
4543
|
const record = {
|
|
@@ -4387,7 +4550,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
4387
4550
|
};
|
|
4388
4551
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4389
4552
|
try {
|
|
4390
|
-
await
|
|
4553
|
+
await writeFile8(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
4391
4554
|
return {
|
|
4392
4555
|
path: lockPath,
|
|
4393
4556
|
operation,
|
|
@@ -4409,15 +4572,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
4409
4572
|
if (now - current.heartbeat_at_ms <= staleAfterMs) {
|
|
4410
4573
|
throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
|
|
4411
4574
|
}
|
|
4412
|
-
await
|
|
4575
|
+
await rename6(lockPath, lockPath + ".stale-" + randomUUID3());
|
|
4413
4576
|
}
|
|
4414
4577
|
}
|
|
4415
4578
|
throw new Error("unable to acquire protocol lock");
|
|
4416
4579
|
}
|
|
4417
4580
|
|
|
4418
4581
|
// ../core/dist/sync/synchronize.js
|
|
4419
|
-
import { lstat as lstat2, readFile as
|
|
4420
|
-
import { join as
|
|
4582
|
+
import { lstat as lstat2, readFile as readFile12, rm as rm4 } from "node:fs/promises";
|
|
4583
|
+
import { join as join13, resolve as resolve9 } from "node:path";
|
|
4421
4584
|
|
|
4422
4585
|
// ../core/dist/update/conflicts.js
|
|
4423
4586
|
function operationTargetPath(operation) {
|
|
@@ -4700,8 +4863,8 @@ async function pathExists(path) {
|
|
|
4700
4863
|
}
|
|
4701
4864
|
}
|
|
4702
4865
|
async function optionalContent(root, path) {
|
|
4703
|
-
const full =
|
|
4704
|
-
return await pathExists(full) ?
|
|
4866
|
+
const full = join13(root, path);
|
|
4867
|
+
return await pathExists(full) ? readFile12(full, "utf8") : null;
|
|
4705
4868
|
}
|
|
4706
4869
|
function manifestPayloadHash(manifest) {
|
|
4707
4870
|
const payload = { ...manifest };
|
|
@@ -4713,10 +4876,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
|
|
|
4713
4876
|
return null;
|
|
4714
4877
|
}
|
|
4715
4878
|
const hash = operation.content_sha256;
|
|
4716
|
-
const cacheRoot =
|
|
4717
|
-
const cachePath =
|
|
4879
|
+
const cacheRoot = join13(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4880
|
+
const cachePath = join13(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
4718
4881
|
if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
|
|
4719
|
-
return
|
|
4882
|
+
return readFile12(cachePath, "utf8");
|
|
4720
4883
|
}
|
|
4721
4884
|
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
4722
4885
|
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
@@ -4822,7 +4985,7 @@ async function planSingleArtifact(root, baseline, manifest, client, requestId, d
|
|
|
4822
4985
|
});
|
|
4823
4986
|
}
|
|
4824
4987
|
async function saveConflictReport(root, requestId, manifest, plan) {
|
|
4825
|
-
const reportPath =
|
|
4988
|
+
const reportPath = join13(root, ".harness", "reports", "conflicts-" + requestId + ".json");
|
|
4826
4989
|
await atomicWriteJson(reportPath, {
|
|
4827
4990
|
schema_version: 1,
|
|
4828
4991
|
request_id: requestId,
|
|
@@ -4833,7 +4996,7 @@ async function saveConflictReport(root, requestId, manifest, plan) {
|
|
|
4833
4996
|
});
|
|
4834
4997
|
}
|
|
4835
4998
|
async function synchronizeArtifacts(options, initialBaseline) {
|
|
4836
|
-
const root =
|
|
4999
|
+
const root = resolve9(options.projectRoot);
|
|
4837
5000
|
const projectId = options.project.project.project_id;
|
|
4838
5001
|
if (projectId === null) {
|
|
4839
5002
|
throw new Error("PROJECT_NOT_BOUND");
|
|
@@ -4940,7 +5103,7 @@ async function synchronizeArtifacts(options, initialBaseline) {
|
|
|
4940
5103
|
}
|
|
4941
5104
|
continue;
|
|
4942
5105
|
}
|
|
4943
|
-
await atomicWriteJson(
|
|
5106
|
+
await atomicWriteJson(join13(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4944
5107
|
const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
|
|
4945
5108
|
try {
|
|
4946
5109
|
const nextBaseline = applyBaselineUpdates(baseline, plan);
|
|
@@ -5010,7 +5173,7 @@ function baselineEntryFromOperation(manifest, operation, localContent) {
|
|
|
5010
5173
|
};
|
|
5011
5174
|
}
|
|
5012
5175
|
async function advanceBaselineFromArtifact(options, baseline) {
|
|
5013
|
-
const root =
|
|
5176
|
+
const root = resolve9(options.projectRoot);
|
|
5014
5177
|
if (options.manifest.project_version === null) {
|
|
5015
5178
|
throw new Error("artifact manifest missing project_version");
|
|
5016
5179
|
}
|
|
@@ -5112,19 +5275,19 @@ async function walkFiles(root, current, output) {
|
|
|
5112
5275
|
return;
|
|
5113
5276
|
}
|
|
5114
5277
|
for (const item2 of await readdir6(current, { withFileTypes: true })) {
|
|
5115
|
-
const path =
|
|
5278
|
+
const path = join14(current, item2.name);
|
|
5116
5279
|
if (item2.isSymbolicLink()) {
|
|
5117
5280
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
5118
5281
|
}
|
|
5119
5282
|
if (item2.isDirectory()) {
|
|
5120
5283
|
await walkFiles(root, path, output);
|
|
5121
5284
|
} else if (item2.isFile()) {
|
|
5122
|
-
output.push(normalizeManagedPath(
|
|
5285
|
+
output.push(normalizeManagedPath(relative3(root, path).replaceAll("\\", "/")));
|
|
5123
5286
|
}
|
|
5124
5287
|
}
|
|
5125
5288
|
}
|
|
5126
5289
|
async function walkArchiveSummaries(root, output) {
|
|
5127
|
-
const archiveRoot =
|
|
5290
|
+
const archiveRoot = join14(root, ".harness", "archive");
|
|
5128
5291
|
if (!await exists3(archiveRoot))
|
|
5129
5292
|
return;
|
|
5130
5293
|
for (const item2 of await readdir6(archiveRoot, { withFileTypes: true })) {
|
|
@@ -5133,7 +5296,7 @@ async function walkArchiveSummaries(root, output) {
|
|
|
5133
5296
|
}
|
|
5134
5297
|
if (!item2.isDirectory())
|
|
5135
5298
|
continue;
|
|
5136
|
-
const summaryPath =
|
|
5299
|
+
const summaryPath = join14(archiveRoot, item2.name, "reports", "final", "summary-data.json");
|
|
5137
5300
|
try {
|
|
5138
5301
|
const stats = await lstat3(summaryPath);
|
|
5139
5302
|
if (stats.isSymbolicLink()) {
|
|
@@ -5146,7 +5309,7 @@ async function walkArchiveSummaries(root, output) {
|
|
|
5146
5309
|
continue;
|
|
5147
5310
|
throw error;
|
|
5148
5311
|
}
|
|
5149
|
-
const relativePath = normalizeManagedPath(
|
|
5312
|
+
const relativePath = normalizeManagedPath(relative3(root, summaryPath).replaceAll("\\", "/"));
|
|
5150
5313
|
if (ARCHIVE_SUMMARY_PATH.test(relativePath)) {
|
|
5151
5314
|
output.push(relativePath);
|
|
5152
5315
|
}
|
|
@@ -5163,17 +5326,17 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
5163
5326
|
return;
|
|
5164
5327
|
for (const item2 of await readdir6(directory, { withFileTypes: true })) {
|
|
5165
5328
|
if (item2.name.startsWith("harness-")) {
|
|
5166
|
-
const path =
|
|
5329
|
+
const path = join14(directory, item2.name);
|
|
5167
5330
|
if (item2.isDirectory()) {
|
|
5168
5331
|
await walkFiles(root, path, output);
|
|
5169
5332
|
} else if (item2.isFile()) {
|
|
5170
|
-
output.push(normalizeManagedPath(
|
|
5333
|
+
output.push(normalizeManagedPath(relative3(root, path).replaceAll("\\", "/")));
|
|
5171
5334
|
}
|
|
5172
5335
|
}
|
|
5173
5336
|
}
|
|
5174
5337
|
}
|
|
5175
5338
|
async function managedFiles(projectRoot, project) {
|
|
5176
|
-
const root =
|
|
5339
|
+
const root = resolve10(projectRoot);
|
|
5177
5340
|
const paths = [];
|
|
5178
5341
|
const adapters = getAdapters(enabledHarnessAgents(project));
|
|
5179
5342
|
const managedFiles2 = [
|
|
@@ -5182,26 +5345,26 @@ async function managedFiles(projectRoot, project) {
|
|
|
5182
5345
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
5183
5346
|
];
|
|
5184
5347
|
for (const path of managedFiles2) {
|
|
5185
|
-
if (await exists3(
|
|
5348
|
+
if (await exists3(join14(root, path))) {
|
|
5186
5349
|
paths.push(path);
|
|
5187
5350
|
}
|
|
5188
5351
|
}
|
|
5189
5352
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
5190
|
-
await walkFiles(root,
|
|
5353
|
+
await walkFiles(root, join14(root, path), paths);
|
|
5191
5354
|
}
|
|
5192
5355
|
await walkArchiveSummaries(root, paths);
|
|
5193
5356
|
for (const adapter of adapters) {
|
|
5194
5357
|
if (adapter.rulesRoot !== null) {
|
|
5195
|
-
await walkHarnessEntries(root,
|
|
5358
|
+
await walkHarnessEntries(root, join14(root, adapter.rulesRoot), paths);
|
|
5196
5359
|
}
|
|
5197
|
-
await walkHarnessEntries(root,
|
|
5360
|
+
await walkHarnessEntries(root, join14(root, adapter.skillsRoot), paths);
|
|
5198
5361
|
if (adapter.agentsRoot !== null) {
|
|
5199
|
-
await walkHarnessEntries(root,
|
|
5362
|
+
await walkHarnessEntries(root, join14(root, adapter.agentsRoot), paths);
|
|
5200
5363
|
}
|
|
5201
5364
|
}
|
|
5202
5365
|
const result = {};
|
|
5203
5366
|
for (const path of [...new Set(paths)].sort()) {
|
|
5204
|
-
result[path] = await
|
|
5367
|
+
result[path] = await readFile13(join14(root, path), "utf8");
|
|
5205
5368
|
}
|
|
5206
5369
|
return result;
|
|
5207
5370
|
}
|
|
@@ -5210,14 +5373,14 @@ function proposalBaseline(baseline) {
|
|
|
5210
5373
|
}
|
|
5211
5374
|
async function readProject(root) {
|
|
5212
5375
|
try {
|
|
5213
|
-
return projectConfigSchema.parse(parseYaml5(await
|
|
5376
|
+
return projectConfigSchema.parse(parseYaml5(await readFile13(join14(root, ".harness", "project.yaml"), "utf8")));
|
|
5214
5377
|
} catch {
|
|
5215
5378
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
5216
5379
|
}
|
|
5217
5380
|
}
|
|
5218
5381
|
async function readOptionalJson(path) {
|
|
5219
5382
|
try {
|
|
5220
|
-
return JSON.parse(await
|
|
5383
|
+
return JSON.parse(await readFile13(path, "utf8"));
|
|
5221
5384
|
} catch (error) {
|
|
5222
5385
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5223
5386
|
return null;
|
|
@@ -5229,7 +5392,7 @@ async function clientIdFor(root, explicit) {
|
|
|
5229
5392
|
if (explicit !== void 0) {
|
|
5230
5393
|
return explicit;
|
|
5231
5394
|
}
|
|
5232
|
-
const path =
|
|
5395
|
+
const path = join14(root, ".harness", "state", "local", "client.json");
|
|
5233
5396
|
const existing = await readOptionalJson(path);
|
|
5234
5397
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
5235
5398
|
return existing.client_id;
|
|
@@ -5425,7 +5588,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
5425
5588
|
return { project: nextProject, baseline: nextBaseline };
|
|
5426
5589
|
}
|
|
5427
5590
|
async function pushProject(options) {
|
|
5428
|
-
const root =
|
|
5591
|
+
const root = resolve10(options.projectRoot);
|
|
5429
5592
|
let project = await readProject(root);
|
|
5430
5593
|
let baseline = await readBaseline(root);
|
|
5431
5594
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
@@ -5491,7 +5654,7 @@ async function pushProject(options) {
|
|
|
5491
5654
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
5492
5655
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
5493
5656
|
}
|
|
5494
|
-
const workflowPath =
|
|
5657
|
+
const workflowPath = join14(root, ".harness", "state", "local", "push-workflow.json");
|
|
5495
5658
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
5496
5659
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
5497
5660
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -5646,7 +5809,7 @@ async function pushProject(options) {
|
|
|
5646
5809
|
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
5647
5810
|
}
|
|
5648
5811
|
}
|
|
5649
|
-
await atomicWriteJson(
|
|
5812
|
+
await atomicWriteJson(join14(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
5650
5813
|
schema_version: 1,
|
|
5651
5814
|
request_id: requestId,
|
|
5652
5815
|
project_id: projectId,
|
|
@@ -5691,8 +5854,8 @@ async function pushProject(options) {
|
|
|
5691
5854
|
}
|
|
5692
5855
|
|
|
5693
5856
|
// ../core/dist/state/cleanup.js
|
|
5694
|
-
import { readFile as
|
|
5695
|
-
import { join as
|
|
5857
|
+
import { readFile as readFile14, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
5858
|
+
import { join as join15 } from "node:path";
|
|
5696
5859
|
function isSafeEntryName(name) {
|
|
5697
5860
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
5698
5861
|
}
|
|
@@ -5716,7 +5879,7 @@ async function cleanupProject(options) {
|
|
|
5716
5879
|
continue;
|
|
5717
5880
|
let journal;
|
|
5718
5881
|
try {
|
|
5719
|
-
journal = JSON.parse(await
|
|
5882
|
+
journal = JSON.parse(await readFile14(join15(layout.transactions, name, "journal.json"), "utf8"));
|
|
5720
5883
|
} catch {
|
|
5721
5884
|
continue;
|
|
5722
5885
|
}
|
|
@@ -5734,7 +5897,7 @@ async function cleanupProject(options) {
|
|
|
5734
5897
|
continue;
|
|
5735
5898
|
pruned.push(entry.id);
|
|
5736
5899
|
if (!options.dryRun) {
|
|
5737
|
-
await rm6(
|
|
5900
|
+
await rm6(join15(layout.transactions, entry.id), { recursive: true, force: true });
|
|
5738
5901
|
}
|
|
5739
5902
|
}
|
|
5740
5903
|
}
|
|
@@ -5743,7 +5906,7 @@ async function cleanupProject(options) {
|
|
|
5743
5906
|
continue;
|
|
5744
5907
|
removedCache.push(name);
|
|
5745
5908
|
if (!options.dryRun) {
|
|
5746
|
-
await rm6(
|
|
5909
|
+
await rm6(join15(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
5747
5910
|
}
|
|
5748
5911
|
}
|
|
5749
5912
|
return {
|
|
@@ -5803,10 +5966,10 @@ var SKILL_TARGET_AGENTS = Object.freeze([
|
|
|
5803
5966
|
]);
|
|
5804
5967
|
|
|
5805
5968
|
// ../core/dist/transaction/recovery.js
|
|
5806
|
-
import { readFile as
|
|
5807
|
-
import { join as
|
|
5969
|
+
import { readFile as readFile15, readdir as readdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
5970
|
+
import { join as join16 } from "node:path";
|
|
5808
5971
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
5809
|
-
const journal = JSON.parse(await
|
|
5972
|
+
const journal = JSON.parse(await readFile15(join16(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
5810
5973
|
if (journal.state === "committed") {
|
|
5811
5974
|
return { transactionId, status: "committed" };
|
|
5812
5975
|
}
|
|
@@ -5833,7 +5996,7 @@ async function listTransactions(projectRoot) {
|
|
|
5833
5996
|
const transactions = [];
|
|
5834
5997
|
for (const transactionId of names) {
|
|
5835
5998
|
try {
|
|
5836
|
-
const journal = JSON.parse(await
|
|
5999
|
+
const journal = JSON.parse(await readFile15(join16(root, transactionId, "journal.json"), "utf8"));
|
|
5837
6000
|
transactions.push({
|
|
5838
6001
|
transactionId,
|
|
5839
6002
|
kind: journal.kind,
|
|
@@ -5864,11 +6027,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5864
6027
|
if (latest === void 0) {
|
|
5865
6028
|
throw new Error("no committed update transaction is available for rollback");
|
|
5866
6029
|
}
|
|
5867
|
-
const transactionRoot =
|
|
5868
|
-
const journal = JSON.parse(await
|
|
5869
|
-
const after = JSON.parse(await
|
|
6030
|
+
const transactionRoot = join16(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
6031
|
+
const journal = JSON.parse(await readFile15(join16(transactionRoot, "journal.json"), "utf8"));
|
|
6032
|
+
const after = JSON.parse(await readFile15(join16(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
5870
6033
|
for (const entry of after) {
|
|
5871
|
-
const target =
|
|
6034
|
+
const target = join16(projectRoot, entry.path);
|
|
5872
6035
|
const exists6 = await pathExists2(target);
|
|
5873
6036
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
5874
6037
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
@@ -5881,10 +6044,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5881
6044
|
continue;
|
|
5882
6045
|
}
|
|
5883
6046
|
seen.add(snapshot.path);
|
|
5884
|
-
const target =
|
|
6047
|
+
const target = join16(projectRoot, snapshot.path);
|
|
5885
6048
|
const exists6 = await pathExists2(target);
|
|
5886
6049
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
5887
|
-
const content = await
|
|
6050
|
+
const content = await readFile15(join16(transactionRoot, "before", snapshot.snapshot_name));
|
|
5888
6051
|
operations.push({
|
|
5889
6052
|
operation: exists6 ? "modify" : "add",
|
|
5890
6053
|
path: snapshot.path,
|
|
@@ -5913,7 +6076,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5913
6076
|
if (!removable) {
|
|
5914
6077
|
continue;
|
|
5915
6078
|
}
|
|
5916
|
-
await rm7(
|
|
6079
|
+
await rm7(join16(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
5917
6080
|
recursive: true,
|
|
5918
6081
|
force: true
|
|
5919
6082
|
});
|
|
@@ -5923,8 +6086,8 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5923
6086
|
}
|
|
5924
6087
|
|
|
5925
6088
|
// ../core/dist/update/update.js
|
|
5926
|
-
import { readFile as
|
|
5927
|
-
import { join as
|
|
6089
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
6090
|
+
import { join as join17, resolve as resolve11 } from "node:path";
|
|
5928
6091
|
import { parse as parseYaml8 } from "yaml";
|
|
5929
6092
|
var UpdateWorkflowError = class extends Error {
|
|
5930
6093
|
exitCode;
|
|
@@ -5937,10 +6100,10 @@ var UpdateWorkflowError = class extends Error {
|
|
|
5937
6100
|
}
|
|
5938
6101
|
};
|
|
5939
6102
|
async function updateProject(options) {
|
|
5940
|
-
const root =
|
|
6103
|
+
const root = resolve11(options.projectRoot);
|
|
5941
6104
|
let project;
|
|
5942
6105
|
try {
|
|
5943
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
6106
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile16(join17(root, ".harness", "project.yaml"), "utf8")));
|
|
5944
6107
|
} catch {
|
|
5945
6108
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
5946
6109
|
}
|
|
@@ -6030,8 +6193,8 @@ async function updateProject(options) {
|
|
|
6030
6193
|
}
|
|
6031
6194
|
|
|
6032
6195
|
// src/config/init-config.ts
|
|
6033
|
-
import { isAbsolute as isAbsolute2, join as
|
|
6034
|
-
import { readFile as
|
|
6196
|
+
import { isAbsolute as isAbsolute2, join as join18 } from "node:path";
|
|
6197
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
6035
6198
|
var InitConfigurationError = class extends Error {
|
|
6036
6199
|
exitCode;
|
|
6037
6200
|
code;
|
|
@@ -6114,9 +6277,9 @@ function hasOwn(record, key) {
|
|
|
6114
6277
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
6115
6278
|
let fileConfig = {};
|
|
6116
6279
|
if (flags.config !== void 0) {
|
|
6117
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
6280
|
+
const path = isAbsolute2(flags.config) ? flags.config : join18(cwd, flags.config);
|
|
6118
6281
|
try {
|
|
6119
|
-
fileConfig = JSON.parse(await
|
|
6282
|
+
fileConfig = JSON.parse(await readFile17(path, "utf8"));
|
|
6120
6283
|
} catch (error) {
|
|
6121
6284
|
throw new InitConfigurationError(
|
|
6122
6285
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -6198,8 +6361,8 @@ function serializeCliResult(result) {
|
|
|
6198
6361
|
}
|
|
6199
6362
|
|
|
6200
6363
|
// src/config/codebuddy-setup.ts
|
|
6201
|
-
import { mkdir as
|
|
6202
|
-
import { basename as
|
|
6364
|
+
import { mkdir as mkdir7, readFile as readFile18, readdir as readdir9, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
|
|
6365
|
+
import { basename as basename5, dirname as dirname7, extname as extname2, join as join19 } from "node:path";
|
|
6203
6366
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
6204
6367
|
"harness-general.md",
|
|
6205
6368
|
"harness-general.mdc",
|
|
@@ -6218,7 +6381,7 @@ async function exists4(path) {
|
|
|
6218
6381
|
}
|
|
6219
6382
|
async function readJsonObject(path) {
|
|
6220
6383
|
try {
|
|
6221
|
-
const parsed = JSON.parse(await
|
|
6384
|
+
const parsed = JSON.parse(await readFile18(path, "utf8"));
|
|
6222
6385
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
6223
6386
|
} catch (error) {
|
|
6224
6387
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
@@ -6226,7 +6389,7 @@ async function readJsonObject(path) {
|
|
|
6226
6389
|
}
|
|
6227
6390
|
}
|
|
6228
6391
|
async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
6229
|
-
const rulesRoot =
|
|
6392
|
+
const rulesRoot = join19(projectRoot, ".claude", "rules");
|
|
6230
6393
|
let ruleNames = [];
|
|
6231
6394
|
try {
|
|
6232
6395
|
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();
|
|
@@ -6237,11 +6400,11 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
|
6237
6400
|
const currentClaudeRules = [];
|
|
6238
6401
|
const conflictingClaudeRules = [];
|
|
6239
6402
|
for (const name of ruleNames) {
|
|
6240
|
-
const sourceContent = await
|
|
6403
|
+
const sourceContent = await readFile18(join19(rulesRoot, name), "utf8");
|
|
6241
6404
|
const targetContents = await Promise.all(
|
|
6242
6405
|
destinationTargets(projectRoot, surface, name).map(async (target) => {
|
|
6243
6406
|
try {
|
|
6244
|
-
return await
|
|
6407
|
+
return await readFile18(target, "utf8");
|
|
6245
6408
|
} catch (error) {
|
|
6246
6409
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
6247
6410
|
throw error;
|
|
@@ -6255,22 +6418,22 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
|
6255
6418
|
currentClaudeRules.push(name);
|
|
6256
6419
|
}
|
|
6257
6420
|
}
|
|
6258
|
-
const mcp = await readJsonObject(
|
|
6421
|
+
const mcp = await readJsonObject(join19(projectRoot, ".mcp.json"));
|
|
6259
6422
|
const servers = mcp?.mcpServers;
|
|
6260
6423
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
6261
6424
|
return {
|
|
6262
6425
|
claudeRules,
|
|
6263
6426
|
currentClaudeRules,
|
|
6264
6427
|
conflictingClaudeRules,
|
|
6265
|
-
hasCodeGraphIndex: await exists4(
|
|
6428
|
+
hasCodeGraphIndex: await exists4(join19(projectRoot, ".codegraph")),
|
|
6266
6429
|
codeGraphConfigured: configured
|
|
6267
6430
|
};
|
|
6268
6431
|
}
|
|
6269
6432
|
function destinationTargets(root, surface, name) {
|
|
6270
|
-
const stem =
|
|
6433
|
+
const stem = basename5(name, extname2(name));
|
|
6271
6434
|
const targets = [];
|
|
6272
|
-
if (surface !== "cli") targets.push(
|
|
6273
|
-
if (surface !== "ide") targets.push(
|
|
6435
|
+
if (surface !== "cli") targets.push(join19(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
6436
|
+
if (surface !== "ide") targets.push(join19(root, ".codebuddy", "rules", `${stem}.md`));
|
|
6274
6437
|
return targets;
|
|
6275
6438
|
}
|
|
6276
6439
|
async function applyCodeBuddySetup(options) {
|
|
@@ -6284,8 +6447,8 @@ async function applyCodeBuddySetup(options) {
|
|
|
6284
6447
|
if (options.syncClaudeRules) {
|
|
6285
6448
|
const plan = await inspectCodeBuddySetup(options.projectRoot, options.surface);
|
|
6286
6449
|
for (const name of plan.claudeRules) {
|
|
6287
|
-
const source =
|
|
6288
|
-
const content = await
|
|
6450
|
+
const source = join19(options.projectRoot, ".claude", "rules", name);
|
|
6451
|
+
const content = await readFile18(source, "utf8");
|
|
6289
6452
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
6290
6453
|
result.skippedSensitive.push(name);
|
|
6291
6454
|
continue;
|
|
@@ -6295,14 +6458,14 @@ async function applyCodeBuddySetup(options) {
|
|
|
6295
6458
|
result.preserved.push(target);
|
|
6296
6459
|
continue;
|
|
6297
6460
|
}
|
|
6298
|
-
await
|
|
6299
|
-
await
|
|
6461
|
+
await mkdir7(dirname7(target), { recursive: true });
|
|
6462
|
+
await writeFile9(target, content, { encoding: "utf8", flag: "wx" });
|
|
6300
6463
|
result.copied.push(target);
|
|
6301
6464
|
}
|
|
6302
6465
|
}
|
|
6303
6466
|
}
|
|
6304
6467
|
if (options.configureCodeGraph) {
|
|
6305
|
-
const path =
|
|
6468
|
+
const path = join19(options.projectRoot, ".mcp.json");
|
|
6306
6469
|
const current = await readJsonObject(path);
|
|
6307
6470
|
if (current === null) {
|
|
6308
6471
|
result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
|
|
@@ -6317,7 +6480,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
6317
6480
|
codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
|
|
6318
6481
|
}
|
|
6319
6482
|
};
|
|
6320
|
-
await
|
|
6483
|
+
await writeFile9(path, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
6321
6484
|
result.mcpUpdated = true;
|
|
6322
6485
|
}
|
|
6323
6486
|
}
|
|
@@ -6326,13 +6489,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
6326
6489
|
}
|
|
6327
6490
|
|
|
6328
6491
|
// src/commands/refresh.ts
|
|
6329
|
-
import { readFile as
|
|
6330
|
-
import { join as
|
|
6492
|
+
import { readFile as readFile19 } from "node:fs/promises";
|
|
6493
|
+
import { join as join20 } from "node:path";
|
|
6331
6494
|
import { parse as parseYaml9 } from "yaml";
|
|
6332
6495
|
async function detectProject(root) {
|
|
6333
6496
|
let content;
|
|
6334
6497
|
try {
|
|
6335
|
-
content = await
|
|
6498
|
+
content = await readFile19(join20(root, ".harness", "project.yaml"), "utf8");
|
|
6336
6499
|
} catch (error) {
|
|
6337
6500
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
6338
6501
|
return { status: "absent" };
|
|
@@ -7195,6 +7358,7 @@ async function runRulesSync(options, dependencies) {
|
|
|
7195
7358
|
configuredSurface(detection.config, options.codebuddySurface)
|
|
7196
7359
|
);
|
|
7197
7360
|
const learning = options.learn === false ? null : await synchronizeRuleCandidates(dependencies.cwd);
|
|
7361
|
+
const ruleReview = learning === null ? null : await exportRuleReviewQueue(dependencies.cwd);
|
|
7198
7362
|
const exitCode = projections.conflicts.length > 0 ? 5 : 0;
|
|
7199
7363
|
const payload = {
|
|
7200
7364
|
schema_version: 1,
|
|
@@ -7211,7 +7375,8 @@ async function runRulesSync(options, dependencies) {
|
|
|
7211
7375
|
unchanged: projections.unchanged.length,
|
|
7212
7376
|
conflicts: projections.conflicts.length,
|
|
7213
7377
|
agent_specific: projections.agent_specific.length,
|
|
7214
|
-
rule_candidates: learning?.candidates ?? 0
|
|
7378
|
+
rule_candidates: learning?.candidates ?? 0,
|
|
7379
|
+
rule_review_pending: ruleReview?.pending.length ?? 0
|
|
7215
7380
|
},
|
|
7216
7381
|
items: [
|
|
7217
7382
|
...projections.migrated.map((path) => ({ path, status: "migrated" })),
|
|
@@ -7222,6 +7387,12 @@ async function runRulesSync(options, dependencies) {
|
|
|
7222
7387
|
status: learning.changed ? "updated" : "unchanged",
|
|
7223
7388
|
candidates: learning.candidates,
|
|
7224
7389
|
scanned: learning.scanned
|
|
7390
|
+
}],
|
|
7391
|
+
...ruleReview === null ? [] : [{
|
|
7392
|
+
path: ".harness/knowledge/rule-decisions.json",
|
|
7393
|
+
status: ruleReview.pending.length > 0 ? "pending-review" : "current",
|
|
7394
|
+
pending: ruleReview.pending.length,
|
|
7395
|
+
decided: ruleReview.decided
|
|
7225
7396
|
}]
|
|
7226
7397
|
],
|
|
7227
7398
|
warnings: [
|
|
@@ -7246,9 +7417,69 @@ async function runRulesSync(options, dependencies) {
|
|
|
7246
7417
|
}
|
|
7247
7418
|
}
|
|
7248
7419
|
|
|
7420
|
+
// src/commands/rules-review.ts
|
|
7421
|
+
import { readFile as readFile20 } from "node:fs/promises";
|
|
7422
|
+
import { resolve as resolve12 } from "node:path";
|
|
7423
|
+
async function runRulesReview(options, dependencies) {
|
|
7424
|
+
const detection = await detectProject(dependencies.cwd);
|
|
7425
|
+
if (detection.status === "absent") {
|
|
7426
|
+
dependencies.stderr("\u5C1A\u672A\u521D\u59CB\u5316 Hunter Harness\uFF1B\u8BF7\u5148\u8FD0\u884C `hunter-harness`\u3002\n");
|
|
7427
|
+
return 3;
|
|
7428
|
+
}
|
|
7429
|
+
if (detection.status === "invalid") {
|
|
7430
|
+
dependencies.stderr("PROJECT_CONFIG_INVALID\uFF1A.harness/project.yaml \u65E0\u6548\n");
|
|
7431
|
+
return 3;
|
|
7432
|
+
}
|
|
7433
|
+
try {
|
|
7434
|
+
const queue = await exportRuleReviewQueue(dependencies.cwd);
|
|
7435
|
+
let summary;
|
|
7436
|
+
let items;
|
|
7437
|
+
if (options.apply === void 0) {
|
|
7438
|
+
summary = { pending: queue.pending.length, decided: queue.decided };
|
|
7439
|
+
items = queue.pending;
|
|
7440
|
+
} else {
|
|
7441
|
+
const input = JSON.parse(
|
|
7442
|
+
await readFile20(resolve12(dependencies.cwd, options.apply), "utf8")
|
|
7443
|
+
);
|
|
7444
|
+
const result = await applyRuleReviewDecisions(dependencies.cwd, input);
|
|
7445
|
+
summary = { applied: result.applied, recorded: result.recorded };
|
|
7446
|
+
items = [{ path: result.path, status: "updated" }];
|
|
7447
|
+
}
|
|
7448
|
+
const payload = {
|
|
7449
|
+
schema_version: 1,
|
|
7450
|
+
command: "rules-review",
|
|
7451
|
+
request_id: uuidV7(),
|
|
7452
|
+
dry_run: false,
|
|
7453
|
+
ok: true,
|
|
7454
|
+
exit_code: 0,
|
|
7455
|
+
project_id: detection.config.project.project_id,
|
|
7456
|
+
summary,
|
|
7457
|
+
items,
|
|
7458
|
+
warnings: [],
|
|
7459
|
+
errors: []
|
|
7460
|
+
};
|
|
7461
|
+
if (options.json === true) dependencies.stdout(serializeCliResult(payload));
|
|
7462
|
+
else if (options.apply === void 0) {
|
|
7463
|
+
dependencies.stdout(
|
|
7464
|
+
`\u516C\u5171\u89C4\u5219\u5019\u9009\uFF1A\u5F85\u8BC4\u5BA1 ${summary.pending ?? 0}\uFF0C\u5DF2\u51B3\u5B9A ${summary.decided ?? 0}\u3002
|
|
7465
|
+
`
|
|
7466
|
+
);
|
|
7467
|
+
} else {
|
|
7468
|
+
dependencies.stdout(
|
|
7469
|
+
`\u516C\u5171\u89C4\u5219\u51B3\u7B56\uFF1A\u5E94\u7528 ${summary.applied ?? 0}\uFF0C\u8BB0\u5F55 ${summary.recorded ?? 0}\u3002
|
|
7470
|
+
`
|
|
7471
|
+
);
|
|
7472
|
+
}
|
|
7473
|
+
return 0;
|
|
7474
|
+
} catch (error) {
|
|
7475
|
+
dependencies.stderr((error instanceof Error ? error.message : String(error)) + "\n");
|
|
7476
|
+
return 1;
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
|
|
7249
7480
|
// src/commands/recovery.ts
|
|
7250
7481
|
import { stat as stat6 } from "node:fs/promises";
|
|
7251
|
-
import { join as
|
|
7482
|
+
import { join as join21 } from "node:path";
|
|
7252
7483
|
async function exists5(path) {
|
|
7253
7484
|
try {
|
|
7254
7485
|
await stat6(path);
|
|
@@ -7308,7 +7539,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
7308
7539
|
return 5;
|
|
7309
7540
|
}
|
|
7310
7541
|
}
|
|
7311
|
-
const initialized = await exists5(
|
|
7542
|
+
const initialized = await exists5(join21(dependencies.cwd, ".harness", "project.yaml"));
|
|
7312
7543
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
7313
7544
|
return null;
|
|
7314
7545
|
}
|
|
@@ -7351,8 +7582,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
7351
7582
|
}
|
|
7352
7583
|
|
|
7353
7584
|
// src/workflow-data/resolve.ts
|
|
7354
|
-
import { cp, mkdir as
|
|
7355
|
-
import { dirname as
|
|
7585
|
+
import { cp, mkdir as mkdir8, readdir as readdir10, readFile as readFile21, rm as rm8, stat as stat7 } from "node:fs/promises";
|
|
7586
|
+
import { dirname as dirname8, join as join22, relative as relative4 } from "node:path";
|
|
7356
7587
|
import { fileURLToPath } from "node:url";
|
|
7357
7588
|
var WorkflowDataResolutionError = class extends Error {
|
|
7358
7589
|
code;
|
|
@@ -7389,14 +7620,14 @@ async function listFilesRecursive(root, base = root) {
|
|
|
7389
7620
|
const entries = await readdir10(root, { withFileTypes: true });
|
|
7390
7621
|
const files = [];
|
|
7391
7622
|
for (const entry of entries) {
|
|
7392
|
-
const absolute =
|
|
7623
|
+
const absolute = join22(root, entry.name);
|
|
7393
7624
|
if (entry.isDirectory()) {
|
|
7394
7625
|
files.push(...await listFilesRecursive(absolute, base));
|
|
7395
7626
|
continue;
|
|
7396
7627
|
}
|
|
7397
7628
|
if (!entry.isFile()) continue;
|
|
7398
|
-
const rel =
|
|
7399
|
-
files.push({ path: rel, content: await
|
|
7629
|
+
const rel = relative4(base, absolute).replaceAll("\\", "/");
|
|
7630
|
+
files.push({ path: rel, content: await readFile21(absolute, "utf8") });
|
|
7400
7631
|
}
|
|
7401
7632
|
return files;
|
|
7402
7633
|
}
|
|
@@ -7409,7 +7640,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
7409
7640
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
7410
7641
|
const expected = manifest.content_sha256;
|
|
7411
7642
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
7412
|
-
const harnessRoot =
|
|
7643
|
+
const harnessRoot = join22(resourcesRoot, "harness");
|
|
7413
7644
|
if (!await pathExists3(harnessRoot)) {
|
|
7414
7645
|
throw new WorkflowDataResolutionError(
|
|
7415
7646
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -7428,40 +7659,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
7428
7659
|
}
|
|
7429
7660
|
}
|
|
7430
7661
|
async function monorepoResourcesRoot() {
|
|
7431
|
-
const here =
|
|
7662
|
+
const here = dirname8(fileURLToPath(import.meta.url));
|
|
7432
7663
|
const candidates = [
|
|
7433
7664
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
7434
|
-
|
|
7665
|
+
join22(here, "../../../workflow-data-harness"),
|
|
7435
7666
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
7436
|
-
|
|
7667
|
+
join22(here, "../../workflow-data-harness"),
|
|
7437
7668
|
// Test/build layouts that preserve additional source directory levels.
|
|
7438
|
-
|
|
7669
|
+
join22(here, "../../../../packages/workflow-data-harness")
|
|
7439
7670
|
];
|
|
7440
7671
|
for (const candidate of candidates) {
|
|
7441
|
-
if (await pathExists3(
|
|
7672
|
+
if (await pathExists3(join22(candidate, "harness", "manifests"))) return candidate;
|
|
7442
7673
|
}
|
|
7443
7674
|
return null;
|
|
7444
7675
|
}
|
|
7445
7676
|
async function siblingWorkflowPackage(cwd) {
|
|
7446
|
-
const here =
|
|
7677
|
+
const here = dirname8(fileURLToPath(import.meta.url));
|
|
7447
7678
|
const candidates = [
|
|
7448
|
-
|
|
7679
|
+
join22(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
7449
7680
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
7450
7681
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
7451
|
-
|
|
7682
|
+
join22(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
7452
7683
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
7453
|
-
|
|
7684
|
+
join22(here, "..", "..", "workflow-data-harness")
|
|
7454
7685
|
];
|
|
7455
7686
|
for (const candidate of candidates) {
|
|
7456
|
-
if (await pathExists3(
|
|
7687
|
+
if (await pathExists3(join22(candidate, "harness", "manifests"))) return candidate;
|
|
7457
7688
|
}
|
|
7458
7689
|
return null;
|
|
7459
7690
|
}
|
|
7460
7691
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
7461
|
-
const manifestPath =
|
|
7692
|
+
const manifestPath = join22(cacheRoot, "package.json");
|
|
7462
7693
|
if (!await pathExists3(manifestPath)) return null;
|
|
7463
7694
|
try {
|
|
7464
|
-
const pkg = JSON.parse(await
|
|
7695
|
+
const pkg = JSON.parse(await readFile21(manifestPath, "utf8"));
|
|
7465
7696
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
7466
7697
|
} catch {
|
|
7467
7698
|
return null;
|
|
@@ -7483,13 +7714,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
7483
7714
|
}
|
|
7484
7715
|
}
|
|
7485
7716
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
7486
|
-
await
|
|
7487
|
-
const staging =
|
|
7717
|
+
await mkdir8(cacheRoot, { recursive: true });
|
|
7718
|
+
const staging = join22(cacheRoot, ".extract");
|
|
7488
7719
|
await rm8(staging, { recursive: true, force: true });
|
|
7489
|
-
await
|
|
7720
|
+
await mkdir8(staging, { recursive: true });
|
|
7490
7721
|
await extract(packageSpec, staging);
|
|
7491
|
-
const packageDir = await pathExists3(
|
|
7492
|
-
if (!await pathExists3(
|
|
7722
|
+
const packageDir = await pathExists3(join22(staging, "harness", "manifests")) ? staging : join22(staging, "package");
|
|
7723
|
+
if (!await pathExists3(join22(packageDir, "harness", "manifests"))) {
|
|
7493
7724
|
throw new WorkflowDataResolutionError(
|
|
7494
7725
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
7495
7726
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -7518,8 +7749,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
7518
7749
|
const packageName = workflowPackageName(family, options.env);
|
|
7519
7750
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
7520
7751
|
const cacheKey = packageSpec.replace("/", "+");
|
|
7521
|
-
const cacheRoot =
|
|
7522
|
-
if (await pathExists3(
|
|
7752
|
+
const cacheRoot = join22(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
7753
|
+
if (await pathExists3(join22(cacheRoot, "harness", "manifests"))) {
|
|
7523
7754
|
if (version === "latest") {
|
|
7524
7755
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
7525
7756
|
if (stale) {
|
|
@@ -7565,9 +7796,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
7565
7796
|
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}`;
|
|
7566
7797
|
}
|
|
7567
7798
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
7568
|
-
const manifestPath =
|
|
7799
|
+
const manifestPath = join22(resourcesRoot, "hunter-workflow-family.json");
|
|
7569
7800
|
if (!await pathExists3(manifestPath)) return {};
|
|
7570
|
-
return JSON.parse(await
|
|
7801
|
+
return JSON.parse(await readFile21(manifestPath, "utf8"));
|
|
7571
7802
|
}
|
|
7572
7803
|
|
|
7573
7804
|
// src/bin.ts
|
|
@@ -7687,6 +7918,12 @@ async function runCli(argv, overrides = {}) {
|
|
|
7687
7918
|
dependencies
|
|
7688
7919
|
);
|
|
7689
7920
|
});
|
|
7921
|
+
program.command("rules-review").description("\u5BFC\u51FA\u5F85\u8BC4\u5BA1\u516C\u5171\u89C4\u5219\u5019\u9009\uFF0C\u6216\u5E94\u7528\u7ECF\u7528\u6237\u786E\u8BA4\u7684\u89C4\u5219\u51B3\u7B56").option("--apply <file>", "\u5E94\u7528\u5305\u542B\u5019\u9009 revision \u548C\u76EE\u6807 hash \u7684\u51B3\u7B56 JSON").option("--json").action(async (options) => {
|
|
7922
|
+
exitCode = await runRulesReview(
|
|
7923
|
+
{ ...program.opts(), ...options },
|
|
7924
|
+
dependencies
|
|
7925
|
+
);
|
|
7926
|
+
});
|
|
7690
7927
|
try {
|
|
7691
7928
|
await program.parseAsync(["node", "hunter-harness", ...argv]);
|
|
7692
7929
|
return exitCode;
|