@mstar-harness/dsh 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -1122,24 +1122,24 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
1122
1122
|
import { fileURLToPath } from "node:url";
|
|
1123
1123
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1124
1124
|
import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
1125
|
-
import { existsSync as
|
|
1125
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1126
1126
|
import { execFileSync } from "node:child_process";
|
|
1127
|
-
import { basename as
|
|
1127
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve7 } from "node:path";
|
|
1128
1128
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
1129
1129
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
|
|
1130
1130
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1131
|
-
import { existsSync as
|
|
1132
|
-
import { dirname as dirname4, join as
|
|
1131
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync } from "node:fs";
|
|
1132
|
+
import { dirname as dirname4, join as join7, resolve as resolve5, sep } from "node:path";
|
|
1133
1133
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1134
|
-
import { existsSync as
|
|
1135
|
-
import { isAbsolute as
|
|
1134
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1135
|
+
import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
|
|
1136
1136
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1137
|
-
import { mkdirSync as mkdirSync6, readdirSync as
|
|
1138
|
-
import { basename as
|
|
1139
|
-
import { existsSync as
|
|
1140
|
-
import { basename as
|
|
1141
|
-
import { existsSync as
|
|
1142
|
-
import { join as
|
|
1137
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1138
|
+
import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute6, join as join10, resolve as resolve9 } from "node:path";
|
|
1139
|
+
import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
1140
|
+
import { basename as basename6, isAbsolute as isAbsolute8, join as join13, relative as relative4, resolve as resolve12, sep as sep4 } from "node:path";
|
|
1141
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
1142
|
+
import { join as join14 } from "node:path";
|
|
1143
1143
|
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
|
|
1144
1144
|
function applyEnforcement(gate, opts) {
|
|
1145
1145
|
return { ...gate, hardBlocked: opts.hard && gate.violations.length > 0 };
|
|
@@ -2147,7 +2147,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
2147
2147
|
if (typeof docOrPath === "string") {
|
|
2148
2148
|
try {
|
|
2149
2149
|
doc = readJson(docOrPath);
|
|
2150
|
-
harnessDir = dirname4(
|
|
2150
|
+
harnessDir = dirname4(resolve5(docOrPath));
|
|
2151
2151
|
} catch (error) {
|
|
2152
2152
|
return {
|
|
2153
2153
|
ok: false,
|
|
@@ -2214,8 +2214,8 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
2214
2214
|
for (const entry of doc.workflows) {
|
|
2215
2215
|
if (!isPlainObject4(entry) || typeof entry.dir !== "string")
|
|
2216
2216
|
continue;
|
|
2217
|
-
const relSnapshot =
|
|
2218
|
-
const snapshotPath =
|
|
2217
|
+
const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
2218
|
+
const snapshotPath = join7(harnessDir, relSnapshot);
|
|
2219
2219
|
const label = typeof entry.id === "string" ? entry.id : relSnapshot;
|
|
2220
2220
|
let physical;
|
|
2221
2221
|
try {
|
|
@@ -2251,19 +2251,19 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
2251
2251
|
var validateStatus = validateStatusV2;
|
|
2252
2252
|
function resolveCompassEnforcement(harnessDir) {
|
|
2253
2253
|
const iterationsDir = resolveIterationDir(harnessDir);
|
|
2254
|
-
if (!
|
|
2254
|
+
if (!existsSync4(iterationsDir))
|
|
2255
2255
|
return { hard: false, source: "none" };
|
|
2256
2256
|
let entries;
|
|
2257
2257
|
try {
|
|
2258
|
-
entries =
|
|
2258
|
+
entries = readdirSync3(iterationsDir, { withFileTypes: true });
|
|
2259
2259
|
} catch {
|
|
2260
2260
|
return { hard: false, source: "none" };
|
|
2261
2261
|
}
|
|
2262
2262
|
for (const entry of entries) {
|
|
2263
2263
|
if (!entry.isDirectory())
|
|
2264
2264
|
continue;
|
|
2265
|
-
const compassPath =
|
|
2266
|
-
if (!
|
|
2265
|
+
const compassPath = join7(iterationsDir, entry.name, "delivery-compass.md");
|
|
2266
|
+
if (!existsSync4(compassPath))
|
|
2267
2267
|
continue;
|
|
2268
2268
|
let content;
|
|
2269
2269
|
try {
|
|
@@ -2282,7 +2282,7 @@ function resolveCompassEnforcement(harnessDir) {
|
|
|
2282
2282
|
return { hard: false, source: "none" };
|
|
2283
2283
|
}
|
|
2284
2284
|
function resolveMstarcEnforcement(harnessDir) {
|
|
2285
|
-
const dir =
|
|
2285
|
+
const dir = resolve5(harnessDir);
|
|
2286
2286
|
const rc = loadMstarc(dir, dirname4(dir));
|
|
2287
2287
|
const value = rc?.config.enforcement;
|
|
2288
2288
|
if (value === "hard")
|
|
@@ -2394,19 +2394,19 @@ function findingsCleanupGate(register, planId, opts) {
|
|
|
2394
2394
|
return { ok: violations.length === 0, violations };
|
|
2395
2395
|
}
|
|
2396
2396
|
function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
2397
|
-
const start =
|
|
2397
|
+
const start = resolve7(startDir);
|
|
2398
2398
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
2399
2399
|
if (explicit)
|
|
2400
|
-
return
|
|
2401
|
-
const boundary =
|
|
2400
|
+
return resolve7(start, explicit);
|
|
2401
|
+
const boundary = resolve7(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
|
|
2402
2402
|
const rc = loadMstarc(start, boundary);
|
|
2403
2403
|
if (rc !== null && rc.config.harnessDir)
|
|
2404
|
-
return
|
|
2404
|
+
return resolve7(rc.dir, rc.config.harnessDir);
|
|
2405
2405
|
let dir = start;
|
|
2406
2406
|
for (;; ) {
|
|
2407
2407
|
if (!isAtOrBelow2(dir, boundary))
|
|
2408
2408
|
return null;
|
|
2409
|
-
for (const candidate of [
|
|
2409
|
+
for (const candidate of [join9(dir, ".mstar"), join9(dir, ".agents"), join9(dir, ".plans"), join9(dir, "plans")]) {
|
|
2410
2410
|
if (isDirectory(candidate))
|
|
2411
2411
|
return candidate;
|
|
2412
2412
|
}
|
|
@@ -2432,19 +2432,19 @@ function defaultWorkspaceRoot(startDir) {
|
|
|
2432
2432
|
if (segment && segment !== ".")
|
|
2433
2433
|
boundary = dirname5(boundary);
|
|
2434
2434
|
}
|
|
2435
|
-
return
|
|
2435
|
+
return resolve7(boundary);
|
|
2436
2436
|
} catch {}
|
|
2437
2437
|
return startDir;
|
|
2438
2438
|
}
|
|
2439
2439
|
function isAtOrBelow2(dir, root) {
|
|
2440
2440
|
const rel = relative2(root, dir);
|
|
2441
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
2441
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
|
|
2442
2442
|
}
|
|
2443
2443
|
function mstarcDirOverride(harnessDir, key) {
|
|
2444
|
-
const dir =
|
|
2444
|
+
const dir = resolve7(harnessDir);
|
|
2445
2445
|
const rc = loadMstarc(dir, dirname5(dir));
|
|
2446
2446
|
const declared = rc?.config[key];
|
|
2447
|
-
return declared ?
|
|
2447
|
+
return declared ? resolve7(rc.dir, declared) : null;
|
|
2448
2448
|
}
|
|
2449
2449
|
function assertSafePathComponent(value, what) {
|
|
2450
2450
|
if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
@@ -2453,24 +2453,24 @@ function assertSafePathComponent(value, what) {
|
|
|
2453
2453
|
}
|
|
2454
2454
|
function resolveSddDir(harnessDir, planId) {
|
|
2455
2455
|
assertSafePathComponent(planId, "planId");
|
|
2456
|
-
const base =
|
|
2456
|
+
const base = resolve7(harnessDir);
|
|
2457
2457
|
const declared = mstarcDirOverride(base, "sddDir");
|
|
2458
|
-
const sddBase = declared !== null ? declared :
|
|
2459
|
-
return
|
|
2458
|
+
const sddBase = declared !== null ? declared : join9(base, "sdd");
|
|
2459
|
+
return join9(sddBase, planId);
|
|
2460
2460
|
}
|
|
2461
2461
|
function resolveIterationDir(harnessDir) {
|
|
2462
2462
|
const declared = mstarcDirOverride(harnessDir, "iterationDir");
|
|
2463
2463
|
if (declared !== null)
|
|
2464
2464
|
return declared;
|
|
2465
|
-
return
|
|
2465
|
+
return join9(resolve7(harnessDir), "iterations");
|
|
2466
2466
|
}
|
|
2467
2467
|
function resolveHarnessSubdir(startDir, opts, key, fallback) {
|
|
2468
2468
|
const harness = resolveHarnessDir(startDir, opts);
|
|
2469
2469
|
if (harness === null) {
|
|
2470
|
-
throw new Error(`harness dir not found from ${
|
|
2470
|
+
throw new Error(`harness dir not found from ${resolve7(startDir)} — cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
2471
2471
|
}
|
|
2472
2472
|
const declared = mstarcDirOverride(harness, key);
|
|
2473
|
-
return declared !== null ? declared :
|
|
2473
|
+
return declared !== null ? declared : join9(resolve7(harness), fallback);
|
|
2474
2474
|
}
|
|
2475
2475
|
function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
|
|
2476
2476
|
return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
|
|
@@ -2560,10 +2560,10 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
2560
2560
|
if (leaseWorkingBranch.trim() === "") {
|
|
2561
2561
|
violations.push(violation7("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
|
|
2562
2562
|
}
|
|
2563
|
-
if (controlWorktreePath !== "" && leaseWorktreePath !== "" &&
|
|
2563
|
+
if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve8(controlWorktreePath) === resolve8(leaseWorktreePath)) {
|
|
2564
2564
|
violations.push(violation7("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path — the feature worktree MUST differ from the control worktree (L1 isolation; product edits never land in the control checkout)`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
|
|
2565
2565
|
}
|
|
2566
|
-
if (leaseWorktreePath !== "" && !
|
|
2566
|
+
if (leaseWorktreePath !== "" && !existsSync7(leaseWorktreePath)) {
|
|
2567
2567
|
violations.push(violation7("high", "worktree.l1.feature-missing", `feature worktree directory "${leaseWorktreePath}" does not exist for plan "${planId}"`, `create it before dispatch: git worktree add ${leaseWorktreePath} <working-branch>`));
|
|
2568
2568
|
} else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
|
|
2569
2569
|
const probe = probeBranch(leaseWorktreePath, opts);
|
|
@@ -2587,17 +2587,17 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
2587
2587
|
violations.push(violation7("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
|
|
2588
2588
|
return;
|
|
2589
2589
|
}
|
|
2590
|
-
if (!
|
|
2590
|
+
if (!isAbsolute5(track.worktreePath)) {
|
|
2591
2591
|
violations.push(violation7("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path — L2 tracks MUST use absolute worktree checkout paths (consistent with the lease validator's absolute worktree_path enforcement)`, `use an absolute path for track ${index + 1} (e.g. /Users/<you>/worktrees/<branch>)`));
|
|
2592
2592
|
return;
|
|
2593
2593
|
}
|
|
2594
|
-
const normalized =
|
|
2594
|
+
const normalized = resolve8(track.worktreePath);
|
|
2595
2595
|
if (seenPaths.has(normalized)) {
|
|
2596
2596
|
violations.push(violation7("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks — L2 parallel-writable isolation requires a distinct absolute Worktree path per track (N parallel invokes ≠ isolation)`, "give every parallel track its own git worktree checkout"));
|
|
2597
2597
|
return;
|
|
2598
2598
|
}
|
|
2599
2599
|
seenPaths.add(normalized);
|
|
2600
|
-
if (!
|
|
2600
|
+
if (!existsSync7(track.worktreePath)) {
|
|
2601
2601
|
violations.push(violation7("high", "worktree.l2.track-missing", `track worktree directory "${track.worktreePath}" does not exist`, `create it before dispatch: git worktree add ${track.worktreePath} ${track.workingBranch}`));
|
|
2602
2602
|
return;
|
|
2603
2603
|
}
|
|
@@ -2632,7 +2632,7 @@ function isFile2(file) {
|
|
|
2632
2632
|
return false;
|
|
2633
2633
|
}
|
|
2634
2634
|
}
|
|
2635
|
-
var GIT_CAPTURE_MAX_BYTES =
|
|
2635
|
+
var GIT_CAPTURE_MAX_BYTES = 67108864;
|
|
2636
2636
|
function gitOut(cwd, args) {
|
|
2637
2637
|
try {
|
|
2638
2638
|
return execFileSync3("git", args, {
|
|
@@ -2646,14 +2646,14 @@ function gitOut(cwd, args) {
|
|
|
2646
2646
|
}
|
|
2647
2647
|
}
|
|
2648
2648
|
function probeHarnessWithStatus(root) {
|
|
2649
|
-
if (isFile2(
|
|
2650
|
-
return
|
|
2651
|
-
if (isFile2(
|
|
2652
|
-
return
|
|
2653
|
-
if (hasWorkflowSnapshot(
|
|
2654
|
-
return
|
|
2655
|
-
if (hasWorkflowSnapshot(
|
|
2656
|
-
return
|
|
2649
|
+
if (isFile2(join10(root, ".mstar", "status.json")))
|
|
2650
|
+
return join10(root, ".mstar");
|
|
2651
|
+
if (isFile2(join10(root, ".agents", "status.json")))
|
|
2652
|
+
return join10(root, ".agents");
|
|
2653
|
+
if (hasWorkflowSnapshot(join10(root, ".mstar")))
|
|
2654
|
+
return join10(root, ".mstar");
|
|
2655
|
+
if (hasWorkflowSnapshot(join10(root, ".agents")))
|
|
2656
|
+
return join10(root, ".agents");
|
|
2657
2657
|
return null;
|
|
2658
2658
|
}
|
|
2659
2659
|
function hasWorkflowSnapshot(harnessDir) {
|
|
@@ -2661,13 +2661,13 @@ function hasWorkflowSnapshot(harnessDir) {
|
|
|
2661
2661
|
try {
|
|
2662
2662
|
workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
2663
2663
|
} catch {
|
|
2664
|
-
workflowsDir =
|
|
2664
|
+
workflowsDir = join10(harnessDir, "workflows");
|
|
2665
2665
|
}
|
|
2666
2666
|
if (!isDirectory2(workflowsDir))
|
|
2667
2667
|
return false;
|
|
2668
2668
|
try {
|
|
2669
|
-
for (const entry of
|
|
2670
|
-
if (entry.isDirectory() && isFile2(
|
|
2669
|
+
for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
|
|
2670
|
+
if (entry.isDirectory() && isFile2(join10(workflowsDir, entry.name, "snapshot.json")))
|
|
2671
2671
|
return true;
|
|
2672
2672
|
}
|
|
2673
2673
|
} catch {
|
|
@@ -2680,14 +2680,14 @@ function isLinkedWorktree(root) {
|
|
|
2680
2680
|
const commonRaw = gitOut(root, ["rev-parse", "--git-common-dir"]);
|
|
2681
2681
|
if (gitDirRaw === null || commonRaw === null)
|
|
2682
2682
|
return false;
|
|
2683
|
-
const gitDir =
|
|
2684
|
-
const common =
|
|
2683
|
+
const gitDir = isAbsolute6(gitDirRaw) ? gitDirRaw : join10(root, gitDirRaw);
|
|
2684
|
+
const common = isAbsolute6(commonRaw) ? commonRaw : join10(root, commonRaw);
|
|
2685
2685
|
if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
|
|
2686
2686
|
return true;
|
|
2687
2687
|
try {
|
|
2688
2688
|
const gdParent = realpathSync3(dirname6(gitDir));
|
|
2689
2689
|
const cmAbs = realpathSync3(common);
|
|
2690
|
-
return
|
|
2690
|
+
return join10(gdParent, basename4(gitDir)) !== cmAbs && gitDir !== cmAbs;
|
|
2691
2691
|
} catch {
|
|
2692
2692
|
return false;
|
|
2693
2693
|
}
|
|
@@ -2695,7 +2695,7 @@ function isLinkedWorktree(root) {
|
|
|
2695
2695
|
function sddWorkspace(planId, opts = {}) {
|
|
2696
2696
|
if (!planId) {
|
|
2697
2697
|
throw new SddScriptError(`usage: mstar sdd workspace PLAN_ID [CONTROL_ROOT]
|
|
2698
|
-
|
|
2698
|
+
Set MSTAR_CONTROL_ROOT=<control_worktree_path> when running from a feature worktree.`, 2);
|
|
2699
2699
|
}
|
|
2700
2700
|
const cwd = opts.cwd ?? process.cwd();
|
|
2701
2701
|
const controlRoot = opts.controlRoot ?? (process.env.MSTAR_CONTROL_ROOT || undefined);
|
|
@@ -2711,35 +2711,35 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
2711
2711
|
}
|
|
2712
2712
|
if (!controlRoot && isLinkedWorktree(root)) {
|
|
2713
2713
|
throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
|
|
2714
|
-
|
|
2715
|
-
|
|
2714
|
+
Refusing to create a second SDD tree under the feature checkout.
|
|
2715
|
+
Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
|
|
2716
2716
|
` + ` See mstar-branch-worktree «Harness path SSOT under default gitignore».`, 1);
|
|
2717
2717
|
}
|
|
2718
2718
|
const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
|
|
2719
2719
|
let harnessDir;
|
|
2720
2720
|
if (harnessOverride) {
|
|
2721
|
-
harnessDir =
|
|
2721
|
+
harnessDir = resolve9(root, harnessOverride);
|
|
2722
2722
|
} else {
|
|
2723
2723
|
const rc = findMstarc(root, root);
|
|
2724
2724
|
const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
|
|
2725
2725
|
if (rcHarnessDir) {
|
|
2726
|
-
harnessDir =
|
|
2726
|
+
harnessDir = resolve9(rc !== null ? dirname6(rc) : root, rcHarnessDir);
|
|
2727
2727
|
} else {
|
|
2728
2728
|
const probed = probeHarnessWithStatus(root);
|
|
2729
2729
|
if (probed) {
|
|
2730
2730
|
harnessDir = probed;
|
|
2731
|
-
} else if (isDirectory2(
|
|
2732
|
-
harnessDir =
|
|
2733
|
-
} else if (isDirectory2(
|
|
2734
|
-
harnessDir =
|
|
2731
|
+
} else if (isDirectory2(join10(root, ".mstar"))) {
|
|
2732
|
+
harnessDir = join10(root, ".mstar");
|
|
2733
|
+
} else if (isDirectory2(join10(root, ".agents"))) {
|
|
2734
|
+
harnessDir = join10(root, ".agents");
|
|
2735
2735
|
} else {
|
|
2736
|
-
harnessDir =
|
|
2736
|
+
harnessDir = join10(root, ".mstar");
|
|
2737
2737
|
}
|
|
2738
2738
|
}
|
|
2739
2739
|
}
|
|
2740
2740
|
const sddDir = resolveSddDir(harnessDir, planId);
|
|
2741
2741
|
mkdirSync6(sddDir, { recursive: true });
|
|
2742
|
-
writeFileSync4(
|
|
2742
|
+
writeFileSync4(join10(sddDir, ".gitignore"), `*
|
|
2743
2743
|
`);
|
|
2744
2744
|
return realpathSync3(sddDir);
|
|
2745
2745
|
}
|
|
@@ -2762,7 +2762,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
2762
2762
|
throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
|
|
2763
2763
|
}
|
|
2764
2764
|
mkdirSync6(sddDir, { recursive: true });
|
|
2765
|
-
out =
|
|
2765
|
+
out = join10(sddDir, `task-${taskN}-brief.md`);
|
|
2766
2766
|
}
|
|
2767
2767
|
const records = content.endsWith(`
|
|
2768
2768
|
`) ? content.split(`
|
|
@@ -3503,7 +3503,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3503
3503
|
if (ref === "" || seen.has(ref))
|
|
3504
3504
|
continue;
|
|
3505
3505
|
seen.add(ref);
|
|
3506
|
-
if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") ||
|
|
3506
|
+
if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") || isAbsolute8(ref)) {
|
|
3507
3507
|
continue;
|
|
3508
3508
|
}
|
|
3509
3509
|
if (ref.includes("/") || REF_EXT_RE.test(ref)) {
|
|
@@ -3522,7 +3522,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3522
3522
|
const dir = stack.pop();
|
|
3523
3523
|
let entries;
|
|
3524
3524
|
try {
|
|
3525
|
-
entries =
|
|
3525
|
+
entries = readdirSync9(dir, { withFileTypes: true });
|
|
3526
3526
|
} catch {
|
|
3527
3527
|
continue;
|
|
3528
3528
|
}
|
|
@@ -3531,7 +3531,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3531
3531
|
break;
|
|
3532
3532
|
if (entry.isDirectory()) {
|
|
3533
3533
|
if (!WALK_SKIP_DIRS.has(entry.name))
|
|
3534
|
-
stack.push(
|
|
3534
|
+
stack.push(join13(dir, entry.name));
|
|
3535
3535
|
} else if (!entry.isSymbolicLink()) {
|
|
3536
3536
|
const base = entry.name.replace(/\.(?:ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
3537
3537
|
if (moduleNames.has(base))
|
|
@@ -3543,7 +3543,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3543
3543
|
for (const { ref, isSymbol, module } of refs) {
|
|
3544
3544
|
if (!isSymbol || module === undefined) {
|
|
3545
3545
|
const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
|
|
3546
|
-
if (
|
|
3546
|
+
if (existsSync9(resolve12(repoRoot, candidate))) {
|
|
3547
3547
|
checked++;
|
|
3548
3548
|
} else {
|
|
3549
3549
|
violations.push(violation10("medium", "compound.reference.missing-file", `referenced path \`${ref}\` does not exist under ${repoRoot} (compound-refresh Phase 2: referenced code still exists?)`, "update the doc to reference an existing path, or delete the stale reference"));
|
|
@@ -3563,14 +3563,14 @@ function collectKnowledgeDocs(dir) {
|
|
|
3563
3563
|
const current = stack.pop();
|
|
3564
3564
|
let entries;
|
|
3565
3565
|
try {
|
|
3566
|
-
entries =
|
|
3566
|
+
entries = readdirSync9(current, { withFileTypes: true });
|
|
3567
3567
|
} catch {
|
|
3568
3568
|
continue;
|
|
3569
3569
|
}
|
|
3570
3570
|
for (const entry of entries) {
|
|
3571
3571
|
if (entry.isSymbolicLink())
|
|
3572
3572
|
continue;
|
|
3573
|
-
const full =
|
|
3573
|
+
const full = join13(current, entry.name);
|
|
3574
3574
|
if (entry.isDirectory()) {
|
|
3575
3575
|
stack.push(full);
|
|
3576
3576
|
} else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
|
|
@@ -3590,8 +3590,8 @@ function normalizeIndexRef(cell) {
|
|
|
3590
3590
|
}
|
|
3591
3591
|
function assertIndexRows(knowledgeDir) {
|
|
3592
3592
|
const violations = [];
|
|
3593
|
-
const readmePath =
|
|
3594
|
-
if (!
|
|
3593
|
+
const readmePath = join13(knowledgeDir, "README.md");
|
|
3594
|
+
if (!existsSync9(readmePath)) {
|
|
3595
3595
|
violations.push(violation10("medium", "compound.index.missing-readme", `missing ${readmePath} — the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
|
|
3596
3596
|
return { ok: false, violations };
|
|
3597
3597
|
}
|
|
@@ -3615,12 +3615,12 @@ function assertIndexRows(knowledgeDir) {
|
|
|
3615
3615
|
return { ok: violations.length === 0, violations };
|
|
3616
3616
|
}
|
|
3617
3617
|
function isFileLikeRoot(root) {
|
|
3618
|
-
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(
|
|
3618
|
+
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename6(root));
|
|
3619
3619
|
}
|
|
3620
3620
|
function scopeGuard(path, allowedRoots) {
|
|
3621
|
-
const resolved =
|
|
3621
|
+
const resolved = resolve12(path);
|
|
3622
3622
|
for (const root of allowedRoots) {
|
|
3623
|
-
const r =
|
|
3623
|
+
const r = resolve12(root);
|
|
3624
3624
|
if (isFileLikeRoot(r)) {
|
|
3625
3625
|
if (resolved === r)
|
|
3626
3626
|
return { ok: true, violations: [] };
|
|
@@ -3778,8 +3778,8 @@ function validateRoleMapping(rolesDir, options = {}) {
|
|
|
3778
3778
|
const violations = [];
|
|
3779
3779
|
const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
|
|
3780
3780
|
for (const { agentId, reference } of mapping) {
|
|
3781
|
-
if (!
|
|
3782
|
-
violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${
|
|
3781
|
+
if (!existsSync10(join14(rolesDir, reference))) {
|
|
3782
|
+
violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join14(rolesDir, reference)} or fix the mapping row`));
|
|
3783
3783
|
}
|
|
3784
3784
|
}
|
|
3785
3785
|
for (const { family, memberIds } of families) {
|
|
@@ -3992,8 +3992,8 @@ class DshMstar extends Service {
|
|
|
3992
3992
|
|
|
3993
3993
|
// src/gates/_shared.ts
|
|
3994
3994
|
var import_schemastery = __toESM(require_lib2(), 1);
|
|
3995
|
-
import { existsSync as
|
|
3996
|
-
import { resolve as
|
|
3995
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
3996
|
+
import { resolve as resolve4 } from "node:path";
|
|
3997
3997
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3998
3998
|
var STATUS_FILE = "status.json";
|
|
3999
3999
|
var PERSONA_INTERPOLATION_HAZARD = /\{\{[\s\S]*\}\}/;
|
|
@@ -4047,7 +4047,7 @@ function resolvePackagedSkillsDir(fileUrl) {
|
|
|
4047
4047
|
for (const rel of ["../harness-skills", "../../harness-skills"]) {
|
|
4048
4048
|
try {
|
|
4049
4049
|
const dir = fileURLToPath2(new URL(rel, fileUrl));
|
|
4050
|
-
if (
|
|
4050
|
+
if (existsSync3(dir))
|
|
4051
4051
|
return dir;
|
|
4052
4052
|
} catch {}
|
|
4053
4053
|
}
|
|
@@ -4066,7 +4066,7 @@ function resolvePackagedAgentsDir(fileUrl) {
|
|
|
4066
4066
|
for (const rel of ["../harness-agents", "../../harness-agents"]) {
|
|
4067
4067
|
try {
|
|
4068
4068
|
const dir = fileURLToPath2(new URL(rel, fileUrl));
|
|
4069
|
-
if (
|
|
4069
|
+
if (existsSync3(dir))
|
|
4070
4070
|
return dir;
|
|
4071
4071
|
} catch {}
|
|
4072
4072
|
}
|
|
@@ -4086,14 +4086,14 @@ class HarnessResolver {
|
|
|
4086
4086
|
explicit;
|
|
4087
4087
|
cache = new Map;
|
|
4088
4088
|
constructor(explicit) {
|
|
4089
|
-
this.explicit = explicit !== undefined && explicit.trim() !== "" ?
|
|
4089
|
+
this.explicit = explicit !== undefined && explicit.trim() !== "" ? resolve4(process.cwd(), explicit) : null;
|
|
4090
4090
|
}
|
|
4091
4091
|
forWorkspace(cwd) {
|
|
4092
4092
|
if (this.explicit !== null)
|
|
4093
4093
|
return this.explicit;
|
|
4094
4094
|
if (cwd === undefined || cwd.trim() === "")
|
|
4095
4095
|
return null;
|
|
4096
|
-
const abs =
|
|
4096
|
+
const abs = resolve4(cwd);
|
|
4097
4097
|
const hit = this.cache.get(abs);
|
|
4098
4098
|
if (hit !== undefined)
|
|
4099
4099
|
return hit;
|
|
@@ -4134,20 +4134,20 @@ function skillLocalConfig(config) {
|
|
|
4134
4134
|
}
|
|
4135
4135
|
|
|
4136
4136
|
// src/gates/catalog.ts
|
|
4137
|
-
import { existsSync as existsSync13, readFileSync as readFileSync8, readdirSync as
|
|
4138
|
-
import { join as
|
|
4137
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
|
|
4138
|
+
import { join as join8 } from "node:path";
|
|
4139
4139
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4140
4140
|
|
|
4141
4141
|
// src/gates/agent-flow.ts
|
|
4142
|
-
import { appendFileSync, closeSync, existsSync as
|
|
4143
|
-
import { join as
|
|
4142
|
+
import { appendFileSync, closeSync, existsSync as existsSync11, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync5, readSync, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4143
|
+
import { join as join6 } from "node:path";
|
|
4144
4144
|
|
|
4145
4145
|
// src/gates/dispatch.ts
|
|
4146
|
-
import { existsSync as
|
|
4147
|
-
import { basename as
|
|
4146
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
4147
|
+
import { basename as basename2, join as join5, resolve as resolve6 } from "node:path";
|
|
4148
4148
|
|
|
4149
4149
|
// src/gates/workflow-selection.ts
|
|
4150
|
-
import { existsSync as
|
|
4150
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
4151
4151
|
import { dirname as dirname7, join as join3, relative as relative3 } from "node:path";
|
|
4152
4152
|
var TERMINAL_STATUS_CACHE_MAX = 64;
|
|
4153
4153
|
var terminalStatusCache = new Map;
|
|
@@ -4182,7 +4182,7 @@ function terminalStatusOf(snapshotPath) {
|
|
|
4182
4182
|
}
|
|
4183
4183
|
function resolveActiveWorkflow(harnessDir) {
|
|
4184
4184
|
const statusPath = join3(harnessDir, STATUS_FILE);
|
|
4185
|
-
if (!
|
|
4185
|
+
if (!existsSync5(statusPath)) {
|
|
4186
4186
|
return {
|
|
4187
4187
|
kind: "error",
|
|
4188
4188
|
code: "status.missing",
|
|
@@ -4240,7 +4240,7 @@ function resolveReadWorkflow(harnessDir) {
|
|
|
4240
4240
|
if (active.kind === "error" && active.code !== "workflow.selection.no-active")
|
|
4241
4241
|
return active;
|
|
4242
4242
|
const workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
4243
|
-
if (!
|
|
4243
|
+
if (!existsSync5(workflowsDir)) {
|
|
4244
4244
|
return {
|
|
4245
4245
|
kind: "error",
|
|
4246
4246
|
code: "workflow.selection.no-snapshot",
|
|
@@ -4249,7 +4249,7 @@ function resolveReadWorkflow(harnessDir) {
|
|
|
4249
4249
|
}
|
|
4250
4250
|
let entries;
|
|
4251
4251
|
try {
|
|
4252
|
-
entries =
|
|
4252
|
+
entries = readdirSync2(workflowsDir, { withFileTypes: true });
|
|
4253
4253
|
} catch {
|
|
4254
4254
|
return {
|
|
4255
4255
|
kind: "error",
|
|
@@ -4262,7 +4262,7 @@ function resolveReadWorkflow(harnessDir) {
|
|
|
4262
4262
|
if (!entry.isDirectory())
|
|
4263
4263
|
continue;
|
|
4264
4264
|
const snapshotPath = join3(workflowsDir, entry.name, WORKFLOW_SNAPSHOT_FILE);
|
|
4265
|
-
if (!
|
|
4265
|
+
if (!existsSync5(snapshotPath)) {
|
|
4266
4266
|
terminalStatusCache.delete(snapshotPath);
|
|
4267
4267
|
continue;
|
|
4268
4268
|
}
|
|
@@ -4274,7 +4274,7 @@ function resolveReadWorkflow(harnessDir) {
|
|
|
4274
4274
|
}
|
|
4275
4275
|
}
|
|
4276
4276
|
for (const cachedPath of terminalStatusCache.keys()) {
|
|
4277
|
-
if (!
|
|
4277
|
+
if (!existsSync5(join3(dirname7(cachedPath), WORKFLOW_SNAPSHOT_FILE))) {
|
|
4278
4278
|
terminalStatusCache.delete(cachedPath);
|
|
4279
4279
|
}
|
|
4280
4280
|
}
|
|
@@ -4437,12 +4437,12 @@ function isNaValue(value) {
|
|
|
4437
4437
|
function planIdOf(headerRegion) {
|
|
4438
4438
|
const planPath = assignmentHeaderValue(headerRegion, "Plan Path");
|
|
4439
4439
|
if (!isNaValue(planPath)) {
|
|
4440
|
-
const id =
|
|
4440
|
+
const id = basename2(firstToken(planPath) ?? "");
|
|
4441
4441
|
return id.endsWith(".md") ? id.slice(0, -3) : id;
|
|
4442
4442
|
}
|
|
4443
4443
|
const sddDir = assignmentHeaderValue(headerRegion, "SDD dir");
|
|
4444
4444
|
if (!isNaValue(sddDir)) {
|
|
4445
|
-
const id =
|
|
4445
|
+
const id = basename2(firstToken(sddDir) ?? "");
|
|
4446
4446
|
return id === "" ? undefined : id;
|
|
4447
4447
|
}
|
|
4448
4448
|
const planId = assignmentHeaderValue(headerRegion, "plan_id");
|
|
@@ -4472,7 +4472,7 @@ function leaseGateViolations(harnessDir, exec, writable, prompt) {
|
|
|
4472
4472
|
return [leaseViolation("lease.dispatch.unverifiable", `${join5(harnessDir, STATUS_FILE)}: ${selection.kind === "error" ? selection.message : "no active workflow"} — the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "create a valid v2 status.json registering an active workflow (first implement dispatch requires a plan row + lease)")];
|
|
4473
4473
|
}
|
|
4474
4474
|
const snapshotPath = join5(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
4475
|
-
if (!
|
|
4475
|
+
if (!existsSync8(snapshotPath)) {
|
|
4476
4476
|
if (!sdd)
|
|
4477
4477
|
return [];
|
|
4478
4478
|
return [leaseViolation("lease.dispatch.unverifiable", `${snapshotPath} is missing — the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "create a valid workflow snapshot registering the plan row (first implement dispatch requires a plan row)")];
|
|
@@ -4505,7 +4505,7 @@ function leaseGateViolations(harnessDir, exec, writable, prompt) {
|
|
|
4505
4505
|
const wt = worktree === undefined ? undefined : firstToken(worktree);
|
|
4506
4506
|
if (isNaValue(wt)) {
|
|
4507
4507
|
violations.push(leaseViolation("lease.dispatch.worktree-mismatch", "Assignment declares no Worktree path — cannot confirm this dispatch matches execution_lease.worktree_path", "add the absolute Worktree path to the Assignment (must equal the lease worktree_path)"));
|
|
4508
|
-
} else if (
|
|
4508
|
+
} else if (resolve6(wt) !== resolve6(String(lease.worktree_path ?? ""))) {
|
|
4509
4509
|
violations.push(leaseViolation("lease.dispatch.worktree-mismatch", `Assignment Worktree path "${wt}" differs from execution_lease.worktree_path "${String(lease.worktree_path)}"`, "align the Assignment with the lease worktree path (or update the lease)"));
|
|
4510
4510
|
}
|
|
4511
4511
|
const forms = parseAssignmentBranchForms(header);
|
|
@@ -4546,7 +4546,7 @@ function worktreeL2Violations(header) {
|
|
|
4546
4546
|
return violations;
|
|
4547
4547
|
}
|
|
4548
4548
|
function activeSnapshotRows(harnessDir) {
|
|
4549
|
-
if (!
|
|
4549
|
+
if (!existsSync8(join5(harnessDir, STATUS_FILE)))
|
|
4550
4550
|
return { rows: null, controlWorktreePath: undefined, unreadable: false };
|
|
4551
4551
|
const selection = resolveActiveWorkflow(harnessDir);
|
|
4552
4552
|
if (selection.kind !== "active") {
|
|
@@ -4557,7 +4557,7 @@ function activeSnapshotRows(harnessDir) {
|
|
|
4557
4557
|
};
|
|
4558
4558
|
}
|
|
4559
4559
|
const snapshotPath = join5(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
4560
|
-
if (!
|
|
4560
|
+
if (!existsSync8(snapshotPath))
|
|
4561
4561
|
return { rows: null, controlWorktreePath: undefined, unreadable: true };
|
|
4562
4562
|
let doc;
|
|
4563
4563
|
try {
|
|
@@ -4837,7 +4837,7 @@ function callPairingKey(exec) {
|
|
|
4837
4837
|
function resolveAgentFlowWriteDir(harnessDir) {
|
|
4838
4838
|
const selection = resolveActiveWorkflow(harnessDir);
|
|
4839
4839
|
if (selection.kind === "active")
|
|
4840
|
-
return
|
|
4840
|
+
return join6(harnessDir, selection.dir);
|
|
4841
4841
|
const message = selection.kind === "error" ? selection.message : "no active lifecycle (unexpected terminal selection — the active-set resolver has no history view)";
|
|
4842
4842
|
if (!noActiveWarned) {
|
|
4843
4843
|
noActiveWarned = true;
|
|
@@ -4854,7 +4854,7 @@ function sleepSync(ms) {
|
|
|
4854
4854
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
4855
4855
|
}
|
|
4856
4856
|
function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
4857
|
-
const lockDir =
|
|
4857
|
+
const lockDir = join6(workflowDir, WORKFLOW_LEDGER_LOCKDIR);
|
|
4858
4858
|
if (heldWorkflowLocks.has(lockDir)) {
|
|
4859
4859
|
throw new Error(`${lockDir} is already held by this process — withWorkflowDirLock is not reentrant; a nested acquisition on the same workflow dir is a bug`);
|
|
4860
4860
|
}
|
|
@@ -4880,7 +4880,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
|
4880
4880
|
heldWorkflowLocks.add(lockDir);
|
|
4881
4881
|
try {
|
|
4882
4882
|
try {
|
|
4883
|
-
writeFileSync2(
|
|
4883
|
+
writeFileSync2(join6(lockDir, WORKFLOW_LOCKER_HOLDER_PID), String(process.pid), "utf8");
|
|
4884
4884
|
} catch {}
|
|
4885
4885
|
return fn();
|
|
4886
4886
|
} finally {
|
|
@@ -4889,7 +4889,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
|
4889
4889
|
const current = statSync5(lockDir);
|
|
4890
4890
|
if (acquired !== null && current.dev === acquired.dev && current.ino === acquired.ino) {
|
|
4891
4891
|
try {
|
|
4892
|
-
unlinkSync2(
|
|
4892
|
+
unlinkSync2(join6(lockDir, WORKFLOW_LOCKER_HOLDER_PID));
|
|
4893
4893
|
} catch {}
|
|
4894
4894
|
rmdirSync(lockDir);
|
|
4895
4895
|
}
|
|
@@ -4897,7 +4897,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
|
4897
4897
|
}
|
|
4898
4898
|
}
|
|
4899
4899
|
function appendEvent(workflowDir, event) {
|
|
4900
|
-
const file =
|
|
4900
|
+
const file = join6(workflowDir, AGENT_FLOW_FILE);
|
|
4901
4901
|
withWorkflowDirLock(workflowDir, () => {
|
|
4902
4902
|
appendFileSync(file, `${JSON.stringify(event)}
|
|
4903
4903
|
`);
|
|
@@ -5286,8 +5286,8 @@ function ledgerContent(file, n) {
|
|
|
5286
5286
|
}
|
|
5287
5287
|
}
|
|
5288
5288
|
function readAgentFlow(workflowDir, limit) {
|
|
5289
|
-
const file =
|
|
5290
|
-
if (!
|
|
5289
|
+
const file = join6(workflowDir, AGENT_FLOW_FILE);
|
|
5290
|
+
if (!existsSync11(file)) {
|
|
5291
5291
|
return { events: [], summary: [] };
|
|
5292
5292
|
}
|
|
5293
5293
|
const n = limit === undefined ? AGENT_FLOW_DEFAULT_LIMIT : Math.max(0, Math.floor(limit));
|
|
@@ -5541,7 +5541,7 @@ function hhmm(ts) {
|
|
|
5541
5541
|
function harnessStateSource(harnessDir) {
|
|
5542
5542
|
if (harnessDir === null)
|
|
5543
5543
|
return null;
|
|
5544
|
-
const statusPath =
|
|
5544
|
+
const statusPath = join8(harnessDir, STATUS_FILE);
|
|
5545
5545
|
if (!existsSync13(statusPath))
|
|
5546
5546
|
return null;
|
|
5547
5547
|
try {
|
|
@@ -5561,7 +5561,7 @@ function harnessStateSource(harnessDir) {
|
|
|
5561
5561
|
if (selection.kind === "error") {
|
|
5562
5562
|
return selectionErrorState(selection, rollup, harnessDir, compass);
|
|
5563
5563
|
}
|
|
5564
|
-
const snapshotPath =
|
|
5564
|
+
const snapshotPath = join8(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
5565
5565
|
let snapshot;
|
|
5566
5566
|
if (!existsSync13(snapshotPath)) {
|
|
5567
5567
|
return selectionErrorState({
|
|
@@ -5625,7 +5625,7 @@ function harnessStateSource(harnessDir) {
|
|
|
5625
5625
|
leases,
|
|
5626
5626
|
knowledge: knowledgeDigest(harnessDir),
|
|
5627
5627
|
direction: compass !== undefined ? compassDirection(compass.compassPath) : null,
|
|
5628
|
-
agentFlow: readAgentFlow(
|
|
5628
|
+
agentFlow: readAgentFlow(join8(harnessDir, selection.dir), 50)
|
|
5629
5629
|
};
|
|
5630
5630
|
} catch {
|
|
5631
5631
|
return null;
|
|
@@ -5658,14 +5658,14 @@ function projectRollupSource(harnessDir, residuals) {
|
|
|
5658
5658
|
if (existsSync13(projectsDir)) {
|
|
5659
5659
|
let entries;
|
|
5660
5660
|
try {
|
|
5661
|
-
entries =
|
|
5661
|
+
entries = readdirSync4(projectsDir, { withFileTypes: true });
|
|
5662
5662
|
} catch {
|
|
5663
5663
|
entries = [];
|
|
5664
5664
|
}
|
|
5665
5665
|
for (const entry of entries) {
|
|
5666
5666
|
if (!entry.isDirectory())
|
|
5667
5667
|
continue;
|
|
5668
|
-
const roadmapPath =
|
|
5668
|
+
const roadmapPath = join8(projectsDir, entry.name, PROJECT_ROADMAP_FILE);
|
|
5669
5669
|
if (!existsSync13(roadmapPath))
|
|
5670
5670
|
continue;
|
|
5671
5671
|
try {
|
|
@@ -5735,7 +5735,7 @@ function projectRegisters(harnessDir) {
|
|
|
5735
5735
|
return [];
|
|
5736
5736
|
let entries;
|
|
5737
5737
|
try {
|
|
5738
|
-
entries =
|
|
5738
|
+
entries = readdirSync4(projectsDir, { withFileTypes: true });
|
|
5739
5739
|
} catch {
|
|
5740
5740
|
return [];
|
|
5741
5741
|
}
|
|
@@ -5743,7 +5743,7 @@ function projectRegisters(harnessDir) {
|
|
|
5743
5743
|
for (const entry of entries) {
|
|
5744
5744
|
if (!entry.isDirectory())
|
|
5745
5745
|
continue;
|
|
5746
|
-
const registerPath =
|
|
5746
|
+
const registerPath = join8(projectsDir, entry.name, PROJECT_REGISTER_FILE);
|
|
5747
5747
|
if (!existsSync13(registerPath))
|
|
5748
5748
|
continue;
|
|
5749
5749
|
try {
|
|
@@ -5755,7 +5755,7 @@ function projectRegisters(harnessDir) {
|
|
|
5755
5755
|
return registers;
|
|
5756
5756
|
}
|
|
5757
5757
|
function knowledgeDigest(harnessDir) {
|
|
5758
|
-
const indexPath =
|
|
5758
|
+
const indexPath = join8(harnessDir, "knowledge", "README.md");
|
|
5759
5759
|
if (!existsSync13(indexPath))
|
|
5760
5760
|
return null;
|
|
5761
5761
|
try {
|
|
@@ -5805,14 +5805,14 @@ function steeringCompassPath(harnessDir) {
|
|
|
5805
5805
|
return;
|
|
5806
5806
|
let entries;
|
|
5807
5807
|
try {
|
|
5808
|
-
entries =
|
|
5808
|
+
entries = readdirSync4(iterationsDir, { withFileTypes: true });
|
|
5809
5809
|
} catch {
|
|
5810
5810
|
return;
|
|
5811
5811
|
}
|
|
5812
5812
|
for (const entry of entries) {
|
|
5813
5813
|
if (!entry.isDirectory())
|
|
5814
5814
|
continue;
|
|
5815
|
-
const compassPath =
|
|
5815
|
+
const compassPath = join8(iterationsDir, entry.name, "delivery-compass.md");
|
|
5816
5816
|
if (!existsSync13(compassPath))
|
|
5817
5817
|
continue;
|
|
5818
5818
|
let content;
|
|
@@ -5831,7 +5831,7 @@ function steeringCompassPath(harnessDir) {
|
|
|
5831
5831
|
function iterationGateSource(harnessDir) {
|
|
5832
5832
|
if (harnessDir === null)
|
|
5833
5833
|
return;
|
|
5834
|
-
const statusPath =
|
|
5834
|
+
const statusPath = join8(harnessDir, STATUS_FILE);
|
|
5835
5835
|
if (!existsSync13(statusPath))
|
|
5836
5836
|
return;
|
|
5837
5837
|
const compass = steeringCompassPath(harnessDir);
|
|
@@ -5840,7 +5840,7 @@ function iterationGateSource(harnessDir) {
|
|
|
5840
5840
|
const selection = resolveReadWorkflow(harnessDir);
|
|
5841
5841
|
if (selection.kind === "error")
|
|
5842
5842
|
return;
|
|
5843
|
-
const snapshotPath =
|
|
5843
|
+
const snapshotPath = join8(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
5844
5844
|
try {
|
|
5845
5845
|
const snapshotDoc = readJson(snapshotPath);
|
|
5846
5846
|
const compassDoc = parseCompassFrontmatter(compass.compassPath);
|
|
@@ -5894,12 +5894,12 @@ function agentDigestKey(agent, cwd) {
|
|
|
5894
5894
|
}
|
|
5895
5895
|
|
|
5896
5896
|
// src/gates/status.ts
|
|
5897
|
-
import { existsSync as existsSync14, readdirSync as
|
|
5898
|
-
import { basename as
|
|
5897
|
+
import { existsSync as existsSync14, readdirSync as readdirSync7 } from "node:fs";
|
|
5898
|
+
import { basename as basename5, join as join11, relative as relative5, resolve as resolve10 } from "node:path";
|
|
5899
5899
|
var LOGGER_NAME = "mstar/status-gate";
|
|
5900
5900
|
function harnessDocKindOfTarget(harnessDir, targetPath) {
|
|
5901
|
-
const resolved =
|
|
5902
|
-
const name =
|
|
5901
|
+
const resolved = resolve10(targetPath);
|
|
5902
|
+
const name = basename5(resolved);
|
|
5903
5903
|
if (name !== STATUS_FILE && name !== WORKFLOW_SNAPSHOT_FILE && name !== PROJECT_REGISTER_FILE)
|
|
5904
5904
|
return null;
|
|
5905
5905
|
const rel = relative5(harnessDir, resolved);
|
|
@@ -5911,8 +5911,8 @@ function harnessDocKindOfTarget(harnessDir, targetPath) {
|
|
|
5911
5911
|
workflowDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
5912
5912
|
projectDir = resolveProjectDir(harnessDir, { harnessDir });
|
|
5913
5913
|
} catch {
|
|
5914
|
-
workflowDir =
|
|
5915
|
-
projectDir =
|
|
5914
|
+
workflowDir = join11(harnessDir, "workflows");
|
|
5915
|
+
projectDir = join11(harnessDir, "projects");
|
|
5916
5916
|
}
|
|
5917
5917
|
if (name === WORKFLOW_SNAPSHOT_FILE && /^[^/]+\/snapshot\.json$/.test(relative5(workflowDir, resolved)))
|
|
5918
5918
|
return "snapshot";
|
|
@@ -5968,7 +5968,7 @@ function projectRegisterDocs(harnessDir) {
|
|
|
5968
5968
|
const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
|
|
5969
5969
|
let entries;
|
|
5970
5970
|
try {
|
|
5971
|
-
entries =
|
|
5971
|
+
entries = readdirSync7(projectsDir, { withFileTypes: true });
|
|
5972
5972
|
} catch {
|
|
5973
5973
|
return [];
|
|
5974
5974
|
}
|
|
@@ -5976,7 +5976,7 @@ function projectRegisterDocs(harnessDir) {
|
|
|
5976
5976
|
for (const entry of entries) {
|
|
5977
5977
|
if (!entry.isDirectory())
|
|
5978
5978
|
continue;
|
|
5979
|
-
const registerPath =
|
|
5979
|
+
const registerPath = join11(projectsDir, entry.name, PROJECT_REGISTER_FILE);
|
|
5980
5980
|
if (!existsSync14(registerPath))
|
|
5981
5981
|
continue;
|
|
5982
5982
|
try {
|
|
@@ -6051,7 +6051,7 @@ async function editIntentListener(ctx, resolver, config, adapter, target, actor,
|
|
|
6051
6051
|
|
|
6052
6052
|
// src/gates/skill-lint.ts
|
|
6053
6053
|
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
|
|
6054
|
-
import { basename as basename7, dirname as dirname9, resolve as
|
|
6054
|
+
import { basename as basename7, dirname as dirname9, resolve as resolve13, sep as sep2 } from "node:path";
|
|
6055
6055
|
var SKILL_LINT_LOGGER = "mstar/skill-lint";
|
|
6056
6056
|
|
|
6057
6057
|
class SkillLintVetoError extends Error {
|
|
@@ -6114,8 +6114,8 @@ function skillRootsOf(config) {
|
|
|
6114
6114
|
function isSkillTarget(roots, target) {
|
|
6115
6115
|
if (basename7(target.displayPath) !== "SKILL.md")
|
|
6116
6116
|
return false;
|
|
6117
|
-
const resolvedPath =
|
|
6118
|
-
return roots.some((root) => resolvedPath.startsWith(
|
|
6117
|
+
const resolvedPath = resolve13(target.displayPath);
|
|
6118
|
+
return roots.some((root) => resolvedPath.startsWith(resolve13(root) + sep2));
|
|
6119
6119
|
}
|
|
6120
6120
|
function skillNameOf(target) {
|
|
6121
6121
|
return basename7(dirname9(target.displayPath));
|
|
@@ -6130,7 +6130,7 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
|
|
|
6130
6130
|
return;
|
|
6131
6131
|
if (!isSkillTarget(roots, target))
|
|
6132
6132
|
return;
|
|
6133
|
-
const skillPath =
|
|
6133
|
+
const skillPath = resolve13(target.displayPath);
|
|
6134
6134
|
if (!existsSync15(skillPath))
|
|
6135
6135
|
return;
|
|
6136
6136
|
let doc;
|
|
@@ -6182,8 +6182,8 @@ async function skillWriteIntentListener(ctx, resolver, config, target, actor, ne
|
|
|
6182
6182
|
}
|
|
6183
6183
|
|
|
6184
6184
|
// src/gates/seams.ts
|
|
6185
|
-
import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as
|
|
6186
|
-
import { basename as basename8, dirname as dirname10, join as
|
|
6185
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as readdirSync10 } from "node:fs";
|
|
6186
|
+
import { basename as basename8, dirname as dirname10, join as join15, resolve as resolve15, sep as sep3 } from "node:path";
|
|
6187
6187
|
|
|
6188
6188
|
// ../engine/dist/audit.js
|
|
6189
6189
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
@@ -6416,7 +6416,7 @@ function rolesDirOf(path) {
|
|
|
6416
6416
|
const idx = segments.indexOf("mstar-roles");
|
|
6417
6417
|
if (idx === -1)
|
|
6418
6418
|
return dirname10(path);
|
|
6419
|
-
return idx === 0 ? sep3 :
|
|
6419
|
+
return idx === 0 ? sep3 : join15(sep3, ...segments.slice(1, idx + 1));
|
|
6420
6420
|
}
|
|
6421
6421
|
function isSeamTarget(seam, harnessDir, target) {
|
|
6422
6422
|
const path = resolve15(target.displayPath);
|
|
@@ -6430,7 +6430,7 @@ function isSeamTarget(seam, harnessDir, target) {
|
|
|
6430
6430
|
case "compound": {
|
|
6431
6431
|
if (harnessDir === null)
|
|
6432
6432
|
return false;
|
|
6433
|
-
return path.startsWith(resolve15(
|
|
6433
|
+
return path.startsWith(resolve15(join15(harnessDir, "knowledge")) + sep3) && isMarkdownDoc(path);
|
|
6434
6434
|
}
|
|
6435
6435
|
case "roles":
|
|
6436
6436
|
return isRolesTarget(path);
|
|
@@ -6448,7 +6448,7 @@ function auditSecretsViolations(findings, file) {
|
|
|
6448
6448
|
function validateDesignDoc(doc, path) {
|
|
6449
6449
|
const violations = [...validateDesignTokenFrontmatter(doc).violations];
|
|
6450
6450
|
const isDark = basename8(path) === "DESIGN.dark.md";
|
|
6451
|
-
const sibling =
|
|
6451
|
+
const sibling = join15(dirname10(path), isDark ? "DESIGN.md" : "DESIGN.dark.md");
|
|
6452
6452
|
if (existsSync16(sibling)) {
|
|
6453
6453
|
const light = isDark ? readFileSync13(sibling, "utf8") : doc;
|
|
6454
6454
|
const dark = isDark ? doc : readFileSync13(sibling, "utf8");
|
|
@@ -6472,10 +6472,10 @@ function validateCompoundDoc(doc, _path, harnessDir) {
|
|
|
6472
6472
|
function validateRolesState(rolesDir, skillsRoot = dirname10(rolesDir)) {
|
|
6473
6473
|
const violations = [...validateRoleMapping(rolesDir).violations];
|
|
6474
6474
|
const skillTexts = {};
|
|
6475
|
-
for (const entry of
|
|
6475
|
+
for (const entry of readdirSync10(skillsRoot, { withFileTypes: true })) {
|
|
6476
6476
|
if (!entry.isDirectory() || !entry.name.startsWith("mstar-"))
|
|
6477
6477
|
continue;
|
|
6478
|
-
const skillFile =
|
|
6478
|
+
const skillFile = join15(skillsRoot, entry.name, "SKILL.md");
|
|
6479
6479
|
if (!existsSync16(skillFile))
|
|
6480
6480
|
continue;
|
|
6481
6481
|
try {
|
|
@@ -52,3 +52,5 @@ Execute **`mstar-audit`** § `pr` variant end to end(SKILL.md common core:re
|
|
|
52
52
|
Review findings that need fixing can be turned into self-contained plans for the normal Prepare → Execute flow (reusing `mstar-audit` SKILL.md **`## Plan output (all variants)`** — same contract as the `pr` variant).
|
|
53
53
|
|
|
54
54
|
Output verdict + findings to the user, with the posted GitHub Review URL. Posting procedure (when a PR number exists) → **`references/pr-review.md`** § Comment posting; the main agent saves the Stage 3 report and writes/consolidates the evidence files — every seat returns evidence / findings in its **result payload** (any seat may be write-blocked); writable seats may **best-effort** write files in addition → **`references/pr-review.md`** § Local report archive. Never auto-approve or merge.
|
|
55
|
+
|
|
56
|
+
**Stage 3 envelope (mandatory):** after the tally, the main agent **must** fold the accepted findings into a `mstar.review/v1` envelope (`synthesizeReview` — or the equivalent engine call) and persist it via `mstar-harness persist review --key <plan-or-pr-id> --stdin` (or `getArtifactStore().put`). The Markdown archive is an optional human copy — **not** a substitute for the envelope. Procedure → **`references/pr-review.md`** § Review pipeline (Stage 3).
|
|
@@ -8,9 +8,11 @@ Deep PR review is a **three-stage pipeline**: collect → domain review → synt
|
|
|
8
8
|
|
|
9
9
|
- **Stage 1 — Collect**: PM fans out lightweight read-only agents by **domain** — business domain / change surface / tech stack; use the host's lightest read-only agent (`scout` / `explorer` / `general` — whatever the host offers). Each collect seat reads the changed files in its domain plus related context and returns **evidence in its result payload** (any seat may be **write-blocked** — read-only sandbox / EPERM; the main agent extracts the payload and writes the evidence file — § Local report archive / `references/pr-review-seat-evidence.md`): `file:line` observations, potential issue surfaces, and security-surface observations (the seat carries a security lens per `security-review.md` §2/§3 **research** discipline — trace origin, never invent an attacker, never record secret values — and still records MEDIUM / unverified items as **leads** in its evidence payload; the HIGH-only filter applies to formal findings, not leads). Collect seats produce **no** findings table, compute **no** verdict, and publish **nothing**.
|
|
10
10
|
- **Stage 2 — Domain review**: mstar built-in roles (`code-reviewer` / `fullstack-dev` / `frontend-dev`) split along the same domain framing, each reviewing code + security in its domain (security via the `security-review.md` lens) and producing findings with **Merge class** (§ Merge class). Each domain seat returns its findings in the **result payload** — any seat may be **write-blocked**; the main agent writes the Stage 2 evidence file (§ Local report archive / `references/pr-review-seat-evidence.md`). A large PR (>~300 changed lines, or spanning multiple change surfaces/domains) or a security-sensitive surface (auth, LLM, supply chain, data — `security-review.md` §9 extended surfaces) adds an **independent cross-domain security seat**.
|
|
11
|
-
- **Stage 3 — Synthesis (main agent)**: the main agent (the command's orchestrator) collects all domain findings + evidence files → **dedupe** → **three-way vet** (open each cited file yourself; `file:line` must genuinely support the claim) → **tally** (§ Tally and derived score — formula unchanged) → **verdict** → report + **publish GitHub Review** (§ Comment posting — publishing authority belongs to the main agent). The main agent does not backfill uncollected / unreviewed domains — a missing domain is declared in the report under `- unverified:` / `- notes:`.
|
|
11
|
+
- **Stage 3 — Synthesis (main agent)**: the main agent (the command's orchestrator) collects all domain findings + evidence files → **dedupe** → **three-way vet** (open each cited file yourself; `file:line` must genuinely support the claim) → **tally** (§ Tally and derived score — formula unchanged) → **verdict** → report + **publish GitHub Review** (§ Comment posting — publishing authority belongs to the main agent). The main agent does not backfill uncollected / unreviewed domains — a missing domain is declared in the report under `- unverified:` / `- notes:`. **The envelope is mandatory**: after the tally, the main agent **must** fold the accepted findings into a `mstar.review/v1` envelope (`synthesizeReview` — or the equivalent engine call) and persist it via `mstar-harness persist review --key <plan-or-pr-id> --stdin` (or `getArtifactStore().put`); the Markdown archive is an optional human copy, **not** a substitute for the envelope.
|
|
12
12
|
- A domain whose seat returned **no evidence** (crashed / Blocked / empty output) is an **uncollected domain**, declared the same way under `- unverified:` / `- notes:`.
|
|
13
13
|
|
|
14
|
+
> **Engine check (when available):** run `mstar-harness persist review --key <plan-or-pr-id> --stdin` (or `import { synthesizeReview, validateMstarReviewV1 } from "@mstar-harness/engine"` in a host hook) to fold the accepted findings into the `mstar.review/v1` envelope and persist it — `synthesizeReview` derives verdict/tally from `computePrTally` (pure, no I/O), and `kind: review` runs `validateMstarReviewV1` before put, refusing invalid envelopes (exit 1, nothing written). On `fail` -> do not proceed; fix and re-run. Skill text below remains authoritative when the runtime is absent.
|
|
15
|
+
|
|
14
16
|
**Scale-driven fan-out** (reuses the existing sizing bands — no new thresholds): Stage 1 collect seats scale with PR size; the extra security seat stays in Stage 2:
|
|
15
17
|
|
|
16
18
|
| Size | Stage 1 collect seats | Extra Stage 2 |
|
package/package.json
CHANGED