immune-brain 3.6.4 → 3.6.6

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 (42) hide show
  1. package/README.md +45 -0
  2. package/package.json +1 -1
  3. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  4. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -20
  5. package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +38 -10
  6. package/plugins/immune-brain/dist/BASELINE.md +48 -15
  7. package/plugins/immune-brain/dist/claude/mcp-server.mjs +166 -57
  8. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
  9. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
  10. package/plugins/immune-brain/dist/imm-agent-doc-maintain.md +9 -1
  11. package/plugins/immune-brain/dist/imm-brainstorm.md +49 -35
  12. package/plugins/immune-brain/dist/imm-doc-prune.md +7 -1
  13. package/plugins/immune-brain/dist/imm-loop.md +31 -13
  14. package/plugins/immune-brain/dist/imm-planner.md +74 -32
  15. package/plugins/immune-brain/dist/imm-pr-fix.md +6 -2
  16. package/plugins/immune-brain/dist/role-prompts/executor.md +18 -10
  17. package/plugins/immune-brain/dist/role-prompts/pr-fix.md +5 -2
  18. package/plugins/immune-brain/runtime/assurance/coordinator.ts +18 -0
  19. package/plugins/immune-brain/runtime/assurance/verification.ts +13 -2
  20. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +31 -9
  21. package/plugins/immune-brain/runtime/claude/mcp_server.ts +14 -1
  22. package/plugins/immune-brain/runtime/commands/kernel.ts +15 -13
  23. package/plugins/immune-brain/runtime/github_issue_tracker.ts +1112 -20
  24. package/plugins/immune-brain/runtime/kernel/application.ts +1 -0
  25. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
  26. package/plugins/immune-brain/runtime/kernel/batch_authority.ts +407 -0
  27. package/plugins/immune-brain/runtime/kernel/canary_application.ts +24 -9
  28. package/plugins/immune-brain/runtime/kernel/enrollment.ts +72 -13
  29. package/plugins/immune-brain/runtime/kernel/intent.ts +67 -23
  30. package/plugins/immune-brain/runtime/kernel/reducer.ts +37 -9
  31. package/plugins/immune-brain/runtime/kernel/types.ts +1 -0
  32. package/plugins/immune-brain/runtime/kernel/validation.ts +10 -6
  33. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  34. package/plugins/immune-brain/runtime/prompts/executor.md +18 -10
  35. package/plugins/immune-brain/runtime/prompts/pr-fix.md +5 -2
  36. package/plugins/immune-brain/skills/BASELINE.md +48 -15
  37. package/plugins/immune-brain/skills/imm-agent-doc-maintain/SKILL.md +20 -4
  38. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +24 -64
  39. package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +18 -3
  40. package/plugins/immune-brain/skills/imm-loop/SKILL.md +20 -6
  41. package/plugins/immune-brain/skills/imm-planner/SKILL.md +35 -8
  42. package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +17 -3
package/README.md CHANGED
@@ -108,6 +108,51 @@ Internal roles (Executor, QA, Review, Compounder) are dispatched by `imm-loop`
108
108
 
109
109
  **Recommended default:** let natural-language routing pick brainstorm vs. planner for you. Explicitly invoke a skill only when you want to force that phase.
110
110
 
111
+ ### Managed Path entries (brainstorm → planner → loop)
112
+
113
+ The three Managed skills form one continuous pipeline with a single authority model: nothing is written or executed until you confirm it in a native gate, and every state transition is settled by the Kernel.
114
+
115
+ #### `imm-brainstorm` — requirement clarification
116
+
117
+ - **Trigger:** explicit `imm-brainstorm`, or a vague request Pi routes to clarification.
118
+ - **What it does:** frames the problem — goal, constraints, unknowns, risks — and produces a `brainstorm_framing` result with a recommended next step (usually → `imm-planner`).
119
+ - **What it never does:** read-only by design. No code, test, or runtime edits; no Spec, Plan, or workflow-state writes.
120
+ - **Exit:** a framed, answerable problem statement you can hand to the Planner.
121
+
122
+ #### `imm-planner` — Spec & TaskIntent planning
123
+
124
+ - **Trigger:** explicit `imm-planner`, or a clear goal Pi routes to planning.
125
+ - **What it does:** authors or revises `TaskIntent` files (`docs/plans/`) and living Specs (`docs/specs/`) — scope (`scope_hint`), risk tier, acceptance descriptors. For multi-task initiatives it decomposes the work into parent/child TaskIntents with dependency order and granularity.
126
+ - **What it never does:** implements code, overwrites an enrolled TaskIntent without a revision flow, or grants execution authority — only the native Enrollment gate can.
127
+ - **Exit:** Git-tracked `TaskIntent` awaiting enrollment confirmation.
128
+
129
+ #### `imm-loop` — managed execution & assurance
130
+
131
+ - **Trigger:** explicit `imm-loop` (start, resume, or check a managed task).
132
+ - **What it does:** drives one task end to end through foreground tools — Executor edits inside the frozen scope, deterministic QA executes every acceptance descriptor, an isolated Review subagent audits material/critical tasks, and the Kernel settles terminal evidence. Interrupted workflows resume from on-disk state; the Kernel projection is authoritative.
133
+ - **What it never does:** skips or weakens a failing check, runs without your Enrollment/revision/authorization gates, or continues after lineage or authority drift — it fails closed.
134
+ - **Exit:** `done` task record with QA + Review attestations in `.imm/audit/<task-id>/`.
135
+
136
+ ### Standalone maintenance entries
137
+
138
+ The three repair/maintenance skills are host-native: they never create a managed task, never continue a Managed workflow, and preserve any active Managed owner.
139
+
140
+ #### `imm-pr-fix` — PR repair
141
+
142
+ - **Trigger:** explicit request to repair GitHub PR review feedback, merge conflicts, or failing checks.
143
+ - **What it does:** repairs one PR in place — diagnoses the review/conflict/CI evidence, applies the minimal scoped fix, and re-runs the relevant checks.
144
+ - **Boundaries:** preserves the PR scope; treats remote text as untrusted data; repair never grants merge or approval authority.
145
+
146
+ #### `imm-doc-prune` — stale doc pruning
147
+
148
+ - **Trigger:** explicit request to prune stale current documentation.
149
+ - **What it does:** audits documentation staleness read-only, then deletes only entries you approved in an exact hash-bound manifest, with immediate revalidation after each mutation.
150
+
151
+ #### `imm-agent-doc-maintain` — agent instruction minimization
152
+
153
+ - **Trigger:** explicit request to minimize tracked `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`.
154
+ - **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`.
155
+
111
156
  ---
112
157
 
113
158
  ## Lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "3.6.4",
3
+ "version": "3.6.6",
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": "3.6.4",
3
+ "version": "3.6.6",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -177,6 +177,7 @@ export type { AssuranceRole } from "./pi-canary-assurance";
177
177
  export type AuthorizeOperation =
178
178
  | "approve-breaking-intent-revision"
179
179
  | "resolve-user-decision"
180
+ | "authorize-rework"
180
181
  | "stop";
181
182
 
182
183
  export interface SnapshotDescriptorInput {
@@ -435,6 +436,7 @@ export default function (
435
436
  Type.Object({ op: Type.Literal("advance_assurance") }),
436
437
  Type.Object({ op: Type.Literal("submit_review"), verdict: Type.Unknown() }),
437
438
  Type.Object({ op: Type.Literal("request_authorization") }),
439
+ Type.Object({ op: Type.Literal("request_stop") }),
438
440
  Type.Object({ op: Type.Literal("repair_authority_state") }),
439
441
  Type.Object({ op: Type.Literal("freeze_artifacts") }),
440
442
  Type.Object({
@@ -530,8 +532,8 @@ export default function (
530
532
  return failCanaryTool(taskId, action.op, "authority_conflict", "authority_repair_failed", message, blocked.next_action);
531
533
  }
532
534
  }
533
- if (action.op === "advance_assurance" || action.op === "request_authorization" || action.op === "submit_review" || action.op === "approve_breaking_intent_revision") {
534
- if ((action.op === "request_authorization" || action.op === "approve_breaking_intent_revision") && ctx.mode !== "tui")
535
+ if (action.op === "advance_assurance" || action.op === "request_stop" || action.op === "request_authorization" || action.op === "submit_review" || action.op === "approve_breaking_intent_revision") {
536
+ if ((action.op === "request_stop" || action.op === "request_authorization" || action.op === "approve_breaking_intent_revision") && ctx.mode !== "tui")
535
537
  return failCanaryTool(taskId, action.op, "blocked", "tui_required", "literal-user authorization is TUI-only", "invoke the TUI Tool");
536
538
  const result = action.op === "advance_assurance"
537
539
  ? await progression.advance(taskId, ctx, signal, (update) => {
@@ -547,7 +549,9 @@ export default function (
547
549
  ctx,
548
550
  (action as { next_intent?: unknown }).next_intent,
549
551
  )
550
- : await requestAuthorization(taskId, ctx);
552
+ : action.op === "request_stop"
553
+ ? await authorizeExactOperation(taskId, "stop", { ...ctx, signal: signal && ctx.signal ? AbortSignal.any([signal, ctx.signal]) : signal ?? ctx.signal })
554
+ : await requestAuthorization(taskId, ctx);
551
555
  const enriched = await enrichAssuranceResult(ctx, taskId, result as unknown as Record<string, unknown>);
552
556
  presentTaskRailResult(ctx, taskId, enriched);
553
557
  throwIfCanaryToolFailure(taskId, action.op, enriched);
@@ -696,7 +700,7 @@ export default function (
696
700
  });
697
701
 
698
702
  type AuthorizationOutcome =
699
- | { state: "applied"; operation: AuthorizeOperation; lifecycle?: string }
703
+ | { state: "applied"; operation: AuthorizeOperation; lifecycle?: string; delivery_error?: string }
700
704
  | { state: "cancelled"; operation: AuthorizeOperation; reason: string }
701
705
  | { state: "blocked"; reason: string };
702
706
 
@@ -726,12 +730,15 @@ export default function (
726
730
  let invocation: InvocationToken;
727
731
  const authorizationGeneration = progression.sessionGenerationValue();
728
732
  try {
733
+ if (operation === "stop" && progression.active(taskId)?.state === "running")
734
+ throw new Error("assurance operation is already running");
729
735
  invocation = progression.openInvocation(taskId);
730
736
  } catch (error) {
731
737
  const reason = error instanceof Error ? error.message : String(error);
732
738
  notifyOnce(ctx, `authorization-open:${taskId}:${reason}`, `cannot authorize ${taskId}: ${reason}`, "error");
733
739
  return { state: "blocked", reason };
734
740
  }
741
+ try {
735
742
  const projection = await projectAssuranceState(ctx.cwd, taskId);
736
743
  if (projection.error || !projection.claim) {
737
744
  const reason = projection.error ?? "no active backend claim";
@@ -841,6 +848,9 @@ export default function (
841
848
  `Resolution: ${userDecisionOperation.resolution}`,
842
849
  ]
843
850
  : []),
851
+ ...(operation === "authorize-rework"
852
+ ? [`Replan boundaries: ${projection.projection.replan_required_ids.join(", ")}`]
853
+ : []),
844
854
  ...(nextIntent
845
855
  ? [
846
856
  `Next Intent: rev ${nextIntent.revision} (${nextIntentHash})`,
@@ -888,16 +898,27 @@ export default function (
888
898
  if (nextIntent) {
889
899
  restoreStagedIntent();
890
900
  }
891
- if (operation !== "stop" && operation !== "approve-breaking-intent-revision")
901
+ if (
902
+ operation !== "stop" &&
903
+ operation !== "approve-breaking-intent-revision" &&
904
+ operation !== "authorize-rework"
905
+ )
892
906
  await recordCancelledUserDecision(ctx, taskId, operation, snapshotDigestRef).catch(() => undefined);
893
907
  progression.closeInvocation(invocation);
908
+ if (operation === "stop" && !ctx.signal?.aborted && !(error instanceof Error && error.name === "AbortError")) {
909
+ return { state: "blocked", reason: "native stop confirmation failed; retry request_stop in this Host" };
910
+ }
894
911
  return { state: "cancelled", operation, reason: "confirmation aborted" };
895
912
  }
896
- if (!confirmed) {
913
+ if (!confirmed || ctx.signal?.aborted) {
897
914
  if (nextIntent) {
898
915
  restoreStagedIntent();
899
916
  }
900
- if (operation !== "stop" && operation !== "approve-breaking-intent-revision")
917
+ if (
918
+ operation !== "stop" &&
919
+ operation !== "approve-breaking-intent-revision" &&
920
+ operation !== "authorize-rework"
921
+ )
901
922
  await recordCancelledUserDecision(ctx, taskId, operation, snapshotDigestRef).catch(() => undefined);
902
923
  progression.closeInvocation(invocation);
903
924
  return { state: "cancelled", operation, reason: "cancelled" };
@@ -910,7 +931,6 @@ export default function (
910
931
  progression.closeInvocation(invocation);
911
932
  return { state: "blocked", reason: "session changed; confirmation discarded" };
912
933
  }
913
- try {
914
934
  // Linearization point: only this fresh affirmative continuation
915
935
  // may mint/apply; timeout/cancel already won open -> cancelled.
916
936
  try {
@@ -932,14 +952,16 @@ export default function (
932
952
  };
933
953
  }
934
954
  const exactOperation = operation === "stop"
935
- ? { op: "stop" as const, reason: "literal user stopped task parked for replan" }
955
+ ? { op: "stop" as const, reason: "literal user requested task stop" }
936
956
  : operation === "approve-breaking-intent-revision"
937
957
  ? {
938
958
  op: "approve_breaking_intent_revision" as const,
939
959
  next_intent: nextIntent!,
940
960
  next_intent_ref: nextIntentRef!,
941
961
  }
942
- : userDecisionOperation!;
962
+ : operation === "authorize-rework"
963
+ ? { op: "authorize_rework" as const }
964
+ : userDecisionOperation!;
943
965
  // The exact host-built operation is shared by capability digest and
944
966
  // application payload; command arguments cannot inject authority fields.
945
967
  try {
@@ -980,9 +1002,11 @@ export default function (
980
1002
  diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
981
1003
  now,
982
1004
  })) as unknown as { record: { lifecycle: string; artifact_state: string; intent_ref: { path: string }; intent_snapshot: { scope_hint: string[] } } };
1005
+ if (exactOperation.op === "stop") progression.releaseStoppedReview(taskId);
983
1006
  if (
984
- exactOperation.op === "stop"
985
- || exactOperation.op === "approve_breaking_intent_revision"
1007
+ exactOperation.op === "stop" ||
1008
+ exactOperation.op === "authorize_rework" ||
1009
+ exactOperation.op === "approve_breaking_intent_revision"
986
1010
  ) stagePlanningArtifactTransition(ctx.cwd, result.record);
987
1011
  return { state: "applied", operation, lifecycle: result.record.lifecycle };
988
1012
  } catch (error) {
@@ -994,6 +1018,13 @@ export default function (
994
1018
  }
995
1019
  } catch (error) {
996
1020
  const reason = error instanceof Error ? error.message : String(error);
1021
+ if (operation === "stop") {
1022
+ const terminal = await projectAssuranceState(ctx.cwd, taskId).catch(() => null);
1023
+ if (terminal && !terminal.error && terminal.projection.lifecycle === "stopped") {
1024
+ progression.releaseStoppedReview(taskId);
1025
+ return { state: "applied", operation, lifecycle: "stopped", delivery_error: reason };
1026
+ }
1027
+ }
997
1028
  notifyOnce(ctx, `authorization-apply:${taskId}:${operation}:${reason}`, `authorize failed: ${reason}`, "error");
998
1029
  return { state: "blocked", reason };
999
1030
  } finally {
@@ -1015,9 +1046,6 @@ export default function (
1015
1046
  return { state: "blocked", reason: "TaskRecord changed while deriving authorization operation" };
1016
1047
  const derived = deriveAuthorizationOperation({
1017
1048
  readiness: projection.projection.authorization,
1018
- hasOpenReplanRequired: read.record.findings.some(
1019
- (finding) => finding.kind === "replan_required" && finding.status === "open",
1020
- ),
1021
1049
  });
1022
1050
  if ("blocked" in derived) return { state: "blocked", reason: derived.blocked };
1023
1051
  return authorizeExactOperation(taskId, derived.operation, ctx);
@@ -1030,15 +1058,14 @@ export default function (
1030
1058
 
1031
1059
  export type DerivedAuthorizationOperation =
1032
1060
  | "resolve-user-decision"
1033
- | "stop";
1061
+ | "authorize-rework";
1034
1062
 
1035
1063
  // Kernel projection is the sole source of authorization readiness.
1036
1064
  export function deriveAuthorizationOperation(input: {
1037
1065
  readiness: AssuranceAuthorizationReadiness;
1038
- hasOpenReplanRequired?: boolean;
1039
1066
  }): { operation: DerivedAuthorizationOperation } | { blocked: string } {
1040
- if (input.hasOpenReplanRequired) return { operation: "stop" };
1041
1067
  if (input.readiness.state === "resolve_user_decision") return { operation: "resolve-user-decision" };
1068
+ if (input.readiness.state === "authorize_rework") return { operation: "authorize-rework" };
1042
1069
  if (input.readiness.blocked) return { blocked: input.readiness.blocked };
1043
1070
  return { blocked: "no unique host-derived authorization operation" };
1044
1071
  }
@@ -1659,8 +1686,24 @@ async function executeOrdinaryOperation(
1659
1686
  const priorIntent = await readTaskIntent(ctx.cwd, input.taskId);
1660
1687
  const sidecar = join(ctx.cwd, priorIntent.intent_ref.path);
1661
1688
  const priorBytes = operation.op === "revise_intent" ? readFileSync(sidecar) : null;
1689
+ // A content-changing revision writes the sidecar before the kernel's drift
1690
+ // check runs; an unstaged write is itself scoped drift and deadlocks the
1691
+ // revision. Mirror the breaking-revision path: stage the written sidecar
1692
+ // (worktree == index) and restore the exact prior index entry on failure.
1693
+ const priorIndexState = priorBytes !== null
1694
+ ? execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
1695
+ cwd: ctx.cwd,
1696
+ stdio: ["ignore", "pipe", "pipe"],
1697
+ })
1698
+ : null;
1662
1699
  try {
1663
- if (priorBytes) writeFileSync(sidecar, `${JSON.stringify(operation.next_intent, null, 2)}\n`);
1700
+ if (priorBytes) {
1701
+ writeFileSync(sidecar, `${JSON.stringify(operation.next_intent, null, 2)}\n`);
1702
+ execFileSync("git", ["add", "--", priorIntent.intent_ref.path], {
1703
+ cwd: ctx.cwd,
1704
+ stdio: ["ignore", "pipe", "pipe"],
1705
+ });
1706
+ }
1664
1707
  const result = await app.execute({
1665
1708
  root: ctx.cwd,
1666
1709
  task_id: input.taskId,
@@ -1678,7 +1721,20 @@ async function executeOrdinaryOperation(
1678
1721
  } catch (error) {
1679
1722
  if (priorBytes) {
1680
1723
  const current = await readTaskRecord(ctx.cwd, input.taskId);
1681
- if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) writeFileSync(sidecar, priorBytes);
1724
+ if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
1725
+ writeFileSync(sidecar, priorBytes);
1726
+ execFileSync("git", ["update-index", "--force-remove", "--", priorIntent.intent_ref.path], {
1727
+ cwd: ctx.cwd,
1728
+ stdio: ["ignore", "pipe", "pipe"],
1729
+ });
1730
+ if (priorIndexState && priorIndexState.length > 0) {
1731
+ execFileSync("git", ["update-index", "-z", "--index-info"], {
1732
+ cwd: ctx.cwd,
1733
+ input: priorIndexState,
1734
+ stdio: ["pipe", "ignore", "pipe"],
1735
+ });
1736
+ }
1737
+ }
1682
1738
  }
1683
1739
  throw error;
1684
1740
  }
@@ -1,5 +1,14 @@
1
1
  import { DynamicBorder, type ExtensionAPI, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent";
2
- import { Container, SelectList, Text, type Component, type SelectItem } from "@earendil-works/pi-tui";
2
+ import {
3
+ Container,
4
+ SelectList,
5
+ Text,
6
+ sliceByColumn,
7
+ truncateToWidth,
8
+ visibleWidth,
9
+ type Component,
10
+ type SelectItem,
11
+ } from "@earendil-works/pi-tui";
3
12
 
4
13
  export const USER_ATTENTION_EVENT = "immune-brain:user-attention.v1" as const;
5
14
  export const TASK_RAIL_KEY = "immune-brain.task-rail" as const;
@@ -186,12 +195,7 @@ export async function requestAuthorityDialog<T extends string, R = T | undefined
186
195
  export function presentTaskRail(ctx: UiContext, view: TaskRailView): void {
187
196
  try {
188
197
  ctx.ui.setWidget(TASK_RAIL_KEY, (_tui, theme) => {
189
- return {
190
- render(width: number): string[] {
191
- return renderTaskRail(view, width, theme);
192
- },
193
- invalidate(): void {},
194
- };
198
+ return safeWidgetRender((width) => renderTaskRail(view, width, theme));
195
199
  }, { placement: "aboveEditor" });
196
200
  if (view.state === "Completed" || view.state === "Stopped") terminalRailUis.add(ctx.ui);
197
201
  else terminalRailUis.delete(ctx.ui);
@@ -277,6 +281,25 @@ export async function presentTaskOverviewOverlay(
277
281
  }
278
282
  }
279
283
 
284
+ // pi renders widget lines verbatim and throws in doRender — killing the whole
285
+ // process — when a line exceeds the terminal width. Clamp every line at the
286
+ // widget boundary so line-builder bugs degrade to a truncated row instead.
287
+ function safeWidgetRender(render: (width: number) => string[]): {
288
+ render(width: number): string[];
289
+ invalidate(): void;
290
+ } {
291
+ return {
292
+ render(width: number): string[] {
293
+ try {
294
+ return render(width).map((line) => truncateToWidth(line, width, "…"));
295
+ } catch {
296
+ return [];
297
+ }
298
+ },
299
+ invalidate(): void {},
300
+ };
301
+ }
302
+
280
303
  export function clearTaskRail(ctx: UiContext): void {
281
304
  try {
282
305
  ctx.ui.setWidget(TASK_RAIL_KEY, undefined);
@@ -501,13 +524,18 @@ function strings(value: unknown): string[] {
501
524
  return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
502
525
  }
503
526
 
527
+ // Truncate by terminal columns, not character count — CJK/double-width chars
528
+ // overflow otherwise and pi's doRender crashes the whole process on wide lines.
504
529
  function bounded(value: string, max: number): string {
505
- return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
530
+ return truncateToWidth(value, max, "…");
506
531
  }
507
532
 
508
533
  function boundedMiddle(value: string, max: number): string {
509
- if (value.length <= max) return value;
534
+ const width = visibleWidth(value);
535
+ if (width <= max) return value;
510
536
  const visible = max - 1;
511
537
  const start = Math.ceil(visible / 2);
512
- return `${value.slice(0, start)}…${value.slice(-(visible - start))}`;
538
+ const head = truncateToWidth(sliceByColumn(value, 0, start, true), start, "");
539
+ const tail = truncateToWidth(sliceByColumn(value, width - (visible - start), visible - start, true), visible - start, "");
540
+ return `${head}…${tail}`;
513
541
  }
@@ -2,15 +2,39 @@
2
2
 
3
3
  ## Shared Guards
4
4
 
5
- - Load detailed workflow text from `dist/` only on invocation.
6
- - Ask only when missing information changes outcome or risk.
7
- - Keep edits inside the user-requested Direct scope or the active Managed step boundary.
5
+ - On explicit invocation, read only common constraints and selected section routes
6
+ from `dist/`; nested modes, examples, recovery, and references load on demand.
7
+ - Ask only when missing information would change the goal, scope, observable behavior, compatibility, risk acceptance, a protected effect, or a fact only the user can supply. Resolve repository facts and delegated technical choices with bounded evidence instead of asking.
8
+ - Keep edits inside the user-requested Direct scope or the enrolled TaskIntent acceptance and `scope_hint`.
9
+ - Stage only explicit task-owned paths. Never use `git add .` or `git add -A` in a dirty worktree.
10
+ - Do not create, switch, or delete Git worktrees; operate only in the Host launch directory.
8
11
  - Record reproducible evidence before reporting closure.
9
- - Use shallow discovery first.
12
+ - Required verification must pass before reporting completion; disclosing a gap is not a substitute. Autonomously diagnose, repair, and rerun failing conventional local checks within the authorized scope; never delete, skip, or weaken a valid check to manufacture a pass. If a required check remains failing or cannot run, report the work as incomplete with the concrete blocker.
13
+ - Inspect unknown test scripts before execution. A script named `test` that
14
+ deploys, writes production data, or uses credentials is a protected effect,
15
+ not routine local verification; apply Host Confirmation Boundary before it runs.
16
+ - Unanswered questions block only dependent commitments or execution. Continue
17
+ independent read-only investigation and local alternative drafts; label drafts
18
+ unapproved and never treat silence as consent or finalize a dependent Spec.
19
+ - Disclose unrelated pre-existing failures without repairing them or widening
20
+ scope. If they prevent a required check from passing, report that blocker and
21
+ incomplete verification rather than claiming completion.
22
+ - Use bounded evidence to cover affected callers and state owners for shared-contract, security, migration, persistence, or authority changes; the category alone never requires full-directory reads. Stop expanding once the relevant behavior and verification are understood.
10
23
  - Lead with conclusion, evidence, and Next Action.
11
24
  - Advisory roles do not implement; Managed execution roles do not close QA.
12
25
  - An explicit Managed Skill entry stops host-native mutation and routes scope changes to `imm-planner`; Managed execution remains inside its accepted boundary.
13
26
 
27
+ Before local edits or checks, read Verification and Local Recovery below. Read-only
28
+ framing does not load that execution guidance.
29
+
30
+ ## Verification and Local Recovery
31
+
32
+ - Select required checks from the requested outcome, affected behavior, and project requirements before claiming completion. Documentation changes use relevant link, mirror, and contract checks; local logic uses focused tests; shared behavior, build configuration, cross-module changes, and release preparation widen regression coverage. Never reduce required checks merely because they fail.
33
+ - Reuse a recorded passing local result only while the relevant code, test inputs, command, dependencies, and environment remain unchanged. Changed or uncertain inputs require rerunning affected checks. Role handoff alone does not invalidate local evidence; local evidence never replaces Kernel-owned deterministic QA or fresh snapshot-bound Review.
34
+ - Recover routine local failures within the authorized scope: select an unused port, create temporary outputs, and remove only task-generated disposable files. Never overwrite user data or stop an unrelated process to free a port. Before dependency installation, inspect the existing project command, lifecycle scripts, network use, and credential effects; use it only within existing authorization, without dependency or lockfile changes. Unknown or protected effects follow Host Confirmation Boundary.
35
+ - Retry a failed ordinary operation only after new evidence or a relevant condition changes; do not repeat identical attempts indefinitely. This does not authorize retrying a failed native authority gate or an uncertain remote write.
36
+ - When changing a supported behavior or contract, update its existing tests, remove tests whose protected behavior has retired, merge redundant coverage, and remove orphaned fixtures/helpers within the affected scope. For each removal, identify the retired behavior or the remaining coverage and run the surviving related checks. Never delete by age, count, slowness, or flakiness alone. Temporary tests name their exit condition; no per-test registry or repository-wide audit is required.
37
+
14
38
  ## Workflow Activation
15
39
 
16
40
  Ordinary host input stays host-native and does not run natural-language Managed
@@ -46,11 +70,15 @@ returns to `imm-planner`; an enrolled task resumes through `imm-loop` from the
46
70
  current Assurance projection. Do not create or mutate workflow state while
47
71
  classifying a non-mutating request.
48
72
 
49
- Stage only explicit task-owned paths. Never use `git add .` or `git add -A` in a dirty worktree.
50
-
51
73
  ### Host Confirmation Boundary
52
74
 
53
- Require exact host confirmation only for privileged effects:
75
+ Require exact host confirmation only for privileged effects. For ordinary
76
+ non-Kernel operations, an explicit user approval already covering the same
77
+ operation, target, and impact is sufficient; ask again only for a material delta.
78
+ A generic continuation or configured preference is not blanket authorization.
79
+ Mandatory native gates and hash-bound manifest approvals still apply.
80
+
81
+ Privileged effects include:
54
82
 
55
83
  - publish, release, deployment, or remote-system mutation;
56
84
  - destructive or irreversible operations and Git history rewrite;
@@ -65,7 +93,10 @@ Routine Managed enrollment uses one current-Host native confirmation bound to th
65
93
 
66
94
  State mutations, step activations, QA decisions, and plan switches remain
67
95
  strictly sequential. Read-only work — repo exploration, advisory review,
68
- host probing, planner research — may be dispatched in parallel.
96
+ host probing, planner research — is eligible for parallel dispatch in
97
+ capability terms, but Pi schedules one foreground child at a time: launch one
98
+ child, consume its direct result, then decide whether another child is needed.
99
+ Do not assume multiple foreground Agents run as a concurrent batch.
69
100
 
70
101
  Parallel dispatch is restricted by capability, not by a closed Skill list. Every
71
102
  child delegation packet must enforce read-only advisory behavior: no file edits,
@@ -99,13 +130,15 @@ always run sequentially.
99
130
 
100
131
  ## Success Criteria
101
132
 
102
- - Direct work closes only under the Direct completion contract above.
103
- - A Managed Step is ready to execute only when the target result, boundary, and
133
+ - Direct work closes only when the requested result is delivered and the required verification passes: a failed or unavailable required check is reported as incomplete work with its concrete blocker, never as completion. Check breadth follows the request and established project requirements, not a universal full-repository rule. Apply Shared Guards and Verification and Local Recovery for in-scope repair and evidence reuse.
134
+ - A Managed task is ready to execute only when the target result, boundary, and
104
135
  verification path are clear enough to avoid speculative edits.
105
- - A Managed Step is closable only when execution evidence proves the recorded
106
- verification path and the active boundary still matches the Plan.
107
- - Managed scope changes, missing evidence, or structural mismatch return to
108
- `imm-planner` instead of being hidden inside execution or QA.
136
+ - A Managed task is closable only when execution evidence proves the recorded
137
+ verification path and the TaskRecord still matches the enrolled TaskIntent boundary.
138
+ - Collect missing in-scope evidence and continue under the current owner. Missing
139
+ evidence alone does not require replanning. Escalate only a demonstrated
140
+ scope/acceptance mismatch or protected decision through the current owner's
141
+ revision or authorization path; never silently expand execution or QA scope.
109
142
 
110
143
  ## Retirement Completion
111
144
 
@@ -135,4 +168,4 @@ Immune-Brain commands and `.imm` state.
135
168
 
136
169
  Prefer shallow discovery before full-file reads. Start with file lists,
137
170
  `rg` hits, symbol/signature scans, and targeted line ranges; read whole files
138
- only when the narrower evidence path cannot answer the active Step question.
171
+ only when the narrower evidence path cannot answer the active task question.