@mstar-harness/engine 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatch.d.ts +50 -28
- package/dist/engine.js +101 -0
- package/dist/index.d.ts +3 -3
- package/dist/iteration.d.ts +14 -0
- package/package.json +1 -1
package/dist/dispatch.d.ts
CHANGED
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Engine dispatch module — Assignment field validation, default-branch gate,
|
|
3
|
-
* execution-mode → QC seat count, tri identity, anti-recursion precheck.
|
|
4
|
-
*
|
|
5
|
-
* Spec sources (semantic SSOT — the skills stay authoritative; this module
|
|
6
|
-
* implements their deterministic rules without forking semantics):
|
|
7
|
-
* - Assignment field contract (`Execute as` / `Delegation` / `Task category`
|
|
8
|
-
* present, non-empty; paste-only assignments missing fields are flagged):
|
|
9
|
-
* `mstar-dispatch-gates` SKILL.md § "调度防串扰(强制)" + § 反模式(派发)
|
|
10
|
-
* ("Assignment 已写、invoke 为零(paste-only)").
|
|
11
|
-
* - Branch-field exactly-one rule + `<base>` requirement: `mstar-branch-worktree`
|
|
12
|
-
* SKILL.md § "Assignment 要求(PM)" + § "`<base>` 与叠分支(stacked
|
|
13
|
-
* branches)" ("若写新建但未写 `<base>`:实现侧应停下问 project-manager…
|
|
14
|
-
* 禁止擅自假设「一定是 main」").
|
|
15
|
-
* - Default-protected-branch gate (`main`/`master` unless an explicit
|
|
16
|
-
* `Branch policy: direct on <branch> — <reason>` exception exists):
|
|
17
|
-
* `mstar-branch-worktree` SKILL.md § "Git 功能分支门禁(业务仓库)".
|
|
18
|
-
* - N→seat mapping (sdd→3 tri, inline→1 single, targeted→listed seats) and
|
|
19
|
-
* tri identity (`qc-specialist` / `qc-specialist-2` / `qc-specialist-3`):
|
|
20
|
-
* `mstar-dispatch-gates` SKILL.md § "QC tri-review(SDD 强制)" / "QC
|
|
21
|
-
* 单席(例外)" / "QC targeted re-review" + `mstar-roles` SKILL.md
|
|
22
|
-
* "QC reviewer" 参数表.
|
|
23
|
-
* - Anti-recursion NEVER red line (role binding == `Execute as`): `mstar-dispatch-gates`
|
|
24
|
-
* SKILL.md § "承接方反递归红线(NEVER / DO NOT;leaf executor 必读)".
|
|
25
|
-
* - Hard-gate enforcement (`Enforcement: hard` flag — per Assignment/compass,
|
|
26
|
-
* never global; rollback = unset flag): `.harness/references/skill-programmatic-roadmap.md`
|
|
27
|
-
* §8.5 C4 + decision D2.
|
|
28
|
-
*/
|
|
29
1
|
import type { GateResult } from "./core.js";
|
|
30
2
|
/** Parsed Assignment header fields relevant to dispatch validation. */
|
|
31
3
|
export type AssignmentFields = {
|
|
@@ -195,6 +167,56 @@ export declare function executionModeToN(executionMode: string, opts?: Execution
|
|
|
195
167
|
* Any other composition — missing seat, duplicate, or foreign role — fails.
|
|
196
168
|
*/
|
|
197
169
|
export declare function assertTriIdentity(reviewerRoles: readonly string[]): GateResult;
|
|
170
|
+
/**
|
|
171
|
+
* Options for {@link composeDispatchGate}.
|
|
172
|
+
*/
|
|
173
|
+
export type ComposeDispatchGateOptions = {
|
|
174
|
+
/**
|
|
175
|
+
* Host role-binding field (omp task entry `agent` / opencode `subagent` /
|
|
176
|
+
* cursor `subagent_type`); the anti-recursion precheck runs only when
|
|
177
|
+
* non-empty.
|
|
178
|
+
*/
|
|
179
|
+
agent?: string;
|
|
180
|
+
/**
|
|
181
|
+
* `false` for read-only roles (scout/explore) — skips the branch-form and
|
|
182
|
+
* default-branch gates. Default: `true` (writable).
|
|
183
|
+
*/
|
|
184
|
+
writable?: boolean;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Result of {@link composeDispatchGate}: a GateResult plus the shape verdict
|
|
188
|
+
* and the header-region enforcement flag.
|
|
189
|
+
*/
|
|
190
|
+
export type ComposeDispatchGateResult = GateResult & {
|
|
191
|
+
/** Assignment-shaped text was recognized (heading or core-field regex). */
|
|
192
|
+
shaped: boolean;
|
|
193
|
+
/** Enforcement parsed from the Assignment HEADER region only (never the body). */
|
|
194
|
+
enforcement: EnforcementFlag;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Shared host dispatch-gate composition (qc1 F-001/F-006, qc2 F-005/F-007,
|
|
198
|
+
* qc3 F-007/F-008) — the SINGLE dispatch-validation composition consumed by
|
|
199
|
+
* the opencode adapter (`validateDispatchAssignment`), the omp blocking hook
|
|
200
|
+
* (Gate 2) and the `mstar_dispatch_validate` tool:
|
|
201
|
+
*
|
|
202
|
+
* 1. Shape guard: `## Assignment` heading OR any core field line
|
|
203
|
+
* (`Execute as` / `Delegation` / `Task category`). Text that is not
|
|
204
|
+
* Assignment-shaped passes silently (`shaped: false`).
|
|
205
|
+
* 2. `validateAssignmentFields` with `writable: false` when `opts.writable
|
|
206
|
+
* === false` (read-only roles), else the writable default.
|
|
207
|
+
* 3. Anti-recursion precheck when `opts.agent` is non-empty.
|
|
208
|
+
* 4. Default-branch gate for writable text: the branch comes from the
|
|
209
|
+
* Assignment's own branch forms (create-form name / Working branch /
|
|
210
|
+
* Branch policy branch), else `$MSTAR_WORKING_BRANCH`; a well-formed
|
|
211
|
+
* `Branch policy: direct on <branch> — <reason>` exception is honored
|
|
212
|
+
* only when its branch is the one being checked.
|
|
213
|
+
* 5. Enforcement flag parsed from the Assignment HEADER region only; the
|
|
214
|
+
* result carries `hardBlocked` via `applyEnforcement`.
|
|
215
|
+
*
|
|
216
|
+
* Never throws on text errors: unexpected failures degrade to the same
|
|
217
|
+
* silent non-shaped result.
|
|
218
|
+
*/
|
|
219
|
+
export declare function composeDispatchGate(text: string, opts?: ComposeDispatchGateOptions): ComposeDispatchGateResult;
|
|
198
220
|
/**
|
|
199
221
|
* Anti-recursion precheck (NEVER red line, mstar-dispatch-gates § 承接方反递归
|
|
200
222
|
* 红线): a leaf executor MUST NOT invoke a Task/subagent whose role-binding
|
package/dist/engine.js
CHANGED
|
@@ -740,6 +740,43 @@ function assertTriIdentity(reviewerRoles) {
|
|
|
740
740
|
]
|
|
741
741
|
};
|
|
742
742
|
}
|
|
743
|
+
var ASSIGNMENT_HEADING_RE = /^#{1,6}\s+Assignment\s*$/m;
|
|
744
|
+
var ASSIGNMENT_FIELD_RE = /^[ \t]*(?:[-*][ \t]+)?\*{0,2}(Execute as|Delegation|Task category)\*{0,2}[ \t]*:[ \t]*(\S.*)$/gm;
|
|
745
|
+
function isAssignmentShaped(assignmentText) {
|
|
746
|
+
return ASSIGNMENT_HEADING_RE.test(assignmentText) || assignmentText.match(ASSIGNMENT_FIELD_RE) !== null;
|
|
747
|
+
}
|
|
748
|
+
function composeDispatchGate(text, opts = {}) {
|
|
749
|
+
const silent = {
|
|
750
|
+
ok: true,
|
|
751
|
+
violations: [],
|
|
752
|
+
shaped: false,
|
|
753
|
+
enforcement: { hard: false, source: "none" }
|
|
754
|
+
};
|
|
755
|
+
try {
|
|
756
|
+
if (!isAssignmentShaped(text))
|
|
757
|
+
return silent;
|
|
758
|
+
const violations = [];
|
|
759
|
+
const writable = opts.writable !== false;
|
|
760
|
+
violations.push(...validateAssignmentFields(text, { writable }).violations);
|
|
761
|
+
const agent = (opts.agent ?? "").trim();
|
|
762
|
+
if (agent !== "") {
|
|
763
|
+
violations.push(...antiRecursionPrecheck(agent, parseAssignmentFields(text).executeAs ?? "").violations);
|
|
764
|
+
}
|
|
765
|
+
if (writable) {
|
|
766
|
+
const forms = parseAssignmentBranchForms(text);
|
|
767
|
+
const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
|
|
768
|
+
if (branch !== undefined && branch.trim() !== "") {
|
|
769
|
+
const directOnException = parseBranchPolicyDirectOnBranch(text) === branch.trim();
|
|
770
|
+
violations.push(...assertDefaultBranchProtected(branch.trim(), { directOnException }).violations);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const enforcement = parseEnforcementFlag(assignmentHeaderRegion(text));
|
|
774
|
+
const gate = { ok: violations.length === 0, violations };
|
|
775
|
+
return { ...applyEnforcement(gate, { hard: enforcement.hard }), shaped: true, enforcement };
|
|
776
|
+
} catch {
|
|
777
|
+
return silent;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
743
780
|
function antiRecursionPrecheck(subagentType, executeAs) {
|
|
744
781
|
const binding = subagentType.trim().toLowerCase();
|
|
745
782
|
const role = executeAs.trim().toLowerCase();
|
|
@@ -1819,6 +1856,68 @@ function assertIndexRowObligations(iterationsDir) {
|
|
|
1819
1856
|
}
|
|
1820
1857
|
return { ok: violations.length === 0, violations };
|
|
1821
1858
|
}
|
|
1859
|
+
function parseCompassFrontmatter(filePath) {
|
|
1860
|
+
const content = readFileSync5(filePath, "utf8");
|
|
1861
|
+
const lines = content.split(/\r?\n/);
|
|
1862
|
+
if (lines[0]?.trim() !== "---") {
|
|
1863
|
+
throw new Error(`no YAML frontmatter fence in ${filePath} (expected first line "---")`);
|
|
1864
|
+
}
|
|
1865
|
+
const end = lines.indexOf("---", 1);
|
|
1866
|
+
if (end === -1) {
|
|
1867
|
+
throw new Error(`unterminated YAML frontmatter in ${filePath} (no closing "---")`);
|
|
1868
|
+
}
|
|
1869
|
+
const doc = {};
|
|
1870
|
+
let listKey = null;
|
|
1871
|
+
for (let i = 1;i < end; i += 1) {
|
|
1872
|
+
const line = lines[i] ?? "";
|
|
1873
|
+
if (!line.trim() || line.trim().startsWith("#"))
|
|
1874
|
+
continue;
|
|
1875
|
+
if (listKey !== null && /^\s*-\s+/.test(line)) {
|
|
1876
|
+
const item = line.replace(/^\s*-\s+/, "").trim().replace(/^["']|["']$/g, "");
|
|
1877
|
+
if (!Array.isArray(doc[listKey]))
|
|
1878
|
+
doc[listKey] = [];
|
|
1879
|
+
doc[listKey].push(item);
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
listKey = null;
|
|
1883
|
+
const kv = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
1884
|
+
if (!kv) {
|
|
1885
|
+
throw new Error(`unsupported frontmatter line in ${filePath}: ${JSON.stringify(line)}`);
|
|
1886
|
+
}
|
|
1887
|
+
const value = kv[2].trim();
|
|
1888
|
+
doc[kv[1]] = value === "" ? null : /^\[.*\]$/.test(value) ? parseFlowArray(value, filePath) : value.replace(/^["']|["']$/g, "");
|
|
1889
|
+
listKey = value === "" ? kv[1] : null;
|
|
1890
|
+
}
|
|
1891
|
+
return doc;
|
|
1892
|
+
}
|
|
1893
|
+
function parseFlowArray(raw, filePath) {
|
|
1894
|
+
const inner = raw.slice(1, -1);
|
|
1895
|
+
if (/[[\]]/.test(inner)) {
|
|
1896
|
+
throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)} — only flat scalar items are supported (e.g. [a, b])`);
|
|
1897
|
+
}
|
|
1898
|
+
let quote = null;
|
|
1899
|
+
for (const ch of inner) {
|
|
1900
|
+
if (ch === '"' || ch === "'") {
|
|
1901
|
+
if (quote === null)
|
|
1902
|
+
quote = ch;
|
|
1903
|
+
else if (quote === ch)
|
|
1904
|
+
quote = null;
|
|
1905
|
+
} else if (ch === "," && quote !== null) {
|
|
1906
|
+
throw new Error(`ambiguous flow-style array in ${filePath}: ${JSON.stringify(raw)} — quoted item containing comma cannot be split unambiguously (flat scalar items only)`);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (quote !== null) {
|
|
1910
|
+
throw new Error(`unterminated ${quote} quote in flow-style array in ${filePath}: ${JSON.stringify(raw)}`);
|
|
1911
|
+
}
|
|
1912
|
+
const items = [];
|
|
1913
|
+
for (const part of inner.split(",")) {
|
|
1914
|
+
const item = part.trim().replace(/^["']|["']$/g, "");
|
|
1915
|
+
if (item === "")
|
|
1916
|
+
continue;
|
|
1917
|
+
items.push(item);
|
|
1918
|
+
}
|
|
1919
|
+
return items;
|
|
1920
|
+
}
|
|
1822
1921
|
// src/design-md.ts
|
|
1823
1922
|
function violation6(severity, code, message, fix) {
|
|
1824
1923
|
return { ok: false, severity, code, message, fix };
|
|
@@ -3379,6 +3478,7 @@ export {
|
|
|
3379
3478
|
planExecutionLeaseLocations,
|
|
3380
3479
|
parseEnforcementFlag,
|
|
3381
3480
|
parseDesignFrontmatter,
|
|
3481
|
+
parseCompassFrontmatter,
|
|
3382
3482
|
parseBranchPolicyDirectOnBranch,
|
|
3383
3483
|
parseAssignmentFields,
|
|
3384
3484
|
parseAssignmentBranchForms,
|
|
@@ -3400,6 +3500,7 @@ export {
|
|
|
3400
3500
|
emitGitignoreSnippet,
|
|
3401
3501
|
detectHost,
|
|
3402
3502
|
compoundRefreshScope,
|
|
3503
|
+
composeDispatchGate,
|
|
3403
3504
|
completenessLevel,
|
|
3404
3505
|
claimLease,
|
|
3405
3506
|
canSteal,
|
package/dist/index.d.ts
CHANGED
|
@@ -28,14 +28,14 @@ export type { ArchiveResult, FindingsCleanupMode, PlanRow, ResidualEntry, Status
|
|
|
28
28
|
export { archiveResiduals, findingsCleanupGate, normalizeSeverity, resolveCompassEnforcement, techDebtRollup, validatePlanRow, validateResidual, validateStatus, } from "./status.js";
|
|
29
29
|
export type { ClaimLeaseFields, ExecutionLease, ExecutionLeaseLocations, IntegrationMergeLease, LeaseTransition, LeaseVerifyResult, } from "./lease.js";
|
|
30
30
|
export { canSteal, claimLease, planExecutionLeaseLocations, releaseLease, sameHolderResume, validateExecutionLease, validateIntegrationMergeLease, verifyPlanExecutionLease, withStatusWriteLock, } from "./lease.js";
|
|
31
|
-
export type { AssignmentBranchForms, AssignmentFields, DefaultBranchOptions, EnforcementFlag, EnforcementSource, ExecutionModeToNOptions, ExecutionModeToNResult, ValidateAssignmentFieldsOptions, } from "./dispatch.js";
|
|
32
|
-
export { antiRecursionPrecheck, assertDefaultBranchProtected, assertTriIdentity, assignmentHeaderRegion, executionModeToN, isReadOnlyAssignmentRole, parseAssignmentBranchForms, parseAssignmentFields, parseBranchPolicyDirectOnBranch, parseEnforcementFlag, validateAssignmentFields, } from "./dispatch.js";
|
|
31
|
+
export type { AssignmentBranchForms, AssignmentFields, ComposeDispatchGateOptions, ComposeDispatchGateResult, DefaultBranchOptions, EnforcementFlag, EnforcementSource, ExecutionModeToNOptions, ExecutionModeToNResult, ValidateAssignmentFieldsOptions, } from "./dispatch.js";
|
|
32
|
+
export { antiRecursionPrecheck, assertDefaultBranchProtected, assertTriIdentity, assignmentHeaderRegion, composeDispatchGate, executionModeToN, isReadOnlyAssignmentRole, parseAssignmentBranchForms, parseAssignmentFields, parseBranchPolicyDirectOnBranch, parseEnforcementFlag, validateAssignmentFields, } from "./dispatch.js";
|
|
33
33
|
export type { BranchProbeOptions, L1PreDispatchInput, L2PreDispatchInput, QcAlignmentAssignment, QcSnapshotAssignment, WorktreeTrack, } from "./worktree.js";
|
|
34
34
|
export { assertBranchAlignment, assertControlVsFeaturePath, assertQcAlignment, l1PreDispatchCheck, l2PreDispatchCheck, singleReviewSnapshot, } from "./worktree.js";
|
|
35
35
|
export type { ImplementerSessionLedger, ReviewPackageOptions, SddWorkspaceOptions, StickyRulesInput, StickyRulesResult, TaskBriefOptions, } from "./sdd.js";
|
|
36
36
|
export { SddScriptError, assertBaseSha, implementerSessionStickyRules, readProgressLedger, reviewPackage, sddWorkspace, taskBrief, taskReportExists, } from "./sdd.js";
|
|
37
37
|
export type { CompassDoc, PhaseGateOptions, PhaseGateResult, PhaseTransition, } from "./iteration.js";
|
|
38
|
-
export { assertIndexRowObligations, evaluatePhaseGate, pushCadenceProbe, validateCompassFrontmatter, } from "./iteration.js";
|
|
38
|
+
export { assertIndexRowObligations, evaluatePhaseGate, parseCompassFrontmatter, pushCadenceProbe, validateCompassFrontmatter, } from "./iteration.js";
|
|
39
39
|
export type { CompletenessItem, CompletenessLevel, CompletenessPlaceholder, CompletenessResult, DesignFrontmatter, } from "./design-md.js";
|
|
40
40
|
export { assertLightDarkParity, completenessLevel, parseDesignFrontmatter, validateDesignTokenFrontmatter, } from "./design-md.js";
|
|
41
41
|
export type { AuditCategory, AuditEffort, AuditFinding, AuditPriority, AuditRisk, RedactResult, ScaffoldAuditPlanOptions, ScaffoldAuditPlanResult, SecretFinding, } from "./audit.js";
|
package/dist/iteration.d.ts
CHANGED
|
@@ -86,3 +86,17 @@ export declare function pushCadenceProbe(ciRunning: boolean, reviewWaveActive: b
|
|
|
86
86
|
* a missing README, a missing header, and missing per-iteration rows.
|
|
87
87
|
*/
|
|
88
88
|
export declare function assertIndexRowObligations(iterationsDir: string): GateResult;
|
|
89
|
+
/**
|
|
90
|
+
* Parse the YAML frontmatter of a delivery-compass.md into a flat doc.
|
|
91
|
+
*
|
|
92
|
+
* The compass frontmatter is a flat YAML subset (scalar keys plus one
|
|
93
|
+
* `plans:` list-of-scalars — see `skills/mstar-iteration/references/
|
|
94
|
+
* iteration-compass-template.md` Fields guide); `validateCompassFrontmatter`
|
|
95
|
+
* validates the parsed doc. The engine deliberately has no YAML dependency,
|
|
96
|
+
* so this hand-rolled flat-subset parser lives here — the single shared
|
|
97
|
+
* parser used by the CLI and the omp `mstar_iteration_gate` tool (no fork).
|
|
98
|
+
*
|
|
99
|
+
* Throws with the file path on structural errors (no fence / unterminated
|
|
100
|
+
* fence / unsupported line) so callers can fail with a precise message.
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseCompassFrontmatter(filePath: string): Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mstar-harness/engine",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Morning Star Harness Workflow Engine — deterministic workflow enforcement library (path, status, lease, dispatch, sdd, iteration, lint gates).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|