@mstar-harness/opencode 3.4.0 → 3.4.1

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,20 @@ The monorepo root [CHANGELOG.md](../../CHANGELOG.md) summarizes cross-surface re
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.4.1] - 2026-08-27
10
+
11
+ ### Bundled harness skills (`harness-skills/` at publish)
12
+
13
+ - **Anti-recursion precheck re-scoped to CALLER semantics (fixes #156)**: `composeDispatchGate` no longer feeds the host role-binding field into `antiRecursionPrecheck` as if it were the dispatching agent — on omp/OpenCode/Cursor that field (`agent` / `subagent` / `subagent_type`) carries the spawn TARGET, and target == `Execute as` is the documented compliant C5 dispatch pattern, so under `Enforcement: hard` every correct dispatch hard-blocked on `dispatch.anti-recursion.self-type`, while omitting the field hard-blocked on `dispatch.anti-recursion.empty-binding` (no spec-compliant binding existed). The composition now takes `caller` (the dispatching agent's OWN role) + `callerRequired`; the precheck runs only when a caller binding exists, and fails closed on an empty one only where the host contract mandates the binding (dsh). The `agent` option is removed — migrate callers to `caller`.
14
+ - **omp plugin**: Gate 2 (task dispatch) no longer runs the anti-recursion leg (omp's `tool_call` event carries no caller identity; the NEVER red line stays prompt-level via `mstar-dispatch-gates`), so the documented `agent: "<Execute as role-id>"` pattern passes under hard. The dispatch gate now ALSO honors the repo-level `.mstarc` / compass `enforcement: hard` (previously header-flag-only — a hard compass left Gate 2 unhardened, diverging from Gate 1 and dsh `resolveDispatchHard`), and soft-mode dispatch violations are warn-logged through the extension logger instead of silently dropped (opencode parity; the silent drop is why the #156 pincer stayed latent for five iterations).
15
+ - **OpenCode surface**: `validateDispatchAssignment` drops the `subagentType` plumbing (the spawn target is not an anti-recursion signal) and composes the repo-level `.mstarc`/compass hard setting below the Assignment header flag, matching the status-write gate and dsh. Compliant dispatches no longer warn `self-type` / `empty-binding`; a hard repo now escalates flag-less dispatch violations to error-level + `hardBlocked`.
16
+ - **dsh surface**: `dispatchGateCore` passes `caller: config.dispatchBinding ?? ''` with `callerRequired: true` — behavior unchanged (self-recursion critical; unset binding fails closed under hard). `mstar_dispatch_validate`'s `agent` param is now `caller` (the DISPATCHING agent's own role; omit when unknown).
17
+ - **Docs**: `mstar-dispatch-gates` records the caller-vs-target engine scope; `mstar-host/references/omp.md` gains the Gate 2 anti-recursion scope note; stale dsh `dispatchBinding` "precheck skipped" rows corrected to the fail-closed contract.
18
+
19
+ - Version alignment with harness **3.4.1** (no OpenCode package API change).
20
+
21
+ See root [CHANGELOG.md](../../CHANGELOG.md) **3.4.1**.
22
+
9
23
  ## [3.4.0] - 2026-08-27
10
24
 
11
25
  ### Harness
package/dist/mstar.js CHANGED
@@ -1102,8 +1102,10 @@ function composeDispatchGate(text, opts = {}) {
1102
1102
  const violations = [];
1103
1103
  const writable = opts.writable !== false;
1104
1104
  violations.push(...validateAssignmentFields(text, { writable }).violations);
1105
- const agent = opts.agent ?? "";
1106
- violations.push(...antiRecursionPrecheck(agent, parseAssignmentFields(text).executeAs ?? "").violations);
1105
+ const caller = opts.caller ?? "";
1106
+ if (caller.trim() !== "" || opts.callerRequired === true) {
1107
+ violations.push(...antiRecursionPrecheck(caller, parseAssignmentFields(text).executeAs ?? "").violations);
1108
+ }
1107
1109
  if (writable) {
1108
1110
  const forms = parseAssignmentBranchForms(text);
1109
1111
  const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
@@ -1126,7 +1128,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
1126
1128
  return {
1127
1129
  ok: false,
1128
1130
  violations: [
1129
- violation3("critical", "dispatch.anti-recursion.empty-binding", `empty host role binding — the host cannot report which agent is calling, so anti-recursion cannot be proven (a dispatch could silently recurse)`, "set the host role-binding field (omp task entry `agent` / opencode `subagent` / cursor `subagent_type` / dsh `dispatchBinding`) before dispatching")
1131
+ violation3("critical", "dispatch.anti-recursion.empty-binding", `empty caller role binding — the host cannot report which agent is calling, so anti-recursion cannot be proven (a dispatch could silently recurse)`, "declare the dispatching agent's own role binding (dsh Config `dispatchBinding`) before dispatching")
1130
1132
  ]
1131
1133
  };
1132
1134
  }
@@ -6236,18 +6238,21 @@ function validateDispatchAssignment(assignmentText, opts = {}) {
6236
6238
  return { ok: true, violations: [] };
6237
6239
  }
6238
6240
  const writable = isReadOnlyAssignmentRole(parseAssignmentFields(assignmentText).executeAs ?? "") ? false : undefined;
6239
- const composed = composeDispatchGate(assignmentText, { agent: opts.subagentType ?? "", writable });
6240
- if (!composed.ok) {
6241
- for (const violation15 of composed.violations) {
6241
+ const composed = composeDispatchGate(assignmentText, { writable });
6242
+ const harnessDir = resolveHarnessDir();
6243
+ const hard = composed.enforcement.hard || harnessDir !== null && resolveRepoEnforcement(harnessDir).hard;
6244
+ const gated = applyEnforcement(composed, { hard });
6245
+ if (!gated.ok) {
6246
+ for (const violation15 of gated.violations) {
6242
6247
  const fix = violation15.fix ? ` (fix: ${violation15.fix})` : "";
6243
- if (composed.enforcement.hard) {
6248
+ if (hard) {
6244
6249
  log("error", `assignment validation (hard gate): [${violation15.severity}] ${violation15.code}: ${violation15.message}${fix} — hardBlocked per Enforcement: hard; refusal requires a host refusal channel (skill: mstar-dispatch-gates)`);
6245
6250
  } else {
6246
6251
  log("warn", `assignment validation: [${violation15.severity}] ${violation15.code}: ${violation15.message}${fix}`);
6247
6252
  }
6248
6253
  }
6249
6254
  }
6250
- return composed;
6255
+ return gated;
6251
6256
  } catch (error) {
6252
6257
  log("error", `assignment validation aborted: ${error.message}`);
6253
6258
  return null;
@@ -6294,8 +6299,7 @@ var MorningStarHarnessPlugin = async () => {
6294
6299
  const rawPath = args.path;
6295
6300
  const filePath = typeof rawFilePath === "string" ? rawFilePath : typeof rawPath === "string" ? rawPath : undefined;
6296
6301
  if (input.tool === "task" && typeof prompt === "string") {
6297
- const subagentType = typeof args.subagent === "string" ? args.subagent : typeof args.subagent_type === "string" ? args.subagent_type : "";
6298
- const gate2 = validateDispatchAssignment(prompt, { subagentType });
6302
+ const gate2 = validateDispatchAssignment(prompt);
6299
6303
  if (gate2?.hardBlocked) {
6300
6304
  defaultStatusLogger("error", "hard-gate blocked (hardBlocked=true) — refusal requires a host refusal channel");
6301
6305
  }
@@ -40,6 +40,8 @@ description: Morning Star 派发与委派门禁 —— 仅 PM 可增派 subagent
40
40
 
41
41
  **Assignment 顶部反模式块**:每个 PM Assignment 开头均有 **`**You are a leaf executor. You MUST NOT:**`** 块(含 IDENTITY + CAPABILITY BOUNDARY + prohibitions),PM 按此 Assignment 的角色+上下文定制反模式清单。leaf executor 收到 Assignment 后须 **首先** 阅读该块;命中任一条 → **停止**(亲自完成或 `Blocked`)。详见 **`mstar-roles/references/project-manager/dispatch-and-assignment.md`**。
42
42
 
43
+ > **Engine 执行范围(caller-scoped,#156)**:engine `antiRecursionPrecheck` 比较的是**派发方自身角色**(caller)与新 Assignment 的 `Execute as`(target)。只有 **dsh**(Config `dispatchBinding`)能观察派发方身份并在 engine 层硬执行(含 `callerRequired` 空绑定 fail-closed);omp / OpenCode / Cursor 的角色绑定字段是**派发目标**——目标 == `Execute as` 正是 C5 合规派发模式——这些宿主上红线保持 prompt 级约束(本节),engine 不做判定。
44
+
43
45
  ## 调度防串扰(强制;leaf executor 已在上方读过反递归红线,此处为完整规则供 PM/对照用)
44
46
 
45
47
  - 只有 **`project-manager`** 可以决定增加/并行 subagent;承接方**默认不得二次分派**。
@@ -58,7 +60,7 @@ description: Morning Star 派发与委派门禁 —— 仅 PM 可增派 subagent
58
60
  - **QC 单席(例外)**:`Execution mode: inline`(hotfix 等),或 Assignment 显式 `QC mode: single` / `QC mode: single — override: <reason>` → `qc-specialist` ×1,`N=1`,写 `{SDD_DIR}/review/qc.md`。
59
61
  - **QC targeted re-review**:Assignment 含 **`QC re-review: targeted — reviewers: …`** 时,**N** = 所列席位数(1–3),同条消息发满 **N**。
60
62
  - **先自检再发送**:发送前核对「Assignment 条数 = 本条消息中的实际 **派发** 调用条数」。
61
- - **先自检字段再发送(与 count 同级门禁)**:核对**每条** invoke 都携带与 **`Execute as`** 匹配的角色绑定字段——omp **`agent`** / Cursor **`subagent_type`** / OpenCode **`subagent`** / Kimi·ZCode **`subagent_type`**。**漏写或取默认通用值**(omp 漏 `agent` ⇒ 自动回退 generic `task`,无报错)= **派发未完成**,与 paste-only(零 invoke)**同等级**:当场补齐重发,不得进入下一 gate。**N=1 顺序链(Review & Edit)不豁免**——count 门在 N=1 恒过,**字段门是唯一保护**。
63
+ - **先自检字段再发送(与 count 同级门禁)**:核对**每条** invoke 都携带与 **`Execute as`** 匹配的角色绑定字段——omp **`agent`** / Cursor **`subagent_type`** / OpenCode **`subagent`** / Kimi·ZCode **`subagent_type`**;宿主列以 **`mstar-host`** §Detect active host 的 tool-shape 检测为准(禁以 config 路径/仓库内容判定)。**漏写或取默认通用值**(omp 漏 `agent` ⇒ 自动回退 generic `task`,无报错)= **派发未完成**,与 paste-only(零 invoke)**同等级**:当场补齐重发,不得进入下一 gate。**N=1 顺序链(Review & Edit)不豁免**——count 门在 N=1 恒过,**字段门是唯一保护**。
62
64
  - **前置步骤与派发回合分离(防串行 rollout)**:为派发准备的 **`bash` / `read` / `glob` / `grep`**(如 `merge-base`、`Review range`、`git rev-parse`)**不计入** `N` 次派发;可在上一条仅含准备的消息完成。准备完成后,**下一条派发消息**须**一次性**含 **`N` 次** Task / subagent invoke。**禁止**先发 `1` 次、等返回再补发其余 `N-1` 次。
63
65
  - **未齐不发(emit zero until batch-ready)**:需并发 `N≥2` 而当前只能发 `1` 条时,本条应发 **`0` 条派发 invoke`**(可继续 read/bash 补齐),**禁止**「先发一个顶一下」;`N` 份 payload 就绪后**单次消息发满 `N`**。见 **`mstar-host`** → `references/parallel-dispatch.md`(具备 invoke / Task / subagent 工具的宿主共用)。
64
66
 
@@ -472,7 +472,9 @@ The dsh web client resolves slash commands against a client-side lexicon driven
472
472
  - The dispatch gate needs the dispatching agent's own role for the
473
473
  anti-recursion precheck: declare it via Config **`dispatchBinding`** (dsh
474
474
  exposes no per-agent role on the tool-execution context). Under hard
475
- enforcement with no binding, the plugin logs the absence.
475
+ enforcement with no binding, the plugin logs the absence AND every
476
+ Assignment-shaped dispatch fails closed (`dispatch.anti-recursion.
477
+ empty-binding` → deny) until the binding is set.
476
478
 
477
479
  ## Files, shell, and approvals
478
480
 
@@ -211,7 +211,8 @@ Cannot emit required **N** → **`Blocked`**.
211
211
  ## In-process engine binding (omp ≥ 17.2.11)
212
212
 
213
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 repo `.mstarc` `[config] enforcement`, else 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 (or `.mstarc` `soft`). Never global.
214
+ |- **Enforcement semantics**: block ONLY under `Enforcement: hard`. Both gates read the repo `.mstarc` `[config] enforcement`, else the harness compass frontmatter (`enforcement: hard`, active/locked iterations only); the dispatch gate ALSO honors each Assignment's own header flag (`assignmentHeaderRegion` — a body example never hardens). A hard repo setting therefore hardens flag-less dispatches (Gate 1 / dsh `resolveDispatchHard` parity). Soft-mode dispatch violations are warn-logged through the extension logger (never blocked); soft status-write violations stay a silent pass. Rollback = unset the flag (or `.mstarc` `soft`). Never global.
215
+ - **Anti-recursion scope (issue #156)**: the engine's `antiRecursionPrecheck` is **caller-scoped** — it compares the DISPATCHING agent's own role against the new Assignment's `Execute as`. omp's `tool_call` event carries no caller identity and the task entry `agent` is the spawn TARGET, which equals `Execute as` on every compliant dispatch (C5 above) — so Gate 2 does NOT run the precheck on omp (the pre-#156 wiring hard-blocked every compliant hard-mode dispatch on `self-type`, or on `empty-binding` when `agent` was omitted). The NEVER red line stays prompt-level on this host (`mstar-dispatch-gates`); dsh enforces it in-engine via Config `dispatchBinding`.
215
216
  - **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
217
  - **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
218
  |- **`MSTAR_HARNESS_DIR` override / `.mstarc`**: the hook and tools discover `{HARNESS_DIR}` via `resolveHarnessDir` — a repo `.mstarc` `[config] harness_dir` (gitignored local config) first, then the probe `.mstar/` → `.agents/` → `.plans/`/`plans/`. Repos using a non-standard harness root can declare it in `.mstarc` or MUST export `MSTAR_HARNESS_DIR` (absolute path) in the omp session env — without either the status gate does not cover those roots and tools like `mstar_path_resolve` / `mstar_lease_verify` error out (parity with the opencode binding).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/opencode",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
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": {