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
|
@@ -42,7 +42,7 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// plugins/immune-brain/runtime/plugin_version.ts
|
|
45
|
-
var PLUGIN_VERSION = "3.6.
|
|
45
|
+
var PLUGIN_VERSION = "3.6.6";
|
|
46
46
|
|
|
47
47
|
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
48
48
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -688,7 +688,7 @@ import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypt
|
|
|
688
688
|
|
|
689
689
|
// plugins/immune-brain/runtime/assurance/verification.ts
|
|
690
690
|
import { createHash as createHash3 } from "node:crypto";
|
|
691
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
691
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
692
692
|
import { realpathSync as realpathSync2, statSync } from "node:fs";
|
|
693
693
|
import { isAbsolute as isAbsolute2, resolve, sep as sep2, relative } from "node:path";
|
|
694
694
|
|
|
@@ -792,7 +792,15 @@ function resolveBunRunner() {
|
|
|
792
792
|
}
|
|
793
793
|
let real;
|
|
794
794
|
try {
|
|
795
|
-
|
|
795
|
+
const execPath = spawnSync(executable, ["-e", "console.log(process.execPath)"], {
|
|
796
|
+
encoding: "utf8",
|
|
797
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
798
|
+
});
|
|
799
|
+
if (execPath.status === 0 && execPath.stdout.trim().length > 0) {
|
|
800
|
+
real = realpathSync2(execPath.stdout.trim());
|
|
801
|
+
} else {
|
|
802
|
+
real = realpathSync2(executable);
|
|
803
|
+
}
|
|
796
804
|
} catch {
|
|
797
805
|
throw new VerificationDescriptorError("bun runner realpath is unresolvable");
|
|
798
806
|
}
|
|
@@ -1351,6 +1359,8 @@ class AssuranceCoordinator {
|
|
|
1351
1359
|
return this.sessionGeneration;
|
|
1352
1360
|
}
|
|
1353
1361
|
async advance(taskId, ctx, signal, onUpdate) {
|
|
1362
|
+
if (this.isInvocationOpen(taskId))
|
|
1363
|
+
return { state: "blocked", reason: "an authority invocation is already open" };
|
|
1354
1364
|
const active = this.active(taskId);
|
|
1355
1365
|
if (active?.state === "review_ready") {
|
|
1356
1366
|
const reservation = this.reviewReservations.get(taskId);
|
|
@@ -1728,6 +1738,17 @@ class AssuranceCoordinator {
|
|
|
1728
1738
|
}
|
|
1729
1739
|
return { state: "blocked", reason: `Kernel requires ${settled.projection.next_obligation} after Review` };
|
|
1730
1740
|
}
|
|
1741
|
+
isReviewVerdictValid(taskId, verdictInput) {
|
|
1742
|
+
const reservation = this.reviewReservations.get(taskId);
|
|
1743
|
+
if (!reservation)
|
|
1744
|
+
return false;
|
|
1745
|
+
try {
|
|
1746
|
+
parseAssuranceVerdict(verdictInput, reservation.snapshot);
|
|
1747
|
+
return true;
|
|
1748
|
+
} catch {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1731
1752
|
abandonReview(taskId, reason) {
|
|
1732
1753
|
const reservation = this.reviewReservations.get(taskId);
|
|
1733
1754
|
if (!reservation)
|
|
@@ -1743,6 +1764,12 @@ class AssuranceCoordinator {
|
|
|
1743
1764
|
return { state: "blocked", code: "verdict_invalid", reason: "Review verdict correction is required before advancing" };
|
|
1744
1765
|
return { state: "review_ready", operation: "review", operation_id: reservation.operationId, snapshot_digest: snapshotDigest(reservation.snapshot), review_bundle_digest: reservation.snapshot.review_bundle_digest ?? "", agent_params: reservation.hostReservation.dispatch };
|
|
1745
1766
|
}
|
|
1767
|
+
releaseStoppedReview(taskId) {
|
|
1768
|
+
const reservation = this.reviewReservations.get(taskId);
|
|
1769
|
+
if (reservation)
|
|
1770
|
+
this.releaseReviewReservation(taskId, reservation);
|
|
1771
|
+
this.rejectedReviewOperations.delete(taskId);
|
|
1772
|
+
}
|
|
1746
1773
|
releaseReviewReservation(taskId, reservation, rejectionReason) {
|
|
1747
1774
|
if (this.reviewReservations.get(taskId) !== reservation)
|
|
1748
1775
|
return;
|
|
@@ -1804,7 +1831,7 @@ import { tmpdir as tmpdir2 } from "node:os";
|
|
|
1804
1831
|
import { join as join4 } from "node:path";
|
|
1805
1832
|
|
|
1806
1833
|
// plugins/immune-brain/runtime/workspace_scope.ts
|
|
1807
|
-
import { spawnSync } from "node:child_process";
|
|
1834
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1808
1835
|
import { createHash as createHash6 } from "node:crypto";
|
|
1809
1836
|
import {
|
|
1810
1837
|
existsSync as existsSync2,
|
|
@@ -1815,7 +1842,7 @@ import {
|
|
|
1815
1842
|
} from "node:fs";
|
|
1816
1843
|
import { resolve as resolve2 } from "node:path";
|
|
1817
1844
|
function git(root, args) {
|
|
1818
|
-
const result =
|
|
1845
|
+
const result = spawnSync2("git", ["-C", root, ...args], {
|
|
1819
1846
|
encoding: "utf8",
|
|
1820
1847
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1821
1848
|
});
|
|
@@ -1839,7 +1866,7 @@ var portablePathCollator = new Intl.Collator("und", {
|
|
|
1839
1866
|
});
|
|
1840
1867
|
var gitTaskSnapshotTestHook;
|
|
1841
1868
|
function gitBytes(root, args) {
|
|
1842
|
-
const result =
|
|
1869
|
+
const result = spawnSync2("git", ["-C", root, ...args], {
|
|
1843
1870
|
encoding: null,
|
|
1844
1871
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1845
1872
|
maxBuffer: 8 * 1024 * 1024
|
|
@@ -3001,6 +3028,15 @@ function classifyIntentRevision(previous, next) {
|
|
|
3001
3028
|
return "breaking";
|
|
3002
3029
|
return next.revision > previous.revision ? "compatible" : "breaking";
|
|
3003
3030
|
}
|
|
3031
|
+
|
|
3032
|
+
class TaskIntentObservationError extends Error {
|
|
3033
|
+
code;
|
|
3034
|
+
constructor(code, message) {
|
|
3035
|
+
super(message);
|
|
3036
|
+
this.name = "TaskIntentObservationError";
|
|
3037
|
+
this.code = code;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3004
3040
|
var intentReaderTestHook = null;
|
|
3005
3041
|
function validateTaskId3(taskId) {
|
|
3006
3042
|
if (!TASK_ID_PATTERN.test(taskId))
|
|
@@ -3065,7 +3101,7 @@ function assertIdentitiesUnchanged(expected, canonicalRoot, relativePath) {
|
|
|
3065
3101
|
throw new Error(`path component changed while being read: ${parts.slice(0, index + 1).join("/")}`);
|
|
3066
3102
|
}
|
|
3067
3103
|
}
|
|
3068
|
-
function
|
|
3104
|
+
function readTaskIntentSource(root, taskId, requestedPath) {
|
|
3069
3105
|
validateTaskId3(taskId);
|
|
3070
3106
|
const canonicalRoot = resolveCanonicalRoot(root);
|
|
3071
3107
|
const activePath = `${INTENT_SIDECAR_RELATIVE_PREFIX}${taskId}.intent.json`;
|
|
@@ -3077,17 +3113,19 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3077
3113
|
if (!target.startsWith(canonicalRoot + sep3))
|
|
3078
3114
|
throw new Error("intent sidecar escapes project root");
|
|
3079
3115
|
if (!sidecarPresent(canonicalRoot, sidecarPath))
|
|
3080
|
-
throw new
|
|
3116
|
+
throw new TaskIntentObservationError("missing", `TaskIntent sidecar is missing at ${sidecarPath}`);
|
|
3081
3117
|
const pathIdentities = collectPathIdentities(canonicalRoot, sidecarPath);
|
|
3082
3118
|
const fileIdentity = pathIdentities[pathIdentities.length - 1];
|
|
3083
3119
|
try {
|
|
3084
3120
|
execFileSync3("git", ["ls-files", "--error-unmatch", "--", sidecarPath], { cwd: canonicalRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
3085
|
-
} catch {
|
|
3086
|
-
|
|
3121
|
+
} catch (error) {
|
|
3122
|
+
if (typeof error === "object" && error !== null && "status" in error && error.status === 1)
|
|
3123
|
+
throw new TaskIntentObservationError("invalid", "TaskIntent sidecar is not Git-tracked");
|
|
3124
|
+
throw error;
|
|
3087
3125
|
}
|
|
3088
3126
|
const before = lstatSync4(target);
|
|
3089
3127
|
if (!before.isFile() || before.size > INTENT_MAX_BYTES)
|
|
3090
|
-
throw new
|
|
3128
|
+
throw new TaskIntentObservationError("invalid", "TaskIntent sidecar must be a regular file no larger than 64 KiB");
|
|
3091
3129
|
const fd = openSync2(target, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
3092
3130
|
let bytes;
|
|
3093
3131
|
try {
|
|
@@ -3099,7 +3137,7 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3099
3137
|
closeSync2(fd);
|
|
3100
3138
|
}
|
|
3101
3139
|
if (bytes.byteLength > INTENT_MAX_BYTES)
|
|
3102
|
-
throw new
|
|
3140
|
+
throw new TaskIntentObservationError("invalid", "TaskIntent sidecar exceeds 64 KiB");
|
|
3103
3141
|
const after = lstatSync4(target);
|
|
3104
3142
|
assertSameIdentity(statIdentity(before), after, "intent sidecar");
|
|
3105
3143
|
assertIdentitiesUnchanged(pathIdentities, canonicalRoot, sidecarPath);
|
|
@@ -3113,23 +3151,11 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3113
3151
|
try {
|
|
3114
3152
|
intent = parseTaskIntentV1(JSON.parse(bytes.toString("utf8")));
|
|
3115
3153
|
} catch (error) {
|
|
3116
|
-
throw new
|
|
3154
|
+
throw new TaskIntentObservationError("invalid", `TaskIntent sidecar is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
3117
3155
|
}
|
|
3118
3156
|
if (intent.task_id !== taskId)
|
|
3119
|
-
throw new
|
|
3157
|
+
throw new TaskIntentObservationError("invalid", "intent.task_id does not match the sidecar filename task id");
|
|
3120
3158
|
const contentHash = canonicalIntentHash(intent);
|
|
3121
|
-
const token = mintToken({
|
|
3122
|
-
canonical_root: canonicalRoot,
|
|
3123
|
-
sidecar_path: sidecarPath,
|
|
3124
|
-
path_dev: fileIdentity.dev,
|
|
3125
|
-
path_ino: fileIdentity.ino,
|
|
3126
|
-
fd_dev: before.dev,
|
|
3127
|
-
fd_ino: before.ino,
|
|
3128
|
-
fd_size: before.size,
|
|
3129
|
-
fd_mtime_ms: before.mtimeMs,
|
|
3130
|
-
source_bytes_sha256: sourceBytesSha256,
|
|
3131
|
-
intent_content_hash: contentHash
|
|
3132
|
-
});
|
|
3133
3159
|
return {
|
|
3134
3160
|
intent,
|
|
3135
3161
|
content_hash: contentHash,
|
|
@@ -3138,9 +3164,24 @@ function readTaskIntent(root, taskId, requestedPath) {
|
|
|
3138
3164
|
revision: intent.revision,
|
|
3139
3165
|
content_hash: contentHash
|
|
3140
3166
|
},
|
|
3141
|
-
|
|
3167
|
+
identity: {
|
|
3168
|
+
canonical_root: canonicalRoot,
|
|
3169
|
+
sidecar_path: sidecarPath,
|
|
3170
|
+
path_dev: fileIdentity.dev,
|
|
3171
|
+
path_ino: fileIdentity.ino,
|
|
3172
|
+
fd_dev: before.dev,
|
|
3173
|
+
fd_ino: before.ino,
|
|
3174
|
+
fd_size: before.size,
|
|
3175
|
+
fd_mtime_ms: before.mtimeMs,
|
|
3176
|
+
source_bytes_sha256: sourceBytesSha256,
|
|
3177
|
+
intent_content_hash: contentHash
|
|
3178
|
+
}
|
|
3142
3179
|
};
|
|
3143
3180
|
}
|
|
3181
|
+
function readTaskIntent(root, taskId, requestedPath) {
|
|
3182
|
+
const { identity, ...observed } = readTaskIntentSource(root, taskId, requestedPath);
|
|
3183
|
+
return { ...observed, token: mintToken(identity) };
|
|
3184
|
+
}
|
|
3144
3185
|
|
|
3145
3186
|
// plugins/immune-brain/runtime/kernel/validation.ts
|
|
3146
3187
|
var GIT_OBJECT_ID3 = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
@@ -3645,6 +3686,7 @@ var ACTION_V2_TYPES = [
|
|
|
3645
3686
|
"request_rework",
|
|
3646
3687
|
"complete",
|
|
3647
3688
|
"stop",
|
|
3689
|
+
"authorize_rework",
|
|
3648
3690
|
"resolve_user_decision"
|
|
3649
3691
|
];
|
|
3650
3692
|
var ACTION_BASE_FIELDS = [
|
|
@@ -3740,7 +3782,8 @@ function parseTaskAction(raw) {
|
|
|
3740
3782
|
};
|
|
3741
3783
|
break;
|
|
3742
3784
|
}
|
|
3743
|
-
case "complete":
|
|
3785
|
+
case "complete":
|
|
3786
|
+
case "authorize_rework": {
|
|
3744
3787
|
rejectUnknown2(value, [...ACTION_BASE_FIELDS], "action", violations);
|
|
3745
3788
|
action = { ...base, type: base.type };
|
|
3746
3789
|
break;
|
|
@@ -3798,7 +3841,7 @@ function assertTaskRecordUpdateV3(previousRaw, nextRaw, action) {
|
|
|
3798
3841
|
violations.push("non-intent action cannot change the intent snapshot");
|
|
3799
3842
|
if (next.intent_ref.content_hash !== previous.intent_ref.content_hash)
|
|
3800
3843
|
violations.push("non-intent action cannot change intent_ref content hash");
|
|
3801
|
-
if (next.intent_ref.path !== previous.intent_ref.path && action.type !== "request_rework" && action.type !== "stop")
|
|
3844
|
+
if (next.intent_ref.path !== previous.intent_ref.path && action.type !== "request_rework" && action.type !== "authorize_rework" && action.type !== "stop")
|
|
3802
3845
|
violations.push("only artifact transitions may change intent_ref path");
|
|
3803
3846
|
}
|
|
3804
3847
|
if (next.attestations.length < previous.attestations.length)
|
|
@@ -3810,7 +3853,7 @@ function assertTaskRecordUpdateV3(previousRaw, nextRaw, action) {
|
|
|
3810
3853
|
if (!current || JSON.stringify(current) !== JSON.stringify(prior))
|
|
3811
3854
|
violations.push(`attestation ${prior.id} was rewritten`);
|
|
3812
3855
|
}
|
|
3813
|
-
const resolvingFindingIds = action.type === "resolve_finding" ? [action.finding_id] : action.type === "resolve_user_decision" ? [action.finding_id] : action.type === "approve_breaking_intent_revision" ? previous.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id) : [];
|
|
3856
|
+
const resolvingFindingIds = action.type === "resolve_finding" ? [action.finding_id] : action.type === "resolve_user_decision" ? [action.finding_id] : action.type === "authorize_rework" || action.type === "approve_breaking_intent_revision" ? previous.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id) : [];
|
|
3814
3857
|
const reworkFindingIds = action.type === "request_rework" ? new Set(action.findings.map((item) => item.id)) : new Set;
|
|
3815
3858
|
for (const prior of previous.findings) {
|
|
3816
3859
|
const current = next.findings.find((item) => item.id === prior.id);
|
|
@@ -5039,6 +5082,8 @@ function deriveAssuranceAuthorization(input) {
|
|
|
5039
5082
|
state: "none",
|
|
5040
5083
|
blocked: `resolve-user-decision requires exactly one open user decision; found ${input.open_user_decision_count}`
|
|
5041
5084
|
};
|
|
5085
|
+
if (input.next_obligation === "revise_intent")
|
|
5086
|
+
return { state: "authorize_rework", blocked: null };
|
|
5042
5087
|
return { state: "none", blocked: null };
|
|
5043
5088
|
}
|
|
5044
5089
|
function emptyProjection() {
|
|
@@ -5286,10 +5331,12 @@ function appendHistory(record, action, from, detail, audit) {
|
|
|
5286
5331
|
record.history.push(entry);
|
|
5287
5332
|
}
|
|
5288
5333
|
function intentRefMatches(intent, ref) {
|
|
5289
|
-
|
|
5334
|
+
const activePath = `docs/plans/${intent.task_id}.intent.json`;
|
|
5335
|
+
const archivedPath = `docs/plans/archive/${intent.task_id}.intent.json`;
|
|
5336
|
+
return (ref.path === activePath || ref.path === archivedPath) && ref.content_hash === canonicalIntentHash(intent);
|
|
5290
5337
|
}
|
|
5291
5338
|
function hasPrivilegedKind(action) {
|
|
5292
|
-
return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5339
|
+
return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "authorize_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5293
5340
|
}
|
|
5294
5341
|
function findingsDigestV2(findings) {
|
|
5295
5342
|
const normalized = findings.map((finding) => ({
|
|
@@ -5507,8 +5554,8 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5507
5554
|
"request_rework requires review, qa, or user authority"
|
|
5508
5555
|
]);
|
|
5509
5556
|
const round = reviewRound(record);
|
|
5510
|
-
const
|
|
5511
|
-
const parkForReplan = authorityAudit.authority_kind === "review" &&
|
|
5557
|
+
const hasPriorBlockingReviewRework = record.findings.some((finding) => finding.source === "review" && finding.kind === "blocking" && finding.review_round !== null);
|
|
5558
|
+
const parkForReplan = authorityAudit.authority_kind === "review" && hasPriorBlockingReviewRework && action.findings.some((finding) => finding.kind === "blocking");
|
|
5512
5559
|
if (!parkForReplan) {
|
|
5513
5560
|
record.artifact_state = "active";
|
|
5514
5561
|
record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
|
|
@@ -5523,8 +5570,8 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5523
5570
|
record.findings.push({
|
|
5524
5571
|
...finding,
|
|
5525
5572
|
status: "open",
|
|
5526
|
-
source: "review",
|
|
5527
|
-
review_round: round
|
|
5573
|
+
source: authorityAudit.authority_kind === "review" ? "review" : "execution",
|
|
5574
|
+
review_round: authorityAudit.authority_kind === "review" ? round : null
|
|
5528
5575
|
});
|
|
5529
5576
|
}
|
|
5530
5577
|
if (parkForReplan && !record.findings.some((item) => item.status === "open" && item.kind === "replan_required")) {
|
|
@@ -5547,6 +5594,27 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5547
5594
|
appendHistory(record, action, from, `review_round_${round}`, authorityAudit);
|
|
5548
5595
|
break;
|
|
5549
5596
|
}
|
|
5597
|
+
case "authorize_rework": {
|
|
5598
|
+
if (record.lifecycle !== "active")
|
|
5599
|
+
throw new KernelInvariantError([
|
|
5600
|
+
`cannot authorize rework while lifecycle is ${record.lifecycle}`
|
|
5601
|
+
]);
|
|
5602
|
+
if (authorityAudit?.authority_kind !== "user")
|
|
5603
|
+
throw new KernelInvariantError([
|
|
5604
|
+
"authorize_rework requires literal-user authority"
|
|
5605
|
+
]);
|
|
5606
|
+
const open = record.findings.filter((finding) => finding.kind === "replan_required" && finding.status === "open");
|
|
5607
|
+
if (open.length === 0)
|
|
5608
|
+
throw new KernelInvariantError([
|
|
5609
|
+
"authorize_rework requires an open replan boundary"
|
|
5610
|
+
]);
|
|
5611
|
+
for (const finding of open)
|
|
5612
|
+
finding.status = "resolved";
|
|
5613
|
+
record.artifact_state = "active";
|
|
5614
|
+
record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
|
|
5615
|
+
appendHistory(record, action, from, open.map((finding) => finding.id).join(","), authorityAudit);
|
|
5616
|
+
break;
|
|
5617
|
+
}
|
|
5550
5618
|
case "complete": {
|
|
5551
5619
|
if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
|
|
5552
5620
|
throw new KernelInvariantError([
|
|
@@ -5677,7 +5745,7 @@ function applyTaskAction(input) {
|
|
|
5677
5745
|
"intent token does not match the committed record intent"
|
|
5678
5746
|
]);
|
|
5679
5747
|
}
|
|
5680
|
-
const privileged = action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5748
|
+
const privileged = action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "authorize_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5681
5749
|
const expectedAuthority = privileged ? {
|
|
5682
5750
|
task_id,
|
|
5683
5751
|
action,
|
|
@@ -5803,6 +5871,8 @@ function capabilityActionFor(input) {
|
|
|
5803
5871
|
return { ...base, approval: input.approval };
|
|
5804
5872
|
case "request_rework":
|
|
5805
5873
|
return { ...base, findings: input.findings };
|
|
5874
|
+
case "authorize_rework":
|
|
5875
|
+
return { ...base, type: "authorize_rework" };
|
|
5806
5876
|
case "stop":
|
|
5807
5877
|
return { ...base, reason: input.reason };
|
|
5808
5878
|
case "approve_breaking_intent_revision":
|
|
@@ -5949,7 +6019,7 @@ function createCanaryApplication(registry) {
|
|
|
5949
6019
|
const hasBoundSpec = snapshot.intent_snapshot.scope_hint.some((path) => /^docs\/specs\/(?!archive\/)[^/]+\.spec\.md$/.test(path) && snapshot.intent_snapshot.scope_hint.includes(archivePath(path)));
|
|
5950
6020
|
if (operation.op === "complete" && hasBoundSpec && snapshot.record.artifact_state !== "frozen")
|
|
5951
6021
|
throw new KernelInvariantError(["complete requires frozen planning artifacts"]);
|
|
5952
|
-
const artifactTransition = snapshot.record.artifact_state === "frozen" && (operation.op === "request_rework" || operation.op === "approve_breaking_intent_revision") ? transitionFor(input.root, snapshot.record, "restore") : operation.op === "stop" && snapshot.record.artifact_state !== "frozen" ? transitionFor(input.root, snapshot.record, "freeze", true) : undefined;
|
|
6022
|
+
const artifactTransition = snapshot.record.artifact_state === "frozen" && (operation.op === "request_rework" || operation.op === "authorize_rework" || operation.op === "approve_breaking_intent_revision") ? transitionFor(input.root, snapshot.record, "restore") : operation.op === "stop" && snapshot.record.artifact_state !== "frozen" ? transitionFor(input.root, snapshot.record, "freeze", true) : undefined;
|
|
5953
6023
|
const event_id = `${operation.op}:${input.task_id}:${at}`;
|
|
5954
6024
|
const base = {
|
|
5955
6025
|
event_id,
|
|
@@ -5991,7 +6061,7 @@ function createCanaryApplication(registry) {
|
|
|
5991
6061
|
type: "revise_intent",
|
|
5992
6062
|
next_intent: operation.next_intent,
|
|
5993
6063
|
next_intent_ref: {
|
|
5994
|
-
path: `docs/plans/${operation.next_intent.task_id}.intent.json`,
|
|
6064
|
+
path: snapshot.record.artifact_state === "frozen" ? `docs/plans/archive/${operation.next_intent.task_id}.intent.json` : `docs/plans/${operation.next_intent.task_id}.intent.json`,
|
|
5995
6065
|
content_hash: canonicalIntentHash(operation.next_intent)
|
|
5996
6066
|
}
|
|
5997
6067
|
};
|
|
@@ -6015,6 +6085,10 @@ function createCanaryApplication(registry) {
|
|
|
6015
6085
|
capability = operation.capability;
|
|
6016
6086
|
action = { ...base, type: "stop", reason: operation.reason };
|
|
6017
6087
|
break;
|
|
6088
|
+
case "authorize_rework":
|
|
6089
|
+
capability = operation.capability;
|
|
6090
|
+
action = { ...base, type: "authorize_rework" };
|
|
6091
|
+
break;
|
|
6018
6092
|
case "resolve_user_decision":
|
|
6019
6093
|
capability = operation.capability;
|
|
6020
6094
|
action = {
|
|
@@ -6272,12 +6346,12 @@ function createEnrollmentAuthorityRegistry() {
|
|
|
6272
6346
|
|
|
6273
6347
|
// plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts
|
|
6274
6348
|
import { createHash as createHash12 } from "node:crypto";
|
|
6275
|
-
import { spawnSync as
|
|
6349
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
6276
6350
|
import { resolve as resolve6 } from "node:path";
|
|
6277
6351
|
var SOURCE_PATH = ".imm/state/workspace.json";
|
|
6278
6352
|
var GIT_OBJECT_ID4 = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
6279
6353
|
function readGitHead(root) {
|
|
6280
|
-
const result =
|
|
6354
|
+
const result = spawnSync3("git", ["-C", root, "rev-parse", "--verify", "HEAD^{commit}"], {
|
|
6281
6355
|
encoding: "utf8"
|
|
6282
6356
|
});
|
|
6283
6357
|
const head = typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
@@ -6477,7 +6551,16 @@ function enrollCanaryTask(root, input, registry) {
|
|
|
6477
6551
|
throw new Error("intent content hash mismatch");
|
|
6478
6552
|
if (checks.gitBaseHead !== gitBaseHead)
|
|
6479
6553
|
throw new Error("Git HEAD moved after the enrollment confirmation");
|
|
6554
|
+
if (input.batch) {
|
|
6555
|
+
const batch = input.batch.registry.inspect(input.batch.capability, input.batch.binding, Date.parse(input.now));
|
|
6556
|
+
if (input.batch.registry.consumedChildren(input.batch.capability).length === 0 && input.batch.expected_head !== batch.base_head)
|
|
6557
|
+
throw new Error(`batch_head_lineage_broken: the first child must enroll on the confirmed base_head ${batch.base_head}, not ${input.batch.expected_head}`);
|
|
6558
|
+
if (checks.gitBaseHead !== input.batch.expected_head)
|
|
6559
|
+
throw new Error(`batch_head_lineage_broken: expected ${input.batch.expected_head}, found ${checks.gitBaseHead}`);
|
|
6560
|
+
}
|
|
6480
6561
|
registry.consume(input.capability, input.capability_binding);
|
|
6562
|
+
if (input.batch)
|
|
6563
|
+
input.batch.registry.consumeChild(input.batch.capability, input.batch.binding, input.task_id, Date.parse(input.now));
|
|
6481
6564
|
if (!gitBaseHead)
|
|
6482
6565
|
throw new Error("enrollment requires a committed Git HEAD");
|
|
6483
6566
|
const record = buildTaskRecordV4(input, checks.intent, gitBaseHead);
|
|
@@ -6496,16 +6579,23 @@ function enrollCanaryTask(root, input, registry) {
|
|
|
6496
6579
|
created_at: input.now,
|
|
6497
6580
|
updated_at: input.now
|
|
6498
6581
|
};
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6582
|
+
let mutation;
|
|
6583
|
+
try {
|
|
6584
|
+
mutation = commitEnrollmentLocked(root, input.task_id, {
|
|
6585
|
+
contract: "assurance_kernel/workspace_transaction/v2",
|
|
6586
|
+
task_id: input.task_id,
|
|
6587
|
+
expected_record_hash: checks.current.revision,
|
|
6588
|
+
next_record_content: `${JSON.stringify(record, null, 2)}
|
|
6504
6589
|
`,
|
|
6505
|
-
|
|
6506
|
-
|
|
6590
|
+
expected_workspace_hash: checks.workspace.revision,
|
|
6591
|
+
next_workspace_content: `${JSON.stringify(nextWorkspace, null, 2)}
|
|
6507
6592
|
`
|
|
6508
|
-
|
|
6593
|
+
}, claim);
|
|
6594
|
+
} catch (error) {
|
|
6595
|
+
if (input.batch)
|
|
6596
|
+
input.batch.registry.releaseChild(input.batch.capability, input.task_id);
|
|
6597
|
+
throw error;
|
|
6598
|
+
}
|
|
6509
6599
|
return {
|
|
6510
6600
|
record: mutation.record,
|
|
6511
6601
|
backend_claim: claim,
|
|
@@ -6645,14 +6735,18 @@ async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput)
|
|
|
6645
6735
|
return coordinator.abandonReview(taskId, observed.reason);
|
|
6646
6736
|
return { state: "blocked", reason: observed.reason };
|
|
6647
6737
|
}
|
|
6738
|
+
const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
|
|
6739
|
+
if (!parentValid)
|
|
6740
|
+
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
6741
|
+
const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
|
|
6742
|
+
if (!receiptValid) {
|
|
6743
|
+
return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
|
|
6744
|
+
}
|
|
6648
6745
|
const parentJson = extractVerdictJson(verdictInput);
|
|
6649
6746
|
const receiptJson = extractVerdictJson(observed.receipt.result);
|
|
6650
|
-
if (
|
|
6747
|
+
if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
6651
6748
|
return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
|
|
6652
6749
|
}
|
|
6653
|
-
if (parentJson && !receiptJson) {
|
|
6654
|
-
return { state: "blocked", reason: "reviewer receipt is not a valid verdict" };
|
|
6655
|
-
}
|
|
6656
6750
|
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
6657
6751
|
}
|
|
6658
6752
|
function stopReason(value) {
|
|
@@ -6948,6 +7042,12 @@ class ClaudeRuntime {
|
|
|
6948
7042
|
async submitReview(taskId, verdictInput) {
|
|
6949
7043
|
return submitClaudeReview(this.host, this.coordinator, { cwd: this.cwd }, taskId, verdictInput);
|
|
6950
7044
|
}
|
|
7045
|
+
async resolveFinding(taskId, findingId) {
|
|
7046
|
+
return this.executeOrdinary({ cwd: this.cwd }, {
|
|
7047
|
+
taskId,
|
|
7048
|
+
operation: { op: "resolve_finding", finding_id: findingId, actor_id: "executor" }
|
|
7049
|
+
});
|
|
7050
|
+
}
|
|
6951
7051
|
async authorize(taskId, operation, meta, extra = {}) {
|
|
6952
7052
|
if (operation === "repair_authority_state") {
|
|
6953
7053
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
@@ -6972,6 +7072,8 @@ class ClaudeRuntime {
|
|
|
6972
7072
|
throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
6973
7073
|
op = "resolve_user_decision";
|
|
6974
7074
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
7075
|
+
} else if (readiness.state === "authorize_rework") {
|
|
7076
|
+
op = "authorize_rework";
|
|
6975
7077
|
} else {
|
|
6976
7078
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
6977
7079
|
}
|
|
@@ -7088,7 +7190,7 @@ class ClaudeRuntime {
|
|
|
7088
7190
|
diffProvider: diffSnapshotOf,
|
|
7089
7191
|
now
|
|
7090
7192
|
});
|
|
7091
|
-
if (op === "stop" || op === "approve_breaking_intent_revision")
|
|
7193
|
+
if (op === "stop" || op === "authorize_rework" || op === "approve_breaking_intent_revision")
|
|
7092
7194
|
stagePlanningArtifactTransition(this.cwd, result.record);
|
|
7093
7195
|
return result;
|
|
7094
7196
|
} catch (error) {
|
|
@@ -7224,7 +7326,8 @@ var TOOLS = [
|
|
|
7224
7326
|
{ name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
|
|
7225
7327
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
7226
7328
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
7227
|
-
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false }
|
|
7329
|
+
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
|
|
7330
|
+
{ name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false }
|
|
7228
7331
|
];
|
|
7229
7332
|
function listMcpTools() {
|
|
7230
7333
|
return TOOLS.map((tool) => ({
|
|
@@ -7236,9 +7339,10 @@ function listMcpTools() {
|
|
|
7236
7339
|
task_id: { type: "string" },
|
|
7237
7340
|
...tool.name === "approve_breaking_intent_revision" ? { next_intent: { type: "object" } } : {},
|
|
7238
7341
|
...tool.name === "stop" ? { reason: { type: "string" } } : {},
|
|
7239
|
-
...tool.name === "submit_review" ? { verdict: { type: "object" } } : {}
|
|
7342
|
+
...tool.name === "submit_review" ? { verdict: { type: "object" } } : {},
|
|
7343
|
+
...tool.name === "resolve_finding" ? { finding_id: { type: "string" } } : {}
|
|
7240
7344
|
},
|
|
7241
|
-
required: tool.name === "submit_review" ? ["task_id", "verdict"] : ["task_id"]
|
|
7345
|
+
required: tool.name === "submit_review" ? ["task_id", "verdict"] : tool.name === "resolve_finding" ? ["task_id", "finding_id"] : ["task_id"]
|
|
7242
7346
|
},
|
|
7243
7347
|
annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" }
|
|
7244
7348
|
}));
|
|
@@ -7305,6 +7409,11 @@ function createMcpRuntime(options = {}) {
|
|
|
7305
7409
|
throw new Error("verdict is required");
|
|
7306
7410
|
return runtime.submitReview(taskId, args.verdict);
|
|
7307
7411
|
}
|
|
7412
|
+
if (name === "resolve_finding") {
|
|
7413
|
+
if (typeof args.finding_id !== "string" || !args.finding_id)
|
|
7414
|
+
throw new Error("finding_id is required");
|
|
7415
|
+
return runtime.resolveFinding(taskId, args.finding_id);
|
|
7416
|
+
}
|
|
7308
7417
|
if (name === "request_authorization" || name === "approve_breaking_intent_revision" || name === "stop" || name === "repair_authority_state") {
|
|
7309
7418
|
return runtime.authorize(taskId, name, toolMeta, args);
|
|
7310
7419
|
}
|
|
@@ -23,7 +23,7 @@ Apply the gate when the task touches one or more of these surfaces:
|
|
|
23
23
|
- **Technical Design baseline**: keep the Spec as the single design authority and make each Plan Step reference the applicable decision or invariant instead of duplicating design prose.
|
|
24
24
|
- **design-view selection**: for Medium/High risk, select every materially relevant technical-design view from architecture layers, service/component interfaces, data flow, state transitions, and temporal sequence. Record selected views and why omitted views cannot affect the design. Low risk remains concise.
|
|
25
25
|
- **TaskIntent decomposition**: use Technical Design boundaries as one retain/split criterion with outcome, Verification, dependency, risk, rollback, compatibility, and authority. Split a successor TaskIntent only when a service, state-machine owner, migration, independently promotable layer, or sequence dependency needs independent verification, rollback, authorization, or settlement. A TaskIntent should normally change one primary trust-boundary invariant, but traversing several boundaries or updating both sides of one authority chain does not itself require a split. Split independently verifiable, reversible, authorizable, migratable, or settleable trust invariants. Keep multiple trust-boundary changes together only for one atomic security outcome whose split would create an unsafe or unusable intermediate state, and record that rationale in the Spec. Treat this as Planner judgment rather than a schema field or Enrollment counting rule. Do not split merely because the design names several layers, files, or services, and do not revive prose Plan authority.
|
|
26
|
-
- **Mermaid intent**: use Mermaid only when it clarifies structure, sequence, data flow, or state transitions; it is not a universal gate or a second source of truth.
|
|
26
|
+
- **Mermaid intent**: use Mermaid only when it clarifies structure, sequence, data flow, or state transitions; it is not a universal gate or a second source of truth. Medium/High risk Specs record `**Diagram decision**: required|not_required` and a non-empty `**Diagram reason**:`. A `required` decision must include Mermaid; `not_required` explains why prose is sufficient. Low-risk Specs omit the empty ceremony and record neither field.
|
|
27
27
|
- **Design Conformance**: before final closure, require Spec-to-implementation evidence. A local implementation mismatch routes to `rework`; a structural or intended design change routes to `replan` through Planner. QA cannot silently approve a design change.
|
|
28
28
|
- **Brainstorm traceability**: ensure every `BR-*` item listed in `Brainstorm manifest` is mapped in `Brainstorm Trace`.
|
|
29
29
|
- **roadmap information preservation**: for large or multi-phase work, distinguish the Roadmap from the current executable slice, preserve deferred phase goals, open questions, promotion criteria, and candidate next Plans.
|
|
@@ -104,7 +104,7 @@ Parent workflow role 必须:
|
|
|
104
104
|
4. 把 partial/error 标记为 `degraded`。
|
|
105
105
|
5. 保留自身 baseline review,不把最终判断权交给 child。
|
|
106
106
|
|
|
107
|
-
普通 advisory/discovery 的每次启动都消耗一个 candidate budget slot;失败、取消、timeout 或 result_untrusted 均丢弃该输出且不得自动重试。Parent 仅在剩余候选仍独立有用且 evidence budget 仍需要时继续,否则转 solo/fail-closed fallback,并记录 `dispatch_failed` 或 `child_timeout
|
|
107
|
+
普通 advisory/discovery 的每次启动都消耗一个 candidate budget slot;失败、取消、timeout 或 result_untrusted 均丢弃该输出且不得自动重试。Parent 仅在剩余候选仍独立有用且 evidence budget 仍需要时继续,否则转 solo/fail-closed fallback,并记录 `dispatch_failed` 或 `child_timeout`。Read-only eligibility 与 Pi 的 one-foreground-child 调度限制是两回事:只读调查可同时存在多个待派发候选,但实际执行仍逐个 foreground child 串行消费,不得把多个 foreground Agent 假定为并发 batch。该规则不改变 Kernel authority Review 的显式恢复协议。Child 永远不获得实现、Plan write、workflow mutation 或 QA closure authority。
|
|
108
108
|
|
|
109
109
|
If Kernel Review dispatch fails, the Parent does not call `submit_review`; the existing Review reservation and immutable evidence remain available for a later foreground retry. A malformed verdict may be corrected and resubmitted. A stale snapshot, explicit release, successful settlement, or session shutdown removes the reservation and evidence. There is no retry counter, dispatch receipt state machine, or provider-specific recovery path.
|
|
110
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: imm-agent-doc-maintain
|
|
3
|
-
description: Use
|
|
3
|
+
description: Use when the user explicitly requests Immune-Brain minimization of tracked AGENTS.md, CLAUDE.md, or GEMINI.md.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Immune-Brain: Agent Doc Maintain
|
|
@@ -37,6 +37,8 @@ commit.
|
|
|
37
37
|
|
|
38
38
|
## Ordered Maintenance Protocol
|
|
39
39
|
|
|
40
|
+
### Inventory and Manifest
|
|
41
|
+
|
|
40
42
|
1. **Establish repository safety.** Mutation requires a Git worktree. Only
|
|
41
43
|
tracked regular files named exactly `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md`,
|
|
42
44
|
at the repository root or in nested tracked directories, are candidates.
|
|
@@ -100,6 +102,8 @@ commit.
|
|
|
100
102
|
repository scope or declared precedence are `BLOCKED`. Filename convention,
|
|
101
103
|
nesting, or guessed host behavior alone may not resolve a conflict.
|
|
102
104
|
|
|
105
|
+
### Manifest Approval and Recovery
|
|
106
|
+
|
|
103
107
|
7. **Produce one exact manifest.** `audit` mode stops after the manifest.
|
|
104
108
|
Mutation mode also stops until the literal user approves exact manifest
|
|
105
109
|
entries (for example, "all recommendations except 4 and 7"). Broad approval
|
|
@@ -107,6 +111,8 @@ commit.
|
|
|
107
111
|
no manifest is persisted. No fixed line, byte, percentage, or Token target
|
|
108
112
|
authorizes removal.
|
|
109
113
|
|
|
114
|
+
### Approved Mutation
|
|
115
|
+
|
|
110
116
|
8. **Revalidate and mutate minimally.** Re-read candidate bytes, Git status,
|
|
111
117
|
content hash, references, precedence evidence, and active scope immediately
|
|
112
118
|
before each approved change. Drift blocks that item. Never execute commands
|
|
@@ -116,6 +122,8 @@ commit.
|
|
|
116
122
|
basics, and explicit user requirements are never simplified away for
|
|
117
123
|
brevity.
|
|
118
124
|
|
|
125
|
+
### Verify and Report
|
|
126
|
+
|
|
119
127
|
9. **Verify and report.** Re-scan modified instruction relationships, local
|
|
120
128
|
pointer targets, duplicate retained meanings, unresolved conflicts,
|
|
121
129
|
source/package public-surface parity, existing focused documentation
|