@mstar-harness/dsh 3.9.2 → 3.9.4
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 +633 -334
- package/harness-commands/iteration-drive.md +16 -2
- package/harness-skills/mstar-artifacts/SKILL.md +6 -4
- package/harness-skills/mstar-artifacts/references/plan-quality-bar.md +1 -1
- package/harness-skills/mstar-artifacts/references/status-and-residuals.md +61 -9
- package/harness-skills/mstar-artifacts/templates/plan.main.md +1 -1
- package/harness-skills/mstar-branch-worktree/SKILL.md +5 -4
- package/harness-skills/mstar-dispatch-gates/SKILL.md +10 -1
- package/harness-skills/mstar-e2e/SKILL.md +1 -1
- package/harness-skills/mstar-harness-core/SKILL.md +1 -1
- package/harness-skills/mstar-host/SKILL.md +1 -0
- package/harness-skills/mstar-host/references/codex.md +1 -1
- package/harness-skills/mstar-host/references/dsh.md +6 -0
- package/harness-skills/mstar-host/references/omp.md +1 -1
- package/harness-skills/mstar-iteration/SKILL.md +12 -0
- package/harness-skills/mstar-iteration/references/command-shared-invariants.md +4 -0
- package/harness-skills/mstar-iteration/references/phase-2-worktree-lease.md +83 -12
- package/harness-skills/mstar-iteration/references/plan-scoped-pm.md +179 -0
- package/harness-skills/mstar-phase-gates/SKILL.md +1 -0
- package/harness-skills/mstar-roles/references/project-manager/dispatch-and-assignment.md +7 -0
- package/harness-skills/mstar-roles/references/project-manager.md +10 -1
- package/harness-skills/mstar-sdd/SKILL.md +3 -1
- package/harness-skills/mstar-sdd/references/file-handoffs.md +1 -0
- package/harness-skills/pm/SKILL.md +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ var require_lib = __commonJS(function(exports, module) {
|
|
|
75
75
|
is: () => is,
|
|
76
76
|
isNonNullable: () => isNonNullable,
|
|
77
77
|
isNullable: () => isNullable,
|
|
78
|
-
isPlainObject: () =>
|
|
78
|
+
isPlainObject: () => isPlainObject4,
|
|
79
79
|
makeArray: () => makeArray,
|
|
80
80
|
mapValues: () => mapValues,
|
|
81
81
|
noop: () => noop,
|
|
@@ -98,7 +98,7 @@ var require_lib = __commonJS(function(exports, module) {
|
|
|
98
98
|
function isNonNullable(value) {
|
|
99
99
|
return !isNullable(value);
|
|
100
100
|
}
|
|
101
|
-
function
|
|
101
|
+
function isPlainObject4(data) {
|
|
102
102
|
return data && typeof data === "object" && !Array.isArray(data);
|
|
103
103
|
}
|
|
104
104
|
function filterKeys(object, filter) {
|
|
@@ -679,7 +679,7 @@ var require_lib2 = __commonJS(function(exports, module) {
|
|
|
679
679
|
Schema.extend = /* @__PURE__ */ __name(function extend(type, resolve22) {
|
|
680
680
|
resolvers[type] = resolve22;
|
|
681
681
|
}, "extend");
|
|
682
|
-
Schema.resolve = /* @__PURE__ */ __name(function
|
|
682
|
+
Schema.resolve = /* @__PURE__ */ __name(function resolve5(data, schema, options = {}, strict = false) {
|
|
683
683
|
if (!schema)
|
|
684
684
|
return [data];
|
|
685
685
|
if (options.ignore?.(data, schema))
|
|
@@ -1123,7 +1123,7 @@ var require_lib2 = __commonJS(function(exports, module) {
|
|
|
1123
1123
|
|
|
1124
1124
|
// src/index.ts
|
|
1125
1125
|
import { existsSync as existsSync24, readFileSync as readFileSync19, readdirSync as readdirSync14 } from "node:fs";
|
|
1126
|
-
import { join as
|
|
1126
|
+
import { join as join29 } from "node:path";
|
|
1127
1127
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1128
1128
|
import {
|
|
1129
1129
|
apply as applySkillLocal,
|
|
@@ -1138,28 +1138,30 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
1138
1138
|
import { fileURLToPath } from "node:url";
|
|
1139
1139
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1140
1140
|
import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
1141
|
-
import { existsSync as
|
|
1141
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync8, realpathSync as realpathSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1142
1142
|
import { execFileSync } from "node:child_process";
|
|
1143
|
-
import { basename as
|
|
1144
|
-
import { existsSync as
|
|
1145
|
-
import { existsSync as
|
|
1146
|
-
import { dirname as
|
|
1143
|
+
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute6, join as join10, relative as relative2, resolve as resolve8 } from "node:path";
|
|
1144
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
1145
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
1146
|
+
import { dirname as dirname5, join as join7, resolve as resolve6, sep as sep2 } from "node:path";
|
|
1147
1147
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "node:path";
|
|
1148
1148
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1149
|
-
import {
|
|
1150
|
-
import { isAbsolute as
|
|
1149
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
1150
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, join as join4, resolve as resolve4 } from "node:path";
|
|
1151
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
1152
|
+
import { isAbsolute as isAbsolute5, join as join6 } from "node:path";
|
|
1151
1153
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1152
|
-
import { existsSync as
|
|
1153
|
-
import { isAbsolute as
|
|
1154
|
-
import { existsSync as
|
|
1155
|
-
import { join as
|
|
1154
|
+
import { existsSync as existsSync9, realpathSync as realpathSync4 } from "node:fs";
|
|
1155
|
+
import { isAbsolute as isAbsolute7, join as join11, resolve as resolve9 } from "node:path";
|
|
1156
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
1157
|
+
import { join as join12, dirname as dirname8 } from "node:path";
|
|
1156
1158
|
import { execFileSync as execFileSync3, spawn } from "node:child_process";
|
|
1157
|
-
import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as
|
|
1158
|
-
import { basename as
|
|
1159
|
-
import { existsSync as
|
|
1160
|
-
import { basename as
|
|
1161
|
-
import { existsSync as
|
|
1162
|
-
import { join as
|
|
1159
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync9, realpathSync as realpathSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1160
|
+
import { basename as basename7, dirname as dirname9, isAbsolute as isAbsolute8, join as join13, relative as relative3, resolve as resolve10 } from "node:path";
|
|
1161
|
+
import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync12 } from "node:fs";
|
|
1162
|
+
import { basename as basename11, isAbsolute as isAbsolute10, join as join17, relative as relative6, resolve as resolve14, sep as sep5 } from "node:path";
|
|
1163
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
1164
|
+
import { join as join18 } from "node:path";
|
|
1163
1165
|
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
|
|
1164
1166
|
function applyEnforcement(gate, opts) {
|
|
1165
1167
|
return { ...gate, hardBlocked: opts.hard && gate.violations.length > 0 };
|
|
@@ -1395,6 +1397,302 @@ function verifyPlanExecutionLease(row, planId) {
|
|
|
1395
1397
|
return { ok: violations.length === 0, violations, lease };
|
|
1396
1398
|
}
|
|
1397
1399
|
var heldLockDirs = new AsyncLocalStorage;
|
|
1400
|
+
function isPlainObject2(value) {
|
|
1401
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1402
|
+
}
|
|
1403
|
+
function isNonEmptyString(value) {
|
|
1404
|
+
return typeof value === "string" && value.trim() !== "";
|
|
1405
|
+
}
|
|
1406
|
+
var writeAuthorizations = new AsyncLocalStorage2;
|
|
1407
|
+
var HANDOFF_STATES = [
|
|
1408
|
+
"submitted",
|
|
1409
|
+
"accepted",
|
|
1410
|
+
"returned",
|
|
1411
|
+
"integrating",
|
|
1412
|
+
"merged",
|
|
1413
|
+
"completed"
|
|
1414
|
+
];
|
|
1415
|
+
var PLAN_PROGRESS_STATUSES = ["InProgress", "InReview", "Blocked"];
|
|
1416
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
1417
|
+
var GIT_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
1418
|
+
function invalid(code, message) {
|
|
1419
|
+
return { ok: false, severity: "high", code, message };
|
|
1420
|
+
}
|
|
1421
|
+
function validateBinding(value, what) {
|
|
1422
|
+
if (!isPlainObject2(value))
|
|
1423
|
+
return [invalid("coordination.row.binding-shape", `${what} must be an object`)];
|
|
1424
|
+
const violations = [];
|
|
1425
|
+
const extra = Object.keys(value).filter((key) => !["session_id", "session_file", "bound_at"].includes(key));
|
|
1426
|
+
if (extra.length > 0) {
|
|
1427
|
+
violations.push(invalid("coordination.row.binding-field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1428
|
+
}
|
|
1429
|
+
if (!isNonEmptyString(value.session_id)) {
|
|
1430
|
+
violations.push(invalid("coordination.row.binding-field", `${what}.session_id must be a non-empty string`));
|
|
1431
|
+
}
|
|
1432
|
+
if (!isNonEmptyString(value.session_file) || !isAbsolute3(String(value.session_file))) {
|
|
1433
|
+
violations.push(invalid("coordination.row.binding-field", `${what}.session_file must be an absolute path`));
|
|
1434
|
+
}
|
|
1435
|
+
if (!isNonEmptyString(value.bound_at)) {
|
|
1436
|
+
violations.push(invalid("coordination.row.binding-field", `${what}.bound_at must be a timestamp`));
|
|
1437
|
+
}
|
|
1438
|
+
return violations;
|
|
1439
|
+
}
|
|
1440
|
+
function validateEvidenceRef(value, what) {
|
|
1441
|
+
if (!isPlainObject2(value))
|
|
1442
|
+
return [invalid("coordination.row.evidence-shape", `${what} must be a hash-pinned reference`)];
|
|
1443
|
+
const violations = [];
|
|
1444
|
+
const extra = Object.keys(value).filter((key) => !["path", "sha256"].includes(key));
|
|
1445
|
+
if (extra.length > 0) {
|
|
1446
|
+
violations.push(invalid("coordination.row.evidence-field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1447
|
+
}
|
|
1448
|
+
if (!isNonEmptyString(value.path) || !isAbsolute3(String(value.path))) {
|
|
1449
|
+
violations.push(invalid("coordination.row.evidence-field", `${what}.path must be an absolute path`));
|
|
1450
|
+
}
|
|
1451
|
+
if (!isNonEmptyString(value.sha256) || !SHA256_HEX.test(String(value.sha256))) {
|
|
1452
|
+
violations.push(invalid("coordination.row.evidence-field", `${what}.sha256 must be 64 lowercase hex`));
|
|
1453
|
+
}
|
|
1454
|
+
return violations;
|
|
1455
|
+
}
|
|
1456
|
+
function validatePlanProgress(value, what = "coordination.progress") {
|
|
1457
|
+
if (!isPlainObject2(value))
|
|
1458
|
+
return [invalid("coordination.row.progress-shape", `${what} must be an object`)];
|
|
1459
|
+
const violations = [];
|
|
1460
|
+
const extra = Object.keys(value).filter((key) => !["status", "summary", "evidence_paths", "track_branches"].includes(key));
|
|
1461
|
+
if (extra.length > 0) {
|
|
1462
|
+
violations.push(invalid("coordination.row.progress-field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1463
|
+
}
|
|
1464
|
+
if (!PLAN_PROGRESS_STATUSES.includes(value.status)) {
|
|
1465
|
+
violations.push(invalid("coordination.row.progress-field", `${what}.status must be one of ${PLAN_PROGRESS_STATUSES.join(", ")}`));
|
|
1466
|
+
}
|
|
1467
|
+
if (!isNonEmptyString(value.summary)) {
|
|
1468
|
+
violations.push(invalid("coordination.row.progress-field", `${what}.summary must be a non-empty string`));
|
|
1469
|
+
}
|
|
1470
|
+
if (!Array.isArray(value.evidence_paths) || !value.evidence_paths.every(isNonEmptyString)) {
|
|
1471
|
+
violations.push(invalid("coordination.row.progress-field", `${what}.evidence_paths must be an array of paths`));
|
|
1472
|
+
}
|
|
1473
|
+
if (value.track_branches !== undefined) {
|
|
1474
|
+
if (!Array.isArray(value.track_branches) || !value.track_branches.every(isNonEmptyString)) {
|
|
1475
|
+
violations.push(invalid("coordination.row.progress-field", `${what}.track_branches must be an array of branch names`));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return violations;
|
|
1479
|
+
}
|
|
1480
|
+
function validatePlanHandoff(value, what = "coordination.handoff") {
|
|
1481
|
+
if (!isPlainObject2(value))
|
|
1482
|
+
return [invalid("coordination.row.handoff-shape", `${what} must be an object`)];
|
|
1483
|
+
const allowed = [
|
|
1484
|
+
"id",
|
|
1485
|
+
"attempt",
|
|
1486
|
+
"state",
|
|
1487
|
+
"submitted_by",
|
|
1488
|
+
"submitted_at",
|
|
1489
|
+
"source_branch",
|
|
1490
|
+
"source_sha",
|
|
1491
|
+
"worktree_path",
|
|
1492
|
+
"review_base",
|
|
1493
|
+
"review_head",
|
|
1494
|
+
"qc",
|
|
1495
|
+
"qa",
|
|
1496
|
+
"accepted_by",
|
|
1497
|
+
"accepted_at",
|
|
1498
|
+
"returned_at",
|
|
1499
|
+
"return_reason",
|
|
1500
|
+
"integration",
|
|
1501
|
+
"completed_at"
|
|
1502
|
+
];
|
|
1503
|
+
const violations = [];
|
|
1504
|
+
const extra = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
1505
|
+
if (extra.length > 0) {
|
|
1506
|
+
violations.push(invalid("coordination.row.handoff-field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1507
|
+
}
|
|
1508
|
+
const required = [
|
|
1509
|
+
"id",
|
|
1510
|
+
"attempt",
|
|
1511
|
+
"state",
|
|
1512
|
+
"submitted_by",
|
|
1513
|
+
"submitted_at",
|
|
1514
|
+
"source_branch",
|
|
1515
|
+
"source_sha",
|
|
1516
|
+
"worktree_path",
|
|
1517
|
+
"review_base",
|
|
1518
|
+
"review_head"
|
|
1519
|
+
];
|
|
1520
|
+
for (const key of required) {
|
|
1521
|
+
if (value[key] === undefined) {
|
|
1522
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.${key} is required`));
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
if (!Number.isInteger(value.attempt) || value.attempt < 1) {
|
|
1526
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.attempt must be a positive integer`));
|
|
1527
|
+
}
|
|
1528
|
+
if (!HANDOFF_STATES.includes(value.state)) {
|
|
1529
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state must be one of ${HANDOFF_STATES.join(", ")}`));
|
|
1530
|
+
}
|
|
1531
|
+
if (value.id !== undefined && !isNonEmptyString(value.id)) {
|
|
1532
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.id must be a non-empty string`));
|
|
1533
|
+
}
|
|
1534
|
+
for (const key of ["source_sha", "review_base", "review_head"]) {
|
|
1535
|
+
if (value[key] !== undefined && !GIT_SHA.test(String(value[key]))) {
|
|
1536
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.${key} must be a 40-hex git object id`));
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
for (const key of ["submitted_at", "accepted_at", "returned_at", "completed_at"]) {
|
|
1540
|
+
if (value[key] !== undefined && !isNonEmptyString(value[key])) {
|
|
1541
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.${key} must be a timestamp`));
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (value.worktree_path !== undefined && (!isNonEmptyString(value.worktree_path) || !isAbsolute3(String(value.worktree_path)))) {
|
|
1545
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.worktree_path must be an absolute path`));
|
|
1546
|
+
}
|
|
1547
|
+
if (value.qc !== undefined) {
|
|
1548
|
+
if (!isPlainObject2(value.qc)) {
|
|
1549
|
+
violations.push(invalid("coordination.row.handoff-shape", `${what}.qc must be an object`));
|
|
1550
|
+
} else {
|
|
1551
|
+
const qc = value.qc;
|
|
1552
|
+
const qcExtra = Object.keys(qc).filter((key) => !["decision", "reports", "consolidated"].includes(key));
|
|
1553
|
+
if (qcExtra.length > 0) {
|
|
1554
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qc has unexpected key(s): ${qcExtra.join(", ")}`));
|
|
1555
|
+
}
|
|
1556
|
+
if (!isNonEmptyString(qc.decision)) {
|
|
1557
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qc.decision must be a non-empty string`));
|
|
1558
|
+
}
|
|
1559
|
+
if (!Array.isArray(qc.reports) || qc.reports.length === 0) {
|
|
1560
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qc.reports must be a non-empty array`));
|
|
1561
|
+
} else {
|
|
1562
|
+
qc.reports.forEach((ref, index) => {
|
|
1563
|
+
violations.push(...validateEvidenceRef(ref, `${what}.qc.reports[${index}]`));
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
violations.push(...validateEvidenceRef(qc.consolidated, `${what}.qc.consolidated`));
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (value.qa !== undefined) {
|
|
1570
|
+
if (!isPlainObject2(value.qa)) {
|
|
1571
|
+
violations.push(invalid("coordination.row.handoff-shape", `${what}.qa must be an object`));
|
|
1572
|
+
} else {
|
|
1573
|
+
const qa = value.qa;
|
|
1574
|
+
const qaExtra = Object.keys(qa).filter((key) => !["gate", "decision", "report"].includes(key));
|
|
1575
|
+
if (qaExtra.length > 0) {
|
|
1576
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qa has unexpected key(s): ${qaExtra.join(", ")}`));
|
|
1577
|
+
}
|
|
1578
|
+
if (!isNonEmptyString(qa.gate)) {
|
|
1579
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qa.gate must be a non-empty string`));
|
|
1580
|
+
}
|
|
1581
|
+
if (!isNonEmptyString(qa.decision)) {
|
|
1582
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.qa.decision must be a non-empty string`));
|
|
1583
|
+
}
|
|
1584
|
+
violations.push(...validateEvidenceRef(qa.report, `${what}.qa.report`));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
if (value.integration !== undefined) {
|
|
1588
|
+
if (!isPlainObject2(value.integration)) {
|
|
1589
|
+
violations.push(invalid("coordination.row.handoff-shape", `${what}.integration must be an object`));
|
|
1590
|
+
} else {
|
|
1591
|
+
const integration = value.integration;
|
|
1592
|
+
const integrationAllowed = [
|
|
1593
|
+
"target_branch",
|
|
1594
|
+
"worktree_path",
|
|
1595
|
+
"base_sha",
|
|
1596
|
+
"started_at",
|
|
1597
|
+
"result_sha",
|
|
1598
|
+
"verified_at"
|
|
1599
|
+
];
|
|
1600
|
+
const integrationExtra = Object.keys(integration).filter((key) => !integrationAllowed.includes(key));
|
|
1601
|
+
if (integrationExtra.length > 0) {
|
|
1602
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.integration has unexpected key(s): ${integrationExtra.join(", ")}`));
|
|
1603
|
+
}
|
|
1604
|
+
for (const key of ["target_branch", "worktree_path", "base_sha", "started_at"]) {
|
|
1605
|
+
if (!isNonEmptyString(integration[key])) {
|
|
1606
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.integration.${key} is required`));
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
for (const key of ["base_sha", "result_sha"]) {
|
|
1610
|
+
if (integration[key] !== undefined && !GIT_SHA.test(String(integration[key]))) {
|
|
1611
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.integration.${key} must be a 40-hex git object id`));
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (integration.result_sha !== undefined && integration.verified_at === undefined) {
|
|
1615
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.integration.result_sha requires verified_at`));
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
if ((value.state === "integrating" || value.state === "merged" || value.state === "completed") && value.integration === undefined) {
|
|
1620
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.state ${String(value.state)} requires integration`));
|
|
1621
|
+
}
|
|
1622
|
+
return violations;
|
|
1623
|
+
}
|
|
1624
|
+
function validatePreparedCoordination(value, what = "coordination.prepared") {
|
|
1625
|
+
if (!isPlainObject2(value))
|
|
1626
|
+
return [invalid("coordination.row.prepared-shape", `${what} must be an object`)];
|
|
1627
|
+
const allowed = [
|
|
1628
|
+
"assignment_path",
|
|
1629
|
+
"assignment_sha256",
|
|
1630
|
+
"plan_sha256",
|
|
1631
|
+
"qa_gate",
|
|
1632
|
+
"findings_cleanup",
|
|
1633
|
+
"prepared_by",
|
|
1634
|
+
"prepared_at"
|
|
1635
|
+
];
|
|
1636
|
+
const violations = [];
|
|
1637
|
+
const extra = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
1638
|
+
if (extra.length > 0) {
|
|
1639
|
+
violations.push(invalid("coordination.row.prepared-field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1640
|
+
}
|
|
1641
|
+
for (const key of allowed) {
|
|
1642
|
+
if (!isNonEmptyString(value[key])) {
|
|
1643
|
+
violations.push(invalid("coordination.row.prepared-field", `${what}.${key} is required`));
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (value.assignment_path !== undefined && !isAbsolute3(String(value.assignment_path))) {
|
|
1647
|
+
violations.push(invalid("coordination.row.prepared-field", `${what}.assignment_path must be absolute`));
|
|
1648
|
+
}
|
|
1649
|
+
for (const key of ["assignment_sha256", "plan_sha256"]) {
|
|
1650
|
+
if (value[key] !== undefined && !SHA256_HEX.test(String(value[key]))) {
|
|
1651
|
+
violations.push(invalid("coordination.row.prepared-field", `${what}.${key} must be 64 lowercase hex`));
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return violations;
|
|
1655
|
+
}
|
|
1656
|
+
function validateRowCoordination(value, what = "coordination") {
|
|
1657
|
+
if (!isPlainObject2(value))
|
|
1658
|
+
return [invalid("coordination.row.shape", `${what} must be an object`)];
|
|
1659
|
+
const allowed = ["revision", "prepared", "session", "progress", "handoff"];
|
|
1660
|
+
const violations = [];
|
|
1661
|
+
const extra = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
1662
|
+
if (extra.length > 0) {
|
|
1663
|
+
violations.push(invalid("coordination.row.field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1664
|
+
}
|
|
1665
|
+
if (!Number.isInteger(value.revision) || value.revision < 0) {
|
|
1666
|
+
violations.push(invalid("coordination.row.revision", `${what}.revision must be a non-negative integer`));
|
|
1667
|
+
}
|
|
1668
|
+
if (value.prepared !== undefined)
|
|
1669
|
+
violations.push(...validatePreparedCoordination(value.prepared, `${what}.prepared`));
|
|
1670
|
+
if (value.session !== undefined)
|
|
1671
|
+
violations.push(...validateBinding(value.session, `${what}.session`));
|
|
1672
|
+
if (value.progress !== undefined)
|
|
1673
|
+
violations.push(...validatePlanProgress(value.progress, `${what}.progress`));
|
|
1674
|
+
if (value.handoff !== undefined)
|
|
1675
|
+
violations.push(...validatePlanHandoff(value.handoff, `${what}.handoff`));
|
|
1676
|
+
if (value.handoff !== undefined && value.session === undefined) {
|
|
1677
|
+
violations.push(invalid("coordination.row.handoff-field", `${what}.handoff requires a bound plan session`));
|
|
1678
|
+
}
|
|
1679
|
+
return violations;
|
|
1680
|
+
}
|
|
1681
|
+
function validateSnapshotCoordination(value, what = "coordination") {
|
|
1682
|
+
if (!isPlainObject2(value))
|
|
1683
|
+
return [invalid("coordination.snapshot.shape", `${what} must be an object`)];
|
|
1684
|
+
const violations = [];
|
|
1685
|
+
const extra = Object.keys(value).filter((key) => key !== "coordinator");
|
|
1686
|
+
if (extra.length > 0) {
|
|
1687
|
+
violations.push(invalid("coordination.snapshot.field", `${what} has unexpected key(s): ${extra.join(", ")}`));
|
|
1688
|
+
}
|
|
1689
|
+
if (value.coordinator === undefined) {
|
|
1690
|
+
violations.push(invalid("coordination.snapshot.field", `${what}.coordinator is required`));
|
|
1691
|
+
} else {
|
|
1692
|
+
violations.push(...validateBinding(value.coordinator, `${what}.coordinator`));
|
|
1693
|
+
}
|
|
1694
|
+
return violations;
|
|
1695
|
+
}
|
|
1398
1696
|
var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"';
|
|
1399
1697
|
var REQUIRED_FIELDS = [
|
|
1400
1698
|
{ key: "executeAs", label: "Execute as", code: "execute-as" },
|
|
@@ -1665,9 +1963,6 @@ var WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
|
|
|
1665
1963
|
var WORKFLOW_LIFECYCLE_STATUSES = ["running", "paused", "completed", "failed", "stopped"];
|
|
1666
1964
|
var WORKFLOW_TERMINAL_STATUSES = ["completed", "failed", "stopped"];
|
|
1667
1965
|
var WORKFLOW_LIFECYCLE_TYPES = ["plan", "iteration"];
|
|
1668
|
-
function isPlainObject2(value) {
|
|
1669
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1670
|
-
}
|
|
1671
1966
|
function violation3(severity, code, message, fix) {
|
|
1672
1967
|
return { ok: false, severity, code, message, fix };
|
|
1673
1968
|
}
|
|
@@ -1679,7 +1974,7 @@ function validateNonEmptyString2(violations, value, field, missingCode, invalidC
|
|
|
1679
1974
|
}
|
|
1680
1975
|
}
|
|
1681
1976
|
function validateWorktreePathValue(violations, value, field) {
|
|
1682
|
-
if (typeof value !== "string" || value.trim() === "" || !
|
|
1977
|
+
if (typeof value !== "string" || value.trim() === "" || !isAbsolute5(value)) {
|
|
1683
1978
|
violations.push(violation3("high", "workflow.snapshot.invalid-integration-worktree-path", `${field} must be a non-empty absolute path — got ${JSON.stringify(value)}`, "record the absolute integration checkout path (integration_worktree_path)"));
|
|
1684
1979
|
}
|
|
1685
1980
|
}
|
|
@@ -1728,8 +2023,14 @@ function validateWorkflowSnapshot(doc) {
|
|
|
1728
2023
|
if (isPlainObject2(row) && row.execution_lease !== undefined) {
|
|
1729
2024
|
violations.push(...validateExecutionLease(row.execution_lease).violations);
|
|
1730
2025
|
}
|
|
2026
|
+
if (isPlainObject2(row) && row.coordination !== undefined) {
|
|
2027
|
+
violations.push(...validateRowCoordination(row.coordination, `plans[${String(row.id)}].coordination`));
|
|
2028
|
+
}
|
|
1731
2029
|
}
|
|
1732
2030
|
}
|
|
2031
|
+
if (doc.coordination !== undefined) {
|
|
2032
|
+
violations.push(...validateSnapshotCoordination(doc.coordination));
|
|
2033
|
+
}
|
|
1733
2034
|
if (doc.execution_policy !== undefined) {
|
|
1734
2035
|
if (!isPlainObject2(doc.execution_policy)) {
|
|
1735
2036
|
violations.push(violation3("medium", "workflow.snapshot.invalid-execution-policy", "execution_policy must be an object"));
|
|
@@ -1796,13 +2097,13 @@ class WorkflowSnapshotValidationError extends Error {
|
|
|
1796
2097
|
}
|
|
1797
2098
|
}
|
|
1798
2099
|
function readWorkflowSnapshot(dir) {
|
|
1799
|
-
const snapshotPath =
|
|
1800
|
-
if (!
|
|
2100
|
+
const snapshotPath = join6(dir, WORKFLOW_SNAPSHOT_FILE);
|
|
2101
|
+
if (!existsSync4(snapshotPath)) {
|
|
1801
2102
|
throw new Error(`workflow snapshot not found: ${snapshotPath}`);
|
|
1802
2103
|
}
|
|
1803
2104
|
let doc;
|
|
1804
2105
|
try {
|
|
1805
|
-
doc = JSON.parse(
|
|
2106
|
+
doc = JSON.parse(readFileSync4(snapshotPath, "utf8"));
|
|
1806
2107
|
} catch (error) {
|
|
1807
2108
|
throw new Error(`Invalid JSON in ${snapshotPath}: ${error.message}`);
|
|
1808
2109
|
}
|
|
@@ -1833,9 +2134,6 @@ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
1833
2134
|
var PLAN_STATUSES = ["Todo", "InProgress", "InReview", "Blocked", "Done"];
|
|
1834
2135
|
var RESIDUAL_DECISIONS = ["defer", "accept", "risk-accepted"];
|
|
1835
2136
|
var RESIDUAL_LIFECYCLES = ["open", "resolved", "waived", "superseded", "duplicate"];
|
|
1836
|
-
function isPlainObject3(value) {
|
|
1837
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1838
|
-
}
|
|
1839
2137
|
function violation4(severity, code, message, fix) {
|
|
1840
2138
|
return { ok: false, severity, code, message, fix };
|
|
1841
2139
|
}
|
|
@@ -1860,7 +2158,7 @@ function validateNonEmptyString3(violations, value, field, missingCode, invalidC
|
|
|
1860
2158
|
}
|
|
1861
2159
|
function validatePlanRow(row) {
|
|
1862
2160
|
const violations = [];
|
|
1863
|
-
if (!
|
|
2161
|
+
if (!isPlainObject2(row)) {
|
|
1864
2162
|
return { ok: false, violations: [violation4("high", "status.plan-row.invalid", "plan row must be an object")] };
|
|
1865
2163
|
}
|
|
1866
2164
|
const { id, plan_id: planId, title, file, status, metadata, execution_lease } = row;
|
|
@@ -1884,10 +2182,10 @@ function validatePlanRow(row) {
|
|
|
1884
2182
|
} else if (typeof status !== "string" || !PLAN_STATUSES.includes(status)) {
|
|
1885
2183
|
violations.push(violation4("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")} — got ${JSON.stringify(status)}`));
|
|
1886
2184
|
}
|
|
1887
|
-
if (metadata !== undefined && !
|
|
2185
|
+
if (metadata !== undefined && !isPlainObject2(metadata)) {
|
|
1888
2186
|
violations.push(violation4("medium", "status.plan-row.invalid-metadata", "metadata must be an object"));
|
|
1889
2187
|
}
|
|
1890
|
-
if (execution_lease !== undefined && !
|
|
2188
|
+
if (execution_lease !== undefined && !isPlainObject2(execution_lease)) {
|
|
1891
2189
|
violations.push(violation4("medium", "status.plan-row.invalid-execution-lease", "execution_lease must be an object"));
|
|
1892
2190
|
}
|
|
1893
2191
|
if (status === "Done" && execution_lease !== undefined) {
|
|
@@ -1897,7 +2195,7 @@ function validatePlanRow(row) {
|
|
|
1897
2195
|
}
|
|
1898
2196
|
function validateResidual(entry) {
|
|
1899
2197
|
const violations = [];
|
|
1900
|
-
if (!
|
|
2198
|
+
if (!isPlainObject2(entry)) {
|
|
1901
2199
|
return { ok: false, violations: [violation4("high", "status.residual.invalid", "residual entry must be an object")] };
|
|
1902
2200
|
}
|
|
1903
2201
|
const { id, title, severity, source, scope, decision, owner, target, tracking, detail_doc, lifecycle, closed_at } = entry;
|
|
@@ -1957,7 +2255,7 @@ function isHarnessRelativePath(dir) {
|
|
|
1957
2255
|
}
|
|
1958
2256
|
function validateWorkflowEntry(entry) {
|
|
1959
2257
|
const violations = [];
|
|
1960
|
-
if (!
|
|
2258
|
+
if (!isPlainObject2(entry)) {
|
|
1961
2259
|
return {
|
|
1962
2260
|
ok: false,
|
|
1963
2261
|
violations: [violation4("high", "status.workflow.invalid", "workflow entry must be an object")]
|
|
@@ -1985,7 +2283,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
1985
2283
|
if (typeof docOrPath === "string") {
|
|
1986
2284
|
try {
|
|
1987
2285
|
doc = readJson(docOrPath);
|
|
1988
|
-
harnessDir =
|
|
2286
|
+
harnessDir = dirname5(resolve6(docOrPath));
|
|
1989
2287
|
} catch (error) {
|
|
1990
2288
|
return {
|
|
1991
2289
|
ok: false,
|
|
@@ -1995,7 +2293,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
1995
2293
|
} else {
|
|
1996
2294
|
doc = docOrPath;
|
|
1997
2295
|
}
|
|
1998
|
-
if (!
|
|
2296
|
+
if (!isPlainObject2(doc)) {
|
|
1999
2297
|
return { ok: false, violations: [violation4("high", "status.invalid-doc", "status document must be an object")] };
|
|
2000
2298
|
}
|
|
2001
2299
|
if (doc.version !== 2) {
|
|
@@ -2036,7 +2334,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
2036
2334
|
const seen = new Set;
|
|
2037
2335
|
for (const entry of doc.workflows) {
|
|
2038
2336
|
violations.push(...validateWorkflowEntry(entry).violations);
|
|
2039
|
-
if (
|
|
2337
|
+
if (isPlainObject2(entry) && typeof entry.id === "string") {
|
|
2040
2338
|
if (seen.has(entry.id)) {
|
|
2041
2339
|
violations.push(violation4("medium", "status.workflow.duplicate-id", `duplicate workflow id in workflows[]: ${JSON.stringify(entry.id)}`));
|
|
2042
2340
|
}
|
|
@@ -2047,22 +2345,22 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
2047
2345
|
if (harnessDir !== undefined && Array.isArray(doc.workflows)) {
|
|
2048
2346
|
let realHarnessDir = null;
|
|
2049
2347
|
try {
|
|
2050
|
-
realHarnessDir =
|
|
2348
|
+
realHarnessDir = realpathSync2(harnessDir);
|
|
2051
2349
|
} catch {}
|
|
2052
2350
|
for (const entry of doc.workflows) {
|
|
2053
|
-
if (!
|
|
2351
|
+
if (!isPlainObject2(entry) || typeof entry.dir !== "string")
|
|
2054
2352
|
continue;
|
|
2055
|
-
const relSnapshot =
|
|
2056
|
-
const snapshotPath =
|
|
2353
|
+
const relSnapshot = join7(entry.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
2354
|
+
const snapshotPath = join7(harnessDir, relSnapshot);
|
|
2057
2355
|
const label = typeof entry.id === "string" ? entry.id : relSnapshot;
|
|
2058
2356
|
let physical;
|
|
2059
2357
|
try {
|
|
2060
|
-
physical =
|
|
2358
|
+
physical = realpathSync2(snapshotPath);
|
|
2061
2359
|
} catch {
|
|
2062
2360
|
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`));
|
|
2063
2361
|
continue;
|
|
2064
2362
|
}
|
|
2065
|
-
if (realHarnessDir !== null && physical !== realHarnessDir && !physical.startsWith(`${realHarnessDir}${
|
|
2363
|
+
if (realHarnessDir !== null && physical !== realHarnessDir && !physical.startsWith(`${realHarnessDir}${sep2}`)) {
|
|
2066
2364
|
violations.push(violation4("high", "status.workflow.snapshot-outside-harness", `workflows[] lists ${JSON.stringify(label)} but its snapshot resolves outside the harness dir (${JSON.stringify(physical)}) — symlinked snapshot paths are rejected; the snapshot must physically live under ${JSON.stringify(harnessDir)}`));
|
|
2067
2365
|
continue;
|
|
2068
2366
|
}
|
|
@@ -2089,7 +2387,7 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
2089
2387
|
var validateStatus = validateStatusV2;
|
|
2090
2388
|
function resolveCompassEnforcement(harnessDir) {
|
|
2091
2389
|
const iterationsDir = resolveIterationDir(harnessDir);
|
|
2092
|
-
if (!
|
|
2390
|
+
if (!existsSync5(iterationsDir))
|
|
2093
2391
|
return { hard: false, source: "none" };
|
|
2094
2392
|
let entries;
|
|
2095
2393
|
try {
|
|
@@ -2100,12 +2398,12 @@ function resolveCompassEnforcement(harnessDir) {
|
|
|
2100
2398
|
for (const entry of entries) {
|
|
2101
2399
|
if (!entry.isDirectory())
|
|
2102
2400
|
continue;
|
|
2103
|
-
const compassPath =
|
|
2104
|
-
if (!
|
|
2401
|
+
const compassPath = join7(iterationsDir, entry.name, "delivery-compass.md");
|
|
2402
|
+
if (!existsSync5(compassPath))
|
|
2105
2403
|
continue;
|
|
2106
2404
|
let content;
|
|
2107
2405
|
try {
|
|
2108
|
-
content =
|
|
2406
|
+
content = readFileSync5(compassPath, "utf8");
|
|
2109
2407
|
} catch {
|
|
2110
2408
|
continue;
|
|
2111
2409
|
}
|
|
@@ -2120,8 +2418,8 @@ function resolveCompassEnforcement(harnessDir) {
|
|
|
2120
2418
|
return { hard: false, source: "none" };
|
|
2121
2419
|
}
|
|
2122
2420
|
function resolveMstarcEnforcement(harnessDir) {
|
|
2123
|
-
const dir =
|
|
2124
|
-
const rc = loadMstarc(dir,
|
|
2421
|
+
const dir = resolve6(harnessDir);
|
|
2422
|
+
const rc = loadMstarc(dir, dirname5(dir));
|
|
2125
2423
|
const value = rc?.config.enforcement;
|
|
2126
2424
|
if (value === "hard")
|
|
2127
2425
|
return { hard: true, source: "mstarc" };
|
|
@@ -2212,11 +2510,11 @@ function validateCompassShape(doc) {
|
|
|
2212
2510
|
function violation5(severity, code, message, fix) {
|
|
2213
2511
|
return { ok: false, severity, code, message, fix };
|
|
2214
2512
|
}
|
|
2215
|
-
function
|
|
2513
|
+
function isPlainObject3(value) {
|
|
2216
2514
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2217
2515
|
}
|
|
2218
2516
|
function validateCompassFrontmatter(doc) {
|
|
2219
|
-
if (!
|
|
2517
|
+
if (!isPlainObject3(doc)) {
|
|
2220
2518
|
return {
|
|
2221
2519
|
ok: false,
|
|
2222
2520
|
violations: [
|
|
@@ -2253,7 +2551,7 @@ function findPlanRow(snapshotDoc, planId) {
|
|
|
2253
2551
|
if (!Array.isArray(snapshotDoc.plans))
|
|
2254
2552
|
return null;
|
|
2255
2553
|
for (const row of snapshotDoc.plans) {
|
|
2256
|
-
if (!
|
|
2554
|
+
if (!isPlainObject3(row))
|
|
2257
2555
|
continue;
|
|
2258
2556
|
const rowId = typeof row.id === "string" ? row.id : typeof row.plan_id === "string" ? row.plan_id : null;
|
|
2259
2557
|
if (rowId === planId)
|
|
@@ -2349,7 +2647,7 @@ function evaluatePhaseGate(snapshotDoc, compassDoc, opts = {}) {
|
|
|
2349
2647
|
};
|
|
2350
2648
|
}
|
|
2351
2649
|
function parseCompassFrontmatter(filePath) {
|
|
2352
|
-
return parseCompassFrontmatterText(
|
|
2650
|
+
return parseCompassFrontmatterText(readFileSync6(filePath, "utf8"), filePath);
|
|
2353
2651
|
}
|
|
2354
2652
|
function parseCompassFrontmatterText(content, filePath) {
|
|
2355
2653
|
const lines = content.split(/\r?\n/);
|
|
@@ -2416,9 +2714,6 @@ var PROJECT_ROADMAP_FILE = "roadmap.md";
|
|
|
2416
2714
|
var PROJECT_REGISTER_FILE = "residuals.json";
|
|
2417
2715
|
var _DEFAULT_PROJECT = "_default";
|
|
2418
2716
|
var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
|
|
2419
|
-
function isPlainObject5(value) {
|
|
2420
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2421
|
-
}
|
|
2422
2717
|
function violation6(severity, code, message, fix) {
|
|
2423
2718
|
return { ok: false, severity, code, message, fix };
|
|
2424
2719
|
}
|
|
@@ -2431,7 +2726,7 @@ function validateNonEmptyString4(violations, value, field, missingCode, invalidC
|
|
|
2431
2726
|
}
|
|
2432
2727
|
function validateProjectRegister(doc) {
|
|
2433
2728
|
const violations = [];
|
|
2434
|
-
if (!
|
|
2729
|
+
if (!isPlainObject2(doc)) {
|
|
2435
2730
|
return {
|
|
2436
2731
|
ok: false,
|
|
2437
2732
|
violations: [violation6("high", "project.register.invalid", "project register must be an object")]
|
|
@@ -2439,7 +2734,7 @@ function validateProjectRegister(doc) {
|
|
|
2439
2734
|
}
|
|
2440
2735
|
if (doc.entries === undefined) {
|
|
2441
2736
|
violations.push(violation6("high", "project.register.missing-entries", "missing required field: entries"));
|
|
2442
|
-
} else if (!
|
|
2737
|
+
} else if (!isPlainObject2(doc.entries)) {
|
|
2443
2738
|
violations.push(violation6("high", "project.register.invalid-entries", "entries must be an object keyed by plan id"));
|
|
2444
2739
|
} else {
|
|
2445
2740
|
for (const [key, entries] of Object.entries(doc.entries)) {
|
|
@@ -2452,7 +2747,7 @@ function validateProjectRegister(doc) {
|
|
|
2452
2747
|
}
|
|
2453
2748
|
for (const entry of entries) {
|
|
2454
2749
|
violations.push(...validateResidual(entry).violations);
|
|
2455
|
-
if (!
|
|
2750
|
+
if (!isPlainObject2(entry))
|
|
2456
2751
|
continue;
|
|
2457
2752
|
validateNonEmptyString4(violations, entry.source_plan, "source_plan", "project.register.missing-source-plan", "project.register.invalid-source-plan");
|
|
2458
2753
|
if (entry.registered_at === undefined) {
|
|
@@ -2474,7 +2769,7 @@ function validateProjectRegister(doc) {
|
|
|
2474
2769
|
function findingsCleanupGate(register, planId, opts) {
|
|
2475
2770
|
const mode = opts?.mode ?? "allow-residual";
|
|
2476
2771
|
const violations = [];
|
|
2477
|
-
const entries =
|
|
2772
|
+
const entries = isPlainObject2(register.entries) ? register.entries[planId] : undefined;
|
|
2478
2773
|
if (entries === undefined) {
|
|
2479
2774
|
return { ok: true, violations };
|
|
2480
2775
|
}
|
|
@@ -2511,25 +2806,25 @@ function findingsCleanupGate(register, planId, opts) {
|
|
|
2511
2806
|
return { ok: violations.length === 0, violations };
|
|
2512
2807
|
}
|
|
2513
2808
|
function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
2514
|
-
const start =
|
|
2809
|
+
const start = resolve8(startDir);
|
|
2515
2810
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
2516
2811
|
if (explicit)
|
|
2517
|
-
return
|
|
2518
|
-
const boundary =
|
|
2812
|
+
return resolve8(start, explicit);
|
|
2813
|
+
const boundary = resolve8(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
|
|
2519
2814
|
const rc = loadMstarc(start, boundary);
|
|
2520
2815
|
if (rc !== null && rc.config.harnessDir)
|
|
2521
|
-
return
|
|
2816
|
+
return resolve8(rc.dir, rc.config.harnessDir);
|
|
2522
2817
|
let dir = start;
|
|
2523
2818
|
for (;; ) {
|
|
2524
2819
|
if (!isAtOrBelow2(dir, boundary))
|
|
2525
2820
|
return null;
|
|
2526
|
-
for (const candidate of [
|
|
2821
|
+
for (const candidate of [join10(dir, ".mstar"), join10(dir, ".agents"), join10(dir, ".plans"), join10(dir, "plans")]) {
|
|
2527
2822
|
if (isDirectory(candidate))
|
|
2528
2823
|
return candidate;
|
|
2529
2824
|
}
|
|
2530
2825
|
if (dir === boundary)
|
|
2531
2826
|
return null;
|
|
2532
|
-
const parent =
|
|
2827
|
+
const parent = dirname6(dir);
|
|
2533
2828
|
if (parent === dir)
|
|
2534
2829
|
return null;
|
|
2535
2830
|
dir = parent;
|
|
@@ -2547,21 +2842,21 @@ function defaultWorkspaceRoot(startDir) {
|
|
|
2547
2842
|
let boundary = startDir;
|
|
2548
2843
|
for (const segment of cdup.split(/[\\/]/)) {
|
|
2549
2844
|
if (segment && segment !== ".")
|
|
2550
|
-
boundary =
|
|
2845
|
+
boundary = dirname6(boundary);
|
|
2551
2846
|
}
|
|
2552
|
-
return
|
|
2847
|
+
return resolve8(boundary);
|
|
2553
2848
|
} catch {}
|
|
2554
2849
|
return startDir;
|
|
2555
2850
|
}
|
|
2556
2851
|
function isAtOrBelow2(dir, root) {
|
|
2557
2852
|
const rel = relative2(root, dir);
|
|
2558
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
2853
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
|
|
2559
2854
|
}
|
|
2560
2855
|
function mstarcDirOverride(harnessDir, key) {
|
|
2561
|
-
const dir =
|
|
2562
|
-
const rc = loadMstarc(dir,
|
|
2856
|
+
const dir = resolve8(harnessDir);
|
|
2857
|
+
const rc = loadMstarc(dir, dirname6(dir));
|
|
2563
2858
|
const declared = rc?.config[key];
|
|
2564
|
-
return declared ?
|
|
2859
|
+
return declared ? resolve8(rc.dir, declared) : null;
|
|
2565
2860
|
}
|
|
2566
2861
|
function assertSafePathComponent(value, what) {
|
|
2567
2862
|
if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
@@ -2569,44 +2864,44 @@ function assertSafePathComponent(value, what) {
|
|
|
2569
2864
|
}
|
|
2570
2865
|
}
|
|
2571
2866
|
function canonicalizeNearestExisting(path) {
|
|
2572
|
-
const abs =
|
|
2867
|
+
const abs = resolve8(path);
|
|
2573
2868
|
let dir = abs;
|
|
2574
2869
|
const tail = [];
|
|
2575
2870
|
for (;; ) {
|
|
2576
|
-
if (
|
|
2871
|
+
if (existsSync8(dir)) {
|
|
2577
2872
|
try {
|
|
2578
|
-
return
|
|
2873
|
+
return join10(realpathSync3(dir), ...tail);
|
|
2579
2874
|
} catch {
|
|
2580
2875
|
return abs;
|
|
2581
2876
|
}
|
|
2582
2877
|
}
|
|
2583
|
-
const parent =
|
|
2878
|
+
const parent = dirname6(dir);
|
|
2584
2879
|
if (parent === dir)
|
|
2585
2880
|
return abs;
|
|
2586
|
-
tail.unshift(
|
|
2881
|
+
tail.unshift(basename5(dir));
|
|
2587
2882
|
dir = parent;
|
|
2588
2883
|
}
|
|
2589
2884
|
}
|
|
2590
2885
|
function resolveSddDir(harnessDir, planId) {
|
|
2591
2886
|
assertSafePathComponent(planId, "planId");
|
|
2592
|
-
const base =
|
|
2887
|
+
const base = resolve8(harnessDir);
|
|
2593
2888
|
const declared = mstarcDirOverride(base, "sddDir");
|
|
2594
|
-
const sddBase = declared !== null ? declared :
|
|
2595
|
-
return
|
|
2889
|
+
const sddBase = declared !== null ? declared : join10(base, "sdd");
|
|
2890
|
+
return join10(sddBase, planId);
|
|
2596
2891
|
}
|
|
2597
2892
|
function resolveIterationDir(harnessDir) {
|
|
2598
2893
|
const declared = mstarcDirOverride(harnessDir, "iterationDir");
|
|
2599
2894
|
if (declared !== null)
|
|
2600
2895
|
return declared;
|
|
2601
|
-
return
|
|
2896
|
+
return join10(resolve8(harnessDir), "iterations");
|
|
2602
2897
|
}
|
|
2603
2898
|
function resolveHarnessSubdir(startDir, opts, key, fallback) {
|
|
2604
2899
|
const harness = resolveHarnessDir(startDir, opts);
|
|
2605
2900
|
if (harness === null) {
|
|
2606
|
-
throw new Error(`harness dir not found from ${
|
|
2901
|
+
throw new Error(`harness dir not found from ${resolve8(startDir)} — cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
2607
2902
|
}
|
|
2608
2903
|
const declared = mstarcDirOverride(harness, key);
|
|
2609
|
-
return declared !== null ? declared :
|
|
2904
|
+
return declared !== null ? declared : join10(resolve8(harness), fallback);
|
|
2610
2905
|
}
|
|
2611
2906
|
function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
|
|
2612
2907
|
return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
|
|
@@ -2686,7 +2981,7 @@ function parseMainWorktree(out) {
|
|
|
2686
2981
|
if (branch === null)
|
|
2687
2982
|
return null;
|
|
2688
2983
|
try {
|
|
2689
|
-
return { root:
|
|
2984
|
+
return { root: realpathSync4(rawPath), branch };
|
|
2690
2985
|
} catch {
|
|
2691
2986
|
return null;
|
|
2692
2987
|
}
|
|
@@ -2754,8 +3049,8 @@ function probeCheckout(worktreePath, opts) {
|
|
|
2754
3049
|
const raw = stdout.trim();
|
|
2755
3050
|
if (raw === "")
|
|
2756
3051
|
return { error: `no git dir reported at "${worktreePath}"` };
|
|
2757
|
-
const abs =
|
|
2758
|
-
return { gitDir:
|
|
3052
|
+
const abs = isAbsolute7(raw) ? raw : join11(worktreePath, raw);
|
|
3053
|
+
return { gitDir: realpathSync4(abs) };
|
|
2759
3054
|
} catch (err) {
|
|
2760
3055
|
const e = err;
|
|
2761
3056
|
if (e.killed === true || e.signal !== undefined) {
|
|
@@ -2776,7 +3071,7 @@ function probeCheckoutRoot(path, opts = {}) {
|
|
|
2776
3071
|
const raw = stdout.trim();
|
|
2777
3072
|
if (raw === "")
|
|
2778
3073
|
return null;
|
|
2779
|
-
return
|
|
3074
|
+
return realpathSync4(raw);
|
|
2780
3075
|
} catch {
|
|
2781
3076
|
return null;
|
|
2782
3077
|
}
|
|
@@ -2785,7 +3080,7 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
2785
3080
|
const violations = [];
|
|
2786
3081
|
const checkoutCache = new Map;
|
|
2787
3082
|
const checkout = (path) => {
|
|
2788
|
-
const key =
|
|
3083
|
+
const key = resolve9(path);
|
|
2789
3084
|
if (!checkoutCache.has(key))
|
|
2790
3085
|
checkoutCache.set(key, probeCheckout(path, opts));
|
|
2791
3086
|
return checkoutCache.get(key);
|
|
@@ -2824,7 +3119,7 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
2824
3119
|
violations.push(violation8("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
|
|
2825
3120
|
}
|
|
2826
3121
|
if (integrationRequired && integrationWorktreePath.trim() !== "" && integrationBranch.trim() !== "") {
|
|
2827
|
-
if (!
|
|
3122
|
+
if (!existsSync9(integrationWorktreePath)) {
|
|
2828
3123
|
violations.push(violation8("high", "worktree.l1.integration-missing", `integration worktree "${integrationWorktreePath}" does not exist for plan "${planId}" — the integration checkout must exist before dispatch`, `create it before dispatch: git worktree add ${shellQuote(integrationWorktreePath)} ${shellQuote(integrationBranch)}`));
|
|
2829
3124
|
} else {
|
|
2830
3125
|
const probe = probeBranch(integrationWorktreePath, opts);
|
|
@@ -2836,10 +3131,10 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
2836
3131
|
}
|
|
2837
3132
|
}
|
|
2838
3133
|
const identityViolation = (aPath, bPath, code, describe, fix) => {
|
|
2839
|
-
if (
|
|
3134
|
+
if (resolve9(aPath) === resolve9(bPath)) {
|
|
2840
3135
|
return violation8("critical", code, `${describe} — the same checkout (normalized path equality) is not isolation`, fix);
|
|
2841
3136
|
}
|
|
2842
|
-
if (!
|
|
3137
|
+
if (!existsSync9(aPath) || !existsSync9(bPath))
|
|
2843
3138
|
return null;
|
|
2844
3139
|
const a = checkout(aPath);
|
|
2845
3140
|
const b = checkout(bPath);
|
|
@@ -2869,7 +3164,7 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
2869
3164
|
if (v !== null)
|
|
2870
3165
|
violations.push(v);
|
|
2871
3166
|
}
|
|
2872
|
-
if (leaseWorktreePath.trim() !== "" && !
|
|
3167
|
+
if (leaseWorktreePath.trim() !== "" && !existsSync9(leaseWorktreePath)) {
|
|
2873
3168
|
violations.push(violation8("high", "worktree.l1.feature-missing", `feature worktree directory "${leaseWorktreePath}" does not exist for plan "${planId}"`, `create it before dispatch: git worktree add ${shellQuote(leaseWorktreePath)} <working-branch>`));
|
|
2874
3169
|
} else if (leaseWorktreePath.trim() !== "" && leaseWorkingBranch.trim() !== "") {
|
|
2875
3170
|
const probe = probeBranch(leaseWorktreePath, opts);
|
|
@@ -2893,17 +3188,17 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
2893
3188
|
violations.push(violation8("high", "worktree.l2.track-invalid", `track ${index + 1} is missing worktreePath and/or workingBranch`, "fill both fields for every track"));
|
|
2894
3189
|
return;
|
|
2895
3190
|
}
|
|
2896
|
-
if (!
|
|
3191
|
+
if (!isAbsolute7(track.worktreePath)) {
|
|
2897
3192
|
violations.push(violation8("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>)`));
|
|
2898
3193
|
return;
|
|
2899
3194
|
}
|
|
2900
|
-
const normalized =
|
|
3195
|
+
const normalized = resolve9(track.worktreePath);
|
|
2901
3196
|
if (seenPaths.has(normalized)) {
|
|
2902
3197
|
violations.push(violation8("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"));
|
|
2903
3198
|
return;
|
|
2904
3199
|
}
|
|
2905
3200
|
seenPaths.add(normalized);
|
|
2906
|
-
if (!
|
|
3201
|
+
if (!existsSync9(track.worktreePath)) {
|
|
2907
3202
|
violations.push(violation8("high", "worktree.l2.track-missing", `track worktree directory "${track.worktreePath}" does not exist`, `create it before dispatch: git worktree add ${track.worktreePath} ${track.workingBranch}`));
|
|
2908
3203
|
return;
|
|
2909
3204
|
}
|
|
@@ -2929,33 +3224,33 @@ function assertBranchAlignment(worktreePath, expectedBranch, opts = {}) {
|
|
|
2929
3224
|
function shellQuote(value) {
|
|
2930
3225
|
return "'" + value.replaceAll("'", `'"'"'`) + "'";
|
|
2931
3226
|
}
|
|
2932
|
-
function
|
|
3227
|
+
function isPlainObject6(value) {
|
|
2933
3228
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2934
3229
|
}
|
|
2935
3230
|
function collectActiveLifecycleBranches(snapshots) {
|
|
2936
3231
|
const owned = new Set;
|
|
2937
3232
|
for (const doc of snapshots) {
|
|
2938
3233
|
const branch = doc.branch;
|
|
2939
|
-
if (
|
|
3234
|
+
if (isPlainObject6(branch) && typeof branch.integration === "string" && branch.integration.trim() !== "") {
|
|
2940
3235
|
owned.add(branch.integration);
|
|
2941
3236
|
}
|
|
2942
3237
|
if (!Array.isArray(doc.plans))
|
|
2943
3238
|
continue;
|
|
2944
3239
|
for (const row of doc.plans) {
|
|
2945
|
-
if (!
|
|
3240
|
+
if (!isPlainObject6(row))
|
|
2946
3241
|
continue;
|
|
2947
3242
|
const lease = row.execution_lease;
|
|
2948
|
-
if (
|
|
3243
|
+
if (isPlainObject6(lease) && typeof lease.working_branch === "string" && lease.working_branch.trim() !== "") {
|
|
2949
3244
|
owned.add(lease.working_branch);
|
|
2950
3245
|
}
|
|
2951
3246
|
const meta = row.metadata;
|
|
2952
|
-
if (
|
|
3247
|
+
if (isPlainObject6(meta) && Array.isArray(meta.track_branches)) {
|
|
2953
3248
|
for (const branch2 of meta.track_branches) {
|
|
2954
3249
|
if (typeof branch2 === "string" && branch2.trim() !== "")
|
|
2955
3250
|
owned.add(branch2);
|
|
2956
3251
|
}
|
|
2957
3252
|
}
|
|
2958
|
-
if (
|
|
3253
|
+
if (isPlainObject6(meta) && typeof meta.working_branch === "string" && meta.working_branch.trim() !== "") {
|
|
2959
3254
|
owned.add(meta.working_branch);
|
|
2960
3255
|
}
|
|
2961
3256
|
}
|
|
@@ -2963,8 +3258,8 @@ function collectActiveLifecycleBranches(snapshots) {
|
|
|
2963
3258
|
return [...owned];
|
|
2964
3259
|
}
|
|
2965
3260
|
function scanActiveLifecycleBranches(harnessDir, governingWorkflowId) {
|
|
2966
|
-
const registerPath =
|
|
2967
|
-
if (!
|
|
3261
|
+
const registerPath = join12(harnessDir, "status.json");
|
|
3262
|
+
if (!existsSync10(registerPath))
|
|
2968
3263
|
return { kind: "ok", branches: [], notes: [] };
|
|
2969
3264
|
let register;
|
|
2970
3265
|
try {
|
|
@@ -2972,7 +3267,7 @@ function scanActiveLifecycleBranches(harnessDir, governingWorkflowId) {
|
|
|
2972
3267
|
} catch (error) {
|
|
2973
3268
|
return { kind: "refusal", code: "worktree.l1.lifecycle-register-unreadable", detail: `${registerPath}: ${error.message}` };
|
|
2974
3269
|
}
|
|
2975
|
-
if (!
|
|
3270
|
+
if (!isPlainObject6(register) || register.version !== 2 || !Array.isArray(register.workflows)) {
|
|
2976
3271
|
return {
|
|
2977
3272
|
kind: "refusal",
|
|
2978
3273
|
code: "worktree.l1.lifecycle-register-unreadable",
|
|
@@ -3003,10 +3298,10 @@ function scanActiveLifecycleBranches(harnessDir, governingWorkflowId) {
|
|
|
3003
3298
|
}
|
|
3004
3299
|
if (id === governingWorkflowId)
|
|
3005
3300
|
continue;
|
|
3006
|
-
const snapshotPath =
|
|
3301
|
+
const snapshotPath = join12(workflowsDir, id, WORKFLOW_SNAPSHOT_FILE);
|
|
3007
3302
|
let snapshot;
|
|
3008
3303
|
try {
|
|
3009
|
-
const read = readWorkflowSnapshot(
|
|
3304
|
+
const read = readWorkflowSnapshot(dirname8(snapshotPath));
|
|
3010
3305
|
snapshot = read.snapshot;
|
|
3011
3306
|
notes.push(...read.diagnostics);
|
|
3012
3307
|
} catch (error) {
|
|
@@ -3059,14 +3354,14 @@ function gitOut(cwd, args) {
|
|
|
3059
3354
|
}
|
|
3060
3355
|
}
|
|
3061
3356
|
function probeHarnessWithStatus(root) {
|
|
3062
|
-
if (isFile2(
|
|
3063
|
-
return
|
|
3064
|
-
if (isFile2(
|
|
3065
|
-
return
|
|
3066
|
-
if (hasWorkflowSnapshot(
|
|
3067
|
-
return
|
|
3068
|
-
if (hasWorkflowSnapshot(
|
|
3069
|
-
return
|
|
3357
|
+
if (isFile2(join13(root, ".mstar", "status.json")))
|
|
3358
|
+
return join13(root, ".mstar");
|
|
3359
|
+
if (isFile2(join13(root, ".agents", "status.json")))
|
|
3360
|
+
return join13(root, ".agents");
|
|
3361
|
+
if (hasWorkflowSnapshot(join13(root, ".mstar")))
|
|
3362
|
+
return join13(root, ".mstar");
|
|
3363
|
+
if (hasWorkflowSnapshot(join13(root, ".agents")))
|
|
3364
|
+
return join13(root, ".agents");
|
|
3070
3365
|
return null;
|
|
3071
3366
|
}
|
|
3072
3367
|
function hasWorkflowSnapshot(harnessDir) {
|
|
@@ -3074,13 +3369,13 @@ function hasWorkflowSnapshot(harnessDir) {
|
|
|
3074
3369
|
try {
|
|
3075
3370
|
workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
3076
3371
|
} catch {
|
|
3077
|
-
workflowsDir =
|
|
3372
|
+
workflowsDir = join13(harnessDir, "workflows");
|
|
3078
3373
|
}
|
|
3079
3374
|
if (!isDirectory2(workflowsDir))
|
|
3080
3375
|
return false;
|
|
3081
3376
|
try {
|
|
3082
3377
|
for (const entry of readdirSync6(workflowsDir, { withFileTypes: true })) {
|
|
3083
|
-
if (entry.isDirectory() && isFile2(
|
|
3378
|
+
if (entry.isDirectory() && isFile2(join13(workflowsDir, entry.name, "snapshot.json")))
|
|
3084
3379
|
return true;
|
|
3085
3380
|
}
|
|
3086
3381
|
} catch {
|
|
@@ -3089,16 +3384,16 @@ function hasWorkflowSnapshot(harnessDir) {
|
|
|
3089
3384
|
return false;
|
|
3090
3385
|
}
|
|
3091
3386
|
function checkoutRootNearestExisting(dir, boundary) {
|
|
3092
|
-
const stop =
|
|
3093
|
-
const startInside = isInside(
|
|
3094
|
-
let current =
|
|
3387
|
+
const stop = resolve10(boundary);
|
|
3388
|
+
const startInside = isInside(resolve10(dir), stop);
|
|
3389
|
+
let current = resolve10(dir);
|
|
3095
3390
|
for (;; ) {
|
|
3096
3391
|
const probed = probeCheckoutRoot(current);
|
|
3097
3392
|
if (probed !== null)
|
|
3098
3393
|
return probed;
|
|
3099
3394
|
if (startInside && current === stop)
|
|
3100
3395
|
return null;
|
|
3101
|
-
const parent =
|
|
3396
|
+
const parent = dirname9(current);
|
|
3102
3397
|
if (parent === current)
|
|
3103
3398
|
return null;
|
|
3104
3399
|
if (startInside && !isInside(parent, stop))
|
|
@@ -3118,7 +3413,7 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
3118
3413
|
if (!isDirectory2(controlRoot)) {
|
|
3119
3414
|
throw new SddScriptError(`mstar sdd workspace: CONTROL_ROOT / MSTAR_CONTROL_ROOT is not a directory: ${controlRoot}`, 1);
|
|
3120
3415
|
}
|
|
3121
|
-
const supplied =
|
|
3416
|
+
const supplied = realpathSync5(controlRoot);
|
|
3122
3417
|
const main = readMainWorktree(supplied);
|
|
3123
3418
|
if (main !== null) {
|
|
3124
3419
|
if (main.root !== supplied) {
|
|
@@ -3127,7 +3422,7 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
3127
3422
|
` + ` Re-run with MSTAR_CONTROL_ROOT=${main.root}`, 1);
|
|
3128
3423
|
}
|
|
3129
3424
|
root = main.root;
|
|
3130
|
-
} else if (isFile2(
|
|
3425
|
+
} else if (isFile2(join13(supplied, ".git")) || gitOut(supplied, ["rev-parse", "--is-inside-work-tree"]) === "true") {
|
|
3131
3426
|
throw new SddScriptError(`mstar sdd workspace: cannot verify the main worktree for CONTROL_ROOT "${supplied}" (git worktree discovery failed).
|
|
3132
3427
|
Refusing to resolve or create any SDD tree without a verified main worktree.`, 1);
|
|
3133
3428
|
} else {
|
|
@@ -3145,22 +3440,22 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
3145
3440
|
const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
|
|
3146
3441
|
let harnessDir;
|
|
3147
3442
|
if (harnessOverride) {
|
|
3148
|
-
harnessDir =
|
|
3443
|
+
harnessDir = resolve10(root, harnessOverride);
|
|
3149
3444
|
} else {
|
|
3150
3445
|
const rc = findMstarc(root, root);
|
|
3151
|
-
const rcHarnessDir = rc !== null ? parseMstarc(
|
|
3446
|
+
const rcHarnessDir = rc !== null ? parseMstarc(readFileSync9(rc, "utf8")).harnessDir : undefined;
|
|
3152
3447
|
if (rcHarnessDir) {
|
|
3153
|
-
harnessDir =
|
|
3448
|
+
harnessDir = resolve10(rc !== null ? dirname9(rc) : root, rcHarnessDir);
|
|
3154
3449
|
} else {
|
|
3155
3450
|
const probed = probeHarnessWithStatus(root);
|
|
3156
3451
|
if (probed) {
|
|
3157
3452
|
harnessDir = probed;
|
|
3158
|
-
} else if (isDirectory2(
|
|
3159
|
-
harnessDir =
|
|
3160
|
-
} else if (isDirectory2(
|
|
3161
|
-
harnessDir =
|
|
3453
|
+
} else if (isDirectory2(join13(root, ".mstar"))) {
|
|
3454
|
+
harnessDir = join13(root, ".mstar");
|
|
3455
|
+
} else if (isDirectory2(join13(root, ".agents"))) {
|
|
3456
|
+
harnessDir = join13(root, ".agents");
|
|
3162
3457
|
} else {
|
|
3163
|
-
harnessDir =
|
|
3458
|
+
harnessDir = join13(root, ".mstar");
|
|
3164
3459
|
}
|
|
3165
3460
|
}
|
|
3166
3461
|
}
|
|
@@ -3171,9 +3466,9 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
3171
3466
|
}
|
|
3172
3467
|
const sddDir = resolveSddDir(harnessDir, planId);
|
|
3173
3468
|
mkdirSync6(sddDir, { recursive: true });
|
|
3174
|
-
writeFileSync4(
|
|
3469
|
+
writeFileSync4(join13(sddDir, ".gitignore"), `*
|
|
3175
3470
|
`);
|
|
3176
|
-
return
|
|
3471
|
+
return realpathSync5(sddDir);
|
|
3177
3472
|
}
|
|
3178
3473
|
function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
3179
3474
|
if (!planFile || !Number.isInteger(taskN) || taskN < 1) {
|
|
@@ -3182,7 +3477,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
3182
3477
|
const bound = opts.context;
|
|
3183
3478
|
const observedCwd = opts.cwd ?? process.cwd();
|
|
3184
3479
|
if (bound) {
|
|
3185
|
-
const inputPlan = canonicalizeNearestExisting(
|
|
3480
|
+
const inputPlan = canonicalizeNearestExisting(resolve10(observedCwd, planFile));
|
|
3186
3481
|
if (inputPlan !== canonicalizeNearestExisting(bound.planFile)) {
|
|
3187
3482
|
throwGateFail([
|
|
3188
3483
|
contextViolation("high", "sdd.context.plan-file-mismatch", `plan file "${planFile}" does not match the bound context plan file "${bound.planFile}" — ` + "bound mode extracts only the resolved context's plan; refused before any read or write")
|
|
@@ -3191,16 +3486,16 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
3191
3486
|
}
|
|
3192
3487
|
let content;
|
|
3193
3488
|
try {
|
|
3194
|
-
content =
|
|
3489
|
+
content = readFileSync9(planFile, "utf8");
|
|
3195
3490
|
} catch {
|
|
3196
3491
|
throw new SddScriptError(`no such plan file: ${planFile}`, 2);
|
|
3197
3492
|
}
|
|
3198
3493
|
let out;
|
|
3199
3494
|
let mkdirAfterGate = null;
|
|
3200
3495
|
if (outFile) {
|
|
3201
|
-
out = bound ?
|
|
3496
|
+
out = bound ? resolve10(observedCwd, outFile) : outFile;
|
|
3202
3497
|
} else if (bound) {
|
|
3203
|
-
out =
|
|
3498
|
+
out = join13(bound.sddDir, `task-${taskN}-brief.md`);
|
|
3204
3499
|
mkdirAfterGate = bound.sddDir;
|
|
3205
3500
|
} else {
|
|
3206
3501
|
const sddDir = opts.sddDir ?? process.env.SDD_DIR;
|
|
@@ -3208,7 +3503,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
3208
3503
|
throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
|
|
3209
3504
|
}
|
|
3210
3505
|
mkdirSync6(sddDir, { recursive: true });
|
|
3211
|
-
out =
|
|
3506
|
+
out = join13(sddDir, `task-${taskN}-brief.md`);
|
|
3212
3507
|
}
|
|
3213
3508
|
if (bound) {
|
|
3214
3509
|
const gate2 = checkSddAction(bound, { kind: "artifact", cwd: observedCwd, target: out });
|
|
@@ -3241,15 +3536,15 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
3241
3536
|
if (printed.length === 0) {
|
|
3242
3537
|
throw new SddScriptError(`task ${taskN} not found in ${planFile} (no heading matching Task ${taskN})`, 3);
|
|
3243
3538
|
}
|
|
3244
|
-
return bound ?
|
|
3539
|
+
return bound ? resolve10(observedCwd, out) : out;
|
|
3245
3540
|
}
|
|
3246
3541
|
function isInside(child, ancestor) {
|
|
3247
3542
|
const rel = relative3(ancestor, child);
|
|
3248
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
3543
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute8(rel);
|
|
3249
3544
|
}
|
|
3250
3545
|
function canonicalDir(path) {
|
|
3251
3546
|
try {
|
|
3252
|
-
return statSync4(path).isDirectory() ?
|
|
3547
|
+
return statSync4(path).isDirectory() ? realpathSync5(path) : null;
|
|
3253
3548
|
} catch {
|
|
3254
3549
|
return null;
|
|
3255
3550
|
}
|
|
@@ -3279,11 +3574,11 @@ function checkSddAction(context, action) {
|
|
|
3279
3574
|
const featureReal = canonicalDir(context.featureCwd);
|
|
3280
3575
|
const canonicalSddDir = canonicalizeNearestExisting(context.sddDir);
|
|
3281
3576
|
const canonicalPlanFile = canonicalizeNearestExisting(context.planFile);
|
|
3282
|
-
const cwdResolved =
|
|
3577
|
+
const cwdResolved = resolve10(action.cwd);
|
|
3283
3578
|
const checkTarget = (baseDir, target, kind) => {
|
|
3284
3579
|
if (featureReal === null)
|
|
3285
3580
|
return;
|
|
3286
|
-
const targetAbs =
|
|
3581
|
+
const targetAbs = resolve10(baseDir, target);
|
|
3287
3582
|
const canonical = canonicalizeNearestExisting(targetAbs);
|
|
3288
3583
|
if (!isInside(canonical, featureReal)) {
|
|
3289
3584
|
const declaredPrefix = targetAbs === featureReal || targetAbs.startsWith(`${featureReal}/`);
|
|
@@ -3311,10 +3606,10 @@ function checkSddAction(context, action) {
|
|
|
3311
3606
|
add("sdd.context.target-missing", "artifact checks require the destination target");
|
|
3312
3607
|
return { ok: false, violations };
|
|
3313
3608
|
}
|
|
3314
|
-
const targetAbs =
|
|
3609
|
+
const targetAbs = resolve10(cwdResolved, action.target);
|
|
3315
3610
|
const canonical = canonicalizeNearestExisting(targetAbs);
|
|
3316
3611
|
if (!isInside(canonical, canonicalSddDir) && canonical !== canonicalPlanFile) {
|
|
3317
|
-
const rawSddDir =
|
|
3612
|
+
const rawSddDir = resolve10(context.sddDir);
|
|
3318
3613
|
const declaredPrefix = targetAbs === rawSddDir || targetAbs.startsWith(`${rawSddDir}/`) || targetAbs === canonicalSddDir || targetAbs.startsWith(`${canonicalSddDir}/`) || targetAbs === canonicalPlanFile;
|
|
3319
3614
|
if (declaredPrefix) {
|
|
3320
3615
|
add("sdd.context.artifact-symlink-escape", `artifact target "${targetAbs}" canonicalizes to "${canonical}", outside the plan's control sddDir — symlink escape refused before write`);
|
|
@@ -4047,7 +4342,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
4047
4342
|
if (ref === "" || seen.has(ref))
|
|
4048
4343
|
continue;
|
|
4049
4344
|
seen.add(ref);
|
|
4050
|
-
if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") ||
|
|
4345
|
+
if (SCHEME_RE.test(ref) || ref.startsWith("{") || ref.startsWith("#") || ref.includes("*") || ref.includes("?") || ref.startsWith("~") || isAbsolute10(ref)) {
|
|
4051
4346
|
continue;
|
|
4052
4347
|
}
|
|
4053
4348
|
if (ref.includes("/") || REF_EXT_RE.test(ref)) {
|
|
@@ -4075,7 +4370,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
4075
4370
|
break;
|
|
4076
4371
|
if (entry.isDirectory()) {
|
|
4077
4372
|
if (!WALK_SKIP_DIRS.has(entry.name))
|
|
4078
|
-
stack.push(
|
|
4373
|
+
stack.push(join17(dir, entry.name));
|
|
4079
4374
|
} else if (!entry.isSymbolicLink()) {
|
|
4080
4375
|
const base = entry.name.replace(/\.(?:ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
4081
4376
|
if (moduleNames.has(base))
|
|
@@ -4087,7 +4382,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
4087
4382
|
for (const { ref, isSymbol, module } of refs) {
|
|
4088
4383
|
if (!isSymbol || module === undefined) {
|
|
4089
4384
|
const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
|
|
4090
|
-
if (
|
|
4385
|
+
if (existsSync13(resolve14(repoRoot, candidate))) {
|
|
4091
4386
|
checked++;
|
|
4092
4387
|
} else {
|
|
4093
4388
|
violations.push(violation11("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"));
|
|
@@ -4114,11 +4409,11 @@ function collectKnowledgeDocs(dir) {
|
|
|
4114
4409
|
for (const entry of entries) {
|
|
4115
4410
|
if (entry.isSymbolicLink())
|
|
4116
4411
|
continue;
|
|
4117
|
-
const full =
|
|
4412
|
+
const full = join17(current, entry.name);
|
|
4118
4413
|
if (entry.isDirectory()) {
|
|
4119
4414
|
stack.push(full);
|
|
4120
4415
|
} else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
|
|
4121
|
-
docs.push(relative6(dir, full).split(
|
|
4416
|
+
docs.push(relative6(dir, full).split(sep5).join("/"));
|
|
4122
4417
|
}
|
|
4123
4418
|
}
|
|
4124
4419
|
}
|
|
@@ -4134,14 +4429,14 @@ function normalizeIndexRef(cell) {
|
|
|
4134
4429
|
}
|
|
4135
4430
|
function assertIndexRows(knowledgeDir) {
|
|
4136
4431
|
const violations = [];
|
|
4137
|
-
const readmePath =
|
|
4138
|
-
if (!
|
|
4432
|
+
const readmePath = join17(knowledgeDir, "README.md");
|
|
4433
|
+
if (!existsSync13(readmePath)) {
|
|
4139
4434
|
violations.push(violation11("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"));
|
|
4140
4435
|
return { ok: false, violations };
|
|
4141
4436
|
}
|
|
4142
4437
|
const docs = collectKnowledgeDocs(knowledgeDir);
|
|
4143
4438
|
const rows = new Set;
|
|
4144
|
-
for (const line of
|
|
4439
|
+
for (const line of readFileSync12(readmePath, "utf8").split(/\r?\n/)) {
|
|
4145
4440
|
if (!line.trim().startsWith("|"))
|
|
4146
4441
|
continue;
|
|
4147
4442
|
const cells = line.split("|").map((c) => c.trim());
|
|
@@ -4159,16 +4454,16 @@ function assertIndexRows(knowledgeDir) {
|
|
|
4159
4454
|
return { ok: violations.length === 0, violations };
|
|
4160
4455
|
}
|
|
4161
4456
|
function isFileLikeRoot(root) {
|
|
4162
|
-
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(
|
|
4457
|
+
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename11(root));
|
|
4163
4458
|
}
|
|
4164
4459
|
function scopeGuard(path, allowedRoots) {
|
|
4165
|
-
const resolved =
|
|
4460
|
+
const resolved = resolve14(path);
|
|
4166
4461
|
for (const root of allowedRoots) {
|
|
4167
|
-
const r =
|
|
4462
|
+
const r = resolve14(root);
|
|
4168
4463
|
if (isFileLikeRoot(r)) {
|
|
4169
4464
|
if (resolved === r)
|
|
4170
4465
|
return { ok: true, violations: [] };
|
|
4171
|
-
} else if (resolved === r || resolved.startsWith(r +
|
|
4466
|
+
} else if (resolved === r || resolved.startsWith(r + sep5)) {
|
|
4172
4467
|
return { ok: true, violations: [] };
|
|
4173
4468
|
}
|
|
4174
4469
|
}
|
|
@@ -4202,6 +4497,8 @@ function findEphemeralCitations(skillText) {
|
|
|
4202
4497
|
}
|
|
4203
4498
|
return citations;
|
|
4204
4499
|
}
|
|
4500
|
+
var DATED_SLUG_TOKEN_SOURCE = "\\b20\\d{6}-[a-z0-9][a-z0-9-]*\\b(?!\\.\\d)";
|
|
4501
|
+
var DATED_SLUG_TOKEN_RE = new RegExp(DATED_SLUG_TOKEN_SOURCE, "g");
|
|
4205
4502
|
var WORKFLOW_VERB_START_RE = /^(?:explains?|describes?|covers?|provides?|walks?|guides?|shows?|lists?|details?|demonstrates?|outlines?|teaches?|summarizes?)\b/i;
|
|
4206
4503
|
var PRONOUN_RE = /\bI\b(?!\/)|\b(?:we|you|my|our|your|us)\b/gi;
|
|
4207
4504
|
var DESCRIPTION_MAX_WORDS = 120;
|
|
@@ -4322,8 +4619,8 @@ function validateRoleMapping(rolesDir, options = {}) {
|
|
|
4322
4619
|
const violations = [];
|
|
4323
4620
|
const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
|
|
4324
4621
|
for (const { agentId, reference } of mapping) {
|
|
4325
|
-
if (!
|
|
4326
|
-
violations.push(violation13("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${
|
|
4622
|
+
if (!existsSync14(join18(rolesDir, reference))) {
|
|
4623
|
+
violations.push(violation13("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join18(rolesDir, reference)} or fix the mapping row`));
|
|
4327
4624
|
}
|
|
4328
4625
|
}
|
|
4329
4626
|
for (const { family, memberIds } of families) {
|
|
@@ -4582,7 +4879,7 @@ class DshMstar extends Service {
|
|
|
4582
4879
|
// src/gates/_shared.ts
|
|
4583
4880
|
var import_schemastery = __toESM(require_lib2(), 1);
|
|
4584
4881
|
import { existsSync as existsSync2 } from "node:fs";
|
|
4585
|
-
import { resolve as
|
|
4882
|
+
import { resolve as resolve5 } from "node:path";
|
|
4586
4883
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4587
4884
|
var STATUS_FILE = "status.json";
|
|
4588
4885
|
var PERSONA_INTERPOLATION_HAZARD = /\{\{[\s\S]*\}\}/;
|
|
@@ -4681,14 +4978,14 @@ class HarnessResolver {
|
|
|
4681
4978
|
explicit;
|
|
4682
4979
|
cache = new Map;
|
|
4683
4980
|
constructor(explicit) {
|
|
4684
|
-
this.explicit = explicit !== undefined && explicit.trim() !== "" ?
|
|
4981
|
+
this.explicit = explicit !== undefined && explicit.trim() !== "" ? resolve5(process.cwd(), explicit) : null;
|
|
4685
4982
|
}
|
|
4686
4983
|
forWorkspace(cwd) {
|
|
4687
4984
|
if (this.explicit !== null)
|
|
4688
4985
|
return this.explicit;
|
|
4689
4986
|
if (cwd === undefined || cwd.trim() === "")
|
|
4690
4987
|
return null;
|
|
4691
|
-
const abs =
|
|
4988
|
+
const abs = resolve5(cwd);
|
|
4692
4989
|
const hit = this.cache.get(abs);
|
|
4693
4990
|
if (hit !== undefined)
|
|
4694
4991
|
return hit;
|
|
@@ -4759,21 +5056,21 @@ function skillLocalConfig(config) {
|
|
|
4759
5056
|
}
|
|
4760
5057
|
|
|
4761
5058
|
// src/gates/catalog.ts
|
|
4762
|
-
import { existsSync as
|
|
4763
|
-
import { basename as
|
|
5059
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, readdirSync as readdirSync4, realpathSync as realpathSync6 } from "node:fs";
|
|
5060
|
+
import { basename as basename6, isAbsolute as isAbsolute9, join as join19, relative as relative7, sep as sep3 } from "node:path";
|
|
4764
5061
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4765
5062
|
|
|
4766
5063
|
// src/gates/agent-flow.ts
|
|
4767
|
-
import { appendFileSync, closeSync, existsSync as
|
|
4768
|
-
import { join as
|
|
5064
|
+
import { appendFileSync, closeSync, existsSync as existsSync11, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync3, readSync, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5065
|
+
import { join as join14 } from "node:path";
|
|
4769
5066
|
|
|
4770
5067
|
// src/gates/dispatch.ts
|
|
4771
|
-
import { existsSync as
|
|
4772
|
-
import { basename as
|
|
5068
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
5069
|
+
import { basename as basename3, join as join8, resolve as resolve7 } from "node:path";
|
|
4773
5070
|
|
|
4774
5071
|
// src/gates/workflow-selection.ts
|
|
4775
|
-
import { existsSync as
|
|
4776
|
-
import { dirname as
|
|
5072
|
+
import { existsSync as existsSync3, readdirSync, realpathSync, statSync as statSync2 } from "node:fs";
|
|
5073
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join5, normalize, relative as relative4, sep } from "node:path";
|
|
4777
5074
|
var TERMINAL_STATUS_CACHE_MAX = 64;
|
|
4778
5075
|
var terminalStatusCache = new Map;
|
|
4779
5076
|
function terminalStatusOf(snapshotPath) {
|
|
@@ -4809,7 +5106,7 @@ function terminalStatusOf(snapshotPath) {
|
|
|
4809
5106
|
}
|
|
4810
5107
|
function stripTrailingSeparators(path) {
|
|
4811
5108
|
let out = path;
|
|
4812
|
-
while (out.length > 1 && out.endsWith(
|
|
5109
|
+
while (out.length > 1 && out.endsWith(sep))
|
|
4813
5110
|
out = out.slice(0, -1);
|
|
4814
5111
|
return out;
|
|
4815
5112
|
}
|
|
@@ -4819,13 +5116,13 @@ function lexical(path) {
|
|
|
4819
5116
|
function canonicalPath(path) {
|
|
4820
5117
|
const fallback = lexical(path);
|
|
4821
5118
|
try {
|
|
4822
|
-
return { kind: "ok", path: lexical(
|
|
5119
|
+
return { kind: "ok", path: lexical(realpathSync(path)) };
|
|
4823
5120
|
} catch (error) {
|
|
4824
5121
|
return error.code === "ENOENT" ? { kind: "missing", path: fallback } : { kind: "error" };
|
|
4825
5122
|
}
|
|
4826
5123
|
}
|
|
4827
5124
|
function within(root, cwd) {
|
|
4828
|
-
if (root === "" || cwd === "" || !
|
|
5125
|
+
if (root === "" || cwd === "" || !isAbsolute4(root) || !isAbsolute4(cwd))
|
|
4829
5126
|
return false;
|
|
4830
5127
|
const rootCanonical = canonicalPath(root);
|
|
4831
5128
|
if (rootCanonical.kind === "error")
|
|
@@ -4839,7 +5136,7 @@ function within(root, cwd) {
|
|
|
4839
5136
|
if (rootPath === cwdPath)
|
|
4840
5137
|
return true;
|
|
4841
5138
|
const rel = relative4(rootPath, cwdPath);
|
|
4842
|
-
return rel !== "" && !
|
|
5139
|
+
return rel !== "" && !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
4843
5140
|
}
|
|
4844
5141
|
function validLeases(snapshot) {
|
|
4845
5142
|
if (!Array.isArray(snapshot.plans))
|
|
@@ -4854,8 +5151,8 @@ function validLeases(snapshot) {
|
|
|
4854
5151
|
return leases;
|
|
4855
5152
|
}
|
|
4856
5153
|
function readSnapshot(harnessDir, entry) {
|
|
4857
|
-
const snapshotPath =
|
|
4858
|
-
if (!
|
|
5154
|
+
const snapshotPath = join5(harnessDir, entry.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
5155
|
+
if (!existsSync3(snapshotPath))
|
|
4859
5156
|
return;
|
|
4860
5157
|
try {
|
|
4861
5158
|
const snapshot = asRecord(readJson(snapshotPath));
|
|
@@ -4911,8 +5208,8 @@ function automaticBinding(harnessDir, entries, hint) {
|
|
|
4911
5208
|
return byCwd === undefined ? undefined : { kind: "active", workflowId: byCwd.id, dir: byCwd.dir };
|
|
4912
5209
|
}
|
|
4913
5210
|
function resolveActiveWorkflow(harnessDir, hint) {
|
|
4914
|
-
const statusPath =
|
|
4915
|
-
if (!
|
|
5211
|
+
const statusPath = join5(harnessDir, STATUS_FILE);
|
|
5212
|
+
if (!existsSync3(statusPath)) {
|
|
4916
5213
|
return {
|
|
4917
5214
|
kind: "error",
|
|
4918
5215
|
code: "status.missing",
|
|
@@ -5002,7 +5299,7 @@ function resolveReadWorkflow(harnessDir, hint) {
|
|
|
5002
5299
|
if (active.kind === "error" && active.code !== "workflow.selection.no-active")
|
|
5003
5300
|
return active;
|
|
5004
5301
|
const workflowsDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
5005
|
-
if (!
|
|
5302
|
+
if (!existsSync3(workflowsDir)) {
|
|
5006
5303
|
return {
|
|
5007
5304
|
kind: "error",
|
|
5008
5305
|
code: "workflow.selection.no-snapshot",
|
|
@@ -5023,8 +5320,8 @@ function resolveReadWorkflow(harnessDir, hint) {
|
|
|
5023
5320
|
for (const entry of entries) {
|
|
5024
5321
|
if (!entry.isDirectory())
|
|
5025
5322
|
continue;
|
|
5026
|
-
const snapshotPath =
|
|
5027
|
-
if (!
|
|
5323
|
+
const snapshotPath = join5(workflowsDir, entry.name, WORKFLOW_SNAPSHOT_FILE);
|
|
5324
|
+
if (!existsSync3(snapshotPath)) {
|
|
5028
5325
|
terminalStatusCache.delete(snapshotPath);
|
|
5029
5326
|
continue;
|
|
5030
5327
|
}
|
|
@@ -5032,11 +5329,11 @@ function resolveReadWorkflow(harnessDir, hint) {
|
|
|
5032
5329
|
if (verdict === undefined || !verdict.terminal)
|
|
5033
5330
|
continue;
|
|
5034
5331
|
if (best === undefined || verdict.mtimeMs > best.mtimeMs) {
|
|
5035
|
-
best = { workflowId: entry.name, dir:
|
|
5332
|
+
best = { workflowId: entry.name, dir: join5(relative4(harnessDir, workflowsDir), entry.name), mtimeMs: verdict.mtimeMs };
|
|
5036
5333
|
}
|
|
5037
5334
|
}
|
|
5038
5335
|
for (const cachedPath of terminalStatusCache.keys()) {
|
|
5039
|
-
if (!
|
|
5336
|
+
if (!existsSync3(join5(dirname7(cachedPath), WORKFLOW_SNAPSHOT_FILE))) {
|
|
5040
5337
|
terminalStatusCache.delete(cachedPath);
|
|
5041
5338
|
}
|
|
5042
5339
|
}
|
|
@@ -5205,12 +5502,12 @@ function isNaValue(value) {
|
|
|
5205
5502
|
function planIdOf(headerRegion) {
|
|
5206
5503
|
const planPath = assignmentHeaderValue(headerRegion, "Plan Path");
|
|
5207
5504
|
if (!isNaValue(planPath)) {
|
|
5208
|
-
const id =
|
|
5505
|
+
const id = basename3(firstToken(planPath) ?? "");
|
|
5209
5506
|
return id.endsWith(".md") ? id.slice(0, -3) : id;
|
|
5210
5507
|
}
|
|
5211
5508
|
const sddDir = assignmentHeaderValue(headerRegion, "SDD dir");
|
|
5212
5509
|
if (!isNaValue(sddDir)) {
|
|
5213
|
-
const id =
|
|
5510
|
+
const id = basename3(firstToken(sddDir) ?? "");
|
|
5214
5511
|
return id === "" ? undefined : id;
|
|
5215
5512
|
}
|
|
5216
5513
|
const planId = assignmentHeaderValue(headerRegion, "plan_id");
|
|
@@ -5235,12 +5532,12 @@ function leaseGateViolations(harnessDir, exec, writable, prompt, hint) {
|
|
|
5235
5532
|
if (!sdd)
|
|
5236
5533
|
return [];
|
|
5237
5534
|
if (selection.kind === "error" && selection.code === "status.unreadable") {
|
|
5238
|
-
return [leaseViolation("lease.dispatch.unreadable", `cannot read ${
|
|
5535
|
+
return [leaseViolation("lease.dispatch.unreadable", `cannot read ${join8(harnessDir, STATUS_FILE)}: the plan's execution_lease state is unverifiable; STOP before writable dispatch`, "restore a valid status.json (the status gate refuses invalid writes)")];
|
|
5239
5536
|
}
|
|
5240
|
-
return [leaseViolation("lease.dispatch.unverifiable", `${
|
|
5537
|
+
return [leaseViolation("lease.dispatch.unverifiable", `${join8(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)")];
|
|
5241
5538
|
}
|
|
5242
|
-
const snapshotPath =
|
|
5243
|
-
if (!
|
|
5539
|
+
const snapshotPath = join8(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
5540
|
+
if (!existsSync7(snapshotPath)) {
|
|
5244
5541
|
if (!sdd)
|
|
5245
5542
|
return [];
|
|
5246
5543
|
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)")];
|
|
@@ -5273,7 +5570,7 @@ function leaseGateViolations(harnessDir, exec, writable, prompt, hint) {
|
|
|
5273
5570
|
const wt = worktree === undefined ? undefined : firstToken(worktree);
|
|
5274
5571
|
if (isNaValue(wt)) {
|
|
5275
5572
|
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)"));
|
|
5276
|
-
} else if (
|
|
5573
|
+
} else if (resolve7(wt) !== resolve7(String(lease.worktree_path ?? ""))) {
|
|
5277
5574
|
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)"));
|
|
5278
5575
|
}
|
|
5279
5576
|
const forms = parseAssignmentBranchForms(header);
|
|
@@ -5314,7 +5611,7 @@ function worktreeL2Violations(header) {
|
|
|
5314
5611
|
return violations;
|
|
5315
5612
|
}
|
|
5316
5613
|
function activeSnapshotRows(harnessDir, hint) {
|
|
5317
|
-
if (!
|
|
5614
|
+
if (!existsSync7(join8(harnessDir, STATUS_FILE)))
|
|
5318
5615
|
return { rows: null, snapshot: null, workflowId: null, unreadable: false };
|
|
5319
5616
|
const selection = resolveActiveWorkflow(harnessDir, hint);
|
|
5320
5617
|
if (selection.kind !== "active") {
|
|
@@ -5325,8 +5622,8 @@ function activeSnapshotRows(harnessDir, hint) {
|
|
|
5325
5622
|
unreadable: selection.kind === "error" && selection.code !== "workflow.selection.no-active"
|
|
5326
5623
|
};
|
|
5327
5624
|
}
|
|
5328
|
-
const snapshotDir =
|
|
5329
|
-
if (!
|
|
5625
|
+
const snapshotDir = join8(harnessDir, selection.dir);
|
|
5626
|
+
if (!existsSync7(join8(snapshotDir, WORKFLOW_SNAPSHOT_FILE))) {
|
|
5330
5627
|
return { rows: null, snapshot: null, workflowId: selection.workflowId, unreadable: true };
|
|
5331
5628
|
}
|
|
5332
5629
|
let snapshot;
|
|
@@ -5430,7 +5727,7 @@ function gateWorkflow(ctx, harnessDir, config, adapter, exec, hintReadOf) {
|
|
|
5430
5727
|
}
|
|
5431
5728
|
const pb = writableFanOutUncovered(harnessDir, hintReadOf().hint);
|
|
5432
5729
|
if (pb.unreadable) {
|
|
5433
|
-
const statusLabel = harnessDir === null ? STATUS_FILE :
|
|
5730
|
+
const statusLabel = harnessDir === null ? STATUS_FILE : join8(harnessDir, STATUS_FILE);
|
|
5434
5731
|
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (fail-open): cannot read ${statusLabel} — P-b lease attribution degraded; call allowed`);
|
|
5435
5732
|
}
|
|
5436
5733
|
const verdict = workflowPolicy(config, adapter.workflowAskCache, {
|
|
@@ -5635,7 +5932,7 @@ function callPairingKey(exec) {
|
|
|
5635
5932
|
function resolveAgentFlowWriteTarget(harnessDir, hint) {
|
|
5636
5933
|
const selection = resolveActiveWorkflow(harnessDir, hint);
|
|
5637
5934
|
if (selection.kind === "active")
|
|
5638
|
-
return { dir:
|
|
5935
|
+
return { dir: join14(harnessDir, selection.dir), selection };
|
|
5639
5936
|
const message = selection.kind === "error" ? selection.message : "no active lifecycle (unexpected terminal selection — the active-set resolver has no history view)";
|
|
5640
5937
|
if (!noActiveWarned) {
|
|
5641
5938
|
noActiveWarned = true;
|
|
@@ -5655,7 +5952,7 @@ function sleepSync(ms) {
|
|
|
5655
5952
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
5656
5953
|
}
|
|
5657
5954
|
function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
5658
|
-
const lockDir =
|
|
5955
|
+
const lockDir = join14(workflowDir, WORKFLOW_LEDGER_LOCKDIR);
|
|
5659
5956
|
if (heldWorkflowLocks.has(lockDir)) {
|
|
5660
5957
|
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`);
|
|
5661
5958
|
}
|
|
@@ -5681,7 +5978,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
|
5681
5978
|
heldWorkflowLocks.add(lockDir);
|
|
5682
5979
|
try {
|
|
5683
5980
|
try {
|
|
5684
|
-
writeFileSync2(
|
|
5981
|
+
writeFileSync2(join14(lockDir, WORKFLOW_LOCKER_HOLDER_PID), String(process.pid), "utf8");
|
|
5685
5982
|
} catch {}
|
|
5686
5983
|
return fn();
|
|
5687
5984
|
} finally {
|
|
@@ -5690,7 +5987,7 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
|
5690
5987
|
const current = statSync5(lockDir);
|
|
5691
5988
|
if (acquired !== null && current.dev === acquired.dev && current.ino === acquired.ino) {
|
|
5692
5989
|
try {
|
|
5693
|
-
unlinkSync2(
|
|
5990
|
+
unlinkSync2(join14(lockDir, WORKFLOW_LOCKER_HOLDER_PID));
|
|
5694
5991
|
} catch {}
|
|
5695
5992
|
rmdirSync(lockDir);
|
|
5696
5993
|
}
|
|
@@ -5698,13 +5995,13 @@ function withWorkflowDirLock(workflowDir, fn, opts = {}) {
|
|
|
5698
5995
|
}
|
|
5699
5996
|
}
|
|
5700
5997
|
function appendEvent(workflowDir, event) {
|
|
5701
|
-
const file =
|
|
5998
|
+
const file = join14(workflowDir, AGENT_FLOW_FILE);
|
|
5702
5999
|
withWorkflowDirLock(workflowDir, () => {
|
|
5703
6000
|
appendFileSync(file, `${JSON.stringify(event)}
|
|
5704
6001
|
`);
|
|
5705
6002
|
if (statSync5(file).size <= AGENT_FLOW_SIZE_GATE_BYTES)
|
|
5706
6003
|
return;
|
|
5707
|
-
const content =
|
|
6004
|
+
const content = readFileSync3(file, "utf8");
|
|
5708
6005
|
const lines = content.replace(/\n$/, "").split(`
|
|
5709
6006
|
`);
|
|
5710
6007
|
if (lines.length > AGENT_FLOW_MAX_EVENTS) {
|
|
@@ -6108,7 +6405,7 @@ function summaryOf(events) {
|
|
|
6108
6405
|
function ledgerContent(file, n) {
|
|
6109
6406
|
const size = statSync5(file).size;
|
|
6110
6407
|
if (size <= AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES) {
|
|
6111
|
-
return
|
|
6408
|
+
return readFileSync3(file, "utf8");
|
|
6112
6409
|
}
|
|
6113
6410
|
let windowBytes = AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES;
|
|
6114
6411
|
const fd = openSync(file, "r");
|
|
@@ -6140,8 +6437,8 @@ function ledgerContent(file, n) {
|
|
|
6140
6437
|
}
|
|
6141
6438
|
}
|
|
6142
6439
|
function readAgentFlow(workflowDir, limit) {
|
|
6143
|
-
const file =
|
|
6144
|
-
if (!
|
|
6440
|
+
const file = join14(workflowDir, AGENT_FLOW_FILE);
|
|
6441
|
+
if (!existsSync11(file)) {
|
|
6145
6442
|
return { events: [], summary: [] };
|
|
6146
6443
|
}
|
|
6147
6444
|
const n = limit === undefined ? AGENT_FLOW_DEFAULT_LIMIT : Math.max(0, Math.floor(limit));
|
|
@@ -6306,8 +6603,8 @@ function reserveCatalogCandidate(pairing, ref, exec) {
|
|
|
6306
6603
|
map.set(label, { ref, fromSeq: seq });
|
|
6307
6604
|
ref.catalog = { session, label };
|
|
6308
6605
|
}
|
|
6309
|
-
function matchCatalogEvent(session, label,
|
|
6310
|
-
const result =
|
|
6606
|
+
function matchCatalogEvent(session, label, join9, envelope) {
|
|
6607
|
+
const result = join9.result;
|
|
6311
6608
|
if (result === undefined)
|
|
6312
6609
|
return;
|
|
6313
6610
|
const rec = asRecord(envelope);
|
|
@@ -6321,7 +6618,7 @@ function matchCatalogEvent(session, label, join8, envelope) {
|
|
|
6321
6618
|
const seq = rec.seq;
|
|
6322
6619
|
if (typeof seq !== "number" || !Number.isInteger(seq))
|
|
6323
6620
|
return;
|
|
6324
|
-
if (seq <
|
|
6621
|
+
if (seq < join9.fromSeq || seq >= session.seq || seq >= WORKFLOW_LEDGER_MAX_SEQ)
|
|
6325
6622
|
return;
|
|
6326
6623
|
const childCreatedAt = data.childCreatedAt;
|
|
6327
6624
|
if (typeof childCreatedAt !== "number" || !Number.isInteger(childCreatedAt) || childCreatedAt < 0)
|
|
@@ -6337,9 +6634,9 @@ function matchCatalogEvent(session, label, join8, envelope) {
|
|
|
6337
6634
|
return;
|
|
6338
6635
|
return childId;
|
|
6339
6636
|
}
|
|
6340
|
-
function consumeCatalogCandidate(map, label,
|
|
6637
|
+
function consumeCatalogCandidate(map, label, join9, childId) {
|
|
6341
6638
|
map.set(label, null);
|
|
6342
|
-
const ref =
|
|
6639
|
+
const ref = join9.ref;
|
|
6343
6640
|
ref.childId = childId;
|
|
6344
6641
|
delete ref.catalog;
|
|
6345
6642
|
recordSubagentLink({ ref, childId, label });
|
|
@@ -6350,16 +6647,16 @@ function advanceCatalogCandidate(pairing, ref, result) {
|
|
|
6350
6647
|
if (catalog === undefined)
|
|
6351
6648
|
return;
|
|
6352
6649
|
const map = pairing.catalogBySession.get(catalog.session);
|
|
6353
|
-
const
|
|
6354
|
-
if (map === undefined ||
|
|
6650
|
+
const join9 = map?.get(catalog.label);
|
|
6651
|
+
if (map === undefined || join9 === undefined || join9 === null || join9.ref !== ref)
|
|
6355
6652
|
return;
|
|
6356
|
-
|
|
6653
|
+
join9.result = result;
|
|
6357
6654
|
const end = catalog.session.seq;
|
|
6358
6655
|
let skippedReads = 0;
|
|
6359
6656
|
let firstSkippedSeq;
|
|
6360
6657
|
let firstSkippedError;
|
|
6361
6658
|
try {
|
|
6362
|
-
for (let seq =
|
|
6659
|
+
for (let seq = join9.fromSeq;seq < end; seq += 1) {
|
|
6363
6660
|
let envelope;
|
|
6364
6661
|
try {
|
|
6365
6662
|
envelope = catalog.session.eventAt(seq);
|
|
@@ -6371,10 +6668,10 @@ function advanceCatalogCandidate(pairing, ref, result) {
|
|
|
6371
6668
|
}
|
|
6372
6669
|
continue;
|
|
6373
6670
|
}
|
|
6374
|
-
const childId = matchCatalogEvent(catalog.session, catalog.label,
|
|
6671
|
+
const childId = matchCatalogEvent(catalog.session, catalog.label, join9, envelope);
|
|
6375
6672
|
if (childId === undefined)
|
|
6376
6673
|
continue;
|
|
6377
|
-
consumeCatalogCandidate(map, catalog.label,
|
|
6674
|
+
consumeCatalogCandidate(map, catalog.label, join9, childId);
|
|
6378
6675
|
return;
|
|
6379
6676
|
}
|
|
6380
6677
|
} finally {
|
|
@@ -6449,13 +6746,13 @@ function registerSubagentCatalogListener(ctx, pairing) {
|
|
|
6449
6746
|
const map = pairing.catalogBySession.get(session);
|
|
6450
6747
|
if (map === undefined)
|
|
6451
6748
|
return;
|
|
6452
|
-
for (const [label,
|
|
6453
|
-
if (
|
|
6749
|
+
for (const [label, join9] of map) {
|
|
6750
|
+
if (join9 === null || join9.result === undefined)
|
|
6454
6751
|
continue;
|
|
6455
|
-
const childId = matchCatalogEvent(session, label,
|
|
6752
|
+
const childId = matchCatalogEvent(session, label, join9, envelope);
|
|
6456
6753
|
if (childId === undefined)
|
|
6457
6754
|
continue;
|
|
6458
|
-
consumeCatalogCandidate(map, label,
|
|
6755
|
+
consumeCatalogCandidate(map, label, join9, childId);
|
|
6459
6756
|
}
|
|
6460
6757
|
} catch (error) {
|
|
6461
6758
|
log("warn", `subagent catalog observation failed (contained — the session append proceeds): ${errorMessage(error)}`);
|
|
@@ -6464,8 +6761,8 @@ function registerSubagentCatalogListener(ctx, pairing) {
|
|
|
6464
6761
|
}
|
|
6465
6762
|
|
|
6466
6763
|
// src/engine-status-store.ts
|
|
6467
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
6468
|
-
import { dirname as
|
|
6764
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync3, rmSync, statSync as statSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6765
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
6469
6766
|
var ENGINE_STATUS_SNAPSHOT_VERSION = 1;
|
|
6470
6767
|
var ENGINE_STATUS_SNAPSHOT_ENTRY_VERSION = 1;
|
|
6471
6768
|
var ENGINE_STATUS_SNAPSHOT_MAX_PER_SESSION = 50;
|
|
@@ -6474,19 +6771,19 @@ var ENGINE_STATUS_SNAPSHOT_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
6474
6771
|
var ENGINE_STATUS_SNAPSHOT_LOCK_TIMEOUT_MS = 250;
|
|
6475
6772
|
var ENGINE_STATUS_SNAPSHOT_RELATIVE_PATH = "snapshots/engine-status.json";
|
|
6476
6773
|
function engineStatusSnapshotPath(harnessDir) {
|
|
6477
|
-
return
|
|
6774
|
+
return join16(harnessDir, ENGINE_STATUS_SNAPSHOT_RELATIVE_PATH);
|
|
6478
6775
|
}
|
|
6479
6776
|
function engineStatusUnavailable(reason) {
|
|
6480
6777
|
return { kind: "unavailable", reason };
|
|
6481
6778
|
}
|
|
6482
|
-
function
|
|
6779
|
+
function isPlainObject4(value) {
|
|
6483
6780
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6484
6781
|
}
|
|
6485
6782
|
function nonEmptyString(value) {
|
|
6486
6783
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
6487
6784
|
}
|
|
6488
6785
|
function asEntry(value) {
|
|
6489
|
-
if (!
|
|
6786
|
+
if (!isPlainObject4(value))
|
|
6490
6787
|
return;
|
|
6491
6788
|
if (value.rv !== ENGINE_STATUS_SNAPSHOT_ENTRY_VERSION)
|
|
6492
6789
|
return;
|
|
@@ -6496,12 +6793,12 @@ function asEntry(value) {
|
|
|
6496
6793
|
return;
|
|
6497
6794
|
if (typeof value.turn !== "number" || !Number.isFinite(value.turn))
|
|
6498
6795
|
return;
|
|
6499
|
-
if (!
|
|
6796
|
+
if (!isPlainObject4(value.payload))
|
|
6500
6797
|
return;
|
|
6501
6798
|
return { rv: value.rv, cwd, at, turn: value.turn, payload: value.payload };
|
|
6502
6799
|
}
|
|
6503
6800
|
function asBinding(value) {
|
|
6504
|
-
if (!
|
|
6801
|
+
if (!isPlainObject4(value))
|
|
6505
6802
|
return;
|
|
6506
6803
|
const cwd = nonEmptyString(value.cwd);
|
|
6507
6804
|
if (cwd === undefined)
|
|
@@ -6532,7 +6829,7 @@ function readEngineStatusSnapshot(harnessDir, sessionId) {
|
|
|
6532
6829
|
return engineStatusUnavailable("no-session-entry");
|
|
6533
6830
|
let raw;
|
|
6534
6831
|
try {
|
|
6535
|
-
raw =
|
|
6832
|
+
raw = readFileSync7(engineStatusSnapshotPath(harnessDir), "utf8");
|
|
6536
6833
|
} catch (error) {
|
|
6537
6834
|
const code = error.code;
|
|
6538
6835
|
return engineStatusUnavailable(code === "ENOENT" ? "absent" : "unreadable");
|
|
@@ -6543,11 +6840,11 @@ function readEngineStatusSnapshot(harnessDir, sessionId) {
|
|
|
6543
6840
|
} catch {
|
|
6544
6841
|
return engineStatusUnavailable("invalid-json");
|
|
6545
6842
|
}
|
|
6546
|
-
if (!
|
|
6843
|
+
if (!isPlainObject4(doc) || doc.sv !== ENGINE_STATUS_SNAPSHOT_VERSION) {
|
|
6547
6844
|
return engineStatusUnavailable("envelope-schema");
|
|
6548
6845
|
}
|
|
6549
6846
|
const entries = doc.entries;
|
|
6550
|
-
if (!
|
|
6847
|
+
if (!isPlainObject4(entries))
|
|
6551
6848
|
return engineStatusUnavailable("envelope-schema");
|
|
6552
6849
|
if (!Object.hasOwn(entries, sessionId))
|
|
6553
6850
|
return engineStatusUnavailable("no-session-entry");
|
|
@@ -6572,7 +6869,7 @@ function serializeEnvelope(entries, bindings) {
|
|
|
6572
6869
|
function loadForWrite(harnessDir) {
|
|
6573
6870
|
let raw;
|
|
6574
6871
|
try {
|
|
6575
|
-
raw =
|
|
6872
|
+
raw = readFileSync7(engineStatusSnapshotPath(harnessDir), "utf8");
|
|
6576
6873
|
} catch (error) {
|
|
6577
6874
|
if (error.code === "ENOENT") {
|
|
6578
6875
|
return {
|
|
@@ -6588,7 +6885,7 @@ function loadForWrite(harnessDir) {
|
|
|
6588
6885
|
} catch {
|
|
6589
6886
|
return { kind: "refused", reason: "store-invalid-json" };
|
|
6590
6887
|
}
|
|
6591
|
-
if (!
|
|
6888
|
+
if (!isPlainObject4(parsed) || parsed.sv !== ENGINE_STATUS_SNAPSHOT_VERSION || !isPlainObject4(parsed.entries)) {
|
|
6592
6889
|
return { kind: "refused", reason: "store-envelope-schema" };
|
|
6593
6890
|
}
|
|
6594
6891
|
const entries = sessionMap();
|
|
@@ -6607,7 +6904,7 @@ function loadForWrite(harnessDir) {
|
|
|
6607
6904
|
}
|
|
6608
6905
|
const bindings = bindingMap();
|
|
6609
6906
|
if (parsed.bindings !== undefined) {
|
|
6610
|
-
if (!
|
|
6907
|
+
if (!isPlainObject4(parsed.bindings))
|
|
6611
6908
|
return { kind: "refused", reason: "store-bindings-schema" };
|
|
6612
6909
|
for (const [key, value] of Object.entries(parsed.bindings)) {
|
|
6613
6910
|
const binding = asBinding(value);
|
|
@@ -6662,12 +6959,12 @@ function writeEngineStatusSnapshot(harnessDir, input) {
|
|
|
6662
6959
|
return { kind: "degraded", reason: "no-session-cwd" };
|
|
6663
6960
|
if (!Number.isFinite(input.turn))
|
|
6664
6961
|
return { kind: "degraded", reason: "invalid-turn" };
|
|
6665
|
-
if (!
|
|
6962
|
+
if (!isPlainObject4(input.payload))
|
|
6666
6963
|
return { kind: "degraded", reason: "payload-not-object" };
|
|
6667
6964
|
const at = (input.now ?? new Date).toISOString();
|
|
6668
6965
|
if (!Number.isFinite(Date.parse(at)))
|
|
6669
6966
|
return { kind: "degraded", reason: "invalid-timestamp" };
|
|
6670
|
-
const dir =
|
|
6967
|
+
const dir = dirname10(engineStatusSnapshotPath(harnessDir));
|
|
6671
6968
|
const file = engineStatusSnapshotPath(harnessDir);
|
|
6672
6969
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
6673
6970
|
try {
|
|
@@ -6730,7 +7027,7 @@ var bindingStoreMemos = new Map;
|
|
|
6730
7027
|
function parseBindingStore(file) {
|
|
6731
7028
|
let raw;
|
|
6732
7029
|
try {
|
|
6733
|
-
raw =
|
|
7030
|
+
raw = readFileSync7(file, "utf8");
|
|
6734
7031
|
} catch {
|
|
6735
7032
|
return { kind: "unavailable", reason: "store-unreadable" };
|
|
6736
7033
|
}
|
|
@@ -6740,13 +7037,13 @@ function parseBindingStore(file) {
|
|
|
6740
7037
|
} catch {
|
|
6741
7038
|
return { kind: "unavailable", reason: "store-invalid-json" };
|
|
6742
7039
|
}
|
|
6743
|
-
if (!
|
|
7040
|
+
if (!isPlainObject4(parsed) || parsed.sv !== ENGINE_STATUS_SNAPSHOT_VERSION || !isPlainObject4(parsed.entries)) {
|
|
6744
7041
|
return { kind: "unavailable", reason: "store-envelope-schema" };
|
|
6745
7042
|
}
|
|
6746
7043
|
const bindings = parsed.bindings;
|
|
6747
7044
|
if (bindings === undefined)
|
|
6748
7045
|
return { kind: "ok", bindings: undefined };
|
|
6749
|
-
if (!
|
|
7046
|
+
if (!isPlainObject4(bindings))
|
|
6750
7047
|
return { kind: "unavailable", reason: "store-bindings-schema" };
|
|
6751
7048
|
return { kind: "ok", bindings };
|
|
6752
7049
|
}
|
|
@@ -6804,7 +7101,7 @@ function updateWorkflowSessionBinding(harnessDir, sessionId, cwd, update) {
|
|
|
6804
7101
|
return { kind: "degraded", reason: "invalid-selected-workflow-id" };
|
|
6805
7102
|
}
|
|
6806
7103
|
const file = engineStatusSnapshotPath(harnessDir);
|
|
6807
|
-
const dir =
|
|
7104
|
+
const dir = dirname10(file);
|
|
6808
7105
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
6809
7106
|
try {
|
|
6810
7107
|
mkdirSync4(dir, { recursive: true });
|
|
@@ -6856,7 +7153,7 @@ function residualSeverityIndex(severity) {
|
|
|
6856
7153
|
function pluginVersion() {
|
|
6857
7154
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
6858
7155
|
try {
|
|
6859
|
-
const pkg = JSON.parse(
|
|
7156
|
+
const pkg = JSON.parse(readFileSync10(new URL(rel, import.meta.url), "utf8"));
|
|
6860
7157
|
if (typeof pkg.version === "string" && pkg.version !== "")
|
|
6861
7158
|
return pkg.version;
|
|
6862
7159
|
} catch {}
|
|
@@ -7022,8 +7319,8 @@ function hhmm(ts) {
|
|
|
7022
7319
|
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
7023
7320
|
}
|
|
7024
7321
|
function harnessStateSource(harnessDir, selection) {
|
|
7025
|
-
const statusPath =
|
|
7026
|
-
if (!
|
|
7322
|
+
const statusPath = join19(harnessDir, STATUS_FILE);
|
|
7323
|
+
if (!existsSync15(statusPath))
|
|
7027
7324
|
return null;
|
|
7028
7325
|
try {
|
|
7029
7326
|
const rollup = residualRollup(harnessDir);
|
|
@@ -7032,9 +7329,9 @@ function harnessStateSource(harnessDir, selection) {
|
|
|
7032
7329
|
if (selection.kind === "error") {
|
|
7033
7330
|
return selectionErrorState(selection, rollup, harnessDir);
|
|
7034
7331
|
}
|
|
7035
|
-
const snapshotPath =
|
|
7332
|
+
const snapshotPath = join19(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
7036
7333
|
let snapshot;
|
|
7037
|
-
if (!
|
|
7334
|
+
if (!existsSync15(snapshotPath)) {
|
|
7038
7335
|
return selectionErrorState({
|
|
7039
7336
|
kind: "error",
|
|
7040
7337
|
code: "workflow.selection.snapshot-unreadable",
|
|
@@ -7042,7 +7339,7 @@ function harnessStateSource(harnessDir, selection) {
|
|
|
7042
7339
|
}, rollup, harnessDir);
|
|
7043
7340
|
}
|
|
7044
7341
|
try {
|
|
7045
|
-
snapshot = readWorkflowSnapshot(
|
|
7342
|
+
snapshot = readWorkflowSnapshot(join19(harnessDir, selection.dir)).snapshot;
|
|
7046
7343
|
} catch {
|
|
7047
7344
|
return selectionErrorState({
|
|
7048
7345
|
kind: "error",
|
|
@@ -7098,7 +7395,7 @@ function harnessStateSource(harnessDir, selection) {
|
|
|
7098
7395
|
leases,
|
|
7099
7396
|
knowledge: knowledgeDigest(harnessDir),
|
|
7100
7397
|
direction: compass !== undefined ? compassDirection(compass.compassPath) : null,
|
|
7101
|
-
agentFlow: readAgentFlow(
|
|
7398
|
+
agentFlow: readAgentFlow(join19(harnessDir, selection.dir), 50)
|
|
7102
7399
|
};
|
|
7103
7400
|
} catch {
|
|
7104
7401
|
return null;
|
|
@@ -7128,7 +7425,7 @@ function selectionErrorState(selection, rollup, harnessDir) {
|
|
|
7128
7425
|
function projectRollupSource(harnessDir, residuals) {
|
|
7129
7426
|
const milestones = [];
|
|
7130
7427
|
const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
|
|
7131
|
-
if (
|
|
7428
|
+
if (existsSync15(projectsDir)) {
|
|
7132
7429
|
let entries;
|
|
7133
7430
|
try {
|
|
7134
7431
|
entries = readdirSync4(projectsDir, { withFileTypes: true });
|
|
@@ -7138,11 +7435,11 @@ function projectRollupSource(harnessDir, residuals) {
|
|
|
7138
7435
|
for (const entry of entries) {
|
|
7139
7436
|
if (!entry.isDirectory())
|
|
7140
7437
|
continue;
|
|
7141
|
-
const roadmapPath =
|
|
7142
|
-
if (!
|
|
7438
|
+
const roadmapPath = join19(projectsDir, entry.name, PROJECT_ROADMAP_FILE);
|
|
7439
|
+
if (!existsSync15(roadmapPath))
|
|
7143
7440
|
continue;
|
|
7144
7441
|
try {
|
|
7145
|
-
const doc = parseCompassFrontmatterText(
|
|
7442
|
+
const doc = parseCompassFrontmatterText(readFileSync10(roadmapPath, "utf8"), roadmapPath);
|
|
7146
7443
|
if (Array.isArray(doc.milestones)) {
|
|
7147
7444
|
for (const milestone of doc.milestones) {
|
|
7148
7445
|
if (typeof milestone === "string" && milestone.trim() !== "")
|
|
@@ -7204,7 +7501,7 @@ function residualRollup(harnessDir) {
|
|
|
7204
7501
|
}
|
|
7205
7502
|
function projectRegisters(harnessDir) {
|
|
7206
7503
|
const projectsDir = resolveProjectDir(harnessDir, { harnessDir });
|
|
7207
|
-
if (!
|
|
7504
|
+
if (!existsSync15(projectsDir))
|
|
7208
7505
|
return [];
|
|
7209
7506
|
let entries;
|
|
7210
7507
|
try {
|
|
@@ -7216,8 +7513,8 @@ function projectRegisters(harnessDir) {
|
|
|
7216
7513
|
for (const entry of entries) {
|
|
7217
7514
|
if (!entry.isDirectory())
|
|
7218
7515
|
continue;
|
|
7219
|
-
const registerPath =
|
|
7220
|
-
if (!
|
|
7516
|
+
const registerPath = join19(projectsDir, entry.name, PROJECT_REGISTER_FILE);
|
|
7517
|
+
if (!existsSync15(registerPath))
|
|
7221
7518
|
continue;
|
|
7222
7519
|
try {
|
|
7223
7520
|
registers.push({ projectId: entry.name, doc: readJson(registerPath) });
|
|
@@ -7228,13 +7525,13 @@ function projectRegisters(harnessDir) {
|
|
|
7228
7525
|
return registers;
|
|
7229
7526
|
}
|
|
7230
7527
|
function knowledgeDigest(harnessDir) {
|
|
7231
|
-
const indexPath =
|
|
7232
|
-
if (!
|
|
7528
|
+
const indexPath = join19(harnessDir, "knowledge", "README.md");
|
|
7529
|
+
if (!existsSync15(indexPath))
|
|
7233
7530
|
return null;
|
|
7234
7531
|
try {
|
|
7235
7532
|
const categories = new Set;
|
|
7236
7533
|
let docCount = 0;
|
|
7237
|
-
for (const line of
|
|
7534
|
+
for (const line of readFileSync10(indexPath, "utf8").split(/\r?\n/)) {
|
|
7238
7535
|
const row = line.trim().match(/^\|(.+)\|$/);
|
|
7239
7536
|
if (row === null)
|
|
7240
7537
|
continue;
|
|
@@ -7257,7 +7554,7 @@ function knowledgeDigest(harnessDir) {
|
|
|
7257
7554
|
}
|
|
7258
7555
|
function compassDirection(compassPath) {
|
|
7259
7556
|
try {
|
|
7260
|
-
const content =
|
|
7557
|
+
const content = readFileSync10(compassPath, "utf8");
|
|
7261
7558
|
const section = content.match(/^## Direction lock[^\n]*\n+([\s\S]*?)(?=\n## |$)/m);
|
|
7262
7559
|
if (section === null)
|
|
7263
7560
|
return null;
|
|
@@ -7274,20 +7571,20 @@ function compassDirection(compassPath) {
|
|
|
7274
7571
|
}
|
|
7275
7572
|
function selectedCompass(harnessDir, workflowId, snapshot) {
|
|
7276
7573
|
const ref = snapshot.compass_ref;
|
|
7277
|
-
if (typeof ref !== "string" || ref.trim() === "" ||
|
|
7574
|
+
if (typeof ref !== "string" || ref.trim() === "" || isAbsolute9(ref))
|
|
7278
7575
|
return;
|
|
7279
|
-
const compassPath =
|
|
7576
|
+
const compassPath = join19(harnessDir, ref);
|
|
7280
7577
|
if (!pathInside(harnessDir, compassPath))
|
|
7281
7578
|
return;
|
|
7282
|
-
if (
|
|
7579
|
+
if (basename6(compassPath) !== "delivery-compass.md")
|
|
7283
7580
|
return;
|
|
7284
|
-
if (!
|
|
7581
|
+
if (!existsSync15(compassPath))
|
|
7285
7582
|
return;
|
|
7286
7583
|
let resolvedHarness;
|
|
7287
7584
|
let resolvedCompass;
|
|
7288
7585
|
try {
|
|
7289
|
-
resolvedHarness =
|
|
7290
|
-
resolvedCompass =
|
|
7586
|
+
resolvedHarness = realpathSync6(harnessDir);
|
|
7587
|
+
resolvedCompass = realpathSync6(compassPath);
|
|
7291
7588
|
} catch {
|
|
7292
7589
|
return;
|
|
7293
7590
|
}
|
|
@@ -7307,15 +7604,15 @@ function selectedCompass(harnessDir, workflowId, snapshot) {
|
|
|
7307
7604
|
}
|
|
7308
7605
|
function pathInside(root, candidate) {
|
|
7309
7606
|
const rel = relative7(root, candidate);
|
|
7310
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep3}`) && !
|
|
7607
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute9(rel);
|
|
7311
7608
|
}
|
|
7312
7609
|
function iterationGateSource(harnessDir, selection) {
|
|
7313
7610
|
if (harnessDir === null || selection === undefined || selection.kind === "error")
|
|
7314
7611
|
return;
|
|
7315
|
-
const statusPath =
|
|
7316
|
-
if (!
|
|
7612
|
+
const statusPath = join19(harnessDir, STATUS_FILE);
|
|
7613
|
+
if (!existsSync15(statusPath))
|
|
7317
7614
|
return;
|
|
7318
|
-
const snapshotPath =
|
|
7615
|
+
const snapshotPath = join19(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
7319
7616
|
try {
|
|
7320
7617
|
const snapshotDoc = readJson(snapshotPath);
|
|
7321
7618
|
const compass = selectedCompass(harnessDir, selection.workflowId, snapshotDoc);
|
|
@@ -7405,7 +7702,7 @@ function persistEngineStatusSnapshot(ctx, harnessDir, cwd, turn, catalogPayload,
|
|
|
7405
7702
|
}
|
|
7406
7703
|
|
|
7407
7704
|
// src/engine-status-endpoint.ts
|
|
7408
|
-
import { isAbsolute as
|
|
7705
|
+
import { isAbsolute as isAbsolute11 } from "node:path";
|
|
7409
7706
|
|
|
7410
7707
|
// ../../node_modules/.bun/@deepseek-ai+dsh-typert-protocol@0.1.5-rc.2+1e4e7adc57ac8890/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
|
|
7411
7708
|
import { Service as Service2 } from "@deepseek-ai/cordis";
|
|
@@ -7461,7 +7758,7 @@ function liveSeqOf(value) {
|
|
|
7461
7758
|
function isRefusedPath(cwd) {
|
|
7462
7759
|
if (cwd.includes("\x00"))
|
|
7463
7760
|
return true;
|
|
7464
|
-
if (!
|
|
7761
|
+
if (!isAbsolute11(cwd))
|
|
7465
7762
|
return true;
|
|
7466
7763
|
return cwd.split(/[\\/]+/).includes("..");
|
|
7467
7764
|
}
|
|
@@ -7698,11 +7995,11 @@ function installEngineStatusEndpoint(ctx, options) {
|
|
|
7698
7995
|
|
|
7699
7996
|
// src/gates/status.ts
|
|
7700
7997
|
import { existsSync as existsSync17, readdirSync as readdirSync7 } from "node:fs";
|
|
7701
|
-
import { basename as
|
|
7998
|
+
import { basename as basename8, join as join21, relative as relative9, resolve as resolve11 } from "node:path";
|
|
7702
7999
|
var LOGGER_NAME = "mstar/status-gate";
|
|
7703
8000
|
function harnessDocKindOfTarget(harnessDir, targetPath) {
|
|
7704
|
-
const resolved =
|
|
7705
|
-
const name =
|
|
8001
|
+
const resolved = resolve11(targetPath);
|
|
8002
|
+
const name = basename8(resolved);
|
|
7706
8003
|
if (name !== STATUS_FILE && name !== WORKFLOW_SNAPSHOT_FILE && name !== PROJECT_REGISTER_FILE)
|
|
7707
8004
|
return null;
|
|
7708
8005
|
const rel = relative9(harnessDir, resolved);
|
|
@@ -7714,8 +8011,8 @@ function harnessDocKindOfTarget(harnessDir, targetPath) {
|
|
|
7714
8011
|
workflowDir = resolveWorkflowDir(harnessDir, { harnessDir });
|
|
7715
8012
|
projectDir = resolveProjectDir(harnessDir, { harnessDir });
|
|
7716
8013
|
} catch {
|
|
7717
|
-
workflowDir =
|
|
7718
|
-
projectDir =
|
|
8014
|
+
workflowDir = join21(harnessDir, "workflows");
|
|
8015
|
+
projectDir = join21(harnessDir, "projects");
|
|
7719
8016
|
}
|
|
7720
8017
|
if (name === WORKFLOW_SNAPSHOT_FILE && /^[^/]+\/snapshot\.json$/.test(relative9(workflowDir, resolved)))
|
|
7721
8018
|
return "snapshot";
|
|
@@ -7779,7 +8076,7 @@ function projectRegisterDocs(harnessDir) {
|
|
|
7779
8076
|
for (const entry of entries) {
|
|
7780
8077
|
if (!entry.isDirectory())
|
|
7781
8078
|
continue;
|
|
7782
|
-
const registerPath =
|
|
8079
|
+
const registerPath = join21(projectsDir, entry.name, PROJECT_REGISTER_FILE);
|
|
7783
8080
|
if (!existsSync17(registerPath))
|
|
7784
8081
|
continue;
|
|
7785
8082
|
try {
|
|
@@ -7853,8 +8150,8 @@ async function editIntentListener(ctx, resolver, config, adapter, target, actor,
|
|
|
7853
8150
|
}
|
|
7854
8151
|
|
|
7855
8152
|
// src/gates/skill-lint.ts
|
|
7856
|
-
import { existsSync as existsSync18, readFileSync as
|
|
7857
|
-
import { basename as
|
|
8153
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13 } from "node:fs";
|
|
8154
|
+
import { basename as basename9, dirname as dirname12, resolve as resolve13, sep as sep6 } from "node:path";
|
|
7858
8155
|
var SKILL_LINT_LOGGER = "mstar/skill-lint";
|
|
7859
8156
|
|
|
7860
8157
|
class SkillLintVetoError extends Error {
|
|
@@ -7895,7 +8192,7 @@ function lintSkillDoc(doc, options = {}) {
|
|
|
7895
8192
|
return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
|
|
7896
8193
|
}
|
|
7897
8194
|
function lintSkillWrite(doc, options) {
|
|
7898
|
-
const skillId =
|
|
8195
|
+
const skillId = basename9(dirname12(resolve13(options.target)));
|
|
7899
8196
|
const result = lintSkillDoc(doc, { skillId });
|
|
7900
8197
|
if (options.hard && !result.ok) {
|
|
7901
8198
|
throw new SkillLintVetoError(options.target, result.violations);
|
|
@@ -7908,13 +8205,13 @@ function skillRootsOf(config) {
|
|
|
7908
8205
|
return roots.map((root) => root.trim()).filter((root) => root !== "");
|
|
7909
8206
|
}
|
|
7910
8207
|
function isSkillTarget(roots, target) {
|
|
7911
|
-
if (
|
|
8208
|
+
if (basename9(target.displayPath) !== "SKILL.md")
|
|
7912
8209
|
return false;
|
|
7913
|
-
const resolvedPath =
|
|
7914
|
-
return roots.some((root) => resolvedPath.startsWith(
|
|
8210
|
+
const resolvedPath = resolve13(target.displayPath);
|
|
8211
|
+
return roots.some((root) => resolvedPath.startsWith(resolve13(root) + sep6));
|
|
7915
8212
|
}
|
|
7916
8213
|
function skillNameOf(target) {
|
|
7917
|
-
return
|
|
8214
|
+
return basename9(dirname12(target.displayPath));
|
|
7918
8215
|
}
|
|
7919
8216
|
function skillCanonicalForm(target) {
|
|
7920
8217
|
return resolveSkillRoot("dsh", { skill: skillNameOf(target), rel: "SKILL.md" });
|
|
@@ -7926,12 +8223,12 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
|
|
|
7926
8223
|
return;
|
|
7927
8224
|
if (!isSkillTarget(roots, target))
|
|
7928
8225
|
return;
|
|
7929
|
-
const skillPath =
|
|
8226
|
+
const skillPath = resolve13(target.displayPath);
|
|
7930
8227
|
if (!existsSync18(skillPath))
|
|
7931
8228
|
return;
|
|
7932
8229
|
let doc;
|
|
7933
8230
|
try {
|
|
7934
|
-
doc =
|
|
8231
|
+
doc = readFileSync13(skillPath, "utf8");
|
|
7935
8232
|
} catch (error) {
|
|
7936
8233
|
ctx.logger(SKILL_LINT_LOGGER).error(`skill lint degraded to allow (cannot read ${skillPath}): ${error.message}`);
|
|
7937
8234
|
ctx.emit("mstar/skill-lint", {
|
|
@@ -7944,7 +8241,7 @@ function gateSkillIntent(ctx, harnessDir, config, target) {
|
|
|
7944
8241
|
});
|
|
7945
8242
|
return;
|
|
7946
8243
|
}
|
|
7947
|
-
const result = lintSkillDoc(doc, { skillId:
|
|
8244
|
+
const result = lintSkillDoc(doc, { skillId: basename9(dirname12(skillPath)) });
|
|
7948
8245
|
if (result.ok)
|
|
7949
8246
|
return;
|
|
7950
8247
|
const hard = resolveSeamHard(harnessDir, config);
|
|
@@ -7979,14 +8276,16 @@ async function skillWriteIntentListener(ctx, resolver, config, target, actor, ne
|
|
|
7979
8276
|
|
|
7980
8277
|
// src/gates/seams.ts
|
|
7981
8278
|
import { existsSync as existsSync19, readFileSync as readFileSync15, readdirSync as readdirSync10 } from "node:fs";
|
|
7982
|
-
import { basename as
|
|
8279
|
+
import { basename as basename10, dirname as dirname13, join as join22, resolve as resolve15, sep as sep8 } from "node:path";
|
|
7983
8280
|
|
|
7984
8281
|
// ../engine/dist/audit.js
|
|
7985
|
-
import { AsyncLocalStorage as
|
|
8282
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
|
|
8283
|
+
import { AsyncLocalStorage as AsyncLocalStorage22 } from "node:async_hooks";
|
|
7986
8284
|
var DATE_PART2 = String.raw`\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])`;
|
|
7987
8285
|
var RFC3339_Z_RE2 = new RegExp(String.raw`^${DATE_PART2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$`);
|
|
7988
8286
|
var DATE_ONLY_RE2 = new RegExp(String.raw`^${DATE_PART2}$`);
|
|
7989
|
-
var heldLockDirs2 = new
|
|
8287
|
+
var heldLockDirs2 = new AsyncLocalStorage3;
|
|
8288
|
+
var writeAuthorizations2 = new AsyncLocalStorage22;
|
|
7990
8289
|
var MSTARC_HARNESS_DIR_KEY2 = "harness_dir";
|
|
7991
8290
|
var MSTARC_PLAN_DIR_KEY2 = "plan_dir";
|
|
7992
8291
|
var MSTARC_SDD_DIR_KEY2 = "sdd_dir";
|
|
@@ -8178,7 +8477,7 @@ var SEAM_LOGGERS = {
|
|
|
8178
8477
|
roles: "mstar/roles-lint"
|
|
8179
8478
|
};
|
|
8180
8479
|
function isIndexFile(path) {
|
|
8181
|
-
const base =
|
|
8480
|
+
const base = basename10(path);
|
|
8182
8481
|
return base === "README.md" || base === "index.md";
|
|
8183
8482
|
}
|
|
8184
8483
|
function isMarkdownDoc(path) {
|
|
@@ -8187,7 +8486,7 @@ function isMarkdownDoc(path) {
|
|
|
8187
8486
|
function isAuditPlanTarget(path) {
|
|
8188
8487
|
if (!isMarkdownDoc(path))
|
|
8189
8488
|
return false;
|
|
8190
|
-
const segments =
|
|
8489
|
+
const segments = resolve15(path).split(sep8);
|
|
8191
8490
|
for (let i = 0;i < segments.length; i++) {
|
|
8192
8491
|
if (segments[i] !== "plans")
|
|
8193
8492
|
continue;
|
|
@@ -8198,7 +8497,7 @@ function isAuditPlanTarget(path) {
|
|
|
8198
8497
|
return false;
|
|
8199
8498
|
}
|
|
8200
8499
|
function isRolesTarget(path) {
|
|
8201
|
-
const segments =
|
|
8500
|
+
const segments = resolve15(path).split(sep8);
|
|
8202
8501
|
const idx = segments.indexOf("mstar-roles");
|
|
8203
8502
|
if (idx === -1)
|
|
8204
8503
|
return false;
|
|
@@ -8208,17 +8507,17 @@ function isRolesTarget(path) {
|
|
|
8208
8507
|
return rest[0] === "SKILL.md" || rest[0] === "references" && rest[rest.length - 1].endsWith(".md");
|
|
8209
8508
|
}
|
|
8210
8509
|
function rolesDirOf(path) {
|
|
8211
|
-
const segments =
|
|
8510
|
+
const segments = resolve15(path).split(sep8);
|
|
8212
8511
|
const idx = segments.indexOf("mstar-roles");
|
|
8213
8512
|
if (idx === -1)
|
|
8214
|
-
return
|
|
8215
|
-
return idx === 0 ?
|
|
8513
|
+
return dirname13(path);
|
|
8514
|
+
return idx === 0 ? sep8 : join22(sep8, ...segments.slice(1, idx + 1));
|
|
8216
8515
|
}
|
|
8217
8516
|
function isSeamTarget(seam, harnessDir, target) {
|
|
8218
|
-
const path =
|
|
8517
|
+
const path = resolve15(target.displayPath);
|
|
8219
8518
|
switch (seam) {
|
|
8220
8519
|
case "design-md": {
|
|
8221
|
-
const base =
|
|
8520
|
+
const base = basename10(path);
|
|
8222
8521
|
return base === "DESIGN.md" || base === "DESIGN.dark.md";
|
|
8223
8522
|
}
|
|
8224
8523
|
case "audit":
|
|
@@ -8226,7 +8525,7 @@ function isSeamTarget(seam, harnessDir, target) {
|
|
|
8226
8525
|
case "compound": {
|
|
8227
8526
|
if (harnessDir === null)
|
|
8228
8527
|
return false;
|
|
8229
|
-
return path.startsWith(
|
|
8528
|
+
return path.startsWith(resolve15(join22(harnessDir, "knowledge")) + sep8) && isMarkdownDoc(path);
|
|
8230
8529
|
}
|
|
8231
8530
|
case "roles":
|
|
8232
8531
|
return isRolesTarget(path);
|
|
@@ -8243,8 +8542,8 @@ function auditSecretsViolations(findings, file) {
|
|
|
8243
8542
|
}
|
|
8244
8543
|
function validateDesignDoc(doc, path) {
|
|
8245
8544
|
const violations = [...validateDesignTokenFrontmatter(doc).violations];
|
|
8246
|
-
const isDark =
|
|
8247
|
-
const sibling =
|
|
8545
|
+
const isDark = basename10(path) === "DESIGN.dark.md";
|
|
8546
|
+
const sibling = join22(dirname13(path), isDark ? "DESIGN.md" : "DESIGN.dark.md");
|
|
8248
8547
|
if (existsSync19(sibling)) {
|
|
8249
8548
|
const light = isDark ? readFileSync15(sibling, "utf8") : doc;
|
|
8250
8549
|
const dark = isDark ? doc : readFileSync15(sibling, "utf8");
|
|
@@ -8261,17 +8560,17 @@ function validateAuditDoc(doc, path) {
|
|
|
8261
8560
|
function validateCompoundDoc(doc, _path, harnessDir) {
|
|
8262
8561
|
const violations = [...validateSchemaYaml(doc).violations];
|
|
8263
8562
|
if (harnessDir !== null) {
|
|
8264
|
-
violations.push(...referenceExists(
|
|
8563
|
+
violations.push(...referenceExists(dirname13(harnessDir), doc).violations);
|
|
8265
8564
|
}
|
|
8266
8565
|
return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
|
|
8267
8566
|
}
|
|
8268
|
-
function validateRolesState(rolesDir, skillsRoot =
|
|
8567
|
+
function validateRolesState(rolesDir, skillsRoot = dirname13(rolesDir)) {
|
|
8269
8568
|
const violations = [...validateRoleMapping(rolesDir).violations];
|
|
8270
8569
|
const skillTexts = {};
|
|
8271
8570
|
for (const entry of readdirSync10(skillsRoot, { withFileTypes: true })) {
|
|
8272
8571
|
if (!entry.isDirectory() || !entry.name.startsWith("mstar-"))
|
|
8273
8572
|
continue;
|
|
8274
|
-
const skillFile =
|
|
8573
|
+
const skillFile = join22(skillsRoot, entry.name, "SKILL.md");
|
|
8275
8574
|
if (!existsSync19(skillFile))
|
|
8276
8575
|
continue;
|
|
8277
8576
|
try {
|
|
@@ -8305,7 +8604,7 @@ function gateSeamIntent(ctx, harnessDir, config, seam, target) {
|
|
|
8305
8604
|
try {
|
|
8306
8605
|
if (!isSeamTarget(seam, harnessDir, target))
|
|
8307
8606
|
return;
|
|
8308
|
-
const path =
|
|
8607
|
+
const path = resolve15(target.displayPath);
|
|
8309
8608
|
if (!existsSync19(path))
|
|
8310
8609
|
return;
|
|
8311
8610
|
let doc;
|
|
@@ -8322,12 +8621,12 @@ function gateSeamIntent(ctx, harnessDir, config, seam, target) {
|
|
|
8322
8621
|
const hard = resolveSeamHard(harnessDir, config);
|
|
8323
8622
|
const verdict = applyEnforcement(result, { hard });
|
|
8324
8623
|
if (verdict.hardBlocked) {
|
|
8325
|
-
logger.error(`${
|
|
8624
|
+
logger.error(`${basename10(path)} write to ${target.displayPath} ALLOWED as repair (Enforcement: hard; the current on-disk document is already invalid — the intent carries no incoming content, so the vetoable signal is only the pre-write state):
|
|
8326
8625
|
${verdict.violations.map(formatViolation).join(`
|
|
8327
8626
|
`)}`);
|
|
8328
8627
|
emitSeamAdvisory(ctx, seam, target.displayPath, verdict, hard, { repair: true });
|
|
8329
8628
|
} else {
|
|
8330
|
-
logger.warn(`${
|
|
8629
|
+
logger.warn(`${basename10(path)} write to ${target.displayPath} (advisory):
|
|
8331
8630
|
${verdict.violations.map(formatViolation).join(`
|
|
8332
8631
|
`)}`);
|
|
8333
8632
|
emitSeamAdvisory(ctx, seam, target.displayPath, verdict, hard);
|
|
@@ -8377,7 +8676,7 @@ function lintRolesWrite(doc, options) {
|
|
|
8377
8676
|
|
|
8378
8677
|
// src/gates/tools.ts
|
|
8379
8678
|
import { existsSync as existsSync20, readFileSync as readFileSync16 } from "node:fs";
|
|
8380
|
-
import { join as
|
|
8679
|
+
import { join as join23, resolve as resolve17 } from "node:path";
|
|
8381
8680
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
8382
8681
|
var ITERATION_VIOLATION_SCHEMA = {
|
|
8383
8682
|
type: "object",
|
|
@@ -8597,8 +8896,8 @@ function registerSeamTools(ctx, resolver) {
|
|
|
8597
8896
|
presentCall: (args) => ({ card: "generic", title: "Validate DESIGN.md", kind: "other", rawInput: args.dir }),
|
|
8598
8897
|
isConcurrencySafe: () => false,
|
|
8599
8898
|
async execute(args) {
|
|
8600
|
-
const abs =
|
|
8601
|
-
const lightPath =
|
|
8899
|
+
const abs = resolve17(args.dir);
|
|
8900
|
+
const lightPath = join23(abs, "DESIGN.md");
|
|
8602
8901
|
if (!existsSync20(lightPath))
|
|
8603
8902
|
throw new Error(`design file not found: ${lightPath}`);
|
|
8604
8903
|
const light = readFileSync16(lightPath, "utf8");
|
|
@@ -8651,7 +8950,7 @@ function registerSeamTools(ctx, resolver) {
|
|
|
8651
8950
|
presentCall: (args) => ({ card: "generic", title: "Validate audit plan", kind: "other", rawInput: args.plan_path }),
|
|
8652
8951
|
isConcurrencySafe: () => false,
|
|
8653
8952
|
async execute(args) {
|
|
8654
|
-
const abs =
|
|
8953
|
+
const abs = resolve17(args.plan_path);
|
|
8655
8954
|
if (!existsSync20(abs))
|
|
8656
8955
|
throw new Error(`plan file not found: ${abs}`);
|
|
8657
8956
|
const text = readFileSync16(abs, "utf8");
|
|
@@ -8691,17 +8990,17 @@ function registerSeamTools(ctx, resolver) {
|
|
|
8691
8990
|
presentCall: (args) => ({ card: "generic", title: "Validate knowledge doc", kind: "other", rawInput: args.doc_path }),
|
|
8692
8991
|
isConcurrencySafe: () => false,
|
|
8693
8992
|
async execute(args, exec) {
|
|
8694
|
-
const abs =
|
|
8993
|
+
const abs = resolve17(args.doc_path);
|
|
8695
8994
|
if (!existsSync20(abs))
|
|
8696
8995
|
throw new Error(`knowledge doc not found: ${abs}`);
|
|
8697
8996
|
const text = readFileSync16(abs, "utf8");
|
|
8698
8997
|
const base = validateCompoundDoc(text, abs, args.repo_root !== undefined ? null : resolver.forAgent(exec.agent));
|
|
8699
8998
|
const violations = [...base.violations];
|
|
8700
8999
|
if (args.repo_root !== undefined) {
|
|
8701
|
-
violations.push(...referenceExists(
|
|
9000
|
+
violations.push(...referenceExists(resolve17(args.repo_root), text).violations);
|
|
8702
9001
|
}
|
|
8703
9002
|
if (args.knowledge_dir !== undefined) {
|
|
8704
|
-
const knowledgeDir =
|
|
9003
|
+
const knowledgeDir = resolve17(args.knowledge_dir);
|
|
8705
9004
|
violations.push(...assertIndexRows(knowledgeDir).violations);
|
|
8706
9005
|
violations.push(...scopeGuard(abs, [knowledgeDir]).violations);
|
|
8707
9006
|
}
|
|
@@ -8736,10 +9035,10 @@ function registerSeamTools(ctx, resolver) {
|
|
|
8736
9035
|
presentCall: (args) => ({ card: "generic", title: "Validate roles mapping", kind: "other", rawInput: args.roles_dir }),
|
|
8737
9036
|
isConcurrencySafe: () => false,
|
|
8738
9037
|
async execute(args) {
|
|
8739
|
-
const rolesDir =
|
|
8740
|
-
if (!existsSync20(
|
|
9038
|
+
const rolesDir = resolve17(args.roles_dir);
|
|
9039
|
+
if (!existsSync20(join23(rolesDir, "SKILL.md")))
|
|
8741
9040
|
throw new Error(`roles dir not found: ${rolesDir}`);
|
|
8742
|
-
const result = validateRolesState(rolesDir, args.skills_root !== undefined ?
|
|
9041
|
+
const result = validateRolesState(rolesDir, args.skills_root !== undefined ? resolve17(args.skills_root) : undefined);
|
|
8743
9042
|
return { ok: result.ok, violations: result.violations.map(iterationViolationView) };
|
|
8744
9043
|
}
|
|
8745
9044
|
}));
|
|
@@ -8748,7 +9047,7 @@ function registerSeamTools(ctx, resolver) {
|
|
|
8748
9047
|
|
|
8749
9048
|
// src/gates/adapter.ts
|
|
8750
9049
|
import { existsSync as existsSync21 } from "node:fs";
|
|
8751
|
-
import { join as
|
|
9050
|
+
import { join as join24 } from "node:path";
|
|
8752
9051
|
import { Service as Service3 } from "@deepseek-ai/cordis";
|
|
8753
9052
|
var HOST_LOGGER = "mstar/host-adapter";
|
|
8754
9053
|
function mergeLeasesMatch(left, right) {
|
|
@@ -8863,7 +9162,7 @@ class DshHostAdapter extends Service3 {
|
|
|
8863
9162
|
if (harnessDir !== null) {
|
|
8864
9163
|
const selection = resolveActiveWorkflow(harnessDir);
|
|
8865
9164
|
if (selection.kind === "active") {
|
|
8866
|
-
const snapshotPath =
|
|
9165
|
+
const snapshotPath = join24(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE);
|
|
8867
9166
|
try {
|
|
8868
9167
|
const snapshot = readJson(snapshotPath);
|
|
8869
9168
|
const stored = snapshot?.integration_merge_lease;
|
|
@@ -8890,7 +9189,7 @@ class DshHostAdapter extends Service3 {
|
|
|
8890
9189
|
|
|
8891
9190
|
// src/gates/agent-personas.ts
|
|
8892
9191
|
import { readFileSync as readFileSync17, readdirSync as readdirSync12, statSync as statSync8 } from "node:fs";
|
|
8893
|
-
import { join as
|
|
9192
|
+
import { join as join25, resolve as resolve18, sep as sep9 } from "node:path";
|
|
8894
9193
|
var ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
|
|
8895
9194
|
var defaultCache = new Map;
|
|
8896
9195
|
function personaFor(roleId, lookup, warn) {
|
|
@@ -8918,7 +9217,7 @@ function subagentRoleIds(agentsDir) {
|
|
|
8918
9217
|
const roleId = name.slice(0, -3);
|
|
8919
9218
|
let content;
|
|
8920
9219
|
try {
|
|
8921
|
-
content = readFileSync17(
|
|
9220
|
+
content = readFileSync17(join25(agentsDir, name), "utf8");
|
|
8922
9221
|
} catch {
|
|
8923
9222
|
continue;
|
|
8924
9223
|
}
|
|
@@ -8931,9 +9230,9 @@ function subagentRoleIds(agentsDir) {
|
|
|
8931
9230
|
function defaultFromMirror(agentsDir, roleId, warn) {
|
|
8932
9231
|
if (!ROLE_ID_PATTERN.test(roleId))
|
|
8933
9232
|
return;
|
|
8934
|
-
const root =
|
|
8935
|
-
const file =
|
|
8936
|
-
if (!file.startsWith(root +
|
|
9233
|
+
const root = resolve18(agentsDir);
|
|
9234
|
+
const file = resolve18(agentsDir, `${roleId}.md`);
|
|
9235
|
+
if (!file.startsWith(root + sep9))
|
|
8937
9236
|
return;
|
|
8938
9237
|
let stat;
|
|
8939
9238
|
try {
|
|
@@ -9180,7 +9479,7 @@ function log2(level, message) {
|
|
|
9180
9479
|
|
|
9181
9480
|
// src/gates/workflow-ledger.ts
|
|
9182
9481
|
import { readFileSync as readFileSync18, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9183
|
-
import { join as
|
|
9482
|
+
import { join as join26 } from "node:path";
|
|
9184
9483
|
var WORKFLOW_LEDGER_LOGGER = "mstar/workflow-ledger";
|
|
9185
9484
|
var TOOL_WORKFLOW_RUN_START = "tool-workflow/run-start";
|
|
9186
9485
|
var TOOL_WORKFLOW_AGENT_START = "tool-workflow/agent-start";
|
|
@@ -9240,7 +9539,7 @@ function loadWatermark(workflowDir, fresh = false) {
|
|
|
9240
9539
|
return cached;
|
|
9241
9540
|
const watermark = new Map;
|
|
9242
9541
|
try {
|
|
9243
|
-
const raw = readFileSync18(
|
|
9542
|
+
const raw = readFileSync18(join26(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
|
|
9244
9543
|
const record = asRecord(JSON.parse(raw));
|
|
9245
9544
|
const cursors = asRecord(record?.cursors);
|
|
9246
9545
|
if (cursors !== undefined) {
|
|
@@ -9263,7 +9562,7 @@ function loadWatermark(workflowDir, fresh = false) {
|
|
|
9263
9562
|
}
|
|
9264
9563
|
function saveWatermark(workflowDir, watermark) {
|
|
9265
9564
|
try {
|
|
9266
|
-
const file =
|
|
9565
|
+
const file = join26(workflowDir, WORKFLOW_LEDGER_WATERMARK_FILE);
|
|
9267
9566
|
const tmp = `${file}.tmp`;
|
|
9268
9567
|
writeFileSync7(tmp, JSON.stringify({ v: 1, cursors: Object.fromEntries(watermark) }));
|
|
9269
9568
|
renameSync4(tmp, file);
|
|
@@ -9583,7 +9882,7 @@ function registerWorkflowLedger(ctx, resolver, workflowAskCache) {
|
|
|
9583
9882
|
|
|
9584
9883
|
// src/gates/goal-bridge.ts
|
|
9585
9884
|
import { existsSync as existsSync22, readdirSync as readdirSync13 } from "node:fs";
|
|
9586
|
-
import { join as
|
|
9885
|
+
import { join as join27 } from "node:path";
|
|
9587
9886
|
var GOAL_BRIDGE_LOGGER = "mstar/goal-bridge";
|
|
9588
9887
|
var GOAL_CHANGE_EVENT_TYPE = "goal/change";
|
|
9589
9888
|
var GOAL_CHANGE_VERSION = 1;
|
|
@@ -9608,12 +9907,12 @@ function projectRegisterPointer(harnessDir) {
|
|
|
9608
9907
|
for (const entry of readdirSync13(projectsDir, { withFileTypes: true })) {
|
|
9609
9908
|
if (!entry.isDirectory())
|
|
9610
9909
|
continue;
|
|
9611
|
-
const registerPath =
|
|
9910
|
+
const registerPath = join27(projectsDir, entry.name, PROJECT_REGISTER_FILE);
|
|
9612
9911
|
if (existsSync22(registerPath))
|
|
9613
9912
|
return registerPath;
|
|
9614
9913
|
}
|
|
9615
9914
|
} catch {}
|
|
9616
|
-
return
|
|
9915
|
+
return join27(projectsDir, _DEFAULT_PROJECT, PROJECT_REGISTER_FILE);
|
|
9617
9916
|
}
|
|
9618
9917
|
function blockedAdvisoryOf(envelope) {
|
|
9619
9918
|
const record = asRecord(envelope);
|
|
@@ -9665,7 +9964,7 @@ function registerGoalBridge(ctx, resolver) {
|
|
|
9665
9964
|
|
|
9666
9965
|
// src/gates/plan-mode-bridge.ts
|
|
9667
9966
|
import { existsSync as existsSync23 } from "node:fs";
|
|
9668
|
-
import { isAbsolute as
|
|
9967
|
+
import { isAbsolute as isAbsolute12, join as join28, relative as relative10, sep as sep10 } from "node:path";
|
|
9669
9968
|
|
|
9670
9969
|
// src/gates/steering.ts
|
|
9671
9970
|
function isRootLikeAgent(agent) {
|
|
@@ -9709,7 +10008,7 @@ function errorMessage5(error) {
|
|
|
9709
10008
|
return error instanceof Error ? error.message : String(error);
|
|
9710
10009
|
}
|
|
9711
10010
|
function selectedLifecycle(harnessDir, hint) {
|
|
9712
|
-
if (!existsSync23(
|
|
10011
|
+
if (!existsSync23(join28(harnessDir, STATUS_FILE)))
|
|
9713
10012
|
return { kind: "none" };
|
|
9714
10013
|
const selection = resolveReadWorkflow(harnessDir, hint);
|
|
9715
10014
|
if (selection.kind === "error") {
|
|
@@ -9717,7 +10016,7 @@ function selectedLifecycle(harnessDir, hint) {
|
|
|
9717
10016
|
}
|
|
9718
10017
|
let doc;
|
|
9719
10018
|
try {
|
|
9720
|
-
doc = readJson(
|
|
10019
|
+
doc = readJson(join28(harnessDir, selection.dir, WORKFLOW_SNAPSHOT_FILE));
|
|
9721
10020
|
} catch {
|
|
9722
10021
|
return { kind: "none" };
|
|
9723
10022
|
}
|
|
@@ -9727,11 +10026,11 @@ function selectedLifecycle(harnessDir, hint) {
|
|
|
9727
10026
|
}
|
|
9728
10027
|
function selectedCompassSteers(harnessDir, lifecycle) {
|
|
9729
10028
|
const ref = lifecycle.doc.compass_ref;
|
|
9730
|
-
if (typeof ref !== "string" || ref.trim() === "" ||
|
|
10029
|
+
if (typeof ref !== "string" || ref.trim() === "" || isAbsolute12(ref))
|
|
9731
10030
|
return false;
|
|
9732
|
-
const compassPath =
|
|
10031
|
+
const compassPath = join28(harnessDir, ref);
|
|
9733
10032
|
const inside = relative10(harnessDir, compassPath);
|
|
9734
|
-
if (inside === "" || inside === ".." || inside.startsWith(`..${
|
|
10033
|
+
if (inside === "" || inside === ".." || inside.startsWith(`..${sep10}`) || isAbsolute12(inside))
|
|
9735
10034
|
return false;
|
|
9736
10035
|
if (!existsSync23(compassPath))
|
|
9737
10036
|
return false;
|
|
@@ -10302,7 +10601,7 @@ function registerMstarCommands(ctx) {
|
|
|
10302
10601
|
for (const file of readdirSync14(dir).sort()) {
|
|
10303
10602
|
if (!file.endsWith(".md"))
|
|
10304
10603
|
continue;
|
|
10305
|
-
const parsed = parseCommandMarkdown(readFileSync19(
|
|
10604
|
+
const parsed = parseCommandMarkdown(readFileSync19(join29(dir, file), "utf8"));
|
|
10306
10605
|
if (parsed === undefined)
|
|
10307
10606
|
continue;
|
|
10308
10607
|
commandsCtx.commands.register({
|