@mstar-harness/opencode 2.0.2 → 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/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The monorepo root [CHANGELOG.md](../../CHANGELOG.md) summarizes cross-surface re
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.0.3] - 2026-08-09
10
+
11
+ ### Bundled harness skills (`harness-skills/` at publish)
12
+
13
+ - Version alignment with harness **2.0.3** (no OpenCode package API change).
14
+
15
+ See root [CHANGELOG.md](../../CHANGELOG.md) **2.0.3**.
16
+
9
17
  ## [2.0.2] - 2026-08-08
10
18
 
11
19
  ### Fixed
package/dist/mstar.js CHANGED
@@ -223,6 +223,43 @@ function assertDefaultBranchProtected(branch, opts = {}) {
223
223
  }
224
224
  return { ok: violations.length === 0, violations };
225
225
  }
226
+ var ASSIGNMENT_HEADING_RE = /^#{1,6}\s+Assignment\s*$/m;
227
+ var ASSIGNMENT_FIELD_RE = /^[ \t]*(?:[-*][ \t]+)?\*{0,2}(Execute as|Delegation|Task category)\*{0,2}[ \t]*:[ \t]*(\S.*)$/gm;
228
+ function isAssignmentShaped(assignmentText) {
229
+ return ASSIGNMENT_HEADING_RE.test(assignmentText) || assignmentText.match(ASSIGNMENT_FIELD_RE) !== null;
230
+ }
231
+ function composeDispatchGate(text, opts = {}) {
232
+ const silent = {
233
+ ok: true,
234
+ violations: [],
235
+ shaped: false,
236
+ enforcement: { hard: false, source: "none" }
237
+ };
238
+ try {
239
+ if (!isAssignmentShaped(text))
240
+ return silent;
241
+ const violations = [];
242
+ const writable = opts.writable !== false;
243
+ violations.push(...validateAssignmentFields(text, { writable }).violations);
244
+ const agent = (opts.agent ?? "").trim();
245
+ if (agent !== "") {
246
+ violations.push(...antiRecursionPrecheck(agent, parseAssignmentFields(text).executeAs ?? "").violations);
247
+ }
248
+ if (writable) {
249
+ const forms = parseAssignmentBranchForms(text);
250
+ const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
251
+ if (branch !== undefined && branch.trim() !== "") {
252
+ const directOnException = parseBranchPolicyDirectOnBranch(text) === branch.trim();
253
+ violations.push(...assertDefaultBranchProtected(branch.trim(), { directOnException }).violations);
254
+ }
255
+ }
256
+ const enforcement = parseEnforcementFlag(assignmentHeaderRegion(text));
257
+ const gate = { ok: violations.length === 0, violations };
258
+ return { ...applyEnforcement(gate, { hard: enforcement.hard }), shaped: true, enforcement };
259
+ } catch {
260
+ return silent;
261
+ }
262
+ }
226
263
  function antiRecursionPrecheck(subagentType, executeAs) {
227
264
  const binding = subagentType.trim().toLowerCase();
228
265
  const role = executeAs.trim().toLowerCase();
@@ -633,48 +670,25 @@ function validateStatusWrite(targetPath, opts = {}) {
633
670
  return null;
634
671
  }
635
672
  }
636
- var ASSIGNMENT_HEADING_RE = /^#{1,6}\s+Assignment\s*$/m;
637
- var ASSIGNMENT_FIELD_RE = /^[ \t]*(?:[-*][ \t]+)?\*{0,2}(Execute as|Delegation|Task category)\*{0,2}[ \t]*:[ \t]*(\S.*)$/gm;
638
- function isAssignmentShaped(assignmentText) {
639
- if (typeof assignmentText !== "string")
640
- return false;
641
- return ASSIGNMENT_HEADING_RE.test(assignmentText) || assignmentText.match(ASSIGNMENT_FIELD_RE) !== null;
642
- }
643
673
  function validateDispatchAssignment(assignmentText, opts = {}) {
644
674
  const log = opts.log ?? defaultStatusLogger;
645
675
  try {
646
- if (typeof assignmentText !== "string" || !isAssignmentShaped(assignmentText)) {
676
+ if (typeof assignmentText !== "string") {
647
677
  return { ok: true, violations: [] };
648
678
  }
649
- const violations = [];
650
- const fields = parseAssignmentFields(assignmentText);
651
- const writable = isReadOnlyAssignmentRole(fields.executeAs ?? "") ? false : undefined;
652
- violations.push(...validateAssignmentFields(assignmentText, { writable }).violations);
653
- const binding = opts.subagentType ?? "";
654
- if (binding.trim() !== "") {
655
- violations.push(...antiRecursionPrecheck(binding, fields.executeAs ?? "").violations);
656
- }
657
- if (writable !== false) {
658
- const forms = parseAssignmentBranchForms(assignmentText);
659
- const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
660
- if (branch !== undefined && branch.trim() !== "") {
661
- const directOnException = parseBranchPolicyDirectOnBranch(assignmentText) === branch.trim();
662
- violations.push(...assertDefaultBranchProtected(branch.trim(), { directOnException }).violations);
663
- }
664
- }
665
- const result = { ok: violations.length === 0, violations };
666
- const enforcement = parseEnforcementFlag(assignmentHeaderRegion(assignmentText));
667
- if (!result.ok) {
668
- for (const violation of result.violations) {
679
+ const writable = isReadOnlyAssignmentRole(parseAssignmentFields(assignmentText).executeAs ?? "") ? false : undefined;
680
+ const composed = composeDispatchGate(assignmentText, { agent: opts.subagentType ?? "", writable });
681
+ if (!composed.ok) {
682
+ for (const violation of composed.violations) {
669
683
  const fix = violation.fix ? ` (fix: ${violation.fix})` : "";
670
- if (enforcement.hard) {
684
+ if (composed.enforcement.hard) {
671
685
  log("error", `assignment validation (hard gate): [${violation.severity}] ${violation.code}: ${violation.message}${fix} — hardBlocked per Enforcement: hard; refusal requires a host refusal channel (skill: mstar-dispatch-gates)`);
672
686
  } else {
673
687
  log("warn", `assignment validation: [${violation.severity}] ${violation.code}: ${violation.message}${fix}`);
674
688
  }
675
689
  }
676
690
  }
677
- return applyEnforcement(result, { hard: enforcement.hard });
691
+ return composed;
678
692
  } catch (error) {
679
693
  log("error", `assignment validation aborted: ${error.message}`);
680
694
  return null;
@@ -208,6 +208,17 @@ Cannot emit required **N** → **`Blocked`**.
208
208
  | Plugin commands | `/iteration-start`, `/iteration-drive`, `/iteration-loop` (filename-based) |
209
209
  | Session entry | `/skill:pm` → `mstar-harness-core` via pm **Read next** |
210
210
 
211
+ ## In-process engine binding (omp ≥ 17.2.11)
212
+
213
+ - **Surfaces** (repo root = plugin root): `hooks/pre/mstar-gates.ts` — one `tool_call` pre-hook that returns `{ block: true, reason }` (structured refusal the model sees as the tool error) or `undefined` (pass); `tools/mstar_{status_validate,dispatch_validate,lease_verify,path_resolve,iteration_gate,worktree_check}/index.ts` — six model-callable validator tools (engine validators only, Zod params via `pi.zod`).
214
+ - **Enforcement semantics**: block ONLY under `Enforcement: hard`. The status gate reads the harness compass frontmatter (`enforcement: hard`, active/locked iterations only); the dispatch gate reads each Assignment's own header flag (`assignmentHeaderRegion` — a body example never hardens). Soft / no flag → silent pass. Rollback = unset the flag. Never global.
215
+ - **Engine dependency**: the adapters import the published engine package (root `package.json` `dependencies` entry). omp git/npm plugin installs run `bun install <spec>` in the plugins tree → declared deps installed; a bare `-l` / `omp plugin link` symlink install without `node_modules` cannot resolve the modules.
216
+ - **Graceful degradation (explicit)**: module load failure → `mstar_*` tools skipped, hook absent (no blocking), `commands/*.md` shell-out fallback intact. Caveat: a partial failure is SILENT — no in-band signal that gates are off; verify with `omp -p '/extensions'`.
217
+ - **`MSTAR_HARNESS_DIR` override**: the hook and tools discover `{HARNESS_DIR}` via `resolveHarnessDir`, which probes only `.mstar/` → `.agents/` → `.plans/`/`plans/` roots. Repos using a non-standard harness root (e.g. this plugin repo's own `.harness/`) MUST export `MSTAR_HARNESS_DIR` (absolute path) in the omp session env — without it the status gate does not cover those roots and tools like `mstar_path_resolve` / `mstar_lease_verify` error out (parity with the opencode binding).
218
+ - **Edit-path limitation**: the status gate validates the on-disk file for `edit` events (pre-edit state) — a corrupting edit is caught by the next write or `mstar_status_validate` (known v1 limitation, parity with opencode).
219
+ - **`mstar_iteration_gate` engine requirement**: the tool requires an engine build exporting `parseCompassFrontmatter` (next published release). On older engines the tool reports an explicit upgrade error instead of loading — no silent absence; CLI fallback: `mstar iteration gate`.
220
+ - **Reload**: edits are picked up by a new session (`?mtime` cache-buster); in-session `/reload-plugins` (omp ≥ 17.2.11) applies them without a new session.
221
+
211
222
  ## Files, shell, and approvals
212
223
 
213
224
  - Prefer host search/edit tools over shell find/sed when available.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/opencode",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Morning Star harness OpenCode plugin — skills bootstrap + engine-backed runtime hooks (status lint, dispatch validation, Enforcement: hard gates).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,6 +36,6 @@
36
36
  "access": "public"
37
37
  },
38
38
  "devDependencies": {
39
- "@mstar-harness/engine": "2.0.2"
39
+ "@mstar-harness/engine": "2.0.3"
40
40
  }
41
41
  }