immune-brain 2.8.2

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 (101) hide show
  1. package/README.md +239 -0
  2. package/README.zh-CN.md +239 -0
  3. package/package.json +84 -0
  4. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +666 -0
  5. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +1622 -0
  6. package/plugins/immune-brain/.pi-extension/package.json +11 -0
  7. package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +711 -0
  8. package/plugins/immune-brain/.pi-extension/pi-canary-assurance.ts +154 -0
  9. package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +349 -0
  10. package/plugins/immune-brain/.pi-extension/pi-canary-invocations.ts +90 -0
  11. package/plugins/immune-brain/.pi-extension/pi-canary-native-review.ts +214 -0
  12. package/plugins/immune-brain/.pi-extension/pi-canary-qa-findings.ts +50 -0
  13. package/plugins/immune-brain/.pi-extension/pi-canary-review-bundle.ts +262 -0
  14. package/plugins/immune-brain/.pi-extension/pi-canary-tool-failure.ts +28 -0
  15. package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +237 -0
  16. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +414 -0
  17. package/plugins/immune-brain/.pi-extension/tsconfig.json +20 -0
  18. package/plugins/immune-brain/bin/imm-kernel +4 -0
  19. package/plugins/immune-brain/bin/imm-plan +4 -0
  20. package/plugins/immune-brain/bin/imm-pr-diag +230 -0
  21. package/plugins/immune-brain/bin/imm-retire-stale-wrapper +4 -0
  22. package/plugins/immune-brain/bin/imm-retired +4 -0
  23. package/plugins/immune-brain/bin/imm-tracker +4 -0
  24. package/plugins/immune-brain/dist/BASELINE.md +138 -0
  25. package/plugins/immune-brain/dist/docs/reference/HANDOFF-template.md +122 -0
  26. package/plugins/immune-brain/dist/docs/reference/design-contract-audit-rubric.md +149 -0
  27. package/plugins/immune-brain/dist/docs/reference/design-contract-review-checklist.md +55 -0
  28. package/plugins/immune-brain/dist/docs/reference/i18n-review-checklist.md +110 -0
  29. package/plugins/immune-brain/dist/docs/reference/immune-brain-config.md +52 -0
  30. package/plugins/immune-brain/dist/docs/reference/planning-artifact-retention.md +94 -0
  31. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +44 -0
  32. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +105 -0
  33. package/plugins/immune-brain/dist/docs/reference/ux-heuristic-checklist.md +131 -0
  34. package/plugins/immune-brain/dist/imm-brainstorm.md +140 -0
  35. package/plugins/immune-brain/dist/imm-doc-prune.md +137 -0
  36. package/plugins/immune-brain/dist/imm-loop.md +158 -0
  37. package/plugins/immune-brain/dist/imm-planner.md +387 -0
  38. package/plugins/immune-brain/dist/imm-pr-fix.md +71 -0
  39. package/plugins/immune-brain/dist/registry.yaml +49 -0
  40. package/plugins/immune-brain/dist/role-prompts/advisory-reviewer.md +16 -0
  41. package/plugins/immune-brain/dist/role-prompts/arch-explorer.md +14 -0
  42. package/plugins/immune-brain/dist/role-prompts/code-review.md +15 -0
  43. package/plugins/immune-brain/dist/role-prompts/compounder.md +20 -0
  44. package/plugins/immune-brain/dist/role-prompts/executor.md +13 -0
  45. package/plugins/immune-brain/dist/role-prompts/pr-fix.md +81 -0
  46. package/plugins/immune-brain/dist/role-prompts/qa.md +23 -0
  47. package/plugins/immune-brain/dist/role-prompts/test-fixer.md +3 -0
  48. package/plugins/immune-brain/dist/role-prompts/ui-review.md +14 -0
  49. package/plugins/immune-brain/runtime/authority_commit_receipts.ts +716 -0
  50. package/plugins/immune-brain/runtime/canonical_json.ts +19 -0
  51. package/plugins/immune-brain/runtime/commands/kernel.ts +1160 -0
  52. package/plugins/immune-brain/runtime/github_issue_tracker.ts +1009 -0
  53. package/plugins/immune-brain/runtime/kernel/application.ts +300 -0
  54. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +284 -0
  55. package/plugins/immune-brain/runtime/kernel/authority_port.ts +208 -0
  56. package/plugins/immune-brain/runtime/kernel/automatic_observations.ts +451 -0
  57. package/plugins/immune-brain/runtime/kernel/backend_claim.ts +197 -0
  58. package/plugins/immune-brain/runtime/kernel/canary_application.ts +507 -0
  59. package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +73 -0
  60. package/plugins/immune-brain/runtime/kernel/completion.ts +160 -0
  61. package/plugins/immune-brain/runtime/kernel/enrollment.ts +194 -0
  62. package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +123 -0
  63. package/plugins/immune-brain/runtime/kernel/index.ts +29 -0
  64. package/plugins/immune-brain/runtime/kernel/intent.ts +563 -0
  65. package/plugins/immune-brain/runtime/kernel/intent_token_registry.ts +80 -0
  66. package/plugins/immune-brain/runtime/kernel/legacy.ts +299 -0
  67. package/plugins/immune-brain/runtime/kernel/legacy_audit.ts +153 -0
  68. package/plugins/immune-brain/runtime/kernel/observation.ts +395 -0
  69. package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +169 -0
  70. package/plugins/immune-brain/runtime/kernel/readiness.ts +282 -0
  71. package/plugins/immune-brain/runtime/kernel/readiness_evidence.ts +132 -0
  72. package/plugins/immune-brain/runtime/kernel/reducer.ts +624 -0
  73. package/plugins/immune-brain/runtime/kernel/storage.ts +1780 -0
  74. package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +791 -0
  75. package/plugins/immune-brain/runtime/kernel/storage_paths.ts +492 -0
  76. package/plugins/immune-brain/runtime/kernel/types.ts +295 -0
  77. package/plugins/immune-brain/runtime/kernel/validation.ts +963 -0
  78. package/plugins/immune-brain/runtime/loop_contract.ts +362 -0
  79. package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +462 -0
  80. package/plugins/immune-brain/runtime/plan_core.ts +1053 -0
  81. package/plugins/immune-brain/runtime/prompts/advisory-reviewer.md +16 -0
  82. package/plugins/immune-brain/runtime/prompts/arch-explorer.md +14 -0
  83. package/plugins/immune-brain/runtime/prompts/code-review.md +15 -0
  84. package/plugins/immune-brain/runtime/prompts/compounder.md +20 -0
  85. package/plugins/immune-brain/runtime/prompts/executor.md +13 -0
  86. package/plugins/immune-brain/runtime/prompts/pr-fix.md +81 -0
  87. package/plugins/immune-brain/runtime/prompts/qa.md +23 -0
  88. package/plugins/immune-brain/runtime/prompts/test-fixer.md +3 -0
  89. package/plugins/immune-brain/runtime/prompts/ui-review.md +14 -0
  90. package/plugins/immune-brain/runtime/role_prompt_bridge.ts +160 -0
  91. package/plugins/immune-brain/runtime/v4_runtime.ts +295 -0
  92. package/plugins/immune-brain/runtime/verification_descriptor.ts +162 -0
  93. package/plugins/immune-brain/runtime/workspace_scope.ts +623 -0
  94. package/plugins/immune-brain/skills/.ignore +1 -0
  95. package/plugins/immune-brain/skills/BASELINE.md +138 -0
  96. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +66 -0
  97. package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +11 -0
  98. package/plugins/immune-brain/skills/imm-loop/SKILL.md +52 -0
  99. package/plugins/immune-brain/skills/imm-planner/SKILL.md +221 -0
  100. package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +10 -0
  101. package/plugins/immune-brain/skills/registry.yaml +49 -0
@@ -0,0 +1,1160 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ constants as fsConstants,
5
+ lstatSync,
6
+ openSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { execFileSync } from "node:child_process";
11
+ import { join, resolve } from "node:path";
12
+ import process from "node:process";
13
+ import { fileURLToPath } from "node:url";
14
+ import {
15
+ KernelStoreSecurityError,
16
+ appendJournalEntry,
17
+ readSecureProjectFile,
18
+ type JournalEntry,
19
+ type JournalReasonCode,
20
+ type TaskPhase,
21
+ } from "../kernel";
22
+ import {
23
+ INTENT_MAX_BYTES,
24
+ canonicalIntentHash,
25
+ parseTaskIntentV1,
26
+ } from "../kernel/intent";
27
+ import {
28
+ canonicalDescriptorBytes,
29
+ parseVerificationDescriptor,
30
+ } from "../verification_descriptor";
31
+ import { inspectRoutingPolicy } from "../managed_task_routing_policy"
32
+ import { projectLegacyAudit } from "../kernel/legacy_audit";
33
+ import { inspectStorageLayout } from "../kernel/storage_paths";
34
+ import { migrateLegacyLayout } from "../kernel/storage_layout_migration";
35
+ import { readBackendClaim } from "../kernel/backend_claim";
36
+ import { withKernelStoreLock } from "../kernel";
37
+
38
+ export interface KernelCommandResult {
39
+ stdout: string;
40
+ stderr: string;
41
+ returncode: number;
42
+ }
43
+
44
+ interface KernelExecution {
45
+ result: KernelCommandResult;
46
+ journal: Omit<JournalEntry, "contract" | "timestamp">;
47
+ }
48
+
49
+ function jsonResult(payload: unknown, returncode = 0): KernelCommandResult {
50
+ return {
51
+ stdout: `${JSON.stringify(payload, null, 2)}\n`,
52
+ stderr: "",
53
+ returncode,
54
+ };
55
+ }
56
+
57
+ function errorResult(code: string, message: string, returncode: number): KernelCommandResult {
58
+ return jsonResult({ error: { code, message } }, returncode);
59
+ }
60
+
61
+ function asRecord(value: unknown): Record<string, unknown> {
62
+ return value && typeof value === "object" && !Array.isArray(value)
63
+ ? (value as Record<string, unknown>)
64
+ : {};
65
+ }
66
+
67
+ function journalFor(
68
+ command: string,
69
+ phase: TaskPhase | null,
70
+ result: JournalEntry["result"],
71
+ reasonCode: JournalReasonCode,
72
+ taskId: string | null,
73
+ recoveryHint: string | null,
74
+ userIntervention = false,
75
+ ): Omit<JournalEntry, "contract" | "timestamp"> {
76
+ return {
77
+ task_id: taskId,
78
+ command,
79
+ entry_phase: phase,
80
+ result,
81
+ reason_code: reasonCode,
82
+ recovery_hint: recoveryHint,
83
+ planner_reentry: false,
84
+ user_intervention: userIntervention,
85
+ };
86
+ }
87
+
88
+ function sourceFailure(command: string, error: unknown): KernelExecution {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ const code: JournalReasonCode = message.startsWith("source_missing:")
91
+ ? "source_missing"
92
+ : error instanceof KernelStoreSecurityError
93
+ ? "source_invalid"
94
+ : "source_read_failed";
95
+ return {
96
+ result: errorResult(code, message, 1),
97
+ journal: journalFor(
98
+ command,
99
+ null,
100
+ "rejected",
101
+ code,
102
+ null,
103
+ "Resolve the reported storage or legacy-reader condition before retrying.",
104
+ ),
105
+ };
106
+ }
107
+
108
+ /**
109
+ * `status --json` is strictly read-only and reports layout facts plus Kernel
110
+ * ownership facts. It never projects the archived v3 Ledger as current
111
+ * authority; `audit --legacy` is the only legacy reader.
112
+ */
113
+ function runStatus(root: string): KernelExecution {
114
+ try {
115
+ const layout = inspectStorageLayout(root);
116
+ const claim = readBackendClaim(root);
117
+ const workspace = (() => {
118
+ try {
119
+ const raw = JSON.parse(readSecureProjectFile(root, ".imm/state/workspace.json")) as {
120
+ current_working?: unknown;
121
+ };
122
+ return typeof raw.current_working === "string" ? raw.current_working : null;
123
+ } catch {
124
+ return null;
125
+ }
126
+ })();
127
+ return {
128
+ result: jsonResult({
129
+ contract: "assurance_kernel/status/v1",
130
+ layout,
131
+ kernel: {
132
+ claim: claim
133
+ ? {
134
+ task_id: claim.task_id,
135
+ lifecycle_status: claim.lifecycle_status,
136
+ }
137
+ : null,
138
+ workspace: { current_working: workspace },
139
+ },
140
+ }),
141
+ journal: journalFor(
142
+ "status",
143
+ null,
144
+ layout.layout === "ready" || layout.layout === "migration_uncommitted" ? "ok" : "escalated",
145
+ layout.layout === "ready" ? "command_ok" : "source_invalid",
146
+ null,
147
+ layout.reason,
148
+ ),
149
+ };
150
+ } catch (error) {
151
+ return sourceFailure("status", error);
152
+ }
153
+ }
154
+
155
+ const INTENT_SIDECAR_PREFIX = "docs/plans/";
156
+ const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
157
+
158
+ function intentSidecarPath(taskId: string): string {
159
+ if (!TASK_ID_PATTERN.test(taskId))
160
+ throw new Error("task id must match [A-Za-z0-9][A-Za-z0-9._-]{0,127}");
161
+ return `${INTENT_SIDECAR_PREFIX}${taskId}.intent.json`;
162
+ }
163
+
164
+ /**
165
+ * Bounded synchronous stdin read, used ONLY by the exact
166
+ * `intent author <path> --stdin` branch. Help, validation, and all pre-existing
167
+ * imm-kernel commands never read file descriptor 0.
168
+ */
169
+ function readBoundedStdin(maxBytes: number): string {
170
+ const bytes = readFileSync(0);
171
+ if (bytes.byteLength > maxBytes)
172
+ throw new Error(`candidate exceeds the ${maxBytes} byte TaskIntent bound`);
173
+ return bytes.toString("utf8");
174
+ }
175
+
176
+ function intentActiveOwner(root: string): { kernel: boolean; v3: boolean } {
177
+ let kernelOwner = false;
178
+ let v3Owner = false;
179
+ try {
180
+ const claim = readBackendClaim(root);
181
+ kernelOwner = claim !== null;
182
+ } catch {
183
+ kernelOwner = false;
184
+ }
185
+ try {
186
+ const state = JSON.parse(
187
+ readSecureProjectFile(root, ".imm/memory/current_iteration.json"),
188
+ ) as Record<string, unknown>;
189
+ if (typeof state.runtime_status === "string" && state.runtime_status !== "idle")
190
+ v3Owner = true;
191
+ } catch {
192
+ // No readable v3 Ledger means no v3 owner.
193
+ }
194
+ return { kernel: kernelOwner, v3: v3Owner };
195
+ }
196
+
197
+ /**
198
+ * Canonical `imm-kernel intent author <path> --stdin --json`.
199
+ * Host-neutral TaskIntent draft creation: strict parsing, verification
200
+ * descriptor canonicalization, and exclusive no-overwrite creation of exactly
201
+ * one untracked draft. Never stages Git, enrolls, writes workflow state or
202
+ * journal, creates parents, or overwrites.
203
+ */
204
+ function runIntentAuthor(args: string[], root: string): KernelExecution {
205
+ const nonFlags = args.filter((arg) => !arg.startsWith("-"));
206
+ if (nonFlags.length !== 1)
207
+ return {
208
+ result: errorResult(
209
+ "invalid_command",
210
+ "imm-kernel intent author requires exactly one destination path",
211
+ 2,
212
+ ),
213
+ journal: journalFor(
214
+ "intent",
215
+ null,
216
+ "rejected",
217
+ "invalid_command",
218
+ null,
219
+ "Run imm-kernel intent author <path> --stdin --json.",
220
+ ),
221
+ };
222
+ if (!args.includes("--stdin"))
223
+ return {
224
+ result: errorResult(
225
+ "stdin_required",
226
+ "imm-kernel intent author requires --stdin for the bounded candidate input",
227
+ 2,
228
+ ),
229
+ journal: journalFor(
230
+ "intent",
231
+ null,
232
+ "rejected",
233
+ "invalid_command",
234
+ null,
235
+ "Run imm-kernel intent author <path> --stdin --json.",
236
+ ),
237
+ };
238
+ const unsupported = args.filter(
239
+ (arg) => arg.startsWith("-") && !["--stdin", "--json"].includes(arg),
240
+ );
241
+ if (unsupported.length > 0)
242
+ return {
243
+ result: errorResult("invalid_command", `unsupported intent author option: ${unsupported[0]}`, 2),
244
+ journal: journalFor(
245
+ "intent",
246
+ null,
247
+ "rejected",
248
+ "invalid_command",
249
+ null,
250
+ "Run imm-kernel intent author <path> --stdin --json.",
251
+ ),
252
+ };
253
+ const pathArg = nonFlags[0];
254
+ // Routing policy must resolve to the active kernel_task_intent route.
255
+ const policy = inspectRoutingPolicy(root);
256
+ if (policy.policy_status === "invalid") {
257
+ return {
258
+ result: errorResult(
259
+ "routing_policy_invalid",
260
+ `routing policy is present but cannot be trusted (${policy.reason_code}); new managed authority is blocked`,
261
+ 1,
262
+ ),
263
+ journal: journalFor(
264
+ "intent",
265
+ null,
266
+ "rejected",
267
+ "routing_policy_invalid",
268
+ null,
269
+ "Repair or remove the policy file; authoring requires the active kernel_task_intent route.",
270
+ ),
271
+ };
272
+ }
273
+ if (policy.policy_status !== "active" || policy.route !== "kernel_task_intent") {
274
+ return {
275
+ result: errorResult(
276
+ "intent_authoring_not_routed",
277
+ "intent authoring requires the active kernel_task_intent routing policy",
278
+ 1,
279
+ ),
280
+ journal: journalFor(
281
+ "intent",
282
+ null,
283
+ "rejected",
284
+ "routing_unavailable",
285
+ null,
286
+ "Activate the Git-owned managed-task routing policy before authoring.",
287
+ ),
288
+ };
289
+ }
290
+
291
+ // Storage-layout gate (BR-REQ-005/006): a stateful mutation recovers or
292
+ // migrates first and then STOPS without authoring. Kernel transaction
293
+ // markers recover under the store lock; the one-release migrator replays
294
+ // its frozen manifest under both locks. The original authoring is retried
295
+ // only after the affected migration diff is committed.
296
+ try {
297
+ withKernelStoreLock(root, () => undefined);
298
+ } catch (error) {
299
+ return {
300
+ result: errorResult(
301
+ "layout_recovery_failed",
302
+ `Kernel transaction recovery failed before authoring: ${error instanceof Error ? error.message : String(error)}`,
303
+ 1,
304
+ ),
305
+ journal: journalFor(
306
+ "intent",
307
+ null,
308
+ "rejected",
309
+ "source_invalid",
310
+ null,
311
+ "Resolve the pending Kernel transaction marker before retrying.",
312
+ ),
313
+ };
314
+ }
315
+ const layout = inspectStorageLayout(root);
316
+ if (layout.layout === "migration_blocked_active") {
317
+ return {
318
+ result: errorResult(
319
+ "layout_migration_blocked",
320
+ layout.reason ?? "an active old-layout owner blocks migration",
321
+ 1,
322
+ ),
323
+ journal: journalFor(
324
+ "intent",
325
+ null,
326
+ "rejected",
327
+ "source_invalid",
328
+ null,
329
+ "Settle or stop the active old-layout owner with the prior runtime before authoring.",
330
+ ),
331
+ };
332
+ }
333
+ if (layout.layout === "recovery_required" || layout.layout === "migration_required") {
334
+ const migration = migrateLegacyLayout(root);
335
+ if (migration.outcome === "migrated") {
336
+ return {
337
+ result: jsonResult({
338
+ contract: "assurance_kernel/migration_completed/v1",
339
+ operation: "intent author",
340
+ affected_paths: migration.affected_paths,
341
+ next_action: "commit the affected migration paths, then retry intent author",
342
+ }),
343
+ journal: journalFor(
344
+ "intent",
345
+ null,
346
+ "escalated",
347
+ "migration_ambiguous",
348
+ null,
349
+ "Legacy evidence was relocated without Git index writes; commit and retry.",
350
+ ),
351
+ };
352
+ }
353
+ if (migration.outcome === "migration_uncommitted") {
354
+ return {
355
+ result: errorResult(
356
+ "migration_uncommitted",
357
+ `affected storage paths differ from HEAD: ${migration.affected_paths.join(", ") || "(none)"}`,
358
+ 1,
359
+ ),
360
+ journal: journalFor(
361
+ "intent",
362
+ null,
363
+ "rejected",
364
+ "migration_ambiguous",
365
+ null,
366
+ "Commit or restore the affected paths before retrying.",
367
+ ),
368
+ };
369
+ }
370
+ return {
371
+ result: errorResult(
372
+ layout.layout === "recovery_required" ? "layout_recovery_required" : "layout_migration_blocked",
373
+ migration.reason ?? layout.reason ?? "storage layout is not ready",
374
+ 1,
375
+ ),
376
+ journal: journalFor(
377
+ "intent",
378
+ null,
379
+ "rejected",
380
+ "source_invalid",
381
+ null,
382
+ "Resolve the reported storage layout condition before authoring.",
383
+ ),
384
+ };
385
+ }
386
+ if (layout.layout !== "ready") {
387
+ return {
388
+ result: errorResult(
389
+ "layout_not_ready",
390
+ layout.reason ?? `storage layout is ${layout.layout}`,
391
+ 1,
392
+ ),
393
+ journal: journalFor(
394
+ "intent",
395
+ null,
396
+ "rejected",
397
+ "source_invalid",
398
+ null,
399
+ "Commit the migration diff or resolve the layout condition before authoring.",
400
+ ),
401
+ };
402
+ }
403
+
404
+ // Existing managed ownership rejects before opening the destination.
405
+ const owner = intentActiveOwner(root);
406
+ if (owner.kernel)
407
+ return {
408
+ result: errorResult(
409
+ "kernel_owner_active",
410
+ "an active Kernel claim already owns this workspace; authoring is blocked",
411
+ 1,
412
+ ),
413
+ journal: journalFor(
414
+ "intent",
415
+ null,
416
+ "rejected",
417
+ "kernel_owner_active",
418
+ null,
419
+ "Route to imm-loop instead of authoring a new draft.",
420
+ ),
421
+ };
422
+ if (owner.v3)
423
+ return {
424
+ result: errorResult(
425
+ "v3_owner_nonterminal",
426
+ "a nonterminal v3 Plan owns this workspace; authoring is blocked",
427
+ 1,
428
+ ),
429
+ journal: journalFor(
430
+ "intent",
431
+ null,
432
+ "rejected",
433
+ "v3_owner_nonterminal",
434
+ null,
435
+ "Finish or terminate the active v3 Plan before authoring.",
436
+ ),
437
+ };
438
+
439
+ // Bounded stdin candidate, then strict parsing.
440
+ let candidate: string;
441
+ try {
442
+ candidate = readBoundedStdin(INTENT_MAX_BYTES);
443
+ } catch (error) {
444
+ return {
445
+ result: errorResult(
446
+ "candidate_oversize",
447
+ `candidate stdin exceeds the TaskIntent bound: ${error instanceof Error ? error.message : error}`,
448
+ 1,
449
+ ),
450
+ journal: journalFor(
451
+ "intent",
452
+ null,
453
+ "rejected",
454
+ "input_oversize",
455
+ null,
456
+ "Reduce the candidate below the TaskIntent byte bound.",
457
+ ),
458
+ };
459
+ }
460
+ let parsed: unknown;
461
+ try {
462
+ parsed = JSON.parse(candidate);
463
+ } catch (error) {
464
+ return {
465
+ result: errorResult(
466
+ "candidate_invalid",
467
+ `candidate is not valid JSON: ${error instanceof Error ? error.message : error}`,
468
+ 1,
469
+ ),
470
+ journal: journalFor(
471
+ "intent",
472
+ null,
473
+ "rejected",
474
+ "input_invalid",
475
+ null,
476
+ "Submit a complete task_intent/v1 JSON candidate.",
477
+ ),
478
+ };
479
+ }
480
+ let intent: ReturnType<typeof parseTaskIntentV1>;
481
+ try {
482
+ intent = parseTaskIntentV1(parsed);
483
+ } catch (error) {
484
+ return {
485
+ result: errorResult(
486
+ "intent_invalid",
487
+ `candidate failed strict task_intent/v1 parsing: ${error instanceof Error ? error.message : error}`,
488
+ 1,
489
+ ),
490
+ journal: journalFor(
491
+ "intent",
492
+ null,
493
+ "rejected",
494
+ "intent_invalid",
495
+ null,
496
+ "Fix the reported task_intent/v1 violations and retry.",
497
+ ),
498
+ };
499
+ }
500
+
501
+ // Task/path identity: destination must be docs/plans/<task-id>.intent.json.
502
+ let sidecarPath: string;
503
+ try {
504
+ sidecarPath = intentSidecarPath(intent.task_id);
505
+ } catch (error) {
506
+ return {
507
+ result: errorResult(
508
+ "task_id_invalid",
509
+ `task id is invalid: ${error instanceof Error ? error.message : error}`,
510
+ 1,
511
+ ),
512
+ journal: journalFor(
513
+ "intent",
514
+ null,
515
+ "rejected",
516
+ "intent_invalid",
517
+ null,
518
+ "Use a task id matching the sidecar naming convention.",
519
+ ),
520
+ };
521
+ }
522
+ if (pathArg !== sidecarPath && resolve(root, pathArg) !== resolve(root, sidecarPath)) {
523
+ return {
524
+ result: errorResult(
525
+ "task_path_mismatch",
526
+ `destination must be ${sidecarPath} for task ${intent.task_id}`,
527
+ 1,
528
+ ),
529
+ journal: journalFor(
530
+ "intent",
531
+ null,
532
+ "rejected",
533
+ "task_path_mismatch",
534
+ null,
535
+ "Author the intent to its canonical sidecar path.",
536
+ ),
537
+ };
538
+ }
539
+
540
+ // Canonicalize every acceptance verification string through the shared parser.
541
+ let canonicalAcceptance: Array<{
542
+ id: string;
543
+ assertion: string;
544
+ verification: string;
545
+ }>;
546
+ try {
547
+ canonicalAcceptance = intent.acceptance.map((item) => ({
548
+ id: item.id,
549
+ assertion: item.assertion,
550
+ verification: canonicalDescriptorBytes(
551
+ parseVerificationDescriptor(item.verification),
552
+ ),
553
+ }));
554
+ } catch (error) {
555
+ return {
556
+ result: errorResult(
557
+ "intent_invalid",
558
+ `candidate acceptance verification failed strict verification_descriptor/v1 parsing: ${error instanceof Error ? error.message : error}`,
559
+ 1,
560
+ ),
561
+ journal: journalFor(
562
+ "intent",
563
+ null,
564
+ "rejected",
565
+ "intent_invalid",
566
+ null,
567
+ "Fix the reported verification_descriptor/v1 violations and retry.",
568
+ ),
569
+ };
570
+ }
571
+ const canonicalIntent = {
572
+ contract: intent.contract,
573
+ task_id: intent.task_id,
574
+ owner: intent.owner,
575
+ goal: intent.goal,
576
+ acceptance: canonicalAcceptance,
577
+ scope_hint: intent.scope_hint,
578
+ risk: intent.risk,
579
+ revision: intent.revision,
580
+ };
581
+ const canonicalBytes = `${JSON.stringify(canonicalIntent, null, 2)}\n`;
582
+ const contentHash = canonicalIntentHash(canonicalIntent);
583
+
584
+ // Exclusive no-overwrite creation of exactly one regular file.
585
+ const destination = resolve(root, sidecarPath);
586
+ if (!destination.startsWith(resolve(root) + "/"))
587
+ return {
588
+ result: errorResult("destination_invalid", "destination escapes the project root", 1),
589
+ journal: journalFor(
590
+ "intent",
591
+ null,
592
+ "rejected",
593
+ "destination_invalid",
594
+ null,
595
+ "The intent destination must stay inside the project.",
596
+ ),
597
+ };
598
+ const parent = resolve(root, INTENT_SIDECAR_PREFIX);
599
+ try {
600
+ const parentStat = lstatSync(parent);
601
+ if (!parentStat.isDirectory())
602
+ return {
603
+ result: errorResult("destination_parent_invalid", "docs/plans is not a directory", 1),
604
+ journal: journalFor(
605
+ "intent",
606
+ null,
607
+ "rejected",
608
+ "destination_parent_invalid",
609
+ null,
610
+ "The docs/plans directory must exist.",
611
+ ),
612
+ };
613
+ } catch {
614
+ return {
615
+ result: errorResult(
616
+ "destination_parent_missing",
617
+ "docs/plans does not exist; parent creation is never implicit",
618
+ 1,
619
+ ),
620
+ journal: journalFor(
621
+ "intent",
622
+ null,
623
+ "rejected",
624
+ "destination_parent_missing",
625
+ null,
626
+ "Create docs/plans explicitly before authoring.",
627
+ ),
628
+ };
629
+ }
630
+ let fd: number;
631
+ try {
632
+ fd = openSync(
633
+ destination,
634
+ fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY,
635
+ 0o644,
636
+ );
637
+ } catch (error) {
638
+ const code = (error as { code?: string }).code;
639
+ const reason =
640
+ code === "EEXIST"
641
+ ? "destination already exists; no-overwrite authoring never replaces it"
642
+ : code === "ENOENT"
643
+ ? "destination parent does not exist"
644
+ : `destination cannot be created: ${error instanceof Error ? error.message : error}`;
645
+ return {
646
+ result: errorResult("destination_exists", reason, 1),
647
+ journal: journalFor(
648
+ "intent",
649
+ null,
650
+ "rejected",
651
+ "destination_exists",
652
+ null,
653
+ "Remove or rename the existing file; drafts are never overwritten.",
654
+ ),
655
+ };
656
+ }
657
+ try {
658
+ writeFileSync(fd, canonicalBytes, "utf8");
659
+ } catch (error) {
660
+ closeSync(fd);
661
+ return {
662
+ result: errorResult(
663
+ "destination_write_failed",
664
+ `draft write failed: ${error instanceof Error ? error.message : error}`,
665
+ 1,
666
+ ),
667
+ journal: journalFor(
668
+ "intent",
669
+ null,
670
+ "rejected",
671
+ "destination_write_failed",
672
+ null,
673
+ "Retry authoring after fixing the destination.",
674
+ ),
675
+ };
676
+ }
677
+ closeSync(fd);
678
+
679
+ return {
680
+ result: jsonResult(
681
+ {
682
+ contract: "assurance_kernel/intent_author/v1",
683
+ path: sidecarPath,
684
+ task_id: intent.task_id,
685
+ content_hash: contentHash,
686
+ revision: intent.revision,
687
+ git_tracked: false,
688
+ enrollment_ready: false,
689
+ },
690
+ 0,
691
+ ),
692
+ journal: journalFor(
693
+ "intent",
694
+ null,
695
+ "ok",
696
+ "command_ok",
697
+ null,
698
+ null,
699
+ ),
700
+ };
701
+ }
702
+
703
+ /**
704
+ * Canonical `imm-kernel intent validate <path> --json`.
705
+ * Zero writes: no friction journal, migration, lock, receipt, observation,
706
+ * TaskRecord, claim, Ledger, Git index, or session write. Returns a stable
707
+ * bounded projection.
708
+ */
709
+ function runIntentValidate(args: string[], root: string): KernelExecution {
710
+ const nonFlags = args.filter((arg) => !arg.startsWith("-"));
711
+ if (nonFlags.length !== 1)
712
+ return {
713
+ result: errorResult(
714
+ "invalid_command",
715
+ "imm-kernel intent validate requires exactly one path",
716
+ 2,
717
+ ),
718
+ journal: journalFor(
719
+ "intent",
720
+ null,
721
+ "rejected",
722
+ "invalid_command",
723
+ null,
724
+ "Run imm-kernel intent validate <path> --json.",
725
+ ),
726
+ };
727
+ const unsupported = args.filter(
728
+ (arg) => arg.startsWith("-") && !["--json"].includes(arg),
729
+ );
730
+ if (unsupported.length > 0)
731
+ return {
732
+ result: errorResult("invalid_command", `unsupported intent validate option: ${unsupported[0]}`, 2),
733
+ journal: journalFor(
734
+ "intent",
735
+ null,
736
+ "rejected",
737
+ "invalid_command",
738
+ null,
739
+ "Run imm-kernel intent validate <path> --json.",
740
+ ),
741
+ };
742
+ const pathArg = nonFlags[0];
743
+
744
+ // Shared zero-write gate (review-4): validation reports the layout
745
+ // condition instead of reading authority bytes while migration/recovery
746
+ // is pending.
747
+ const layout = inspectStorageLayout(root);
748
+ if (!["ready", "migration_uncommitted"].includes(layout.layout))
749
+ return {
750
+ result: errorResult(
751
+ "layout_not_ready",
752
+ `intent validate blocked by storage layout (${layout.layout}): ${layout.reason ?? ""}`,
753
+ 1,
754
+ ),
755
+ journal: journalFor(
756
+ "intent",
757
+ null,
758
+ "rejected",
759
+ "source_invalid",
760
+ null,
761
+ layout.reason,
762
+ ),
763
+ };
764
+
765
+ // Canonical-root containment, no symlink components.
766
+ let canonicalRoot: string;
767
+ try {
768
+ canonicalRoot = resolve(root);
769
+ } catch {
770
+ return {
771
+ result: errorResult("root_invalid", "project root is unavailable", 1),
772
+ journal: journalFor(
773
+ "intent",
774
+ null,
775
+ "rejected",
776
+ "source_read_failed",
777
+ null,
778
+ null,
779
+ ),
780
+ };
781
+ }
782
+ const target = resolve(canonicalRoot, pathArg);
783
+ const relative = target.startsWith(canonicalRoot + "/")
784
+ ? target.slice(canonicalRoot.length + 1)
785
+ : null;
786
+ if (!relative || !relative.startsWith(INTENT_SIDECAR_PREFIX) || !relative.endsWith(".intent.json")) {
787
+ return {
788
+ result: errorResult(
789
+ "path_invalid",
790
+ "intent path must be docs/plans/<task-id>.intent.json inside the project",
791
+ 1,
792
+ ),
793
+ journal: journalFor(
794
+ "intent",
795
+ null,
796
+ "rejected",
797
+ "source_invalid",
798
+ null,
799
+ "Validate a canonical intent sidecar path.",
800
+ ),
801
+ };
802
+ }
803
+ const taskId = relative
804
+ .slice(INTENT_SIDECAR_PREFIX.length)
805
+ .replace(/\.intent\.json$/, "");
806
+
807
+ let raw: string;
808
+ try {
809
+ raw = readSecureProjectFile(root, relative);
810
+ } catch (error) {
811
+ const message = error instanceof Error ? error.message : String(error);
812
+ return {
813
+ result: jsonResult(
814
+ {
815
+ contract: "assurance_kernel/intent_validation/v1",
816
+ valid: false,
817
+ path: relative,
818
+ task_id: taskId,
819
+ reason: message,
820
+ },
821
+ 0,
822
+ ),
823
+ journal: journalFor(
824
+ "intent",
825
+ null,
826
+ "ok",
827
+ "command_ok",
828
+ null,
829
+ null,
830
+ ),
831
+ };
832
+ }
833
+ if (Buffer.byteLength(raw, "utf8") > INTENT_MAX_BYTES) {
834
+ return {
835
+ result: jsonResult(
836
+ {
837
+ contract: "assurance_kernel/intent_validation/v1",
838
+ valid: false,
839
+ path: relative,
840
+ task_id: taskId,
841
+ reason: "intent sidecar exceeds 64 KiB",
842
+ },
843
+ 0,
844
+ ),
845
+ journal: journalFor(
846
+ "intent",
847
+ null,
848
+ "ok",
849
+ "command_ok",
850
+ null,
851
+ null,
852
+ ),
853
+ };
854
+ }
855
+
856
+ let parsed: unknown;
857
+ try {
858
+ parsed = JSON.parse(raw);
859
+ } catch (error) {
860
+ return {
861
+ result: jsonResult(
862
+ {
863
+ contract: "assurance_kernel/intent_validation/v1",
864
+ valid: false,
865
+ path: relative,
866
+ task_id: taskId,
867
+ reason: `not valid JSON: ${error instanceof Error ? error.message : error}`,
868
+ },
869
+ 0,
870
+ ),
871
+ journal: journalFor(
872
+ "intent",
873
+ null,
874
+ "ok",
875
+ "command_ok",
876
+ null,
877
+ null,
878
+ ),
879
+ };
880
+ }
881
+
882
+ // Strict parse + verification eligibility per acceptance item.
883
+ let intent: ReturnType<typeof parseTaskIntentV1>;
884
+ try {
885
+ intent = parseTaskIntentV1(parsed);
886
+ } catch (error) {
887
+ return {
888
+ result: jsonResult(
889
+ {
890
+ contract: "assurance_kernel/intent_validation/v1",
891
+ valid: false,
892
+ path: relative,
893
+ task_id: taskId,
894
+ reason: `strict task_intent/v1 parsing failed: ${error instanceof Error ? error.message : error}`,
895
+ },
896
+ 0,
897
+ ),
898
+ journal: journalFor(
899
+ "intent",
900
+ null,
901
+ "ok",
902
+ "command_ok",
903
+ null,
904
+ null,
905
+ ),
906
+ };
907
+ }
908
+ if (intent.task_id !== taskId) {
909
+ return {
910
+ result: jsonResult(
911
+ {
912
+ contract: "assurance_kernel/intent_validation/v1",
913
+ valid: false,
914
+ path: relative,
915
+ task_id: taskId,
916
+ reason: `intent.task_id ${intent.task_id} does not match the sidecar filename task id`,
917
+ },
918
+ 0,
919
+ ),
920
+ journal: journalFor(
921
+ "intent",
922
+ null,
923
+ "ok",
924
+ "command_ok",
925
+ null,
926
+ null,
927
+ ),
928
+ };
929
+ }
930
+
931
+ const verification: Array<{ id: string; eligible: boolean; reason: string | null }> = [];
932
+ let allEligible = true;
933
+ for (const item of intent.acceptance) {
934
+ try {
935
+ parseVerificationDescriptor(item.verification);
936
+ verification.push({ id: item.id, eligible: true, reason: null });
937
+ } catch (error) {
938
+ allEligible = false;
939
+ verification.push({
940
+ id: item.id,
941
+ eligible: false,
942
+ reason: error instanceof Error ? error.message : String(error),
943
+ });
944
+ }
945
+ }
946
+
947
+ // Git ownership status: tracked, untracked, or unavailable.
948
+ let gitTracked: boolean;
949
+ try {
950
+ execFileSync(
951
+ "git",
952
+ ["ls-files", "--error-unmatch", "--", relative],
953
+ { cwd: canonicalRoot, stdio: ["ignore", "pipe", "pipe"] },
954
+ );
955
+ gitTracked = true;
956
+ } catch {
957
+ gitTracked = false;
958
+ }
959
+
960
+ const contentHash = canonicalIntentHash(intent);
961
+ const enrollmentReady = gitTracked && allEligible;
962
+ return {
963
+ result: jsonResult(
964
+ {
965
+ contract: "assurance_kernel/intent_validation/v1",
966
+ valid: true,
967
+ path: relative,
968
+ task_id: intent.task_id,
969
+ content_hash: contentHash,
970
+ risk: intent.risk,
971
+ acceptance_ids: intent.acceptance.map((item) => item.id),
972
+ verification: verification,
973
+ git_ownership: gitTracked ? "tracked" : "untracked",
974
+ enrollment_ready: enrollmentReady,
975
+ },
976
+ 0,
977
+ ),
978
+ journal: journalFor(
979
+ "intent",
980
+ null,
981
+ "ok",
982
+ "command_ok",
983
+ null,
984
+ null,
985
+ ),
986
+ };
987
+ }
988
+
989
+ function executeKernelCommand(args: string[], root: string): KernelExecution {
990
+ const command = args[0] ?? "";
991
+ const flags = args.slice(1);
992
+ if (command === "intent") {
993
+ const sub = args[1] ?? "";
994
+ if (sub === "author") return runIntentAuthor(args.slice(2), root);
995
+ if (sub === "validate") return runIntentValidate(args.slice(2), root);
996
+ return {
997
+ result: errorResult(
998
+ "invalid_command",
999
+ "usage: imm-kernel intent author <path> --stdin --json | validate <path> --json",
1000
+ 2,
1001
+ ),
1002
+ journal: journalFor(
1003
+ "intent",
1004
+ null,
1005
+ "rejected",
1006
+ "invalid_command",
1007
+ null,
1008
+ "Run imm-kernel intent --help.",
1009
+ ),
1010
+ };
1011
+ }
1012
+ if (command === "status") {
1013
+ if (flags.some((flag) => flag !== "--json"))
1014
+ return {
1015
+ result: errorResult("invalid_command", "status accepts only --json", 2),
1016
+ journal: journalFor(
1017
+ command,
1018
+ null,
1019
+ "rejected",
1020
+ "invalid_command",
1021
+ null,
1022
+ "Run imm-kernel status --json.",
1023
+ ),
1024
+ };
1025
+ return runStatus(root);
1026
+ }
1027
+ if (command === "audit") {
1028
+ if (args.length !== 2 || args[1] !== "--legacy")
1029
+ return {
1030
+ result: errorResult(
1031
+ "invalid_command",
1032
+ "audit accepts only --legacy",
1033
+ 2,
1034
+ ),
1035
+ journal: journalFor(
1036
+ "audit",
1037
+ null,
1038
+ "rejected",
1039
+ "invalid_command",
1040
+ null,
1041
+ "Run imm-kernel audit --legacy.",
1042
+ ),
1043
+ };
1044
+ // Shared zero-write gate (review-4): the audit reports the layout
1045
+ // condition instead of reading legacy authority while a migration or
1046
+ // recovery is pending.
1047
+ const layout = inspectStorageLayout(root);
1048
+ if (!["ready", "migration_uncommitted"].includes(layout.layout))
1049
+ return {
1050
+ result: jsonResult({
1051
+ contract: "assurance_kernel/legacy_audit/v1",
1052
+ source: null,
1053
+ read_only: true,
1054
+ writes_performed: false,
1055
+ plan_path: null,
1056
+ runtime_status: null,
1057
+ active_step: null,
1058
+ step_count: 0,
1059
+ phase: null,
1060
+ digest: "sha256:none",
1061
+ redacted: true,
1062
+ layout: {
1063
+ layout: layout.layout,
1064
+ reason: layout.reason,
1065
+ next_action:
1066
+ layout.layout === "migration_required"
1067
+ ? "run the one-release migration through the next stateful mutation and commit the affected diff"
1068
+ : "resolve the reported layout condition before legacy audit",
1069
+ },
1070
+ }),
1071
+ journal: journalFor(
1072
+ "audit",
1073
+ null,
1074
+ "escalated",
1075
+ "source_invalid",
1076
+ null,
1077
+ layout.reason,
1078
+ ),
1079
+ };
1080
+ try {
1081
+ return {
1082
+ result: jsonResult(projectLegacyAudit(root)),
1083
+ journal: journalFor(
1084
+ "audit",
1085
+ null,
1086
+ "ok",
1087
+ "command_ok",
1088
+ null,
1089
+ null,
1090
+ ),
1091
+ };
1092
+ } catch (error) {
1093
+ return sourceFailure("audit", error);
1094
+ }
1095
+ }
1096
+ if (command === "--help" || command === "help")
1097
+ return {
1098
+ result: {
1099
+ stdout:
1100
+ "usage: imm-kernel status --json\n imm-kernel audit --legacy\n imm-kernel intent author <path> --stdin --json\n imm-kernel intent validate <path> --json\n",
1101
+ stderr: "",
1102
+ returncode: 0,
1103
+ },
1104
+ journal: journalFor("help", null, "ok", "command_ok", null, null),
1105
+ };
1106
+ return {
1107
+ result: errorResult(
1108
+ "invalid_command",
1109
+ "usage: imm-kernel status --json | audit --legacy | intent author <path> --stdin --json | intent validate <path> --json",
1110
+
1111
+ 2,
1112
+ ),
1113
+ journal: journalFor(
1114
+ command || "<missing>",
1115
+ null,
1116
+ "rejected",
1117
+ "invalid_command",
1118
+ null,
1119
+ "Run imm-kernel --help.",
1120
+ ),
1121
+ };
1122
+ }
1123
+
1124
+ export function runKernelCommand(
1125
+ args: string[],
1126
+ root = process.cwd(),
1127
+ ): KernelCommandResult {
1128
+ const execution = executeKernelCommand(args, root);
1129
+ // Retained read-only commands (intent/status/audit) and retired
1130
+ // subcommands (migrate/readiness/journal) are no-journal: they never
1131
+ // append the friction journal. Only the fallback unknown-command path
1132
+ // keeps journaling.
1133
+ if (["intent", "status", "audit", "migrate", "readiness", "journal"].includes(args[0] ?? ""))
1134
+ return execution.result;
1135
+ let warning = "";
1136
+ try {
1137
+ appendJournalEntry(root, {
1138
+ contract: "assurance_kernel/journal/v1",
1139
+ timestamp: new Date().toISOString(),
1140
+ ...execution.journal,
1141
+ });
1142
+ } catch (error) {
1143
+ warning = `warning: kernel journal append failed: ${error instanceof Error ? error.message : error}\n`;
1144
+ }
1145
+ return {
1146
+ ...execution.result,
1147
+ stderr: `${execution.result.stderr}${warning}`,
1148
+ };
1149
+ }
1150
+
1151
+ async function main(args: string[]): Promise<number> {
1152
+ const result = runKernelCommand(args);
1153
+ if (result.stdout) process.stdout.write(result.stdout);
1154
+ if (result.stderr) process.stderr.write(result.stderr);
1155
+ return result.returncode;
1156
+ }
1157
+
1158
+ if (fileURLToPath(import.meta.url) === process.argv[1]) {
1159
+ process.exit(await main(process.argv.slice(2)));
1160
+ }