@mstar-harness/engine 3.1.1 → 3.1.3
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/audit.d.ts +42 -0
- package/dist/audit.js +755 -0
- package/dist/core.d.ts +1 -1
- package/dist/dispatch.d.ts +14 -5
- package/dist/engine.js +221 -132
- package/dist/index.d.ts +2 -2
- package/dist/status.d.ts +20 -0
- package/package.json +7 -3
package/dist/core.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare function readJson(filePath: string): Record<string, unknown>;
|
|
|
83
83
|
* stored state; revisit if the harness moves to a filesystem without
|
|
84
84
|
* rename-atomicity guarantees.
|
|
85
85
|
*/
|
|
86
|
-
export declare function writeJson(filePath: string, value:
|
|
86
|
+
export declare function writeJson<T>(filePath: string, value: T): void;
|
|
87
87
|
/**
|
|
88
88
|
* Resolve the project root by walking up from `startDir` (default: cwd) to
|
|
89
89
|
* the nearest ancestor containing `package.json` or `bun.lock`. Falls back
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -173,8 +173,10 @@ export declare function assertTriIdentity(reviewerRoles: readonly string[]): Gat
|
|
|
173
173
|
export type ComposeDispatchGateOptions = {
|
|
174
174
|
/**
|
|
175
175
|
* Host role-binding field (omp task entry `agent` / opencode `subagent` /
|
|
176
|
-
* cursor `subagent_type`)
|
|
177
|
-
*
|
|
176
|
+
* cursor `subagent_type` / dsh `dispatchBinding`). The anti-recursion
|
|
177
|
+
* precheck runs for EVERY Assignment-shaped text: an empty or omitted
|
|
178
|
+
* binding fails closed (`dispatch.anti-recursion.empty-binding`) — the
|
|
179
|
+
* host cannot prove the dispatching agent is not recursing.
|
|
178
180
|
*/
|
|
179
181
|
agent?: string;
|
|
180
182
|
/**
|
|
@@ -204,7 +206,10 @@ export type ComposeDispatchGateResult = GateResult & {
|
|
|
204
206
|
* Assignment-shaped passes silently (`shaped: false`).
|
|
205
207
|
* 2. `validateAssignmentFields` with `writable: false` when `opts.writable
|
|
206
208
|
* === false` (read-only roles), else the writable default.
|
|
207
|
-
* 3. Anti-recursion precheck
|
|
209
|
+
* 3. Anti-recursion precheck — runs for every Assignment-shaped text; an
|
|
210
|
+
* empty/omitted host binding fails closed (`dispatch.anti-recursion.
|
|
211
|
+
* empty-binding`), a binding equal to `Execute as` is the existing
|
|
212
|
+
* `dispatch.anti-recursion.self-type` critical.
|
|
208
213
|
* 4. Default-branch gate for writable text: the branch comes from the
|
|
209
214
|
* Assignment's own branch forms (create-form name / Working branch /
|
|
210
215
|
* Branch policy branch), else `$MSTAR_WORKING_BRANCH`; a well-formed
|
|
@@ -221,7 +226,11 @@ export declare function composeDispatchGate(text: string, opts?: ComposeDispatch
|
|
|
221
226
|
* Anti-recursion precheck (NEVER red line, mstar-dispatch-gates § 承接方反递归
|
|
222
227
|
* 红线): a leaf executor MUST NOT invoke a Task/subagent whose role-binding
|
|
223
228
|
* field (`subagent_type` / `agent` / `subagent`) equals its own `Execute as`.
|
|
224
|
-
* Comparison is case-insensitive after trim
|
|
225
|
-
*
|
|
229
|
+
* Comparison is case-insensitive after trim. An EMPTY binding fails closed
|
|
230
|
+
* (`dispatch.anti-recursion.empty-binding`, critical): the host cannot
|
|
231
|
+
* report which agent is calling, so anti-recursion cannot be proven — the
|
|
232
|
+
* dispatch must not proceed as if the NEVER red line held. An empty
|
|
233
|
+
* `executeAs` with a set binding stays ok (field presence is
|
|
234
|
+
* `validateAssignmentFields`' job, not this precheck).
|
|
226
235
|
*/
|
|
227
236
|
export declare function antiRecursionPrecheck(subagentType: string, executeAs: string): GateResult;
|
package/dist/engine.js
CHANGED
|
@@ -170,7 +170,7 @@ function isAtOrBelow(dir, root) {
|
|
|
170
170
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
171
171
|
}
|
|
172
172
|
// src/path.ts
|
|
173
|
-
import { mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
173
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "node:fs";
|
|
174
174
|
import { execFileSync } from "node:child_process";
|
|
175
175
|
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
|
|
176
176
|
function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
@@ -425,6 +425,23 @@ function assertPlanWritingPath(planPath, harnessDir) {
|
|
|
425
425
|
fix: `write the plan under ${planDir}`
|
|
426
426
|
};
|
|
427
427
|
}
|
|
428
|
+
if (existsSync2(planAbs)) {
|
|
429
|
+
try {
|
|
430
|
+
const canonicalPlan = realpathSync(planAbs);
|
|
431
|
+
const canonicalPlanDir = existsSync2(planDir) ? realpathSync(planDir) : resolve3(planDir);
|
|
432
|
+
const canonicalRel = relative2(canonicalPlanDir, canonicalPlan);
|
|
433
|
+
const canonicalInside = canonicalRel === "" || !canonicalRel.startsWith("..") && !isAbsolute2(canonicalRel);
|
|
434
|
+
if (!canonicalInside) {
|
|
435
|
+
return {
|
|
436
|
+
ok: false,
|
|
437
|
+
severity: "high",
|
|
438
|
+
code: "plan-path.symlink-escape",
|
|
439
|
+
message: `plan file ${planAbs} resolves to ${canonicalPlan}, outside {PLAN_DIR} (${canonicalPlanDir})`,
|
|
440
|
+
fix: `write the plan under ${planDir}`
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
} catch {}
|
|
444
|
+
}
|
|
428
445
|
return {
|
|
429
446
|
ok: true,
|
|
430
447
|
severity: "low",
|
|
@@ -455,7 +472,7 @@ function hasFiles(dir) {
|
|
|
455
472
|
}
|
|
456
473
|
}
|
|
457
474
|
// src/status.ts
|
|
458
|
-
import { existsSync as
|
|
475
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
459
476
|
import { dirname as dirname5, join as join6, resolve as resolve5, sep } from "node:path";
|
|
460
477
|
|
|
461
478
|
// src/lease.ts
|
|
@@ -910,10 +927,8 @@ function composeDispatchGate(text, opts = {}) {
|
|
|
910
927
|
const violations = [];
|
|
911
928
|
const writable = opts.writable !== false;
|
|
912
929
|
violations.push(...validateAssignmentFields(text, { writable }).violations);
|
|
913
|
-
const agent =
|
|
914
|
-
|
|
915
|
-
violations.push(...antiRecursionPrecheck(agent, parseAssignmentFields(text).executeAs ?? "").violations);
|
|
916
|
-
}
|
|
930
|
+
const agent = opts.agent ?? "";
|
|
931
|
+
violations.push(...antiRecursionPrecheck(agent, parseAssignmentFields(text).executeAs ?? "").violations);
|
|
917
932
|
if (writable) {
|
|
918
933
|
const forms = parseAssignmentBranchForms(text);
|
|
919
934
|
const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
|
|
@@ -932,7 +947,15 @@ function composeDispatchGate(text, opts = {}) {
|
|
|
932
947
|
function antiRecursionPrecheck(subagentType, executeAs) {
|
|
933
948
|
const binding = subagentType.trim().toLowerCase();
|
|
934
949
|
const role = executeAs.trim().toLowerCase();
|
|
935
|
-
if (binding
|
|
950
|
+
if (binding === "") {
|
|
951
|
+
return {
|
|
952
|
+
ok: false,
|
|
953
|
+
violations: [
|
|
954
|
+
violation2("critical", "dispatch.anti-recursion.empty-binding", `empty host role binding — the host cannot report which agent is calling, so anti-recursion cannot be proven (a dispatch could silently recurse)`, "set the host role-binding field (omp task entry `agent` / opencode `subagent` / cursor `subagent_type` / dsh `dispatchBinding`) before dispatching")
|
|
955
|
+
]
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (binding === role) {
|
|
936
959
|
return {
|
|
937
960
|
ok: false,
|
|
938
961
|
violations: [
|
|
@@ -1294,7 +1317,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
1294
1317
|
if (harnessDir !== undefined && Array.isArray(doc.workflows)) {
|
|
1295
1318
|
let realHarnessDir = null;
|
|
1296
1319
|
try {
|
|
1297
|
-
realHarnessDir =
|
|
1320
|
+
realHarnessDir = realpathSync2(harnessDir);
|
|
1298
1321
|
} catch {}
|
|
1299
1322
|
for (const entry of doc.workflows) {
|
|
1300
1323
|
if (!isPlainObject3(entry) || typeof entry.dir !== "string")
|
|
@@ -1304,7 +1327,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
1304
1327
|
const label = typeof entry.id === "string" ? entry.id : relSnapshot;
|
|
1305
1328
|
let physical;
|
|
1306
1329
|
try {
|
|
1307
|
-
physical =
|
|
1330
|
+
physical = realpathSync2(snapshotPath);
|
|
1308
1331
|
} catch {
|
|
1309
1332
|
violations.push(violation4("high", "status.workflow.snapshot-missing", `workflows[] lists ${JSON.stringify(label)} but its snapshot does not exist at ${JSON.stringify(relSnapshot)} — the root holds active lifecycles only; unregister the id when its snapshot is removed`));
|
|
1310
1333
|
continue;
|
|
@@ -1327,41 +1350,42 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
1327
1350
|
violations.push(violation4("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} — the root entry mirrors the snapshot; align them`));
|
|
1328
1351
|
}
|
|
1329
1352
|
if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
|
|
1330
|
-
violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — the root entry mirrors the snapshot
|
|
1353
|
+
violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot — align them or remove the colliding workflow`));
|
|
1331
1354
|
}
|
|
1332
1355
|
}
|
|
1333
1356
|
}
|
|
1334
1357
|
return { ok: violations.length === 0, violations };
|
|
1335
1358
|
}
|
|
1336
1359
|
var validateStatus = validateStatusV2;
|
|
1360
|
+
function registerWorkflowEntryLocked(statusPath, entry) {
|
|
1361
|
+
const harnessDir = dirname5(statusPath);
|
|
1362
|
+
const current = readJson(statusPath);
|
|
1363
|
+
const fresh = Object.keys(current).length === 0;
|
|
1364
|
+
const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
|
|
1365
|
+
if (!fresh && !Array.isArray(doc.workflows)) {
|
|
1366
|
+
throw new Error("refusing to modify status.json: workflows must be an array — a v1 root must be migrated first (run `mstar migrate`)");
|
|
1367
|
+
}
|
|
1368
|
+
const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
|
|
1369
|
+
if (existing >= 0) {
|
|
1370
|
+
doc.workflows[existing] = entry;
|
|
1371
|
+
} else {
|
|
1372
|
+
doc.workflows.push(entry);
|
|
1373
|
+
}
|
|
1374
|
+
doc.updated_at = todayString();
|
|
1375
|
+
const gate = validateStatusV2(doc, { harnessDir });
|
|
1376
|
+
if (!gate.ok) {
|
|
1377
|
+
throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
1378
|
+
}
|
|
1379
|
+
writeJson(statusPath, doc);
|
|
1380
|
+
return doc;
|
|
1381
|
+
}
|
|
1337
1382
|
async function registerWorkflow(root, entry) {
|
|
1338
1383
|
const entryGate = validateWorkflowEntry(entry);
|
|
1339
1384
|
if (!entryGate.ok) {
|
|
1340
1385
|
throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
|
|
1341
1386
|
}
|
|
1342
1387
|
const statusPath = resolve5(root);
|
|
1343
|
-
|
|
1344
|
-
return withStatusWriteLock(statusPath, () => {
|
|
1345
|
-
const current = readJson(statusPath);
|
|
1346
|
-
const fresh = Object.keys(current).length === 0;
|
|
1347
|
-
const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
|
|
1348
|
-
if (!fresh && !Array.isArray(doc.workflows)) {
|
|
1349
|
-
throw new Error("refusing to modify status.json: workflows must be an array — a v1 root must be migrated first (run `mstar migrate`)");
|
|
1350
|
-
}
|
|
1351
|
-
const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
|
|
1352
|
-
if (existing >= 0) {
|
|
1353
|
-
doc.workflows[existing] = entry;
|
|
1354
|
-
} else {
|
|
1355
|
-
doc.workflows.push(entry);
|
|
1356
|
-
}
|
|
1357
|
-
doc.updated_at = todayString();
|
|
1358
|
-
const gate = validateStatusV2(doc, { harnessDir });
|
|
1359
|
-
if (!gate.ok) {
|
|
1360
|
-
throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
1361
|
-
}
|
|
1362
|
-
writeJson(statusPath, doc);
|
|
1363
|
-
return doc;
|
|
1364
|
-
});
|
|
1388
|
+
return withStatusWriteLock(statusPath, () => registerWorkflowEntryLocked(statusPath, entry));
|
|
1365
1389
|
}
|
|
1366
1390
|
async function unregisterWorkflow(root, id) {
|
|
1367
1391
|
if (typeof id !== "string" || id.trim() === "") {
|
|
@@ -1394,7 +1418,7 @@ async function unregisterWorkflow(root, id) {
|
|
|
1394
1418
|
}
|
|
1395
1419
|
function resolveCompassEnforcement(harnessDir) {
|
|
1396
1420
|
const iterationsDir = resolveIterationDir(harnessDir);
|
|
1397
|
-
if (!
|
|
1421
|
+
if (!existsSync3(iterationsDir))
|
|
1398
1422
|
return { hard: false, source: "none" };
|
|
1399
1423
|
let entries;
|
|
1400
1424
|
try {
|
|
@@ -1406,7 +1430,7 @@ function resolveCompassEnforcement(harnessDir) {
|
|
|
1406
1430
|
if (!entry.isDirectory())
|
|
1407
1431
|
continue;
|
|
1408
1432
|
const compassPath = join6(iterationsDir, entry.name, "delivery-compass.md");
|
|
1409
|
-
if (!
|
|
1433
|
+
if (!existsSync3(compassPath))
|
|
1410
1434
|
continue;
|
|
1411
1435
|
let content;
|
|
1412
1436
|
try {
|
|
@@ -1442,7 +1466,7 @@ function resolveRepoEnforcement(harnessDir) {
|
|
|
1442
1466
|
}
|
|
1443
1467
|
// src/worktree.ts
|
|
1444
1468
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1445
|
-
import { existsSync as
|
|
1469
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1446
1470
|
import { isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
|
|
1447
1471
|
var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
|
|
1448
1472
|
function probeTimeoutMs() {
|
|
@@ -1497,7 +1521,7 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
1497
1521
|
if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve6(controlWorktreePath) === resolve6(leaseWorktreePath)) {
|
|
1498
1522
|
violations.push(violation5("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"));
|
|
1499
1523
|
}
|
|
1500
|
-
if (leaseWorktreePath !== "" && !
|
|
1524
|
+
if (leaseWorktreePath !== "" && !existsSync4(leaseWorktreePath)) {
|
|
1501
1525
|
violations.push(violation5("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>`));
|
|
1502
1526
|
} else if (leaseWorktreePath !== "" && leaseWorkingBranch !== "") {
|
|
1503
1527
|
const probe = probeBranch(leaseWorktreePath, opts);
|
|
@@ -1531,7 +1555,7 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
1531
1555
|
return;
|
|
1532
1556
|
}
|
|
1533
1557
|
seenPaths.add(normalized);
|
|
1534
|
-
if (!
|
|
1558
|
+
if (!existsSync4(track.worktreePath)) {
|
|
1535
1559
|
violations.push(violation5("high", "worktree.l2.track-missing", `track worktree directory "${track.worktreePath}" does not exist`, `create it before dispatch: git worktree add ${track.worktreePath} ${track.workingBranch}`));
|
|
1536
1560
|
return;
|
|
1537
1561
|
}
|
|
@@ -1594,7 +1618,7 @@ function singleReviewSnapshot(assignments) {
|
|
|
1594
1618
|
}
|
|
1595
1619
|
// src/sdd.ts
|
|
1596
1620
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1597
|
-
import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync5, realpathSync as
|
|
1621
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1598
1622
|
import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5, join as join7, resolve as resolve7 } from "node:path";
|
|
1599
1623
|
class SddScriptError extends Error {
|
|
1600
1624
|
exitCode;
|
|
@@ -1671,8 +1695,8 @@ function isLinkedWorktree(root) {
|
|
|
1671
1695
|
if (gitDir.includes("/.git/worktrees/") || gitDir.includes("/worktrees/"))
|
|
1672
1696
|
return true;
|
|
1673
1697
|
try {
|
|
1674
|
-
const gdParent =
|
|
1675
|
-
const cmAbs =
|
|
1698
|
+
const gdParent = realpathSync3(dirname6(gitDir));
|
|
1699
|
+
const cmAbs = realpathSync3(common);
|
|
1676
1700
|
return join7(gdParent, basename3(gitDir)) !== cmAbs && gitDir !== cmAbs;
|
|
1677
1701
|
} catch {
|
|
1678
1702
|
return false;
|
|
@@ -1690,10 +1714,10 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
1690
1714
|
if (!isDirectory2(controlRoot)) {
|
|
1691
1715
|
throw new SddScriptError(`mstar sdd workspace: CONTROL_ROOT / MSTAR_CONTROL_ROOT is not a directory: ${controlRoot}`, 1);
|
|
1692
1716
|
}
|
|
1693
|
-
root =
|
|
1717
|
+
root = realpathSync3(controlRoot);
|
|
1694
1718
|
} else {
|
|
1695
1719
|
const topLevel = gitOut(cwd, ["rev-parse", "--show-toplevel"]);
|
|
1696
|
-
root =
|
|
1720
|
+
root = realpathSync3(topLevel ?? cwd);
|
|
1697
1721
|
}
|
|
1698
1722
|
if (!controlRoot && isLinkedWorktree(root)) {
|
|
1699
1723
|
throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
|
|
@@ -1727,7 +1751,7 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
1727
1751
|
mkdirSync5(sddDir, { recursive: true });
|
|
1728
1752
|
writeFileSync3(join7(sddDir, ".gitignore"), `*
|
|
1729
1753
|
`);
|
|
1730
|
-
return
|
|
1754
|
+
return realpathSync3(sddDir);
|
|
1731
1755
|
}
|
|
1732
1756
|
function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
1733
1757
|
if (!planFile || !Number.isInteger(taskN) || taskN < 1) {
|
|
@@ -1879,7 +1903,7 @@ function implementerSessionStickyRules(input) {
|
|
|
1879
1903
|
return { resume: true, reason: `sticky resume OK: host_agent_id ${session.host_agent_id}, next task ${nextTask}` };
|
|
1880
1904
|
}
|
|
1881
1905
|
// src/iteration.ts
|
|
1882
|
-
import { existsSync as
|
|
1906
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "node:fs";
|
|
1883
1907
|
import { join as join8 } from "node:path";
|
|
1884
1908
|
var COMPASS_STATUSES = ["active", "locked", "completed"];
|
|
1885
1909
|
var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -2108,7 +2132,7 @@ function pushCadenceProbe(ciRunning, reviewWaveActive) {
|
|
|
2108
2132
|
return { ok: violations.length === 0, violations };
|
|
2109
2133
|
}
|
|
2110
2134
|
function assertIndexRowObligations(iterationsDir) {
|
|
2111
|
-
if (!
|
|
2135
|
+
if (!existsSync5(iterationsDir)) {
|
|
2112
2136
|
return {
|
|
2113
2137
|
ok: false,
|
|
2114
2138
|
violations: [
|
|
@@ -2116,9 +2140,9 @@ function assertIndexRowObligations(iterationsDir) {
|
|
|
2116
2140
|
]
|
|
2117
2141
|
};
|
|
2118
2142
|
}
|
|
2119
|
-
const iterationIds = readdirSync4(iterationsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) =>
|
|
2143
|
+
const iterationIds = readdirSync4(iterationsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => existsSync5(join8(iterationsDir, entry.name, COMPASS_FILE))).map((entry) => entry.name).sort();
|
|
2120
2144
|
const readmePath = join8(iterationsDir, INDEX_README);
|
|
2121
|
-
if (!
|
|
2145
|
+
if (!existsSync5(readmePath)) {
|
|
2122
2146
|
return {
|
|
2123
2147
|
ok: false,
|
|
2124
2148
|
violations: [
|
|
@@ -2209,7 +2233,7 @@ function parseFlowArray(raw, filePath) {
|
|
|
2209
2233
|
return items;
|
|
2210
2234
|
}
|
|
2211
2235
|
// src/project.ts
|
|
2212
|
-
import { existsSync as
|
|
2236
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
2213
2237
|
import { join as join9 } from "node:path";
|
|
2214
2238
|
var PROJECT_ROADMAP_FILE = "roadmap.md";
|
|
2215
2239
|
var PROJECT_REFERENCES_DIR = "references";
|
|
@@ -2393,7 +2417,7 @@ function techDebtRollup(projectDir) {
|
|
|
2393
2417
|
if (!project.isDirectory())
|
|
2394
2418
|
continue;
|
|
2395
2419
|
const registerPath = join9(projectDir, project.name, PROJECT_REGISTER_FILE);
|
|
2396
|
-
if (!
|
|
2420
|
+
if (!existsSync6(registerPath))
|
|
2397
2421
|
continue;
|
|
2398
2422
|
let register;
|
|
2399
2423
|
try {
|
|
@@ -2967,9 +2991,11 @@ async function applyMigratePlan(plan) {
|
|
|
2967
2991
|
if (!gate2.ok) {
|
|
2968
2992
|
throw new Error(`refusing to apply migration: invalid project register: ${gate2.violations.map((v) => v.message).join("; ")}`);
|
|
2969
2993
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2994
|
+
if (Object.keys(plan.register.data.entries ?? {}).length > 0) {
|
|
2995
|
+
const filePath = projectTargetOf(plan.register.file);
|
|
2996
|
+
mkdirSync6(dirname7(filePath), { recursive: true });
|
|
2997
|
+
writeJson(filePath, plan.register.data);
|
|
2998
|
+
}
|
|
2973
2999
|
}
|
|
2974
3000
|
if (plan.roadmap !== null) {
|
|
2975
3001
|
const filePath = projectTargetOf(plan.roadmap.file);
|
|
@@ -3396,8 +3422,8 @@ function completenessLevel(frontmatterText, checklist) {
|
|
|
3396
3422
|
return { level, items, missing, placeholders, upgradeTo, bodyUnverified };
|
|
3397
3423
|
}
|
|
3398
3424
|
// src/audit.ts
|
|
3399
|
-
import { mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3400
|
-
import { join as join11, resolve as resolve9 } from "node:path";
|
|
3425
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3426
|
+
import { basename as basename4, join as join11, resolve as resolve9, sep as sep3 } from "node:path";
|
|
3401
3427
|
function violation9(severity, code, message, fix) {
|
|
3402
3428
|
return { ok: false, severity, code, message, fix };
|
|
3403
3429
|
}
|
|
@@ -3469,74 +3495,6 @@ function validateAuditStatusBlocks(planText) {
|
|
|
3469
3495
|
});
|
|
3470
3496
|
return { ok: violations.length === 0, violations };
|
|
3471
3497
|
}
|
|
3472
|
-
var WHOLE_MATCH_PATTERNS = [
|
|
3473
|
-
{ type: "private-key", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/g },
|
|
3474
|
-
{ type: "aws-access-key", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
|
|
3475
|
-
{ type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
|
|
3476
|
-
{ type: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
3477
|
-
{ type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\b/g },
|
|
3478
|
-
{ type: "api-secret-key", re: /\bsk-[A-Za-z0-9-]{20,}\b/g }
|
|
3479
|
-
];
|
|
3480
|
-
var VALUE_PATTERNS = [
|
|
3481
|
-
{
|
|
3482
|
-
typeOf: (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[_-]+/g, "-"),
|
|
3483
|
-
re: /(["']?)\b(password|passwd|api[_-]?key|access[_-]?token|auth[_-]?token|secret|token)\b(["']?)(\s*[:=]\s*)("[^"\n]{8,}"|'[^'\n]{8,}'|[A-Za-z0-9_./+\-=]{16,})/gi
|
|
3484
|
-
}
|
|
3485
|
-
];
|
|
3486
|
-
function buildLineStarts(text) {
|
|
3487
|
-
const starts = [0];
|
|
3488
|
-
for (let i = 0;i < text.length; i++) {
|
|
3489
|
-
if (text[i] === `
|
|
3490
|
-
`)
|
|
3491
|
-
starts.push(i + 1);
|
|
3492
|
-
}
|
|
3493
|
-
return starts;
|
|
3494
|
-
}
|
|
3495
|
-
function lineAt(starts, index) {
|
|
3496
|
-
let lo = 0;
|
|
3497
|
-
let hi = starts.length - 1;
|
|
3498
|
-
while (lo < hi) {
|
|
3499
|
-
const mid = lo + hi + 1 >> 1;
|
|
3500
|
-
if (starts[mid] <= index)
|
|
3501
|
-
lo = mid;
|
|
3502
|
-
else
|
|
3503
|
-
hi = mid - 1;
|
|
3504
|
-
}
|
|
3505
|
-
return lo + 1;
|
|
3506
|
-
}
|
|
3507
|
-
function redactSecrets(text, filePath) {
|
|
3508
|
-
const starts = buildLineStarts(text);
|
|
3509
|
-
const marker = (type, index) => `[REDACTED ${type}@${lineAt(starts, index)}${filePath === undefined ? "" : ` in ${filePath}`}]`;
|
|
3510
|
-
const replacements = [];
|
|
3511
|
-
const findings = [];
|
|
3512
|
-
for (const pattern of WHOLE_MATCH_PATTERNS) {
|
|
3513
|
-
for (const match of text.matchAll(pattern.re)) {
|
|
3514
|
-
if (match.index === undefined)
|
|
3515
|
-
continue;
|
|
3516
|
-
replacements.push({ index: match.index, length: match[0].length, text: marker(pattern.type, match.index) });
|
|
3517
|
-
findings.push({ line: lineAt(starts, match.index), type: pattern.type });
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
for (const pattern of VALUE_PATTERNS) {
|
|
3521
|
-
for (const match of text.matchAll(pattern.re)) {
|
|
3522
|
-
if (match.index === undefined)
|
|
3523
|
-
continue;
|
|
3524
|
-
const type = pattern.typeOf(match[2]);
|
|
3525
|
-
const replacement = `${match[1]}${match[2]}${match[3]}${match[4]}${marker(type, match.index)}`;
|
|
3526
|
-
replacements.push({ index: match.index, length: match[0].length, text: replacement });
|
|
3527
|
-
findings.push({ line: lineAt(starts, match.index), type });
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
replacements.sort((a, b) => b.index - a.index);
|
|
3531
|
-
let out = text;
|
|
3532
|
-
for (const r of replacements)
|
|
3533
|
-
out = out.slice(0, r.index) + r.text + out.slice(r.index + r.length);
|
|
3534
|
-
const deduped = new Map;
|
|
3535
|
-
for (const f of findings)
|
|
3536
|
-
deduped.set(`${f.line}:${f.type}`, f);
|
|
3537
|
-
const sorted = [...deduped.values()].sort((a, b) => a.line - b.line || a.type.localeCompare(b.type));
|
|
3538
|
-
return { text: out, findings: sorted };
|
|
3539
|
-
}
|
|
3540
3498
|
function slugify(title) {
|
|
3541
3499
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3542
3500
|
}
|
|
@@ -3673,9 +3631,140 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
3673
3631
|
}));
|
|
3674
3632
|
return { outDir: resolve9(outDir), date, files: written, nextNumber: next };
|
|
3675
3633
|
}
|
|
3634
|
+
async function promoteAuditPlans(outDir, selected, options) {
|
|
3635
|
+
if (selected.length === 0) {
|
|
3636
|
+
throw new Error("promoteAuditPlans: at least one plan id must be selected (--plans 001,002,…)");
|
|
3637
|
+
}
|
|
3638
|
+
if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
|
|
3639
|
+
throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
|
|
3640
|
+
}
|
|
3641
|
+
const workflowId = options.workflowId ?? basename4(resolve9(outDir));
|
|
3642
|
+
assertSafePathComponent(workflowId, "workflow id");
|
|
3643
|
+
const harnessDir = resolve9(options.harnessDir);
|
|
3644
|
+
const statusPath = join11(harnessDir, "status.json");
|
|
3645
|
+
const workflowDir = join11(harnessDir, "workflows", workflowId);
|
|
3646
|
+
const snapshotPath = join11(workflowDir, WORKFLOW_SNAPSHOT_FILE);
|
|
3647
|
+
const planFiles = resolveSelectedPlanFiles(outDir, selected);
|
|
3648
|
+
const indexRows = readExecutionOrderIndex(outDir);
|
|
3649
|
+
const plans = planFiles.map((planFile) => {
|
|
3650
|
+
const stem = planFile.replace(/\.md$/, "");
|
|
3651
|
+
const num = stem.slice(0, 3);
|
|
3652
|
+
const indexRow = indexRows.get(num);
|
|
3653
|
+
const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
|
|
3654
|
+
return {
|
|
3655
|
+
id: stem,
|
|
3656
|
+
title,
|
|
3657
|
+
file: planFileRel(outDir, planFile),
|
|
3658
|
+
status: "Todo"
|
|
3659
|
+
};
|
|
3660
|
+
});
|
|
3661
|
+
const now = new Date;
|
|
3662
|
+
const snapshot = {
|
|
3663
|
+
schema_version: 1,
|
|
3664
|
+
id: workflowId,
|
|
3665
|
+
type: "plan",
|
|
3666
|
+
status: "running",
|
|
3667
|
+
started_at: now.toISOString(),
|
|
3668
|
+
updated_at: now.toISOString().slice(0, 10),
|
|
3669
|
+
plans
|
|
3670
|
+
};
|
|
3671
|
+
const entry = {
|
|
3672
|
+
id: workflowId,
|
|
3673
|
+
type: "plan",
|
|
3674
|
+
started_at: snapshot.started_at,
|
|
3675
|
+
dir: `workflows/${workflowId}`
|
|
3676
|
+
};
|
|
3677
|
+
const entryGate = validateWorkflowEntry(entry);
|
|
3678
|
+
if (!entryGate.ok) {
|
|
3679
|
+
throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
|
|
3680
|
+
}
|
|
3681
|
+
await withStatusWriteLock(statusPath, () => {
|
|
3682
|
+
if (existsSync7(snapshotPath)) {
|
|
3683
|
+
throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists ` + `(snapshot at ${snapshotPath}) — re-promote would drop its registered plan rows; ` + `remove that workflow before promoting again`);
|
|
3684
|
+
}
|
|
3685
|
+
mkdirSync7(workflowDir, { recursive: true });
|
|
3686
|
+
try {
|
|
3687
|
+
writeJson(snapshotPath, snapshot);
|
|
3688
|
+
registerWorkflowEntryLocked(statusPath, entry);
|
|
3689
|
+
} catch (error) {
|
|
3690
|
+
rmSync(snapshotPath, { force: true });
|
|
3691
|
+
try {
|
|
3692
|
+
if (readdirSync7(workflowDir).length === 0) {
|
|
3693
|
+
rmdirSync2(workflowDir);
|
|
3694
|
+
}
|
|
3695
|
+
} catch {}
|
|
3696
|
+
throw error;
|
|
3697
|
+
}
|
|
3698
|
+
return { workflowId, snapshotPath };
|
|
3699
|
+
});
|
|
3700
|
+
return { workflowId, snapshotPath };
|
|
3701
|
+
}
|
|
3702
|
+
function resolveSelectedPlanFiles(outDir, selected) {
|
|
3703
|
+
const files = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
|
|
3704
|
+
const byNum = new Map;
|
|
3705
|
+
const byStem = new Map;
|
|
3706
|
+
for (const file of files) {
|
|
3707
|
+
const stem = file.replace(/\.md$/, "");
|
|
3708
|
+
if (!byNum.has(stem.slice(0, 3))) {
|
|
3709
|
+
byNum.set(stem.slice(0, 3), file);
|
|
3710
|
+
}
|
|
3711
|
+
byStem.set(stem, file);
|
|
3712
|
+
}
|
|
3713
|
+
const resolved = [];
|
|
3714
|
+
const seen = new Set;
|
|
3715
|
+
for (const id of selected) {
|
|
3716
|
+
const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
|
|
3717
|
+
if (file === undefined) {
|
|
3718
|
+
throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve9(outDir)}`);
|
|
3719
|
+
}
|
|
3720
|
+
if (!seen.has(file)) {
|
|
3721
|
+
seen.add(file);
|
|
3722
|
+
resolved.push(file);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
return resolved;
|
|
3726
|
+
}
|
|
3727
|
+
function readExecutionOrderIndex(outDir) {
|
|
3728
|
+
const readmePath = join11(outDir, "README.md");
|
|
3729
|
+
let text;
|
|
3730
|
+
try {
|
|
3731
|
+
text = readFileSync9(readmePath, "utf8");
|
|
3732
|
+
} catch {
|
|
3733
|
+
return new Map;
|
|
3734
|
+
}
|
|
3735
|
+
const rows = new Map;
|
|
3736
|
+
const lines = text.split(`
|
|
3737
|
+
`);
|
|
3738
|
+
let inSection = false;
|
|
3739
|
+
for (const line of lines) {
|
|
3740
|
+
if (/^##\s+Execution order & status/.test(line)) {
|
|
3741
|
+
inSection = true;
|
|
3742
|
+
continue;
|
|
3743
|
+
}
|
|
3744
|
+
if (inSection && /^#/.test(line)) {
|
|
3745
|
+
break;
|
|
3746
|
+
}
|
|
3747
|
+
if (!inSection)
|
|
3748
|
+
continue;
|
|
3749
|
+
const cells = line.split(/(?<!\\)\|/).map((c) => c.trim());
|
|
3750
|
+
if (cells.length >= 3 && /^\d{3}$/.test(cells[1])) {
|
|
3751
|
+
rows.set(cells[1], { title: cells[2].replace(/\\\|/g, "|") });
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
return rows;
|
|
3755
|
+
}
|
|
3756
|
+
function planFileRel(outDir, planFile) {
|
|
3757
|
+
const resolved = resolve9(outDir);
|
|
3758
|
+
const parts = resolved.split(sep3);
|
|
3759
|
+
const plansIdx = parts.lastIndexOf("plans");
|
|
3760
|
+
if (plansIdx >= 0) {
|
|
3761
|
+
return `${parts.slice(plansIdx + 1).join(sep3)}${sep3}${planFile}`;
|
|
3762
|
+
}
|
|
3763
|
+
return planFile;
|
|
3764
|
+
}
|
|
3676
3765
|
// src/compound.ts
|
|
3677
|
-
import { existsSync as
|
|
3678
|
-
import { basename as
|
|
3766
|
+
import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
3767
|
+
import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve10, sep as sep4 } from "node:path";
|
|
3679
3768
|
function violation10(severity, code, message, fix) {
|
|
3680
3769
|
return { ok: false, severity, code, message, fix };
|
|
3681
3770
|
}
|
|
@@ -3982,7 +4071,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3982
4071
|
for (const { ref, isSymbol, module } of refs) {
|
|
3983
4072
|
if (!isSymbol || module === undefined) {
|
|
3984
4073
|
const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
|
|
3985
|
-
if (
|
|
4074
|
+
if (existsSync8(resolve10(repoRoot, candidate))) {
|
|
3986
4075
|
checked++;
|
|
3987
4076
|
} else {
|
|
3988
4077
|
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"));
|
|
@@ -4013,7 +4102,7 @@ function collectKnowledgeDocs(dir) {
|
|
|
4013
4102
|
if (entry.isDirectory()) {
|
|
4014
4103
|
stack.push(full);
|
|
4015
4104
|
} else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
|
|
4016
|
-
docs.push(relative4(dir, full).split(
|
|
4105
|
+
docs.push(relative4(dir, full).split(sep4).join("/"));
|
|
4017
4106
|
}
|
|
4018
4107
|
}
|
|
4019
4108
|
}
|
|
@@ -4030,7 +4119,7 @@ function normalizeIndexRef(cell) {
|
|
|
4030
4119
|
function assertIndexRows(knowledgeDir) {
|
|
4031
4120
|
const violations = [];
|
|
4032
4121
|
const readmePath = join12(knowledgeDir, "README.md");
|
|
4033
|
-
if (!
|
|
4122
|
+
if (!existsSync8(readmePath)) {
|
|
4034
4123
|
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"));
|
|
4035
4124
|
return { ok: false, violations };
|
|
4036
4125
|
}
|
|
@@ -4062,7 +4151,7 @@ function compoundRefreshScope(harnessDir, projectRoot) {
|
|
|
4062
4151
|
];
|
|
4063
4152
|
}
|
|
4064
4153
|
function isFileLikeRoot(root) {
|
|
4065
|
-
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(
|
|
4154
|
+
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
|
|
4066
4155
|
}
|
|
4067
4156
|
function scopeGuard(path, allowedRoots) {
|
|
4068
4157
|
const resolved = resolve10(path);
|
|
@@ -4071,7 +4160,7 @@ function scopeGuard(path, allowedRoots) {
|
|
|
4071
4160
|
if (isFileLikeRoot(r)) {
|
|
4072
4161
|
if (resolved === r)
|
|
4073
4162
|
return { ok: true, violations: [] };
|
|
4074
|
-
} else if (resolved === r || resolved.startsWith(r +
|
|
4163
|
+
} else if (resolved === r || resolved.startsWith(r + sep4)) {
|
|
4075
4164
|
return { ok: true, violations: [] };
|
|
4076
4165
|
}
|
|
4077
4166
|
}
|
|
@@ -4315,7 +4404,7 @@ function lintStrategySections(docText) {
|
|
|
4315
4404
|
return { ok: violations.length === 0, violations };
|
|
4316
4405
|
}
|
|
4317
4406
|
// src/roles.ts
|
|
4318
|
-
import { existsSync as
|
|
4407
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
4319
4408
|
import { join as join13 } from "node:path";
|
|
4320
4409
|
function violation12(severity, code, message, fix) {
|
|
4321
4410
|
return { ok: false, severity, code, message, fix };
|
|
@@ -4372,7 +4461,7 @@ function validateRoleMapping(rolesDir, options = {}) {
|
|
|
4372
4461
|
const violations = [];
|
|
4373
4462
|
const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
|
|
4374
4463
|
for (const { agentId, reference } of mapping) {
|
|
4375
|
-
if (!
|
|
4464
|
+
if (!existsSync9(join13(rolesDir, reference))) {
|
|
4376
4465
|
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 ${join13(rolesDir, reference)} or fix the mapping row`));
|
|
4377
4466
|
}
|
|
4378
4467
|
}
|
|
@@ -4628,11 +4717,11 @@ export {
|
|
|
4628
4717
|
releaseLease,
|
|
4629
4718
|
registerWorkflow,
|
|
4630
4719
|
referenceExists,
|
|
4631
|
-
redactSecrets,
|
|
4632
4720
|
readProgressLedger,
|
|
4633
4721
|
readJson,
|
|
4634
4722
|
readHarnessVersion,
|
|
4635
4723
|
pushCadenceProbe,
|
|
4724
|
+
promoteAuditPlans,
|
|
4636
4725
|
planQualityBar,
|
|
4637
4726
|
planExecutionLeaseLocations,
|
|
4638
4727
|
parseMstarc,
|
package/dist/index.d.ts
CHANGED
|
@@ -46,8 +46,8 @@ export type { MigrateNotesFile, MigrateOptions, MigratePlan, MigrateRegister, Mi
|
|
|
46
46
|
export { ARCHIVED_STATUS_V1_FILE, MIGRATE_STATUS_FILE, NOTES_LEDGER_FILE, applyMigratePlan, migrateHarnessTree, } from "./migrate.js";
|
|
47
47
|
export type { CompletenessItem, CompletenessLevel, CompletenessPlaceholder, CompletenessResult, DesignFrontmatter, } from "./design-md.js";
|
|
48
48
|
export { assertLightDarkParity, completenessLevel, parseDesignFrontmatter, validateDesignTokenFrontmatter, } from "./design-md.js";
|
|
49
|
-
export type { AuditCategory, AuditEffort, AuditFinding, AuditPriority, AuditRisk, RedactResult, ScaffoldAuditPlanOptions, ScaffoldAuditPlanResult, SecretFinding, } from "./audit.js";
|
|
50
|
-
export { AUDIT_CATEGORIES, AUDIT_EFFORTS, AUDIT_PRIORITIES, AUDIT_RISKS,
|
|
49
|
+
export type { AuditCategory, AuditEffort, AuditFinding, AuditPriority, AuditRisk, PromoteAuditPlansOptions, RedactResult, ScaffoldAuditPlanOptions, ScaffoldAuditPlanResult, SecretFinding, } from "./audit.js";
|
|
50
|
+
export { AUDIT_CATEGORIES, AUDIT_EFFORTS, AUDIT_PRIORITIES, AUDIT_RISKS, promoteAuditPlans, scaffoldAuditPlan, validateAuditStatusBlocks, } from "./audit.js";
|
|
51
51
|
export type { ReferenceCheckResult } from "./compound.js";
|
|
52
52
|
export { KNOWLEDGE_BUG_PROBLEM_TYPES, KNOWLEDGE_CATEGORY_MAP, KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_REQUIRED_FIELDS, KNOWLEDGE_RESOLUTION_TYPES, KNOWLEDGE_SEVERITIES, assertIndexRows, compoundRefreshScope, referenceExists, scopeGuard, validateSchemaYaml, } from "./compound.js";
|
|
53
53
|
export type { EphemeralCitation, PlanQualityFinding, PlanQualityResult, SimplifyMarker, TemporaryMarker, TemporaryMarkerResult, } from "./lint.js";
|