@mstar-harness/engine 2.0.2 → 2.0.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.
@@ -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";
@@ -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.2",
3
+ "version": "2.0.4",
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": {