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.
- package/README.md +45 -0
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -20
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +38 -10
- package/plugins/immune-brain/dist/BASELINE.md +48 -15
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +166 -57
- package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-agent-doc-maintain.md +9 -1
- package/plugins/immune-brain/dist/imm-brainstorm.md +49 -35
- package/plugins/immune-brain/dist/imm-doc-prune.md +7 -1
- package/plugins/immune-brain/dist/imm-loop.md +31 -13
- package/plugins/immune-brain/dist/imm-planner.md +74 -32
- package/plugins/immune-brain/dist/imm-pr-fix.md +6 -2
- package/plugins/immune-brain/dist/role-prompts/executor.md +18 -10
- package/plugins/immune-brain/dist/role-prompts/pr-fix.md +5 -2
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +18 -0
- package/plugins/immune-brain/runtime/assurance/verification.ts +13 -2
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +31 -9
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +14 -1
- package/plugins/immune-brain/runtime/commands/kernel.ts +15 -13
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +1112 -20
- package/plugins/immune-brain/runtime/kernel/application.ts +1 -0
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +407 -0
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +24 -9
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +72 -13
- package/plugins/immune-brain/runtime/kernel/intent.ts +67 -23
- package/plugins/immune-brain/runtime/kernel/reducer.ts +37 -9
- package/plugins/immune-brain/runtime/kernel/types.ts +1 -0
- package/plugins/immune-brain/runtime/kernel/validation.ts +10 -6
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +18 -10
- package/plugins/immune-brain/runtime/prompts/pr-fix.md +5 -2
- package/plugins/immune-brain/skills/BASELINE.md +48 -15
- package/plugins/immune-brain/skills/imm-agent-doc-maintain/SKILL.md +20 -4
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +24 -64
- package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +18 -3
- package/plugins/immune-brain/skills/imm-loop/SKILL.md +20 -6
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +35 -8
- 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
|
@@ -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
|
-
:
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
-
| "
|
|
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)
|
|
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)
|
|
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 {
|
|
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
|
|
530
|
+
return truncateToWidth(value, max, "…");
|
|
506
531
|
}
|
|
507
532
|
|
|
508
533
|
function boundedMiddle(value: string, max: number): string {
|
|
509
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
6
|
-
|
|
7
|
-
-
|
|
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
|
-
-
|
|
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 —
|
|
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
|
|
103
|
-
- A Managed
|
|
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
|
|
106
|
-
verification path and the
|
|
107
|
-
-
|
|
108
|
-
|
|
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
|
|
171
|
+
only when the narrower evidence path cannot answer the active task question.
|