immune-brain 5.1.2 → 5.2.0

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.
Files changed (34) hide show
  1. package/README.md +13 -6
  2. package/README.zh-CN.md +12 -5
  3. package/package.json +1 -1
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +14 -2
  6. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +73 -0
  7. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +15 -2
  8. package/plugins/immune-brain/agents/immune-brain-reviewer.md +5 -0
  9. package/plugins/immune-brain/dist/BASELINE.md +1 -1
  10. package/plugins/immune-brain/dist/claude/mcp-server.mjs +425 -140
  11. package/plugins/immune-brain/dist/imm-brainstorm.md +2 -2
  12. package/plugins/immune-brain/dist/imm-doc-prune.md +2 -1
  13. package/plugins/immune-brain/dist/imm-planner.md +26 -33
  14. package/plugins/immune-brain/dist/imm-run.md +14 -3
  15. package/plugins/immune-brain/dist/imm-test-prune.md +233 -0
  16. package/plugins/immune-brain/dist/registry.yaml +9 -0
  17. package/plugins/immune-brain/dist/role-prompts/code-review.md +5 -0
  18. package/plugins/immune-brain/dist/role-prompts/executor.md +8 -0
  19. package/plugins/immune-brain/hooks/hooks.json +10 -1
  20. package/plugins/immune-brain/runtime/assurance/alias_file.ts +24 -0
  21. package/plugins/immune-brain/runtime/assurance/phase_entry.ts +143 -0
  22. package/plugins/immune-brain/runtime/claude/interaction.ts +5 -1
  23. package/plugins/immune-brain/runtime/claude/lane_guard.ts +180 -3
  24. package/plugins/immune-brain/runtime/claude/mcp_server.ts +21 -3
  25. package/plugins/immune-brain/runtime/claude/review_host.ts +6 -6
  26. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  27. package/plugins/immune-brain/runtime/prompts/code-review.md +5 -0
  28. package/plugins/immune-brain/runtime/prompts/executor.md +8 -0
  29. package/plugins/immune-brain/skills/BASELINE.md +1 -1
  30. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +2 -1
  31. package/plugins/immune-brain/skills/imm-planner/SKILL.md +5 -5
  32. package/plugins/immune-brain/skills/imm-run/SKILL.md +2 -1
  33. package/plugins/immune-brain/skills/imm-test-prune/SKILL.md +28 -0
  34. package/plugins/immune-brain/skills/registry.yaml +9 -0
package/README.md CHANGED
@@ -28,7 +28,7 @@ Pi and Claude Code are the supported hosts. Undeclared adapters remain unsupport
28
28
  - [Recommended Workflows: Composing with mattpocock/skills](#recommended-workflows-composing-with-mattpocockskills)
29
29
  - [Real-World Case Studies](#real-world-case-studies)
30
30
  - [Core Philosophy: Turn "Judgment" into "Table Lookup"](#core-philosophy-turn-judgment-into-table-lookup-harnessing-multi-model-tiers-with-determinism)
31
- - [The 7 Skills](#the-7-skills)
31
+ - [The 8 Skills](#the-8-skills)
32
32
  - [Lifecycle](#lifecycle)
33
33
  - [Unattended Batch Runs](#unattended-batch-runs)
34
34
  - [Configuration](#configuration)
@@ -54,7 +54,7 @@ Pi discovers Skills and extensions from `package.json` (or your global Pi config
54
54
  }
55
55
  ```
56
56
 
57
- No extra server config is needed. Installing the package via Pi makes all 7 Skills available automatically.
57
+ No extra server config is needed. Installing the package via Pi makes all 8 Skills available automatically.
58
58
 
59
59
  ### In Claude Code
60
60
 
@@ -123,6 +123,7 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
123
123
  | PR has review comments or failing CI | `/imm-pr-fix` on that PR | → Standalone repair: minimal scoped fix in place, no managed task created |
124
124
  | Project docs out of date | `/imm-doc-prune` | → Read-only audit; deletes only user-approved stale docs from manifest |
125
125
  | Agent instructions bloated | `/imm-doc-slim` | → Minimizes tracked `AGENTS.md` / `CLAUDE.md` to essential non-discoverable rules |
126
+ | Test suite bloated with redundant cases | `/imm-test-prune` | → Audits tests read-only, then merges or deletes only approved entries backed by retirement or coverage evidence |
126
127
  | Which model's edits keep coming back for review | `/imm-retro` | → Ranks models by review load and reports project usage from session logs |
127
128
 
128
129
  > **Core Principle: Skill-Explicit Entry**
@@ -412,7 +413,7 @@ Without explicit cost levers, multi-agent workflows compound expenses exponentia
412
413
 
413
414
  ---
414
415
 
415
- ## The 7 Skills
416
+ ## The 8 Skills
416
417
 
417
418
  | Skill | Type | When to use | What it does |
418
419
  |---|---|---|---|
@@ -422,11 +423,12 @@ Without explicit cost levers, multi-agent workflows compound expenses exponentia
422
423
  | `imm-pr-fix` | Standalone | CI failed / review comments on a PR | Repairs one PR in place, no managed authority |
423
424
  | `imm-doc-prune` | Standalone | Stale current docs | Deletes only the hash-approved manifest entries |
424
425
  | `imm-doc-slim` | Standalone | Bloated agent instructions | Minimizes tracked AGENTS/CLAUDE/GEMINI.md to necessary context |
426
+ | `imm-test-prune` | Standalone | Bloated or redundant tests | Merges, parameterizes, or deletes approved test entries without weakening assertions |
425
427
  | `imm-retro` | Standalone | Compare models by review load | Ranks authors of reviewed code and reports project usage |
426
428
 
427
429
  Internal roles (Executor, QA, Review, Compounder) are dispatched by `imm-run` — you never invoke them directly.
428
430
 
429
- All 7 skills are invoked explicitly. For new features, start with `imm-brainstorm` (if requirements are uncertain) or `imm-planner` (if requirements are clear), then proceed to `imm-run` once enrolled.
431
+ All 8 skills are invoked explicitly. For new features, start with `imm-brainstorm` (if requirements are uncertain) or `imm-planner` (if requirements are clear), then proceed to `imm-run` once enrolled.
430
432
 
431
433
  ### Managed Path entries (brainstorm → planner → loop)
432
434
 
@@ -474,6 +476,11 @@ The three repair/maintenance skills are host-native: they never create a managed
474
476
  - **Trigger:** explicit request to minimize tracked `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`.
475
477
  - **What it does:** keeps only the necessary non-discoverable rules in agent instruction files, under the same read-only-audit + hash-bound-manifest-approval model as `imm-doc-prune`.
476
478
 
479
+ #### `imm-test-prune` — test suite pruning
480
+
481
+ - **Trigger:** explicit request to audit or prune redundant, bloated, or orphaned tests.
482
+ - **What it does:** inventories tests with the repository's own test and coverage commands, then merges, parameterizes, or deletes only entries you approved in an exact hash-bound manifest. Deletion needs retirement proof or coverage-backed subsumption; without coverage tooling those entries stay unverified. It never weakens an assertion or touches production source.
483
+
477
484
  #### `imm-retro` — review load and project usage
478
485
 
479
486
  - **Trigger:** explicit request for a cross-model review retro or project usage look-back.
@@ -652,7 +659,7 @@ See [`docs/reference/immune-brain-config.md`](docs/reference/immune-brain-config
652
659
  package.json # Pi package manifest (skills + extensions)
653
660
  plugins/immune-brain/
654
661
  ├── .pi-extension/ # Pi TUI + Kernel authority extension
655
- ├── skills/ # 7 public Skills (trigger shims)
662
+ ├── skills/ # 8 public Skills (trigger shims)
656
663
  ├── dist/ # Built skill contracts & references
657
664
  ├── runtime/ # Bun + TypeScript runtime & Kernel
658
665
  └── bin/ # CLI wrappers (→ runtime/v4_runtime.ts)
@@ -670,7 +677,7 @@ docs/specs/ # Living specs (updated in place)
670
677
 
671
678
  ## FAQ
672
679
 
673
- **Do I need to learn all 7 skills?** No. Most of the time you only need `/imm-planner` (to plan and enroll a task) and `/imm-run` (to build and verify it). Use `imm-brainstorm` when requirements need clarifying first, and the maintenance skills (`imm-pr-fix`, etc.) only when specific repair needs arise. Ordinary chat and simple edits don't need any skills at all.
680
+ **Do I need to learn all 8 skills?** No. Most of the time you only need `/imm-planner` (to plan and enroll a task) and `/imm-run` (to build and verify it). Use `imm-brainstorm` when requirements need clarifying first, and the maintenance skills (`imm-pr-fix`, etc.) only when specific repair needs arise. Ordinary chat and simple edits don't need any skills at all.
674
681
 
675
682
  **What if I interrupt or close the session mid-task?** State is safely stored on disk (`.imm/` + TaskIntent). In Pi or Claude Code, simply re-enter `/imm-run` to resume — the Kernel projection is authoritative.
676
683
 
package/README.zh-CN.md CHANGED
@@ -28,7 +28,7 @@ Pi 与 Claude Code 是支持的宿主。未声明的适配器仍不受支持。C
28
28
  - [推荐工作流:与 mattpocock/skills 组合使用](#推荐工作流与-mattpocockskills-组合使用)
29
29
  - [实战案例](#实战案例)
30
30
  - [核心设计哲学:把“判断”变成“查表”](#核心设计哲学把判断变成查表用确定性工程驾驭多模型)
31
- - [7 个 Skills](#7-个-skills)
31
+ - [8 个 Skills](#8-个-skills)
32
32
  - [生命周期](#生命周期)
33
33
  - [无人值守批次运行](#无人值守批次运行)
34
34
  - [配置](#配置)
@@ -54,7 +54,7 @@ Pi 通过 `package.json`(或全局 Pi 配置)自动发现 Skills 与扩展
54
54
  }
55
55
  ```
56
56
 
57
- 无需额外 server 配置,通过 Pi 安装本 package 后 7 个 Skill 即自动可用。
57
+ 无需额外 server 配置,通过 Pi 安装本 package 后 8 个 Skill 即自动可用。
58
58
 
59
59
  ### 在 Claude Code 中使用
60
60
 
@@ -123,6 +123,7 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
123
123
  | PR 被评论 / CI 挂了 | 对该 PR 使用 `/imm-pr-fix` | → 独立修复:在当前 PR 内针对性修复,不创建新 managed 任务 |
124
124
  | 文档过时需要清理 | `/imm-doc-prune` | → 只读审计过时文档,仅删除经哈希审批的条目 |
125
125
  | Agent 指令文件膨胀 | `/imm-doc-slim` | → 将 tracked `AGENTS.md` / `CLAUDE.md` 压到最小必要上下文 |
126
+ | 测试用例膨胀、重复 | `/imm-test-prune` | → 只读审计测试,仅合并或删除经审批、有退休或覆盖率证据的条目 |
126
127
  | 想知道哪个模型的改动总被审查 | `/imm-retro` | → 按模型排名审查负载,并从 session logs 汇报项目使用量 |
127
128
 
128
129
  > **核心原则:Skill 显式调用**
@@ -415,7 +416,7 @@ Immune-Brain 的核心假设是:**不要让弱模型做架构决策,把它
415
416
 
416
417
  ---
417
418
 
418
- ## 7 个 Skills
419
+ ## 8 个 Skills
419
420
 
420
421
  | Skill | 类型 | 何时使用 | 职责 |
421
422
  |---|---|---|---|
@@ -425,11 +426,12 @@ Immune-Brain 的核心假设是:**不要让弱模型做架构决策,把它
425
426
  | `imm-pr-fix` | 独立 | PR 需修复 | 原地修复单个 PR,不触及 managed authority |
426
427
  | `imm-doc-prune` | 独立 | 清理过时文档 | 仅删除哈希绑定的 manifest 条目 |
427
428
  | `imm-doc-slim` | 独立 | Agent instruction 膨胀 | 将 tracked AGENTS/CLAUDE/GEMINI.md 压到最小必要上下文 |
429
+ | `imm-test-prune` | 独立 | 测试膨胀或重复 | 合并、参数化或删除经审批的测试条目,不弱化断言 |
428
430
  | `imm-retro` | 独立 | 比较模型的审查负载 | 排名被审查代码的作者并汇报项目使用量 |
429
431
 
430
432
  Executor、QA、Review、Compounder 等为 `imm-run` 内部调度的角色,无需手动调用。
431
433
 
432
- 所有 7 个 Skill 均显式调用。新需求开发时:若需求含糊先调 `imm-brainstorm`,目标清晰直接调 `imm-planner`,完成确认后调 `imm-run` 推进闭环。
434
+ 所有 8 个 Skill 均显式调用。新需求开发时:若需求含糊先调 `imm-brainstorm`,目标清晰直接调 `imm-planner`,完成确认后调 `imm-run` 推进闭环。
433
435
 
434
436
  ### Managed Path 入口(brainstorm → planner → loop)
435
437
 
@@ -477,6 +479,11 @@ Executor、QA、Review、Compounder 等为 `imm-run` 内部调度的角色,无
477
479
  - **触发方式:** 显式要求精简版本控制下的 `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`。
478
480
  - **职责:** 遵循与 `imm-doc-prune` 相同的「只读审计 + 哈希清单审批」模式,仅保留无法直接推导的必要规则。
479
481
 
482
+ #### `imm-test-prune` — 测试套件瘦身
483
+
484
+ - **触发方式:** 显式要求审计或清理冗余、膨胀或孤立的测试。
485
+ - **职责:** 使用仓库自身的测试与覆盖率命令盘点测试,按用户明确审批的哈希绑定 manifest 合并、参数化或删除条目。删除需要行为退休证明或覆盖率支撑的包含关系;项目没有覆盖率工具时这类条目保持 unverified。从不弱化断言,也不修改生产代码。
486
+
480
487
  #### `imm-retro` — 审查负载与项目使用回顾
481
488
 
482
489
  - **触发方式:** 显式要求跨模型审查复盘或项目使用量回顾。
@@ -660,7 +667,7 @@ Immune-Brain **没有独立配置文件**,偏好设置写在仓库根目录下
660
667
  package.json # Pi package manifest(skills + extensions)
661
668
  plugins/immune-brain/
662
669
  ├── .pi-extension/ # Pi TUI + Kernel 扩展
663
- ├── skills/ # 7 个公开 Skills(触发 shim)
670
+ ├── skills/ # 8 个公开 Skills(触发 shim)
664
671
  ├── dist/ # 构建后的 skill 契约与参考文档
665
672
  ├── runtime/ # Bun + TypeScript 运行时与 Kernel
666
673
  └── bin/ # CLI wrappers(→ runtime/v4_runtime.ts)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "5.1.2",
3
+ "version": "5.2.0",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "5.1.2",
3
+ "version": "5.2.0",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -26,7 +26,7 @@ import {
26
26
  enrollmentGitBaseNotice,
27
27
  initializeEnrollmentGitBase,
28
28
  } from "../runtime/assurance/enrollment_git_base";
29
- import { readTaskIntentForRecord } from "./imm-canary-work";
29
+ import { piRunEntryRefusal, readTaskIntentForRecord } from "./imm-canary-work";
30
30
  import {
31
31
  presentTaskRail,
32
32
  presentTaskRailResult,
@@ -609,7 +609,7 @@ export default function (pi: ExtensionAPI) {
609
609
  description: "Run one exact Kernel new-task enrollment synchronously in the foreground with host cancellation before authority commit.",
610
610
  promptSnippet: "Kernel enrollment: invoke once in foreground and consume the direct terminal result.",
611
611
  promptGuidelines: [
612
- "Call from the Parent after natural-language routing selects Enrollment; execute once in the foreground and consume the terminal Tool result.",
612
+ "Call from the Parent only as the first step of an explicit imm-run entry, never from imm-planner or imm-brainstorm; execute once in the foreground and consume the terminal Tool result.",
613
613
  "Do not run this Tool in background, poll for completion, or issue a cancel subcommand; host cancellation is the only pre-commit cancellation path.",
614
614
  ],
615
615
  parameters: Type.Object(
@@ -647,6 +647,18 @@ export default function (pi: ExtensionAPI) {
647
647
  message: "imm_canary_enrollment is TUI-only",
648
648
  next_action: "invoke the TUI launcher",
649
649
  });
650
+ // The phase entry gate (ADR 0019), before any preparation or dialog.
651
+ const entryRefusal = piRunEntryRefusal("enroll", ctx);
652
+ if (entryRefusal)
653
+ throwToolFailure({
654
+ tool: "imm_canary_enrollment",
655
+ task_id: taskId,
656
+ operation: action,
657
+ state: "blocked",
658
+ code: entryRefusal.reason_code,
659
+ message: entryRefusal.detail,
660
+ next_action: entryRefusal.recovery_action,
661
+ });
650
662
  const authorityRegistry = await registry();
651
663
  const result = await coordinator.run(action, taskId, signal, (foregroundSignal, beginCommit) =>
652
664
  executeForegroundEnrollment(
@@ -21,6 +21,7 @@ import type { ConfirmationReferenceSource } from "../runtime/assurance/host_port
21
21
  import { execFileSync } from "node:child_process";
22
22
  import { createHash, randomUUID } from "node:crypto";
23
23
  import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
24
+ import { homedir } from "node:os";
24
25
  import { join, resolve } from "node:path";
25
26
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
26
27
  import { Type } from "typebox";
@@ -35,6 +36,7 @@ import {
35
36
  writeNativeReviewEvidence,
36
37
  } from "./pi-canary-review-bundle";
37
38
  import type { InvocationToken } from "./pi-canary-invocations";
39
+ import { readAliasFile } from "../runtime/assurance/alias_file";
38
40
  import { runDeterministicQa } from "../runtime/assurance/qa";
39
41
  import {
40
42
  captureStagedIntent,
@@ -132,6 +134,15 @@ import {
132
134
  import { reviewReworkFindings } from "../runtime/assurance/coordinator";
133
135
  import { laneWriteRefusal } from "../runtime/unattended/lane_workspace";
134
136
  import { activeTaskNotice } from "../runtime/assurance/active_task_notice";
137
+ import {
138
+ admitPhaseOperation,
139
+ applyPrompt,
140
+ NO_PHASE_ENTRY,
141
+ recognizePhaseEntry,
142
+ type AliasReader,
143
+ type PhaseAdmission,
144
+ type PhaseEntryState,
145
+ } from "../runtime/assurance/phase_entry";
135
146
 
136
147
  const LOOP_OWNERS = ["plan", "kernel", "brainstorm", "planner", "loop"] as const;
137
148
  const LOOP_TARGETS = [
@@ -287,6 +298,67 @@ export async function advancePiTask(root: string, taskId: string): Promise<any>
287
298
  return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
288
299
  }
289
300
 
301
+ const GLOBAL_PI_PHASE_ENTRY_KEY = Symbol.for("immune_brain.pi_phase_entry");
302
+
303
+ type PhaseEntryContext = { cwd?: string; sessionManager?: { getSessionId?: () => unknown } } | undefined;
304
+
305
+ // Pi loads each extension file as its own module graph, so the entry state
306
+ // lives under a global key that every copy of this module shares. It is
307
+ // process memory: a resumed session starts without an entry and refuses.
308
+ function piPhaseEntries(): Map<string, PhaseEntryState> {
309
+ const holder = globalThis as unknown as Record<symbol, Map<string, PhaseEntryState> | undefined>;
310
+ return holder[GLOBAL_PI_PHASE_ENTRY_KEY] ??= new Map();
311
+ }
312
+
313
+ function piSessionId(ctx: PhaseEntryContext): string {
314
+ try {
315
+ const id = ctx?.sessionManager?.getSessionId?.();
316
+ return typeof id === "string" ? id : "";
317
+ } catch {
318
+ return "";
319
+ }
320
+ }
321
+
322
+ /** Prompt templates in the order Pi resolves them: the user's, then the project's. */
323
+ function piAliasReader(cwd: string): AliasReader {
324
+ const agentDir = process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
325
+ return (name) => {
326
+ for (const dir of [join(agentDir, "prompts"), join(cwd, ".pi", "prompts")]) {
327
+ const body = readAliasFile(join(dir, `${name}.md`));
328
+ if (body !== null) return body;
329
+ }
330
+ return null;
331
+ };
332
+ }
333
+
334
+ /**
335
+ * One Host input: record whether this literal prompt entered a phase. Input an
336
+ * extension injected is never the user's and leaves the entry state untouched.
337
+ */
338
+ export function recordPiPhaseInput(event: { text?: unknown; source?: unknown }, ctx: PhaseEntryContext): void {
339
+ if (event.source === "extension") return;
340
+ const sessionId = piSessionId(ctx);
341
+ if (!sessionId) return;
342
+ const entries = piPhaseEntries();
343
+ const text = typeof event.text === "string" ? event.text : "";
344
+ const entry = recognizePhaseEntry(text, piAliasReader(ctx?.cwd ?? process.cwd()));
345
+ entries.set(sessionId, applyPrompt(entries.get(sessionId) ?? NO_PHASE_ENTRY, entry));
346
+ }
347
+
348
+ /**
349
+ * The phase entry gate for a Pi tool, checked before any preparation or
350
+ * authority dialog. A session without an identity refuses.
351
+ */
352
+ export function piRunEntryRefusal(
353
+ operation: "enroll" | "start_unattended_batch",
354
+ ctx: PhaseEntryContext,
355
+ ): Extract<PhaseAdmission, { ok: false }> | null {
356
+ const sessionId = piSessionId(ctx);
357
+ const state = sessionId ? piPhaseEntries().get(sessionId) ?? NO_PHASE_ENTRY : null;
358
+ const admission = admitPhaseOperation({ kind: "tool", name: operation }, state, { activeClaim: false });
359
+ return admission.ok ? null : admission;
360
+ }
361
+
290
362
  export default function (
291
363
  pi: ExtensionAPI,
292
364
  dependencies: CanaryWorkExtensionDependencies = {},
@@ -342,6 +414,7 @@ export default function (
342
414
  });
343
415
  pi.on("input", async (event, ctx) => {
344
416
  if (event.source === "extension") return { action: "continue" } as const;
417
+ recordPiPhaseInput(event, ctx);
345
418
  railContext = ctx;
346
419
  clearTerminalTaskRailOnInput(ctx);
347
420
  await refreshTaskRail(ctx);
@@ -20,7 +20,7 @@ import {
20
20
  import { createBatchKernelPort } from "../runtime/unattended/batch_kernel_port";
21
21
  import type { BatchRunnerGitPort } from "../runtime/unattended/batch_git";
22
22
  import type { InitiativeObservationReader } from "../runtime/unattended/types";
23
- import { advancePiTask } from "./imm-canary-work";
23
+ import { advancePiTask, piRunEntryRefusal } from "./imm-canary-work";
24
24
  import { batchReason } from "../runtime/unattended/batch_reasons";
25
25
  import { parseFinalVerification } from "../runtime/unattended/batch_final_verification";
26
26
  import { parseLaneRevision, REVISION_DELEGATION_TEXT, type LaneRevisionRequest } from "../runtime/unattended/batch_delegation";
@@ -427,7 +427,7 @@ export default function (
427
427
  description: "Start an unattended serial batch run for an Initiative after native confirmation.",
428
428
  promptSnippet: "Start unattended batch run: invoke once in foreground after plan confirmation.",
429
429
  promptGuidelines: [
430
- "Call only after plan confirmation; execute once in the foreground and consume the direct terminal result.",
430
+ "Call only inside an explicit imm-run entry, after plan confirmation; execute once in the foreground and consume the direct terminal result.",
431
431
  "Do not run this Tool in background, poll for completion, or issue a cancel subcommand.",
432
432
  ],
433
433
  parameters: Type.Object({
@@ -469,6 +469,19 @@ export default function (
469
469
  next_action: refusal.recovery_action,
470
470
  });
471
471
  }
472
+ // The phase entry gate (ADR 0019), before any preflight or dialog. The
473
+ // batch's own reuseOnly continuation never passes through this Tool.
474
+ const entryRefusal = piRunEntryRefusal("start_unattended_batch", ctx);
475
+ if (entryRefusal)
476
+ throwToolFailure({
477
+ tool: "imm_canary_enrollment",
478
+ task_id: initiativeSlug,
479
+ operation: "start_unattended_batch",
480
+ state: "blocked",
481
+ code: entryRefusal.reason_code,
482
+ message: entryRefusal.detail,
483
+ next_action: entryRefusal.recovery_action,
484
+ });
472
485
 
473
486
  const maxParallel = parseMaxParallel(params.max_parallel);
474
487
  const laneOffers = parseLaneOffers(params.lane_offers);
@@ -32,6 +32,11 @@ material task-local maintenance risks. Pure naming, length, complexity
32
32
  thresholds, formatting, and design preference are not findings and must not
33
33
  cause style-only rework.
34
34
 
35
+ Test-suite growth is advisory, never blocking: note a delivered test that
36
+ duplicates an existing assertion, asserts only private implementation, or
37
+ leaves a test protecting behavior the change retired. Weakened or missing
38
+ tests for an acceptance remain blocking under the rules above.
39
+
35
40
  When a finding repeats an acceptance id or anchor that already produced rework in
36
41
  this task, do not file it as a fresh single-trigger defect. Name the invariant
37
42
  class it belongs to and state in `evidence.trigger` why the previously accepted
@@ -108,7 +108,7 @@ An existing grant for one of these never inherits another: a stage-only
108
108
  instruction stays stage-only, a commit is not push or publication authority,
109
109
  and a valid batch capability commits only its own scope-bounded child.
110
110
 
111
- Routine Managed enrollment uses one current-Host native confirmation bound to the TaskIntent content hash after Planner validation. Explicit Plan-only requests stop with candidate artifacts and do not invoke Enrollment; execution-bearing requests open the native gate directly without chat pre-confirmation. Enrollment validates intent, Git ownership, scope, workspace claim, and final authority preconditions without executing acceptance descriptors; deterministic QA executes them after implementation. The routine task proceeds from that single confirmation through enrollment, execution, and QA without a second human stop. Do not request confirmation for local in-scope edits, local verification, ordinary Direct rework, scoped diff review, or completion reporting. Managed evidence, QA, Review, and completion authority remain governed by their Managed contracts; R2 does not weaken them. Managed native-authority failures fail closed with one stable reason and exactly one same-Host recovery action; never offer a Pi, Direct Path, cross-Host/worktree, unmanaged, or automatic-retry fallback.
111
+ Routine Managed enrollment is the first step of an explicit `imm-run` entry and uses one current-Host native confirmation bound to the TaskIntent content hash after Planner validation. Planner and Brainstorm stop with candidate artifacts and never invoke Enrollment, whether or not the request also asks for execution; explicit Plan-only requests stop the same way. `imm-run` opens the native gate directly without chat pre-confirmation. Enrollment validates intent, Git ownership, scope, workspace claim, and final authority preconditions without executing acceptance descriptors; deterministic QA executes them after implementation. Once the user has entered `imm-run`, the routine task proceeds from that single confirmation through enrollment, execution, and QA with no further confirmation. Do not request confirmation for local in-scope edits, local verification, ordinary Direct rework, scoped diff review, or completion reporting. Managed evidence, QA, Review, and completion authority remain governed by their Managed contracts; R2 does not weaken them. Managed native-authority failures fail closed with one stable reason and exactly one same-Host recovery action; never offer a Pi, Direct Path, cross-Host/worktree, unmanaged, or automatic-retry fallback.
112
112
 
113
113
  For an unborn symbolic Git HEAD, the same Enrollment confirmation discloses an empty root commit. After approval, the Host creates that commit without staging project content or changing the index/worktree, then revalidates ownership and rehearses Enrollment against the new base. Git identity uses existing environment/config values with an explicit automation fallback, never config writes. Decline or pre-initialization cancellation leaves HEAD unborn; if later Enrollment fails or is cancelled, report the remaining commit. Invalid Git refs are errors, not initialization candidates.
114
114