@mstar-harness/dsh 3.3.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/README.i18n.yaml +2 -2
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/bundle/README.md +2 -2
- package/dist/gates/_shared.d.ts +3 -3
- package/dist/index.js +126 -49
- package/harness-commands/amazing-pr-review.md +54 -0
- package/harness-skills/mstar-audit/SKILL.md +4 -4
- package/harness-skills/mstar-audit/references/codebase-audit.md +2 -0
- package/harness-skills/mstar-audit/references/pr-review-seat-evidence.md +28 -0
- package/harness-skills/mstar-audit/references/pr-review.md +118 -71
- package/harness-skills/mstar-audit/references/security-review.md +3 -5
- package/harness-skills/mstar-dispatch-gates/SKILL.md +3 -1
- package/harness-skills/mstar-host/references/dsh.md +3 -1
- package/harness-skills/mstar-host/references/omp.md +2 -1
- package/harness-skills/mstar-roles/references/_shared/leaf-executor-core.md +5 -7
- package/harness-skills/mstar-roles/references/code-reviewer.md +7 -7
- package/harness-skills/mstar-roles/references/frontend-dev.md +1 -0
- package/harness-skills/mstar-roles/references/fullstack-dev-shared.md +3 -2
- package/harness-skills/mstar-roles/references/ops-engineer.md +1 -1
- package/harness-skills/mstar-roles/references/prompt-engineer.md +1 -1
- package/harness-skills/mstar-roles/references/qa-engineer.md +1 -1
- package/package.json +1 -1
- package/harness-commands/pr-deep-review.md +0 -39
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# Blob hashes (git hash-object) of each side as of the last confirmation that
|
|
3
3
|
# both languages say the same thing (dsh i18n contract: a pair is three
|
|
4
4
|
# sibling files; editing either side obligates re-confirming and re-recording).
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: eff5613d26d4ca7cae84f6970cae737603ccf73e
|
|
6
|
+
README.zh.md: 908af5a5e893f39b8479928adeca722b07d49f0e
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ The **two-command install is the contract** — folding a `dsh-llm-fallbacks` ro
|
|
|
50
50
|
| `harnessDir` | `string` | repo `.mstarc` `[config] harness_dir`, else per-session workspace probe (`.mstar/` → `.agents/` → `.plans/` → `plans/`, from the session workspace root — **never the launch cwd**) | Explicit harness root; wins over engine probing. **Required for repos whose harness root is not a probed name**; the probe starts from the session workspace root (never the launch cwd) and STOPS there — it never walks above the session workspace, so a harness dir above it (e.g. a global `~/.mstar`) is never adopted. |
|
|
51
51
|
| `enforcement` | `'hard' \| 'soft'` | compass, else warn-only | Per-deployment override. Precedence: Config wins; else the Assignment's own `**Enforcement**: hard` header flag (dispatch gate only); else the repo `.mstarc` `[config] enforcement`; else the iteration compass frontmatter; else warn-only. Config / `.mstarc` `soft` are the ONLY local rollbacks — an Assignment-level `soft` does NOT override a hard compass. |
|
|
52
52
|
| `dispatchTools` | `string[]` | `['subagent', 'subagent_fork']` | Delegation tool names the dispatch gate matches — the dsh preset's TWO delegation tools, `subagent` and its fork sibling `subagent_fork` (both carry Assignment-shaped `{ description, prompt }` args; a `toolName` config may rename instances). |
|
|
53
|
-
| `dispatchBinding` | `string` | unset
|
|
53
|
+
| `dispatchBinding` | `string` | unset → fail-closed `empty-binding` under hard | The dispatching agent's own harness role (the anti-recursion CALLER); an Assignment whose `Execute as` equals it is self-recursion. |
|
|
54
54
|
| `roleMap` | `Record<string, string>` | unset | mstar role id (`Execute as`) → dsh-llm-fallbacks role id. A taxonomy bridge for logging + future rule-driven interop ONLY — never consulted by the decoration (see LLM fallbacks integration). |
|
|
55
55
|
| `rolePersonas` | `Record<string, string>` | unset (bundled mirror default) | mstar role id (`Execute as`) → persona text; the role-based subagent decoration's **override** source — a role-matched `subagent/start` registers the persona as the child's `mstar:role-persona` system-prompt section; when unset for a role, the bundled `harness-agents/` mirror default is used (see LLM fallbacks integration). |
|
|
56
56
|
| `skillRoots` | `string[]` | unset (no custom-root registration) | Additional skill roots registered with the dsh skill-filesystem provider (`customSkillDirs` semantics — scanned before user roots). Dev-time: the mirror `<repo-root>/skills` absolute path. |
|
|
@@ -191,7 +191,7 @@ The mstar skills mount through the dsh skill-filesystem provider as a **single c
|
|
|
191
191
|
|
|
192
192
|
| Path | Mechanism | When |
|
|
193
193
|
| --- | --- | --- |
|
|
194
|
-
| Bundled default | `bundledSkillDir` defaults to the package's OWN `harness-skills/` mirror — the repo-root `skills/` (19 `mstar-*` + `pm`) synced by `bundle-assets` at build
|
|
194
|
+
| Bundled default | `bundledSkillDir` defaults to the package's OWN `harness-skills/` mirror — the repo-root `skills/` (19 `mstar-*` + `pm`) synced by `bundle-assets` at build time (gitignored), resolved **package-relative** (not cwd-anchored — works from any launch cwd) | Published package / any deployment without an override |
|
|
195
195
|
| Custom roots | `skillRoots` / explicit `bundledSkillDir` → skill-filesystem `customSkillDirs` / `bundledSkillDir` entries (explicit values win) | Local development / tests / deployments with a different mirror |
|
|
196
196
|
|
|
197
197
|
The packaged mirror is a **single canonical mount**: skill content lives once in the repo-root `skills/` mirror and is synced into the package (like opencode's `harness-skills/`), so mstar skills stay standalone-usable everywhere. No double-loading: the opencode plugin ships the same skills in its own package, so dsh must mount them ONLY through this single skill-filesystem path.
|
|
@@ -200,7 +200,7 @@ Dev-time reality: the `@deepseek-ai/dsh-skill-filesystem` runtime is a peer-stub
|
|
|
200
200
|
|
|
201
201
|
## Commands
|
|
202
202
|
|
|
203
|
-
The plugin registers the bundled mstar commands (omp/opencode parity surface) on `ctx.commands`: `harness-commands/*.md` — the repo-root `commands/` mirror (`iteration-start`, `iteration-drive`, `iteration-loop`, `codebase-audit`) synced by `bundle-assets` at build
|
|
203
|
+
The plugin registers the bundled mstar commands (omp/opencode parity surface) on `ctx.commands`: `harness-commands/*.md` — the repo-root `commands/` mirror (`iteration-start`, `iteration-drive`, `iteration-loop`, `codebase-audit`) synced by `bundle-assets` at build time (gitignored). Each registration reads the command's `name`/`description`/`input` frontmatter; a declared `input` hint is advertised as `input.hint`, which flips the dsh web client's decision table from detached bare execution to a leadingInput **claim** — the menu pick inserts `/name ` into the composer (command-colored token, the hint as ghost text) and the line submits only on Enter, so the user can type follow-up args (the `/plan` / `/goal` / `/advisor` interaction). The handler **steers the command body into the receiving agent as a USER-source message** (the dsh-plan-mode command precedent — `source: { kind: 'user' }`, so the model treats the body as a task to execute, not injected context; the dsh-commands "explicitly schedule model-visible work through the receiving Agent" path), appending the user's typed args as a `## User input` section when present, and returns a success result. Registration is deferred with `ctx.inject(['commands'], …)` — the same optional-unit pattern as the tools — so the plugin boots without the commands service; an absent mirror (no `bundle-assets` run) registers nothing.
|
|
204
204
|
|
|
205
205
|
## Engine seam mapping
|
|
206
206
|
|
|
@@ -554,7 +554,7 @@ The catalog row is appended at the END of the composed step messages, after dele
|
|
|
554
554
|
- **skill-lint × roles-seam double-fire on `<root>/mstar-roles/SKILL.md`** — when a configured skill root contains the `mstar-roles` dir (the repo-root mirror case in dev, and the bundled mirror in the published form), one write to `mstar-roles/SKILL.md` fires BOTH the skill-authoring lint gate and the roles seam gate (two advisories / two repair-escape logs in hard). Both validators legitimately apply — the double-lint is advisory-only, not a correctness break; the "scopes are disjoint" property holds among the four seams only, not across the skill gate.
|
|
555
555
|
- **Content-blind skill-lint blind spots** — the `fs/write-intent` slot carries only `(target, actor)`: first-create incoming content is not linted, and valid→invalid overwrites are not detected on the listener path (it lints the pre-write on-disk document only). Warn/hard advisories surface pre-existing on-disk violations only — the same class of limitation as the status gate.
|
|
556
556
|
- **Explicit relative `bundledSkillDir` overrides are cwd-anchored** — skill-filesystem resolves a relative bundled root with plain `join()` semantics against the dsh **process cwd** at boot. The plugin's DEFAULT bundled root is the package's OWN `harness-skills/` mirror resolved package-relative (NOT cwd-anchored — works from any launch cwd); only an explicit RELATIVE override inherits the cwd anchoring, so deployments overriding the default should pass an **absolute path in the profile layer** (see `bundle/README.md`).
|
|
557
|
-
- **Bundled mirror is a build-time sync** — `harness-skills/` + `harness-commands/` are produced by `bundle-assets` at build
|
|
557
|
+
- **Bundled mirror is a build-time sync** — `harness-skills/` + `harness-commands/` are produced by `bundle-assets` at build time (repo-root `skills/` + `commands/` mirrors; gitignored). A checkout where `bundle-assets` has not run mounts no bundled skills and registers no commands (the default mount is inert, not an error).
|
|
558
558
|
- **Profile-bundle install into the `web` profile: registry and local checkout** — `dsh plugin --profile web add @mstar-harness/dsh` (registry) and `add <local checkout>` are the supported paths; both run through the same pnpm + reconcile mechanism (the reconcile step joins `@mstar-harness/dsh` to `dsh.profile.bundles`). A local checkout needs a prior `bun run build` — the package has no `prepare` script (the monorepo builds packages explicitly), so an unbuilt checkout installs an empty `dist/`.
|
|
559
559
|
- **`lintSkillWrite` typed veto not production-wired** — the incoming-document hard veto (`SkillLintVetoError`, code `skill-lint.veto`) is exported and test-covered, but has no production caller yet: the engine `HostAdapter` has no content-carrying skill-write hook (only `beforeStatusWrite`/`beforeDispatch`/`beforeMerge`), and the fs intent slot is content-blind. Wiring lands with a future content-carrying hook; until then the listener path enforces only via the repair-escape advisory (never a veto).
|
|
560
560
|
- **CLI `HOST_SIGNALS` lacks the `subagent` token** — the engine `ToolSignal` union includes it and `detectHost` handles it, but `packages/cli` `HOST_SIGNALS` is not updated yet, so `mstar host detect --signals subagent` would reject until the CLI list is updated on upstreaming.
|
package/README.zh.md
CHANGED
|
@@ -50,7 +50,7 @@ dsh plugin --profile web add dsh-llm-fallbacks
|
|
|
50
50
|
| `harnessDir` | `string` | 仓库 `.mstarc` `[config] harness_dir`,否则按会话工作区探测(`.mstar/` → `.agents/` → `.plans/` → `plans/`,从会话工作区根目录开始——**绝不从启动 cwd**) | 显式 harness 根目录;优先于 engine 探测。**harness 根不在探测名列表中的仓库必须配置**;探测从会话工作区根开始(绝不从启动 cwd)并在那里**停止**——永不越过会话工作区向上,因此其上方的 harness 目录(如全局 `~/.mstar`)永远不会被采纳。 |
|
|
51
51
|
| `enforcement` | `'hard' \| 'soft'` | compass,否则仅告警 | 按部署覆盖。优先级:Config 优先;否则取 Assignment 自身的 `**Enforcement**: hard` 头字段(仅派发闸门);否则取仓库 `.mstarc` `[config] enforcement`;否则取迭代 compass frontmatter;否则仅告警。Config / `.mstarc` `soft` 是仅有的本地回滚——Assignment 级 `soft` 不能覆盖 hard compass。 |
|
|
52
52
|
| `dispatchTools` | `string[]` | `['subagent', 'subagent_fork']` | 派发闸门匹配的委派工具名——dsh preset 的**两个**委派工具:`subagent` 及其 fork 兄弟 `subagent_fork`(两者都携带 Assignment 形态的 `{ description, prompt }` 参数;`toolName` 配置可重命名实例)。 |
|
|
53
|
-
| `dispatchBinding` | `string` |
|
|
53
|
+
| `dispatchBinding` | `string` | 未设置 → hard 下 fail-closed `empty-binding` | 派发方 agent 自身的 harness 角色(反递归 caller);Assignment 的 `Execute as` 等于它即自我递归。 |
|
|
54
54
|
| `roleMap` | `Record<string, string>` | 未设置 | mstar 角色 id(`Execute as`)→ dsh-llm-fallbacks 角色 id。**仅**作日志与未来规则驱动互操作的分类桥——装饰从不读取它(见 LLM fallbacks integration)。 |
|
|
55
55
|
| `rolePersonas` | `Record<string, string>` | 未设置(打包镜像默认) | mstar 角色 id(`Execute as`)→ persona 文本;基于角色的 subagent 装饰的**覆盖**来源——角色匹配的 `subagent/start` 会把 persona 注册为子会话的 `mstar:role-persona` system-prompt 段;某角色未设置时使用打包的 `harness-agents/` 镜像默认值(见 LLM fallbacks integration)。 |
|
|
56
56
|
| `skillRoots` | `string[]` | 未设置(不注册自定义根) | 向 dsh skill-filesystem 提供者注册的额外技能根(`customSkillDirs` 语义——先于用户根扫描)。开发期:镜像 `<repo-root>/skills` 的绝对路径。 |
|
|
@@ -191,7 +191,7 @@ mstar 技能通过 dsh skill-filesystem 提供者以**单一规范挂载**接入
|
|
|
191
191
|
|
|
192
192
|
| 路径 | 机制 | 时机 |
|
|
193
193
|
| --- | --- | --- |
|
|
194
|
-
| Bundled 默认 | `bundledSkillDir` 默认取包内自带的 `harness-skills/` 镜像——仓库根 `skills/`(19 个 `mstar-*` + `pm`)由 `bundle-assets`
|
|
194
|
+
| Bundled 默认 | `bundledSkillDir` 默认取包内自带的 `harness-skills/` 镜像——仓库根 `skills/`(19 个 `mstar-*` + `pm`)由 `bundle-assets` 在构建时同步(gitignore),按**包相对路径**解析(非 cwd 锚定——任意启动 cwd 都可用) | 发布包 / 无覆盖的任何部署 |
|
|
195
195
|
| 自定义根 | `skillRoots` / 显式 `bundledSkillDir` → skill-filesystem `customSkillDirs` / `bundledSkillDir` 条目(显式值优先) | 本地开发 / 测试 / 使用不同镜像的部署 |
|
|
196
196
|
|
|
197
197
|
打包镜像是**单一规范挂载**:技能内容只在仓库根 `skills/` 镜像中存一份并同步进包(与 opencode 的 `harness-skills/` 相同),mstar 技能在任何地方都保持可独立使用。不重复加载:opencode 插件在自己的包里携带同一批技能,因此 dsh 只能通过这条 skill-filesystem 路径挂载它们。
|
|
@@ -200,7 +200,7 @@ mstar 技能通过 dsh skill-filesystem 提供者以**单一规范挂载**接入
|
|
|
200
200
|
|
|
201
201
|
## Commands
|
|
202
202
|
|
|
203
|
-
插件把 bundled 的 mstar 命令(omp/opencode 对齐面)注册到 `ctx.commands`:`harness-commands/*.md`——仓库根 `commands/` 镜像(`iteration-start`、`iteration-drive`、`iteration-loop`、`codebase-audit`)由 `bundle-assets`
|
|
203
|
+
插件把 bundled 的 mstar 命令(omp/opencode 对齐面)注册到 `ctx.commands`:`harness-commands/*.md`——仓库根 `commands/` 镜像(`iteration-start`、`iteration-drive`、`iteration-loop`、`codebase-audit`)由 `bundle-assets` 在构建时同步(gitignore)。每条注册读取命令的 `name`/`description`/`input` frontmatter;声明了 `input` hint 的注册会将其作为 `input.hint` 公布,使 dsh web 客户端的决策表从「脱离式裸执行」翻转为 leadingInput **claim**——菜单点选后把 `/name ` 插入输入框(命令色 token + ghost hint),按 Enter 才提交,用户可以键入后续参数(与 `/plan`、`/goal`、`/advisor` 相同的交互)。handler 把**命令正文以 USER source 消息 steer 进接收 agent**(dsh-plan-mode 命令先例——`source: { kind: 'user' }`,模型把正文当作要执行的任务而非注入上下文;即 dsh-commands 的「经接收 Agent 显式调度模型可见工作」路径),用户键入的参数以 `## User input` 小节追加在正文后,返回成功结果。注册以 `ctx.inject(['commands'], …)` 延迟进行——与工具注册相同的可选单元模式——插件在无 commands 服务时也能启动;镜像缺失(未跑 `bundle-assets`)则不注册任何命令。
|
|
204
204
|
|
|
205
205
|
## Engine seam mapping
|
|
206
206
|
|
|
@@ -327,7 +327,7 @@ catalog 行在委托之后追加到组合步骤消息的**末尾**——请求
|
|
|
327
327
|
- **`<root>/mstar-roles/SKILL.md` 上 skill-lint × roles seam 双重触发**——当某个已配置技能根包含 `mstar-roles` 目录(开发期的仓库根镜像情形,以及发布形态的打包镜像)时,对 `mstar-roles/SKILL.md` 的一次写入会同时触发技能撰写 lint 闸门与 roles seam 闸门(hard 下两条咨询 / 两条修复逃生日志)。两个校验器都合理适用——双重 lint 仅为咨询,并非正确性破坏;「作用域互不重叠」的性质只在四个 seam 之间成立,不跨技能闸门。
|
|
328
328
|
- **内容盲的 skill-lint 盲区**——`fs/write-intent` 槽位只携带 `(target, actor)`:首次创建的传入内容不被 lint,合法→非法覆盖在监听器路径上无法检出(它只 lint 写入前的磁盘文档)。告警/hard 咨询只呈现**已存在**的磁盘违规——与状态闸门同类限制。
|
|
329
329
|
- **显式相对 `bundledSkillDir` 覆盖锚定 cwd**——skill-filesystem 对相对打包根按普通 `join()` 语义解析到 dsh **进程 cwd**。插件的**默认**打包根是包内自带的 `harness-skills/` 镜像,按包相对路径解析(**非** cwd 锚定——任意启动 cwd 都可用);只有显式的**相对**覆盖继承 cwd 锚定,因此覆盖默认的部署应在 **profile 层传绝对路径**(见 `bundle/README.md`)。
|
|
330
|
-
- **Bundled 镜像是构建期同步**——`harness-skills/` + `harness-commands/` 由 `bundle-assets`
|
|
330
|
+
- **Bundled 镜像是构建期同步**——`harness-skills/` + `harness-commands/` 由 `bundle-assets` 在构建时产出(仓库根 `skills/` + `commands/` 镜像;gitignore)。未跑 `bundle-assets` 的检出不挂载 bundled 技能、不注册命令(默认挂载惰性,而非报错)。
|
|
331
331
|
- **profile-bundle 安装到 `web` profile:registry 与 local checkout**——`dsh plugin --profile web add @mstar-harness/dsh`(registry)与 `add <本地检出>` 为受支持途径,均走同一 pnpm + reconcile 机制(reconcile 步骤把 `@mstar-harness/dsh` 并入 `dsh.profile.bundles`)。local checkout 需要先执行 `bun run build`——本包没有 `prepare` 脚本(monorepo 显式构建各包),未构建的检出会装入空的 `dist/`。
|
|
332
332
|
- **`lintSkillWrite` 类型化否决尚未接入生产**——传入文档分支的 hard 否决(`SkillLintVetoError`,码 `skill-lint.veto`)已导出并测试覆盖,但尚无生产调用方:engine `HostAdapter` 没有携带内容的技能写入钩子(只有 `beforeStatusWrite`/`beforeDispatch`/`beforeMerge`),且 fs intent 槽位内容盲。接线随未来携带内容的钩子落地;在此之前监听器路径只通过修复逃生咨询执行(从不否决)。
|
|
333
333
|
- **CLI `HOST_SIGNALS` 缺少 `subagent` token**——engine `ToolSignal` 联合已包含它且 `detectHost` 能处理,但 `packages/cli` 的 `HOST_SIGNALS` 尚未更新,`mstar host detect --signals subagent` 会拒绝,直到上游化时更新 CLI 列表。
|
package/bundle/README.md
CHANGED
|
@@ -79,9 +79,9 @@ The `mstar` row accepts the plugin `Config` (see `src/index.ts`):
|
|
|
79
79
|
| `harnessDir` | unset (resolved per session workspace) | explicit `{HARNESS_DIR}` root — **required for repos whose harness root is not a probed name** (`.mstar/` → `.agents/` → `.plans/` → `plans/`); set it to the absolute root in the profile layer (or declare `[config] harness_dir` in a repo `.mstarc` — gitignored local config, honored above probing). Without either, the probe starts from the SESSION workspace root (the session cwd — **never the process/launch cwd**) and **stops there** — it never walks above the session workspace, so a global `~/.mstar` is never adopted |
|
|
80
80
|
| `enforcement` | **unset — default OFF** | `hard` / `soft` override; absent → the repo `.mstarc` `[config] enforcement`, else the iteration compass decides, warn-only when no compass hardens (never a global always-on hard gate) |
|
|
81
81
|
| `dispatchTools` | unset (plugin default `['subagent', 'subagent_fork']`) | delegation tool names the dispatch gate matches — the dsh preset's TWO delegation tools (`subagent` + its fork sibling `subagent_fork`, both Assignment-shaped); a custom list overrides the default wholesale, so it must include `subagent_fork` to keep fork dispatches gated |
|
|
82
|
-
| `dispatchBinding` | unset | the dispatching agent's role
|
|
82
|
+
| `dispatchBinding` | unset → fail-closed `empty-binding` under hard | the dispatching agent's own role (the anti-recursion CALLER) for the precheck |
|
|
83
83
|
| `skillRoots` | unset | additional skill roots (custom mirrors) |
|
|
84
|
-
| `bundledSkillDir` | unset → plugin resolves its OWN packaged `harness-skills/` mirror package-relative | bundled skill mount — the repo-root `skills/` mirror synced by `bundle-assets` at build
|
|
84
|
+
| `bundledSkillDir` | unset → plugin resolves its OWN packaged `harness-skills/` mirror package-relative | bundled skill mount — the repo-root `skills/` mirror synced by `bundle-assets` at build time (gitignored), resolved package-relative (NOT cwd-anchored). An explicit value wins; a RELATIVE override stays cwd-anchored, so pass an absolute path in the profile layer |
|
|
85
85
|
| `catalogTtlMs` | unset → `60000` | pre-step catalog cache refresh interval (ms) — how often the per-workspace unified `mstar-engine-status` catalog row (watermark + iteration gate + workspace-state digest) re-reads `status.json` / the compass / the knowledge index; the hot path is a timestamp compare + cache hit between refreshes |
|
|
86
86
|
|
|
87
87
|
## Client half (workflow panel)
|
package/dist/gates/_shared.d.ts
CHANGED
|
@@ -175,7 +175,7 @@ export declare function asRecord(value: unknown): Record<string, unknown> | unde
|
|
|
175
175
|
export declare function resolveSeamHard(harnessDir: string | null, config: Config): boolean;
|
|
176
176
|
/**
|
|
177
177
|
* Resolve the plugin package's own `harness-skills/` mirror (synced from the
|
|
178
|
-
* repo root by `bundle-assets` at build
|
|
178
|
+
* repo root by `bundle-assets` at build time; gitignored), anchored at
|
|
179
179
|
* the file URL of one module in the package. Pure — the caller passes its own
|
|
180
180
|
* module URL string, so the dual-depth resolution is directly unit-testable
|
|
181
181
|
* at either layout depth without touching `import.meta`. Resolved
|
|
@@ -207,7 +207,7 @@ export declare function resolvePackagedSkillsDir(fileUrl: string): string | unde
|
|
|
207
207
|
export declare function packagedSkillsDir(): string | undefined;
|
|
208
208
|
/**
|
|
209
209
|
* Resolve the plugin package's own `harness-agents/` mirror (synced from the
|
|
210
|
-
* repo root by `bundle-assets` at build
|
|
210
|
+
* repo root by `bundle-assets` at build time; gitignored) — the
|
|
211
211
|
* zero-config role-persona default source (plan
|
|
212
212
|
* `20260815-dsh-fallbacks-personas` Task 3). Same dual-depth probe semantics
|
|
213
213
|
* as {@link resolvePackagedSkillsDir}: `'../harness-agents'` (dist layout
|
|
@@ -289,7 +289,7 @@ export declare function iterationGateView(gate: GateResult): IterationGateListVi
|
|
|
289
289
|
* registration happens.
|
|
290
290
|
*
|
|
291
291
|
* The bundled default is the package's OWN `harness-skills/` mirror (synced
|
|
292
|
-
* from the repo root by `bundle-assets` at build
|
|
292
|
+
* from the repo root by `bundle-assets` at build time; gitignored),
|
|
293
293
|
* resolved package-relative — NOT cwd-anchored — so a deployment launching
|
|
294
294
|
* from any cwd gets the bundled mount (this resolves the
|
|
295
295
|
* cwd-anchoring limitation for the shipped default; an explicit
|
package/dist/index.js
CHANGED
|
@@ -1122,22 +1122,22 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
1122
1122
|
import { fileURLToPath } from "node:url";
|
|
1123
1123
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1124
1124
|
import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
1125
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
1125
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1126
1126
|
import { execFileSync } from "node:child_process";
|
|
1127
|
-
import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as
|
|
1127
|
+
import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve6 } from "node:path";
|
|
1128
1128
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
1129
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "node:fs";
|
|
1130
|
-
import { dirname as dirname4, join as join6, resolve as resolve4, sep } from "node:path";
|
|
1131
1129
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
|
|
1132
1130
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1131
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "node:fs";
|
|
1132
|
+
import { dirname as dirname4, join as join6, resolve as resolve4, sep } from "node:path";
|
|
1133
1133
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1134
1134
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1135
|
-
import { isAbsolute as isAbsolute4, resolve as
|
|
1135
|
+
import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
|
|
1136
1136
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1137
|
-
import { mkdirSync as
|
|
1138
|
-
import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5, join as join9, resolve as
|
|
1137
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1138
|
+
import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute5, join as join9, resolve as resolve8 } from "node:path";
|
|
1139
1139
|
import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
1140
|
-
import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as
|
|
1140
|
+
import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve11, sep as sep4 } from "node:path";
|
|
1141
1141
|
import { existsSync as existsSync9 } from "node:fs";
|
|
1142
1142
|
import { join as join13 } from "node:path";
|
|
1143
1143
|
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
|
|
@@ -1841,8 +1841,10 @@ function composeDispatchGate(text, opts = {}) {
|
|
|
1841
1841
|
const violations = [];
|
|
1842
1842
|
const writable = opts.writable !== false;
|
|
1843
1843
|
violations.push(...validateAssignmentFields(text, { writable }).violations);
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1844
|
+
const caller = opts.caller ?? "";
|
|
1845
|
+
if (caller.trim() !== "" || opts.callerRequired === true) {
|
|
1846
|
+
violations.push(...antiRecursionPrecheck(caller, parseAssignmentFields(text).executeAs ?? "").violations);
|
|
1847
|
+
}
|
|
1846
1848
|
if (writable) {
|
|
1847
1849
|
const forms = parseAssignmentBranchForms(text);
|
|
1848
1850
|
const branch = forms.createForm?.name ?? forms.workingBranch ?? forms.directOn?.branch ?? process.env.MSTAR_WORKING_BRANCH;
|
|
@@ -1865,7 +1867,7 @@ function antiRecursionPrecheck(subagentType, executeAs) {
|
|
|
1865
1867
|
return {
|
|
1866
1868
|
ok: false,
|
|
1867
1869
|
violations: [
|
|
1868
|
-
violation3("critical", "dispatch.anti-recursion.empty-binding", `empty
|
|
1870
|
+
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")
|
|
1869
1871
|
]
|
|
1870
1872
|
};
|
|
1871
1873
|
}
|
|
@@ -2392,14 +2394,14 @@ function findingsCleanupGate(register, planId, opts) {
|
|
|
2392
2394
|
return { ok: violations.length === 0, violations };
|
|
2393
2395
|
}
|
|
2394
2396
|
function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
2395
|
-
const start =
|
|
2397
|
+
const start = resolve6(startDir);
|
|
2396
2398
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
2397
2399
|
if (explicit)
|
|
2398
|
-
return
|
|
2399
|
-
const boundary =
|
|
2400
|
+
return resolve6(start, explicit);
|
|
2401
|
+
const boundary = resolve6(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
|
|
2400
2402
|
const rc = loadMstarc(start, boundary);
|
|
2401
2403
|
if (rc !== null && rc.config.harnessDir)
|
|
2402
|
-
return
|
|
2404
|
+
return resolve6(rc.dir, rc.config.harnessDir);
|
|
2403
2405
|
let dir = start;
|
|
2404
2406
|
for (;; ) {
|
|
2405
2407
|
if (!isAtOrBelow2(dir, boundary))
|
|
@@ -2430,7 +2432,7 @@ function defaultWorkspaceRoot(startDir) {
|
|
|
2430
2432
|
if (segment && segment !== ".")
|
|
2431
2433
|
boundary = dirname5(boundary);
|
|
2432
2434
|
}
|
|
2433
|
-
return
|
|
2435
|
+
return resolve6(boundary);
|
|
2434
2436
|
} catch {}
|
|
2435
2437
|
return startDir;
|
|
2436
2438
|
}
|
|
@@ -2439,10 +2441,10 @@ function isAtOrBelow2(dir, root) {
|
|
|
2439
2441
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
2440
2442
|
}
|
|
2441
2443
|
function mstarcDirOverride(harnessDir, key) {
|
|
2442
|
-
const dir =
|
|
2444
|
+
const dir = resolve6(harnessDir);
|
|
2443
2445
|
const rc = loadMstarc(dir, dirname5(dir));
|
|
2444
2446
|
const declared = rc?.config[key];
|
|
2445
|
-
return declared ?
|
|
2447
|
+
return declared ? resolve6(rc.dir, declared) : null;
|
|
2446
2448
|
}
|
|
2447
2449
|
function assertSafePathComponent(value, what) {
|
|
2448
2450
|
if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
@@ -2451,7 +2453,7 @@ function assertSafePathComponent(value, what) {
|
|
|
2451
2453
|
}
|
|
2452
2454
|
function resolveSddDir(harnessDir, planId) {
|
|
2453
2455
|
assertSafePathComponent(planId, "planId");
|
|
2454
|
-
const base =
|
|
2456
|
+
const base = resolve6(harnessDir);
|
|
2455
2457
|
const declared = mstarcDirOverride(base, "sddDir");
|
|
2456
2458
|
const sddBase = declared !== null ? declared : join8(base, "sdd");
|
|
2457
2459
|
return join8(sddBase, planId);
|
|
@@ -2460,15 +2462,15 @@ function resolveIterationDir(harnessDir) {
|
|
|
2460
2462
|
const declared = mstarcDirOverride(harnessDir, "iterationDir");
|
|
2461
2463
|
if (declared !== null)
|
|
2462
2464
|
return declared;
|
|
2463
|
-
return join8(
|
|
2465
|
+
return join8(resolve6(harnessDir), "iterations");
|
|
2464
2466
|
}
|
|
2465
2467
|
function resolveHarnessSubdir(startDir, opts, key, fallback) {
|
|
2466
2468
|
const harness = resolveHarnessDir(startDir, opts);
|
|
2467
2469
|
if (harness === null) {
|
|
2468
|
-
throw new Error(`harness dir not found from ${
|
|
2470
|
+
throw new Error(`harness dir not found from ${resolve6(startDir)} — cannot resolve the ${fallback} dir (run \`mstar harness scaffold\`, pass opts.harnessDir, or set MSTAR_HARNESS_DIR)`);
|
|
2469
2471
|
}
|
|
2470
2472
|
const declared = mstarcDirOverride(harness, key);
|
|
2471
|
-
return declared !== null ? declared : join8(
|
|
2473
|
+
return declared !== null ? declared : join8(resolve6(harness), fallback);
|
|
2472
2474
|
}
|
|
2473
2475
|
function resolveWorkflowDir(startDir = process.cwd(), opts = {}) {
|
|
2474
2476
|
return resolveHarnessSubdir(startDir, opts, "workflowDir", "workflows");
|
|
@@ -2558,7 +2560,7 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
2558
2560
|
if (leaseWorkingBranch.trim() === "") {
|
|
2559
2561
|
violations.push(violation7("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
|
|
2560
2562
|
}
|
|
2561
|
-
if (controlWorktreePath !== "" && leaseWorktreePath !== "" &&
|
|
2563
|
+
if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve7(controlWorktreePath) === resolve7(leaseWorktreePath)) {
|
|
2562
2564
|
violations.push(violation7("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path — the feature worktree MUST differ from the control worktree (L1 isolation; product edits never land in the control checkout)`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
|
|
2563
2565
|
}
|
|
2564
2566
|
if (leaseWorktreePath !== "" && !existsSync6(leaseWorktreePath)) {
|
|
@@ -2589,7 +2591,7 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
2589
2591
|
violations.push(violation7("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>)`));
|
|
2590
2592
|
return;
|
|
2591
2593
|
}
|
|
2592
|
-
const normalized =
|
|
2594
|
+
const normalized = resolve7(track.worktreePath);
|
|
2593
2595
|
if (seenPaths.has(normalized)) {
|
|
2594
2596
|
violations.push(violation7("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"));
|
|
2595
2597
|
return;
|
|
@@ -2716,12 +2718,12 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
2716
2718
|
const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
|
|
2717
2719
|
let harnessDir;
|
|
2718
2720
|
if (harnessOverride) {
|
|
2719
|
-
harnessDir =
|
|
2721
|
+
harnessDir = resolve8(root, harnessOverride);
|
|
2720
2722
|
} else {
|
|
2721
2723
|
const rc = findMstarc(root, root);
|
|
2722
2724
|
const rcHarnessDir = rc !== null ? parseMstarc(readFileSync7(rc, "utf8")).harnessDir : undefined;
|
|
2723
2725
|
if (rcHarnessDir) {
|
|
2724
|
-
harnessDir =
|
|
2726
|
+
harnessDir = resolve8(rc !== null ? dirname6(rc) : root, rcHarnessDir);
|
|
2725
2727
|
} else {
|
|
2726
2728
|
const probed = probeHarnessWithStatus(root);
|
|
2727
2729
|
if (probed) {
|
|
@@ -2736,7 +2738,7 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
2736
2738
|
}
|
|
2737
2739
|
}
|
|
2738
2740
|
const sddDir = resolveSddDir(harnessDir, planId);
|
|
2739
|
-
|
|
2741
|
+
mkdirSync6(sddDir, { recursive: true });
|
|
2740
2742
|
writeFileSync4(join9(sddDir, ".gitignore"), `*
|
|
2741
2743
|
`);
|
|
2742
2744
|
return realpathSync3(sddDir);
|
|
@@ -2759,7 +2761,7 @@ function taskBrief(planFile, taskN, outFile, opts = {}) {
|
|
|
2759
2761
|
if (!sddDir) {
|
|
2760
2762
|
throw new SddScriptError("mstar sdd task-brief: set SDD_DIR or pass OUTFILE (run mstar sdd workspace PLAN_ID first)", 2);
|
|
2761
2763
|
}
|
|
2762
|
-
|
|
2764
|
+
mkdirSync6(sddDir, { recursive: true });
|
|
2763
2765
|
out = join9(sddDir, `task-${taskN}-brief.md`);
|
|
2764
2766
|
}
|
|
2765
2767
|
const records = content.endsWith(`
|
|
@@ -3190,6 +3192,9 @@ function completenessLevel(frontmatterText, checklist) {
|
|
|
3190
3192
|
function violation9(severity, code, message, fix) {
|
|
3191
3193
|
return { ok: false, severity, code, message, fix };
|
|
3192
3194
|
}
|
|
3195
|
+
var AUDIT_EFFORTS = ["XS", "S", "M", "L", "XL"];
|
|
3196
|
+
var AUDIT_RISKS = ["LOW", "MED", "HIGH"];
|
|
3197
|
+
var AUDIT_CONFIDENCES = ["HIGH", "MED", "LOW"];
|
|
3193
3198
|
var AUDIT_STATUS_FIELDS = ["Priority", "Effort", "Risk", "Depends on", "Category", "Planned at"];
|
|
3194
3199
|
function parseStatusBlocks(planText) {
|
|
3195
3200
|
const blocks = [];
|
|
@@ -3538,7 +3543,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3538
3543
|
for (const { ref, isSymbol, module } of refs) {
|
|
3539
3544
|
if (!isSymbol || module === undefined) {
|
|
3540
3545
|
const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
|
|
3541
|
-
if (existsSync8(
|
|
3546
|
+
if (existsSync8(resolve11(repoRoot, candidate))) {
|
|
3542
3547
|
checked++;
|
|
3543
3548
|
} else {
|
|
3544
3549
|
violations.push(violation10("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"));
|
|
@@ -3613,9 +3618,9 @@ function isFileLikeRoot(root) {
|
|
|
3613
3618
|
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
|
|
3614
3619
|
}
|
|
3615
3620
|
function scopeGuard(path, allowedRoots) {
|
|
3616
|
-
const resolved =
|
|
3621
|
+
const resolved = resolve11(path);
|
|
3617
3622
|
for (const root of allowedRoots) {
|
|
3618
|
-
const r =
|
|
3623
|
+
const r = resolve11(root);
|
|
3619
3624
|
if (isFileLikeRoot(r)) {
|
|
3620
3625
|
if (resolved === r)
|
|
3621
3626
|
return { ok: true, violations: [] };
|
|
@@ -3947,6 +3952,9 @@ function lintFiveQuestion(bodyText, mode = "authoring") {
|
|
|
3947
3952
|
function resolveAssetPath(skillName, relPath, host) {
|
|
3948
3953
|
return `skill \`${skillName}\` → ${relPath} (${resolveSkillRoot(host, { skill: skillName, rel: relPath })})`;
|
|
3949
3954
|
}
|
|
3955
|
+
var EFFORT_ENUM_RE = new RegExp(`^(?:${AUDIT_EFFORTS.join("|")})(?:\\s*\\(|$)`);
|
|
3956
|
+
var RISK_ENUM_RE = new RegExp(`^(?:${AUDIT_RISKS.join("|")})(?:\\b|$)`);
|
|
3957
|
+
var CONFIDENCE_ENUM_RE = new RegExp(`^(${[...AUDIT_CONFIDENCES, "MEDIUM"].join("|")})\\b`, "i");
|
|
3950
3958
|
|
|
3951
3959
|
// src/index.ts
|
|
3952
3960
|
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
@@ -3985,7 +3993,7 @@ class DshMstar extends Service {
|
|
|
3985
3993
|
// src/gates/_shared.ts
|
|
3986
3994
|
var import_schemastery = __toESM(require_lib2(), 1);
|
|
3987
3995
|
import { existsSync as existsSync4 } from "node:fs";
|
|
3988
|
-
import { resolve as
|
|
3996
|
+
import { resolve as resolve5 } from "node:path";
|
|
3989
3997
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3990
3998
|
var STATUS_FILE = "status.json";
|
|
3991
3999
|
var PERSONA_INTERPOLATION_HAZARD = /\{\{[\s\S]*\}\}/;
|
|
@@ -4078,14 +4086,14 @@ class HarnessResolver {
|
|
|
4078
4086
|
explicit;
|
|
4079
4087
|
cache = new Map;
|
|
4080
4088
|
constructor(explicit) {
|
|
4081
|
-
this.explicit = explicit !== undefined && explicit.trim() !== "" ?
|
|
4089
|
+
this.explicit = explicit !== undefined && explicit.trim() !== "" ? resolve5(process.cwd(), explicit) : null;
|
|
4082
4090
|
}
|
|
4083
4091
|
forWorkspace(cwd) {
|
|
4084
4092
|
if (this.explicit !== null)
|
|
4085
4093
|
return this.explicit;
|
|
4086
4094
|
if (cwd === undefined || cwd.trim() === "")
|
|
4087
4095
|
return null;
|
|
4088
|
-
const abs =
|
|
4096
|
+
const abs = resolve5(cwd);
|
|
4089
4097
|
const hit = this.cache.get(abs);
|
|
4090
4098
|
if (hit !== undefined)
|
|
4091
4099
|
return hit;
|
|
@@ -4136,7 +4144,7 @@ import { join as join7 } from "node:path";
|
|
|
4136
4144
|
|
|
4137
4145
|
// src/gates/dispatch.ts
|
|
4138
4146
|
import { existsSync as existsSync10 } from "node:fs";
|
|
4139
|
-
import { basename as basename4, join as join5, resolve as
|
|
4147
|
+
import { basename as basename4, join as join5, resolve as resolve9 } from "node:path";
|
|
4140
4148
|
|
|
4141
4149
|
// src/gates/workflow-selection.ts
|
|
4142
4150
|
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
|
|
@@ -4497,7 +4505,7 @@ function leaseGateViolations(harnessDir, exec, writable, prompt) {
|
|
|
4497
4505
|
const wt = worktree === undefined ? undefined : firstToken(worktree);
|
|
4498
4506
|
if (isNaValue(wt)) {
|
|
4499
4507
|
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)"));
|
|
4500
|
-
} else if (
|
|
4508
|
+
} else if (resolve9(wt) !== resolve9(String(lease.worktree_path ?? ""))) {
|
|
4501
4509
|
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)"));
|
|
4502
4510
|
}
|
|
4503
4511
|
const forms = parseAssignmentBranchForms(header);
|
|
@@ -4603,7 +4611,7 @@ function dispatchGateCore(config, harnessDir, prompt) {
|
|
|
4603
4611
|
const header = assignmentHeaderRegion(prompt);
|
|
4604
4612
|
const violations = [...worktreeL2Violations(header), ...worktreeL1Violations(harnessDir, header)];
|
|
4605
4613
|
const writable = isReadOnlyAssignmentRole(parseAssignmentFields(header).executeAs ?? "") ? false : undefined;
|
|
4606
|
-
violations.push(...composeDispatchGate(header, {
|
|
4614
|
+
violations.push(...composeDispatchGate(header, { caller: config.dispatchBinding ?? "", callerRequired: true, writable }).violations);
|
|
4607
4615
|
return { violations, writable };
|
|
4608
4616
|
}
|
|
4609
4617
|
function workflowGateInputOf(exec) {
|
|
@@ -5887,10 +5895,10 @@ function agentDigestKey(agent, cwd) {
|
|
|
5887
5895
|
|
|
5888
5896
|
// src/gates/status.ts
|
|
5889
5897
|
import { existsSync as existsSync14, readdirSync as readdirSync9 } from "node:fs";
|
|
5890
|
-
import { basename as basename6, join as join14, relative as relative5, resolve as
|
|
5898
|
+
import { basename as basename6, join as join14, relative as relative5, resolve as resolve12 } from "node:path";
|
|
5891
5899
|
var LOGGER_NAME = "mstar/status-gate";
|
|
5892
5900
|
function harnessDocKindOfTarget(harnessDir, targetPath) {
|
|
5893
|
-
const resolved =
|
|
5901
|
+
const resolved = resolve12(targetPath);
|
|
5894
5902
|
const name = basename6(resolved);
|
|
5895
5903
|
if (name !== STATUS_FILE && name !== WORKFLOW_SNAPSHOT_FILE && name !== PROJECT_REGISTER_FILE)
|
|
5896
5904
|
return null;
|
|
@@ -6229,9 +6237,11 @@ var GITIGNORE_PROCESS_ENTRIES2 = GITIGNORE_SNIPPET2.split(`
|
|
|
6229
6237
|
var GITIGNORE_PROCESS_ENTRIES_AGENTS2 = GITIGNORE_SNIPPET_AGENTS2.split(`
|
|
6230
6238
|
`).filter((line) => line.startsWith(".agents/") || line.startsWith("!.agents/")).map((line) => line.trim());
|
|
6231
6239
|
var WHOLE_MATCH_PATTERNS = [
|
|
6232
|
-
{ type: "private-key", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/g },
|
|
6240
|
+
{ type: "private-key", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g },
|
|
6233
6241
|
{ type: "aws-access-key", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
|
|
6234
6242
|
{ type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
|
|
6243
|
+
{ type: "github-pat", re: /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g },
|
|
6244
|
+
{ type: "stripe-live-key", re: /\bsk_live_[A-Za-z0-9]{16,}\b/g },
|
|
6235
6245
|
{ type: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
6236
6246
|
{ type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\b/g },
|
|
6237
6247
|
{ type: "api-secret-key", re: /\bsk-[A-Za-z0-9-]{20,}\b/g }
|
|
@@ -6242,6 +6252,28 @@ var VALUE_PATTERNS = [
|
|
|
6242
6252
|
re: /(["']?)\b(password|passwd|api[_-]?key|access[_-]?token|auth[_-]?token|secret|token)\b(["']?)(\s*[:=]\s*)("[^"\n]{8,}"|'[^'\n]{8,}'|[A-Za-z0-9_./+\-=]{16,})/gi
|
|
6243
6253
|
}
|
|
6244
6254
|
];
|
|
6255
|
+
var CI_IAC_LEAK_SHAPES = [
|
|
6256
|
+
{
|
|
6257
|
+
kind: "actions-plaintext-env",
|
|
6258
|
+
description: "GitHub Actions env assignment with plaintext literal",
|
|
6259
|
+
re: /^\s*(?:-\s+)?env:\s*[A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|KEY)[A-Z0-9_]*\s*[:=]\s*["']?[A-Za-z0-9_/+=-]{8,}["']?\s*$/
|
|
6260
|
+
},
|
|
6261
|
+
{
|
|
6262
|
+
kind: "actions-secret-echo",
|
|
6263
|
+
description: "echo of a GitHub Actions secrets context value",
|
|
6264
|
+
re: /\becho\b[^#\n]*\$\{\{\s*secrets\.[A-Za-z0-9_]+\s*\}\}/
|
|
6265
|
+
},
|
|
6266
|
+
{
|
|
6267
|
+
kind: "dockerfile-credential-env",
|
|
6268
|
+
description: "Dockerfile ENV/ARG with credential-looking name",
|
|
6269
|
+
re: /^\s*(?:ENV|ARG)\s+[A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|APIKEY|API_KEY|ACCESS_KEY|PRIVATE_KEY)[A-Z0-9_]*\b/i
|
|
6270
|
+
},
|
|
6271
|
+
{
|
|
6272
|
+
kind: "terraform-hardcoded-password",
|
|
6273
|
+
description: "Terraform hardcoded password attribute",
|
|
6274
|
+
re: /^\s*password\s*=\s*"[^$\{][^"]*"\s*$/
|
|
6275
|
+
}
|
|
6276
|
+
];
|
|
6245
6277
|
function buildLineStarts(text) {
|
|
6246
6278
|
const starts = [0];
|
|
6247
6279
|
for (let i = 0;i < text.length; i++) {
|
|
@@ -6263,17 +6295,25 @@ function lineAt(starts, index) {
|
|
|
6263
6295
|
}
|
|
6264
6296
|
return lo + 1;
|
|
6265
6297
|
}
|
|
6298
|
+
function lineStartOf(text, index) {
|
|
6299
|
+
return text.lastIndexOf(`
|
|
6300
|
+
`, index - 1) + 1;
|
|
6301
|
+
}
|
|
6266
6302
|
function redactSecrets(text, filePath) {
|
|
6267
6303
|
const starts = buildLineStarts(text);
|
|
6268
6304
|
const marker = (type, index) => `[REDACTED ${type}@${lineAt(starts, index)}${filePath === undefined ? "" : ` in ${filePath}`}]`;
|
|
6269
|
-
const
|
|
6270
|
-
const findings = [];
|
|
6305
|
+
const spans = [];
|
|
6271
6306
|
for (const pattern of WHOLE_MATCH_PATTERNS) {
|
|
6272
6307
|
for (const match of text.matchAll(pattern.re)) {
|
|
6273
6308
|
if (match.index === undefined)
|
|
6274
6309
|
continue;
|
|
6275
|
-
|
|
6276
|
-
|
|
6310
|
+
spans.push({
|
|
6311
|
+
start: match.index,
|
|
6312
|
+
end: match.index + match[0].length,
|
|
6313
|
+
priority: 0,
|
|
6314
|
+
text: marker(pattern.type, match.index),
|
|
6315
|
+
type: pattern.type
|
|
6316
|
+
});
|
|
6277
6317
|
}
|
|
6278
6318
|
}
|
|
6279
6319
|
for (const pattern of VALUE_PATTERNS) {
|
|
@@ -6282,14 +6322,51 @@ function redactSecrets(text, filePath) {
|
|
|
6282
6322
|
continue;
|
|
6283
6323
|
const type = pattern.typeOf(match[2]);
|
|
6284
6324
|
const replacement = `${match[1]}${match[2]}${match[3]}${match[4]}${marker(type, match.index)}`;
|
|
6285
|
-
|
|
6286
|
-
|
|
6325
|
+
spans.push({ start: match.index, end: match.index + match[0].length, priority: 1, text: replacement, type });
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
for (const shape of CI_IAC_LEAK_SHAPES) {
|
|
6329
|
+
const lineScoped = new RegExp(shape.re.source, shape.re.ignoreCase ? "gim" : "gm");
|
|
6330
|
+
for (const match of text.matchAll(lineScoped)) {
|
|
6331
|
+
if (match.index === undefined)
|
|
6332
|
+
continue;
|
|
6333
|
+
const lineEnd = text.indexOf(`
|
|
6334
|
+
`, match.index);
|
|
6335
|
+
const end = lineEnd === -1 ? text.length : lineEnd;
|
|
6336
|
+
spans.push({
|
|
6337
|
+
start: match.index,
|
|
6338
|
+
end,
|
|
6339
|
+
priority: 2,
|
|
6340
|
+
text: `${" ".repeat(match.index - lineStartOf(text, match.index))}${marker(shape.kind, match.index)}`,
|
|
6341
|
+
type: shape.kind
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
spans.sort((a, b) => a.start - b.start || b.end - a.end || b.priority - a.priority);
|
|
6346
|
+
const merged = [];
|
|
6347
|
+
let groupMaxEnd = -1;
|
|
6348
|
+
let best = null;
|
|
6349
|
+
for (const span of spans) {
|
|
6350
|
+
if (span.start < groupMaxEnd) {
|
|
6351
|
+
if (groupMaxEnd < span.end)
|
|
6352
|
+
groupMaxEnd = span.end;
|
|
6353
|
+
if (span.end - span.start > best.end - best.start)
|
|
6354
|
+
best = span;
|
|
6355
|
+
} else {
|
|
6356
|
+
if (best !== null)
|
|
6357
|
+
merged.push(best);
|
|
6358
|
+
groupMaxEnd = span.end;
|
|
6359
|
+
best = span;
|
|
6287
6360
|
}
|
|
6288
6361
|
}
|
|
6289
|
-
|
|
6362
|
+
if (best !== null)
|
|
6363
|
+
merged.push(best);
|
|
6290
6364
|
let out = text;
|
|
6291
|
-
for (
|
|
6292
|
-
|
|
6365
|
+
for (let i = merged.length - 1;i >= 0; i--) {
|
|
6366
|
+
const r = merged[i];
|
|
6367
|
+
out = out.slice(0, r.start) + r.text + out.slice(r.end);
|
|
6368
|
+
}
|
|
6369
|
+
const findings = merged.map((r) => ({ line: lineAt(starts, r.start), type: r.type }));
|
|
6293
6370
|
const deduped = new Map;
|
|
6294
6371
|
for (const f of findings)
|
|
6295
6372
|
deduped.set(`${f.line}:${f.type}`, f);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: amazing-pr-review
|
|
3
|
+
description: Deep, pre-merge review of a PR / branch / diff at three strengths — quick / default (default) / deep — one verdict, posted to GitHub when a PR number is given. Decides whether a change is safe to ship with evidence-backed findings, rather than a shallow "looks good" pass. Produces a `ship it` / `needs fixes` / `blocked` verdict. Do not use for self-checking a change you just authored.
|
|
4
|
+
agent: project-manager
|
|
5
|
+
input: "[pr|branch|scope] [quick|default|deep]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Deep PR Review
|
|
9
|
+
|
|
10
|
+
Run a read-only, evidence-first deep review of a pull request, branch, or diff and decide whether it is safe to ship. When a PR number exists, posting the GitHub Review is **mandatory** — the review is not complete until comments land on the PR. Output: verdict + findings presented to the user, plus the posted review URL. Never auto-approve, never REQUEST_CHANGES, never merge.
|
|
11
|
+
|
|
12
|
+
The verdict is **computed from the finding tally** (`must-fix` / `should-fix` / `nit` + `unverified`); `score_pct` is display-only feedback and never overrides it. Procedure and formula → **`references/pr-review.md`** § Verdict synthesis / Tally and derived score.
|
|
13
|
+
|
|
14
|
+
**Read-only advisory.** The review does not enter the harness plan state machine (`Todo → InProgress → InReview → Done`). Reviewers never edit the worktree, never merge, and never approve-as-merge.
|
|
15
|
+
|
|
16
|
+
## Boot
|
|
17
|
+
|
|
18
|
+
1. `mstar-harness-core`
|
|
19
|
+
2. `mstar-audit` → SKILL.md(common core)+ `references/pr-review.md`(`pr` variant 全量)
|
|
20
|
+
3. `mstar-coding-behavior` (evidence discipline)
|
|
21
|
+
4. `mstar-branch-worktree` (worktree isolation)
|
|
22
|
+
5. `mstar-host` → active host reference (invoke capability for parallel subagents)
|
|
23
|
+
6. 多 PR 输入的 batch 语义 → `references/pr-review.md` § Batch(one session = one PR;first-only + audit todos)
|
|
24
|
+
7. 档位解析后 → 按 `references/pr-review.md` § Review depth (tiers) 确认 seat 计划(quick 1 席 / default 2 席 / deep 三阶段)
|
|
25
|
+
|
|
26
|
+
## Routing(谁执行 review)
|
|
27
|
+
|
|
28
|
+
**档位解析** — 显式档位 token 优先;无 flag 时按推断阶梯(自上而下,首个命中即定档):
|
|
29
|
+
|
|
30
|
+
1. **显式档位 token**(仅认专用 token 形式:`--quick` / `--default` / `--deep` flag,或参数末尾独立档位词;绝不匹配 `[pr|branch|scope]` 参数中的子串)→ 直接定档(用户意图 > 一切启发式)。任意两个档位 token 同现(`quick` / `default` / `deep` 任取其二)→ **硬停止冲突**:报告冲突,请用户二选一,不静默取优先级。
|
|
31
|
+
2. **too-large**(> ~1000 变更行)→ advise split(既有规则不变);坚持审 → `deep`。
|
|
32
|
+
3. **敏感面**(`security-review.md` §9 扩展面出现在 diff:auth / LLM / 供应链 / 数据面)→ `deep`(任何尺寸;安全敏感不打薄)。
|
|
33
|
+
4. **large**(> ~300 变更行 / 跨多变更面)→ `deep`(大 PR 不静默降档)。
|
|
34
|
+
5. **small**(≤ ~300 单面):tiny-mechanical 形状(docs-only / 重命名 / 格式化 / 纯删除)→ `quick`;其余真实代码变更 → `default`。
|
|
35
|
+
|
|
36
|
+
复用既有 100 / 300 / 1000 sizing bands(`pr-review.md` § Sizing & change shape),不引入第二套数字:`~100`(tiny-mechanical 带)≈ `quick` 推荐域;`~300`(one logical change 上沿 = 扇出阈值)为 `default` 上界;`~1000`(too-large → advise split)语义不变。显式 `quick` + 敏感面 → 尊重用户,但该席仍须执行领域内 security lens,且报告 `- notes:` 声明「quick tier — reduced coverage on a security-sensitive surface」。
|
|
37
|
+
|
|
38
|
+
| Context | Who runs the review |
|
|
39
|
+
|---------|-------------------|
|
|
40
|
+
| **Single PR — `quick`**(1 席,≈ ¼ deep 席位时间) | 收集 + review 合入**同一席**一趟(不切领域);席内带领域内 security lens;**无独立 security 席**;报告 `tier: quick` + `- notes:` 降档覆盖声明 |
|
|
41
|
+
| **Single PR — `default`**(2 席 ≈ ½ deep 席位时间) | 2 个领域席:收集合入领域席 = **席位复用**(Stage 1 不单独成波);两席均带领域内 security lens;**无独立 cross-domain 席**(跨域边界问题记 `- notes:`,主代理可宣布升级 `deep`);报告 `tier: default` |
|
|
42
|
+
| **Single PR — `deep`**(4–7 席) | 三阶段全走(= 现状逐字):**Stage 1** 按领域扇出 2–3 轻量只读收集 agents(host scout/explorer/general)→ **Stage 2** 按同一领域派 2–3 mstar 内置角色(`code-reviewer` / `fullstack-dev` / `frontend-dev`)+ 大 PR/安全敏感面 0–1 独立 cross-domain security 席 → **Stage 3** 主代理合成:dedupe + three-way vet → tally/verdict → 报告 + 发布 GitHub Review;报告 `tier: deep` |
|
|
43
|
+
| **Multiple PRs** | 只对**第一个** PR 按档位路由走 review(同 Single PR);其余 PR 登记为 audit todos(`{PROJECT_DIR}/_default/residuals.json`,`decision: defer`、`target: next session`、`tracking: pr-deep-review backlog`),并建议每个 PR 开独立 session |
|
|
44
|
+
|
|
45
|
+
All review seats are **read-only** in this flow: never edit the reviewed worktree, never merge, never approve-as-merge. **Stage 1** collect seats are the host's lightweight read-only agents (`scout` / `explorer` / `general`); **Stage 2** domain seats are mstar built-in roles (`code-reviewer` / `fullstack-dev` / `frontend-dev`). All seats run in **Audit Mode** (shared contract → `mstar-roles` `references/_shared/leaf-executor-core.md`). PM dispatches; every seat returns evidence / findings in its **result payload** (any seat may be **write-blocked**; writable seats may **best-effort** write their evidence file directly — the main agent writes / consolidates) — no verdict token, no posting; the main agent (PM / command thread) synthesizes and publishes.
|
|
46
|
+
|
|
47
|
+
## Execute
|
|
48
|
+
|
|
49
|
+
Execute **`mstar-audit`** § `pr` variant end to end(SKILL.md common core:recon + three-way attack & vet;variant detail:**`references/pr-review.md`** —— resolve the review **tier** first → **§ Review depth (tiers)**(档位表 + 推断阶梯 + seat 计划 + 报告 `tier` 声明); the linear `scope → guidance load → concern lenses → evidence → verdict → output` path is **Stage 3's** read of the variant; seats run per the tier's seat plan and **§ Review pipeline**). Review is run in a dedicated worktree against a diff from the PR's **real base** — resolve the base per `references/pr-review.md` § Worktree isolation (never assume `main`).
|
|
50
|
+
**Tiered execution** — the resolved tier(§ Review depth (tiers))shapes the run: `deep` = 三阶段全走(现状逐字);`default` = 2 领域席、收集合入领域席(Stage 1 不单独成波);`quick` = 1 席、收集 + review 同席。The deep tier runs the pipeline in **`references/pr-review.md`** § Review pipeline: **Stage 1** collect seats fan out by domain and return evidence in their result payload; **Stage 2** domain seats review code + security and return findings; **Stage 3** the main agent (PM / command thread) synthesizes. **Any seat may be write-blocked** (read-only sandbox / EPERM) — seats return evidence / findings in their **result payload** and are **never required to write files**; writable seats may **best-effort** write their evidence file directly; the main agent **writes / consolidates all evidence files** from seat payloads → dedupe + three-way vet → tally/verdict → report + GitHub Review POST. Posting has three branches (§ Comment posting): `posted: yes` / `n/a-no-pr` / `failed` — chat output and the local report are delivered in all three. Worktree cleanup is done by the main agent once the local report is saved — the save runs in all three posting branches (`posted: yes` / `n/a-no-pr` / `failed`), so cleanup does not wait on POST success (§ Worktree isolation / § Local report archive).
|
|
51
|
+
|
|
52
|
+
Review findings that need fixing can be turned into self-contained plans for the normal Prepare → Execute flow (reusing `mstar-audit` SKILL.md **`## Plan output (all variants)`** — same contract as the `pr` variant).
|
|
53
|
+
|
|
54
|
+
Output verdict + findings to the user, with the posted GitHub Review URL. Posting procedure (when a PR number exists) → **`references/pr-review.md`** § Comment posting; the main agent saves the Stage 3 report and writes/consolidates the evidence files — every seat returns evidence / findings in its **result payload** (any seat may be write-blocked); writable seats may **best-effort** write files in addition → **`references/pr-review.md`** § Local report archive. Never auto-approve or merge.
|