immune-brain 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Pi and Claude Code are the supported hosts. Undeclared adapters remain unsupport
23
23
  - [Installation](#installation)
24
24
  - [Quick Start](#quick-start)
25
25
  - [How to Use](#how-to-use)
26
- - [The 5 Skills](#the-5-skills)
26
+ - [The 6 Skills](#the-6-skills)
27
27
  - [Lifecycle](#lifecycle)
28
28
  - [Configuration](#configuration)
29
29
  - [Project Layout](#project-layout)
@@ -87,12 +87,13 @@ You rarely need to remember skill names — **just describe your intent**:
87
87
  | Plan is approved, ready to build | "Start building" / `imm-loop` | → Executor builds, QA verifies, Review checks |
88
88
  | PR needs fixes after review | `imm-pr-fix` on that PR | → Standalone repair, no new managed task |
89
89
  | Docs are stale after changes | `imm-doc-prune` with manifest | → Prunes only approved stale docs |
90
+ | Agent instruction files are bloated | `imm-agent-doc-maintain` with manifest | → Keeps only necessary non-discoverable rules |
90
91
 
91
92
  > **Rule:** Managed work (brainstorm → plan → loop) starts only from explicit `imm-brainstorm`, `imm-planner`, or `imm-loop`. Ordinary Q&A or read-only requests stay host-native and never enroll a task.
92
93
 
93
94
  ---
94
95
 
95
- ## The 5 Skills
96
+ ## The 6 Skills
96
97
 
97
98
  | Skill | Type | When to use | What it does |
98
99
  |---|---|---|---|
@@ -101,6 +102,7 @@ You rarely need to remember skill names — **just describe your intent**:
101
102
  | `imm-loop` | Managed coordinator | Plan is validated | Drives execution → QA → Review → completion via foreground tools |
102
103
  | `imm-pr-fix` | Standalone | CI failed / review comments on a PR | Repairs one PR in place, no managed authority |
103
104
  | `imm-doc-prune` | Standalone | Stale current docs | Deletes only the hash-approved manifest entries |
105
+ | `imm-agent-doc-maintain` | Standalone | Bloated agent instructions | Minimizes tracked AGENTS/CLAUDE/GEMINI.md to necessary context |
104
106
 
105
107
  Internal roles (Executor, QA, Review, Compounder) are dispatched by `imm-loop` — you never invoke them directly.
106
108
 
package/README.zh-CN.md CHANGED
@@ -23,7 +23,7 @@ Pi 与 Claude Code 是支持的宿主。未声明的适配器仍不受支持。C
23
23
  - [安装](#安装)
24
24
  - [快速开始](#快速开始)
25
25
  - [如何使用](#如何使用)
26
- - [5 个 Skills](#5-个-skills)
26
+ - [6 个 Skills](#6-个-skills)
27
27
  - [生命周期](#生命周期)
28
28
  - [配置](#配置)
29
29
  - [项目结构](#项目结构)
@@ -87,12 +87,13 @@ QA 与 Review 以 foreground Tool 形式运行并回传结果,返回 `phase=do
87
87
  | 计划已确认,准备开干 | "开始构建" / `imm-loop` | → Executor 构建 → QA 验证 → Review 审查 |
88
88
  | PR 被评论 / CI 挂了 | 对该 PR 使用 `imm-pr-fix` | → 独立修复,不创建新 managed 任务 |
89
89
  | 文档过时需要清理 | `imm-doc-prune` + manifest | → 仅删除已审批的过时文档 |
90
+ | Agent instruction 文件膨胀 | `imm-agent-doc-maintain` + manifest | → 只保留不可直接推导的必要规则 |
90
91
 
91
92
  > **规则:** Managed 工作流(brainstorm → plan → loop)仅由显式的 `imm-brainstorm`、`imm-planner`、`imm-loop` 启动。普通问答、只读解释不会 Enrollment。
92
93
 
93
94
  ---
94
95
 
95
- ## 5 个 Skills
96
+ ## 6 个 Skills
96
97
 
97
98
  | Skill | 类型 | 何时使用 | 职责 |
98
99
  |---|---|---|---|
@@ -101,6 +102,7 @@ QA 与 Review 以 foreground Tool 形式运行并回传结果,返回 `phase=do
101
102
  | `imm-loop` | Managed 协调器 | 计划已验证 | 通过 foreground Tools 协调 执行 → QA → Review → 收尾 |
102
103
  | `imm-pr-fix` | 独立 | PR 需修复 | 原地修复单个 PR,不触及 managed authority |
103
104
  | `imm-doc-prune` | 独立 | 清理过时文档 | 仅删除哈希绑定的 manifest 条目 |
105
+ | `imm-agent-doc-maintain` | 独立 | Agent instruction 膨胀 | 将 tracked AGENTS/CLAUDE/GEMINI.md 压到最小必要上下文 |
104
106
 
105
107
  Executor、QA、Review、Compounder 等为 `imm-loop` 内部调度的角色,无需手动调用。
106
108
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "3.0.2",
3
+ "version": "3.2.0",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "3.0.2",
3
+ "version": "3.2.0",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -35,10 +35,14 @@ TaskRecords. Set one of these fixed directives in `AGENTS.md`:
35
35
 
36
36
  A repository directive overrides the global directive. A configured `github`
37
37
  default is standing opt-in for GitHub projection, but the literal user still
38
- confirms the Initiative name and immutable slug before the first remote
39
- mutation. Planner reports the selected carrier and its source. Projection
40
- failure is reported with a retry action; it does not silently switch carrier or
41
- block TaskIntent authoring, Enrollment, or execution.
38
+ confirms the Initiative name, immutable slug, complete Parent/Child decomposition,
39
+ granularity, and dependencies before the first remote mutation. Planner reports
40
+ the selected carrier and its source. After confirmation it publishes the complete
41
+ Issue graph in one idempotent batch and reports the recommended first unblocked
42
+ Task, stable dependency order, and parallel groups. Projection failure is reported
43
+ with a batch retry action and does not invalidate the authored planning files,
44
+ but it blocks Enrollment and execution handoff for that Initiative until the same
45
+ complete batch succeeds. It never silently switches carrier.
42
46
 
43
47
  ## Other Preferences
44
48
 
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: imm-agent-doc-maintain
3
+ description: Use to minimize tracked AGENTS.md, CLAUDE.md, and GEMINI.md files to necessary non-discoverable context after an explicit, hash-bound, user-approved manifest; never installs or validates project contracts.
4
+ ---
5
+
6
+ # Immune-Brain: Agent Doc Maintain
7
+
8
+ Minimize tracked agent instruction files in one Git repository to the smallest
9
+ set of persistent rules that are non-obvious, repeatable, stable, and costly to
10
+ violate. This is a standalone host-native maintenance entry, not a Managed Path
11
+ continuation and not an `imm-loop` internal-role dispatch.
12
+
13
+ ## Authority Boundary
14
+
15
+ This Skill maintains agent instruction files without creating or mutating
16
+ TaskIntent, TaskRecord, Kernel, Spec, or Plan authority. An already active
17
+ Managed task remains owned by `imm-loop`. Read-only inventory and manifest
18
+ production remain available. Classify each candidate overlapping the active
19
+ TaskIntent `scope_hint` as `BLOCKED_ACTIVE_SCOPE` and continue auditing
20
+ unaffected candidates. If the routing owner or scope cannot be read reliably,
21
+ fail closed for mutation. This Skill does not invoke `imm-doc-prune`; stale
22
+ deletion of evidence-proven content remains that Skill's separate job.
23
+
24
+ ## Invocation
25
+
26
+ Requires explicit invocation: `imm-agent-doc-maintain` or
27
+ `/imm-agent-doc-maintain`. Ordinary "is this AGENTS.md too long?" questions stay
28
+ host-native and do not enter this Skill.
29
+
30
+ - `imm-agent-doc-maintain audit`: read-only. Produce the manifest and stop.
31
+ - `imm-agent-doc-maintain`: produce the manifest, then wait for exact manifest
32
+ approval before any mutation.
33
+
34
+ No automatic invocation. No daemon, no telemetry, no automatic learning, no
35
+ cron, no CI, no allowlist, no runtime, no persistent report, and no automatic
36
+ commit.
37
+
38
+ ## Ordered Maintenance Protocol
39
+
40
+ 1. **Establish repository safety.** Mutation requires a Git worktree. Only
41
+ tracked regular files named exactly `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md`,
42
+ at the repository root or in nested tracked directories, are candidates.
43
+ Git-tracked symlinks (`120000`) and other non-regular modes are `BLOCKED`
44
+ before inventory, reading, or mutation, even when the basename matches.
45
+ User-level and external files, including `~/.pi/agent/AGENTS.md` and
46
+ `~/.agents/`, are never inventoried or modified. Record the candidate path,
47
+ blob/content hash, tracked status, candidate-local worktree status, Git file
48
+ mode, line count, and byte count. Unrelated dirty files do not block the run;
49
+ a dirty or untracked candidate is `BLOCKED`.
50
+
51
+ 2. **Resolve Managed ownership.** Read the existing routing projection without
52
+ creating authority. An already active Managed task remains owned by
53
+ `imm-loop`. A candidate overlapping its `scope_hint` is
54
+ `BLOCKED_ACTIVE_SCOPE`; continue auditing unaffected candidates. If the
55
+ routing owner or scope cannot be read reliably, fail closed for mutation.
56
+
57
+ 3. **Inventory instruction relationships.** Determine nesting, explicit
58
+ precedence statements, inbound references, and existing authority pointers.
59
+ Preserve each file's native organization. Do not normalize files to a shared
60
+ template and do not infer unsupported cross-host inheritance semantics.
61
+
62
+ 4. **Build repository facts.** Resolve current truth in this order:
63
+ executable/public registries, package exports, CLI/runtime entrypoints;
64
+ behavior tests; active Spec/TaskIntent; current `CONTEXT.md`/ADR/
65
+ reference/README; Solution/Brainstorm/archive. Repository facts are evidence
66
+ for classification, not a reason to copy discoverable context into
67
+ instructions.
68
+
69
+ 5. **Apply the four-part persistent-rule value gate.** A retained or newly
70
+ proposed persistent rule must be all of: non-obvious from ordinary
71
+ repository inspection; plausibly repeatable across later tasks; stable beyond
72
+ the current task; and costly to violate. Repository overviews, directory
73
+ listings, technology summaries, discoverable command inventories, vague
74
+ exhortations, and unconditional broad exploration or testing fail this gate
75
+ unless the manifest proves they encode a hidden behavioral constraint.
76
+
77
+ 6. **Classify exact actions.** Classify entries as `REMOVE`, `REWRITE`,
78
+ `POINTER`, `KEEP`, `BLOCKED`, `BLOCKED_ACTIVE_SCOPE`, `UNVERIFIED`, or
79
+ `MISSING_OWNER`. Every mutation entry identifies exact file/section bytes,
80
+ preserved meaning, evidence, candidate hash, and resulting text. `POINTER`
81
+ may target only an existing current authority and must include a concrete
82
+ trigger condition. Missing reference ownership is `MISSING_OWNER`; this Skill
83
+ does not create a reference document. Unknown or one-off-looking rules remain
84
+ `UNVERIFIED` and are not deleted by default. Unresolved precedence or
85
+ semantic conflicts that cannot be decided from explicit repository scope or
86
+ declared precedence are `BLOCKED`. Filename convention, nesting, or guessed
87
+ host behavior alone may not resolve a conflict.
88
+
89
+ 7. **Produce one exact manifest.** `audit` mode stops after the manifest.
90
+ Mutation mode also stops until the literal user approves exact manifest
91
+ entries (for example, "all recommendations except 4 and 7"). Broad approval
92
+ such as "clean AGENTS.md" is insufficient. Interruption starts a fresh scan;
93
+ no manifest is persisted. No fixed line, byte, percentage, or Token target
94
+ authorizes removal.
95
+
96
+ 8. **Revalidate and mutate minimally.** Re-read candidate bytes, Git status,
97
+ content hash, references, precedence evidence, and active scope immediately
98
+ before each approved change. Drift blocks that item. Never execute commands
99
+ copied from instruction files. Never alter ordinary documentation, install
100
+ project contracts, commit, or mutate Managed authority. Necessary hard
101
+ guardrails, security constraints, data-loss prevention, accessibility
102
+ basics, and explicit user requirements are never simplified away for
103
+ brevity.
104
+
105
+ 9. **Verify and report.** Re-scan modified instruction relationships, local
106
+ pointer targets, unresolved conflicts, source/package public-surface parity,
107
+ existing focused documentation contracts, and `git diff --check`. This Skill
108
+ does not execute arbitrary documented commands, real-model success-rate
109
+ benchmarks, or Token measurements unless the user literally requests a
110
+ benchmark. This Skill does not commit. Report only `Removed`, `Rewritten`,
111
+ `Moved to pointer`, `Kept`, `Blocked`, `Unverified`, `Verification`, and
112
+ before/after line and byte counts. Do not translate byte changes into Token
113
+ or task-success claims.
114
+
115
+ ## Mutation Envelope
116
+
117
+ This Skill may modify only approved agent instruction files. It must stop when
118
+ completion requires creating a new reference document, changing ordinary docs,
119
+ runtime behavior, business-test semantics, package exports, public API,
120
+ credentials, network writes, Git history rewriting, project-contract
121
+ installation or continuous validation, automatic rule learning, or any Managed
122
+ authority mutation.
123
+
124
+ A failed focused check leaves an inspectable diff and stops; this Skill does
125
+ not stash, reset, checkout, or revert user work.
126
+
127
+ ## Non-goals
128
+
129
+ - User-level and external instruction files are never inventoried or modified.
130
+ - Runtime still does not install or continuously validate project-level
131
+ `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `IMMUNE.md`, or `CONTEXT.md`
132
+ contracts.
133
+ - No daemon, telemetry, automatic learning, persistent report, or automatic
134
+ commit.
135
+ - No 200-line, Token, or success-rate target.
136
+ - `imm-doc-prune` remains able to remove evidence-proven stale
137
+ agent-instruction content; the two Skills never invoke each other.
@@ -14,7 +14,7 @@ Agent envelope exactly. Brainstorm and Planner use the same Tool for bounded
14
14
  `arch-explorer` and explicit-lens `advisory-reviewer` dispatches. Loop may
15
15
  dispatch `compounder` only when a closed Step supplies structured evidence for
16
16
  a reusable Learning; routine work without that evidence returns `next: none`
17
- and creates no Learning. Do not discover or load a Pi Skill for these roles. The Managed Path public entries remain `imm-brainstorm`, `imm-planner`, and `imm-loop`; standalone `imm-pr-fix` and `imm-doc-prune` are host-native and are never dispatched as the Loop role.
17
+ and creates no Learning. Do not discover or load a Pi Skill for these roles. The Managed Path public entries remain `imm-brainstorm`, `imm-planner`, and `imm-loop`; standalone `imm-pr-fix`, `imm-doc-prune`, and `imm-agent-doc-maintain` are host-native and are never dispatched as the Loop role.
18
18
  Dispatch authorization follows the [shared Subagent Dispatch
19
19
  Protocol](docs/reference/subagent-dispatch-protocol.md#authorization-authority).
20
20
  Same-boundary `follow_up` is not a Plan mutation; it repeats the current
@@ -140,56 +140,65 @@ A repository directive overrides the global directive. Report an invalid value
140
140
  and ask instead of guessing. After resolving it, display one non-blocking line
141
141
  with the selected carrier and its source. A configured `github` default is
142
142
  standing opt-in for GitHub projection, but the literal user must still confirm
143
- the named Initiative and its immutable slug before the first remote mutation.
144
- Surface that name, slug, and the proposed Parent/Child creation together as soon
145
- as decomposition establishes multiple TaskIntents. Recommend one answer so the
146
- user may adopt the complete current decision frontier in bulk. A prior bulk
147
- approval cannot confirm a name or slug that had not yet been shown.
148
- Resolve `../bin/imm-tracker` from this packaged contract; do not assume a bare
149
- command is on `PATH`. After the first TaskIntent has been authored, staged, and
150
- validated with `valid: true` and `enrollment_ready: true`, and both the named
151
- Initiative and its immutable slug are confirmed, attempt the GitHub projection
152
- before returning the final Planner result or invoking Enrollment: call
153
- `imm-tracker create-initiative --stdin --json` once with the confirmed goal,
154
- stable Slice summaries, and the public Parent projection fields. `create-initiative`
155
- receives the stable Initiative goal and Slice summaries plus the public Parent
156
- projection fields `problem`, `result`, `decisions`, `testing_strategy`, and
157
- `out_of_scope`. It creates a result-oriented Parent title
158
- `[<initiative>] <result>` and never rewrites an existing Parent.
159
-
160
- Then call
161
- `imm-tracker upsert-task --initiative-id <slug> --slice-id <id> --intent <path> --projection-json <json> --json`
162
- to create one neutral open Child Issue and attach it to the Parent as a native
163
- Sub-issue. The projection JSON is public planning context only and may contain
164
- `result`, `current_behavior`, `desired_behavior`, `key_interfaces`,
165
- `verification`, `blocked_by` Task IDs, `out_of_scope`, and `agent_handoff`.
166
- The tracker rereads the canonical TaskIntent for identity, risk, and acceptance;
167
- projection fields never widen TaskIntent scope or authority. The Child title is
168
- `[<initiative>/<slice>] <result>` with no `IB:` prefix or Task ID. Its body is an
169
- Agent Brief with Parent, What to build, Current behavior, Desired behavior, Key
170
- interfaces, Acceptance criteria, Verification, Blocked by, Out of scope, Agent
171
- handoff, and Authority boundary sections. Native `blocked_by` relations are
172
- created only for exact marker-owned Task Issues and are idempotently observed.
143
+ the named Initiative, its immutable slug, and the complete Parent/Child
144
+ decomposition before the first remote mutation. A prior bulk approval cannot
145
+ confirm a name, slug, Child, or dependency that had not yet been shown.
146
+
147
+ Once decomposition is complete, present one review table containing the Parent
148
+ result and every Child's stable Slice ID, result, scope boundary, risk, blockers,
149
+ and proposed execution order. Ask one focused question: whether the coverage,
150
+ granularity, and dependencies are correct. Recommend the complete current
151
+ frontier so the user can approve it in one response. Before that approval,
152
+ perform zero GitHub mutations. A partial or progressively disclosed issue set is
153
+ not eligible for publication.
154
+
155
+ After approval, author, stage, and validate every TaskIntent in the decomposition
156
+ with `valid: true` and `enrollment_ready: true`. Resolve `../bin/imm-tracker` from this packaged contract; do not assume a bare command is on `PATH`. Submit the entire approved set once through
157
+ `imm-tracker publish-initiative --stdin --json`. Its input contains the confirmed
158
+ Initiative slug and goal, Parent projection, and every Child's `slice_id`,
159
+ canonical TaskIntent path, and public projection. The Parent projection requires
160
+ `problem`, `result`, and `design`, and may include `decisions`,
161
+ `testing_strategy`, and `out_of_scope`. `design` records Initiative-level
162
+ invariants, Slice boundaries and ordering, shared interfaces or state flow, and
163
+ material compatibility decisions. Every Parent Slice must correspond to one
164
+ published Child; future checklist-only Slices are not allowed in the batch.
165
+
166
+ Each Child projection may contain `result`, `current_behavior`,
167
+ `desired_behavior`, `key_interfaces`, `verification`, `blocked_by` Task IDs,
168
+ `out_of_scope`, and `agent_handoff`. The tracker rereads every canonical
169
+ TaskIntent for identity, risk, and acceptance; projection fields never widen
170
+ TaskIntent scope or authority. It validates the complete dependency graph before
171
+ remote writes, creates the Parent once, creates all Children, attaches every
172
+ Child as a native Sub-issue, creates native `blocked_by` relations, and rereads
173
+ the complete topology. The Child Agent Brief includes a direct Parent Issue link.
173
174
  Internal role prompts, tool policies, review gates, model reservations, and
174
- prompt digests never belong in this external handoff.
175
- If `docs/initiatives/<slug>.md` exists, the tracker fails with a
176
- carrier conflict; Local mode performs zero GitHub operations. A future Slice remains a parent checklist entry until its own TaskIntent is
177
- canonically authored and validated.
175
+ prompt digests never belong in this external handoff. If
176
+ `docs/initiatives/<slug>.md` exists, publication fails with a carrier conflict;
177
+ Local mode performs zero GitHub operations.
178
+
179
+ The batch result includes an execution recommendation: the first unblocked Task,
180
+ a stable dependency order, and parallel groups. For a plan-only request, report
181
+ that recommendation and stop. For a request that includes execution, invoke the
182
+ native Enrollment gate for the recommended first TaskIntent after successful
183
+ publication; do not ask for another chat confirmation. GitHub selection never
184
+ bypasses Enrollment.
178
185
 
179
186
  Tracker output is observation, never authority. Before the Planner returns, its
180
- GitHub carrier outcome must be exactly one of: `tracker_associated` after both
181
- operations return `created`, `updated`, or `already_current`;
182
- `awaiting_user_initiative_confirmation` with the single pending Initiative
183
- name-and-slug decision; or `tracker_projection_failed` with the returned failure
184
- and exact retry action.
185
- A candidate Initiative name or slug recorded only in the Spec or final summary
186
- is neither user confirmation nor a completed carrier outcome. Report `retryable_failure`,
187
- `permanent_failure`, or `ambiguous_remote_state` and the exact retry action, but
188
- do not block planning, Enrollment, execution, QA, Review, settlement, or another
189
- association. Do not infer opt-in from tracker output or Issue state,
190
- auto-close the parent, import Issue state, create a TaskIntent from an Issue, or store Issue identity in TaskIntent or
191
- TaskRecord. Existing Issue markers grant permission only for later one-way
192
- projection updates to that same Initiative; they never grant execution authority.
187
+ GitHub carrier outcome must be exactly one of: `tracker_associated` after the
188
+ complete batch returns `created`, `updated`, or `already_current`;
189
+ `awaiting_user_initiative_confirmation` with the single pending name, slug, and
190
+ complete-decomposition decision; or `tracker_projection_failed` with the returned
191
+ failure and exact retry action. A candidate Initiative or partial Issue set
192
+ recorded only in the Spec or final summary is neither user confirmation nor a
193
+ completed carrier outcome. Report `retryable_failure`, `permanent_failure`, or
194
+ `ambiguous_remote_state` and the exact batch retry action. This does not invalidate
195
+ already-authored planning files, but it blocks `tracker_associated` and every
196
+ Enrollment or execution handoff for that Initiative until the same complete
197
+ batch succeeds. Do not infer opt-in from tracker output or Issue state, auto-close the Parent,
198
+ import Issue state, create a TaskIntent from an Issue, or store Issue identity in
199
+ TaskIntent or TaskRecord. Existing Issue markers grant permission only for
200
+ idempotent retry of that same approved Initiative; they never grant execution
201
+ authority.
193
202
 
194
203
  ### Verification Descriptor Discipline
195
204
 
@@ -47,3 +47,12 @@ skills:
47
47
  output_artifacts: [prune_report]
48
48
  next_actions: []
49
49
  boundary: Prune stale current documentation after explicit manifest approval; no Managed authority mutation or authority-artifact deletion.
50
+ - name: imm-agent-doc-maintain
51
+ path: skills/imm-agent-doc-maintain/SKILL.md
52
+ role: execute
53
+ title: Agent Doc Maintain
54
+ role_class: repair
55
+ canonical: true
56
+ output_artifacts: [maintain_report]
57
+ next_actions: []
58
+ boundary: Minimize tracked agent-instruction context after explicit manifest approval; no Managed authority mutation, contract installation, or reference-document creation.
@@ -30,6 +30,7 @@ export interface GithubTrackerResult {
30
30
  association_found: boolean;
31
31
  issue_number?: number;
32
32
  issue_url?: string;
33
+ node_id?: string;
33
34
  message: string;
34
35
  }
35
36
 
@@ -43,11 +44,47 @@ export interface InitiativeSlice {
43
44
  export interface InitiativeProjection {
44
45
  problem?: string;
45
46
  result?: string;
47
+ design?: string;
46
48
  decisions?: string[];
47
49
  testing_strategy?: string;
48
50
  out_of_scope?: string[];
49
51
  }
50
52
 
53
+ export interface InitiativePublicationInput {
54
+ initiative_id: string;
55
+ goal: string;
56
+ projection: InitiativeProjection;
57
+ tasks: Array<{
58
+ slice_id: string;
59
+ intent: string;
60
+ projection?: TaskProjection;
61
+ }>;
62
+ }
63
+
64
+ export interface GithubInitiativePublicationResult {
65
+ contract: "immune_brain/github_initiative_publication/v1";
66
+ operation: "publish-initiative";
67
+ status: TrackerStatus;
68
+ initiative?: GithubTrackerResult;
69
+ tasks: Array<{
70
+ task_id: string;
71
+ slice_id: string;
72
+ status: TrackerStatus;
73
+ issue_number?: number;
74
+ issue_url?: string;
75
+ node_id?: string;
76
+ }>;
77
+ execution?: {
78
+ recommended_first_task_id: string;
79
+ recommended_first_issue_number: number;
80
+ order: string[];
81
+ issue_order: number[];
82
+ parallel_groups: string[][];
83
+ parallel_issue_groups: number[][];
84
+ };
85
+ message: string;
86
+ }
87
+
51
88
  export interface TaskProjection {
52
89
  result?: string;
53
90
  current_behavior?: string;
@@ -215,7 +252,7 @@ function result(
215
252
  operation,
216
253
  status,
217
254
  association_found: issue !== undefined,
218
- ...(issue ? { issue_number: issue.number, issue_url: issue.url } : {}),
255
+ ...(issue ? { issue_number: issue.number, issue_url: issue.url, node_id: String(issue.id) } : {}),
219
256
  message: redactGithubDiagnostic(message),
220
257
  };
221
258
  }
@@ -431,24 +468,34 @@ function sliceCount(parentBody: string, sliceId: string): number {
431
468
  return countLiteral(parentBody, marker("slice-id", sliceId));
432
469
  }
433
470
 
434
- async function confirmAttachment(
471
+ async function readSubIssueNumbers(
435
472
  root: string,
436
473
  gh: GhTransport,
437
474
  operation: TrackerOperation["op"],
438
475
  repository: RepositoryInfo,
439
476
  parentNumber: number,
440
- childNumber: number,
441
- ): Promise<GithubTrackerResult | { attached: boolean }> {
477
+ ): Promise<GithubTrackerResult | number[]> {
442
478
  const listed = await gh.run(["api", "--paginate", "--slurp", `repos/${repository.name_with_owner}/issues/${parentNumber}/sub_issues?per_page=100`], { cwd: root });
443
479
  if (listed.exit_code !== 0 || listed.output_exceeded)
444
480
  return ghFailure(operation, listed, "cannot read native Sub-issue relations");
445
- let numbers: number[];
446
481
  try {
447
- numbers = parseSubIssueNumbers(listed.stdout);
482
+ return parseSubIssueNumbers(listed.stdout);
448
483
  } catch (error) {
449
484
  return result(operation, "permanent_failure", error instanceof Error ? error.message : String(error));
450
485
  }
451
- const matches = numbers.filter((candidate) => candidate === childNumber).length;
486
+ }
487
+
488
+ async function confirmAttachment(
489
+ root: string,
490
+ gh: GhTransport,
491
+ operation: TrackerOperation["op"],
492
+ repository: RepositoryInfo,
493
+ parentNumber: number,
494
+ childNumber: number,
495
+ ): Promise<GithubTrackerResult | { attached: boolean }> {
496
+ const read = await readSubIssueNumbers(root, gh, operation, repository, parentNumber);
497
+ if (!Array.isArray(read)) return read;
498
+ const matches = read.filter((candidate) => candidate === childNumber).length;
452
499
  if (matches > 1) return result(operation, "ambiguous_remote_state", `Issue #${parentNumber} lists the Task Issue more than once`);
453
500
  return { attached: matches === 1 };
454
501
  }
@@ -565,7 +612,7 @@ function createInitiativeBody(repository: RepositoryInfo, operation: Extract<Tra
565
612
  KIND_INITIATIVE_MARKER,
566
613
  marker("repo-id", repository.id),
567
614
  marker("initiative-id", operation.initiative_id),
568
- ].join("\n")}\n\n# ${titleText(projection.result ?? operation.goal)}\n\nOpt-in, non-authoritative Immune-Brain Initiative planning carrier. Kernel TaskIntent, TaskRecord, and Assurance remain the execution authority.\n\n## How to use this Issue\n\n- Edit planning prose and Slice ordering directly after creation.\n- Keep each Slice marker attached to exactly one stable Slice entry.\n- The tracker never rewrites or closes this Parent after creation; the tracker never changes or closes it automatically.\n\n## Problem\n\n${publicText(projection.problem ?? "The Initiative addresses the bounded delivery described below.", "projection.problem")}\n\n## Result\n\n${publicText(projection.result ?? operation.goal, "projection.result")}\n\n## Decisions\n\n${listText(projection.decisions, "- No additional Initiative decisions recorded.")}\n\n## Testing strategy\n\n${publicText(projection.testing_strategy ?? "Each Child closes from its focused acceptance verification.", "projection.testing_strategy")}\n\n## Out of scope\n\n${listText(projection.out_of_scope, "- Unrelated work outside this Initiative.")}\n\n## Slices\n\n${operation.slices.length === 0 ? "No Slices recorded yet." : operation.slices.map((slice) => `- [ ] ${marker("slice-id", slice.id)} **${slice.id}**: ${slice.result ?? slice.goal}${slice.blocked_by?.length ? ` (blocked by: ${slice.blocked_by.join(", ")})` : ""}`).join("\n")}\n\n## Authority boundary\n\nThis Issue is outbound visibility only. GitHub state never starts, authorizes, reprioritizes, or settles work. Native Sub-issues identify published Tasks; their state is only an observation.\n`;
615
+ ].join("\n")}\n\n# ${titleText(projection.result ?? operation.goal)}\n\nOpt-in, non-authoritative Immune-Brain Initiative planning carrier. Kernel TaskIntent, TaskRecord, and Assurance remain the execution authority.\n\n## How to use this Issue\n\n- Edit planning prose and Slice ordering directly after creation.\n- Keep each Slice marker attached to exactly one stable Slice entry.\n- The tracker never rewrites or closes this Parent after creation; the tracker never changes or closes it automatically.\n\n## Problem\n\n${publicText(projection.problem ?? "The Initiative addresses the bounded delivery described below.", "projection.problem")}\n\n## Result\n\n${publicText(projection.result ?? operation.goal, "projection.result")}\n\n## Initiative design\n\n${publicText(projection.design ?? "Each Child preserves the shared Initiative decisions and boundaries recorded here.", "projection.design")}\n\n## Decisions\n\n${listText(projection.decisions, "- No additional Initiative decisions recorded.")}\n\n## Testing strategy\n\n${publicText(projection.testing_strategy ?? "Each Child closes from its focused acceptance verification.", "projection.testing_strategy")}\n\n## Out of scope\n\n${listText(projection.out_of_scope, "- Unrelated work outside this Initiative.")}\n\n## Slices\n\n${operation.slices.length === 0 ? "No Slices recorded yet." : operation.slices.map((slice) => `- [ ] ${marker("slice-id", slice.id)} **${slice.id}**: ${slice.result ?? slice.goal}${slice.blocked_by?.length ? ` (blocked by: ${slice.blocked_by.join(", ")})` : ""}`).join("\n")}\n\n## Authority boundary\n\nThis Issue is outbound visibility only. GitHub state never starts, authorizes, reprioritizes, or settles work. Native Sub-issues identify published Tasks; their state is only an observation.\n`;
569
616
  }
570
617
 
571
618
  async function createInitiative(
@@ -610,7 +657,11 @@ async function createInitiative(
610
657
  );
611
658
  }
612
659
 
613
- function childBody(repository: RepositoryInfo, operation: Extract<TrackerOperation, { op: "upsert-task" }>): string {
660
+ function childBody(
661
+ repository: RepositoryInfo,
662
+ operation: Extract<TrackerOperation, { op: "upsert-task" }>,
663
+ parent: GithubIssue,
664
+ ): string {
614
665
  const projection = operation.projection ?? {};
615
666
  const acceptance = operation.acceptance.map((item) => `- \`${item.id}\`: ${item.summary}`).join("\n");
616
667
  return `${[
@@ -620,7 +671,7 @@ function childBody(repository: RepositoryInfo, operation: Extract<TrackerOperati
620
671
  marker("initiative-id", operation.initiative_id),
621
672
  marker("slice-id", operation.slice_id),
622
673
  marker("task-id", operation.task_id),
623
- ].join("\n")}\n\n# ${titleText(projection.result ?? operation.goal)}\n\nOpt-in, non-authoritative Immune-Brain Task Issue. Kernel TaskIntent, TaskRecord, and Assurance remain the execution authority.\n\n## Parent\n\n| Initiative | \`${operation.initiative_id}\` |\n| Slice | \`${operation.slice_id}\` |\n| Risk | \`${operation.risk}\` |\n\n## What to build\n\n${publicText(projection.result ?? operation.goal, "projection.result")}\n\n## Current behavior\n\n${publicText(projection.current_behavior ?? "The current behavior is defined by the repository's existing contract.", "projection.current_behavior")}\n\n## Desired behavior\n\n${publicText(projection.desired_behavior ?? operation.goal, "projection.desired_behavior")}\n\n## Key interfaces\n\n${listText(projection.key_interfaces, "- Canonical TaskIntent acceptance and Kernel lifecycle remain authoritative.")}\n\n## Acceptance criteria\n\n${acceptance}\n\n## Verification\n\n${publicText(projection.verification ?? "Run the focused acceptance verification declared by the TaskIntent.", "projection.verification")}\n\n## Blocked by\n\n${projection.blocked_by?.length ? projection.blocked_by.map((id) => `- \`${identifier(id, "blocked_by task_id")}\``).join("\n") : "None"}\n\n## Out of scope\n\n${listText(projection.out_of_scope, "- Scope not declared by the validated TaskIntent.")}\n\n## Agent handoff\n\n${publicText(projection.agent_handoff ?? "Implement only the bounded TaskIntent result and run the focused checks. Do not widen scope or treat GitHub as authorization.", "projection.agent_handoff")}\n\n## Lifecycle\n\n- **Open** means this Task still needs attention; it does not mean the Task is authorized or executing.\n- Only a fresh claimless terminal projection can close this Issue: \`done\` becomes **Completed**, and \`stopped\` becomes **Not planned**.\n\n## Authority boundary\n\nThis Issue is outbound visibility only. GitHub state never changes TaskIntent, TaskRecord, QA, Review, authorization, or Kernel settlement. Internal role prompts, tool policies, review gates, model reservations, and prompt digests are not part of this external handoff.\n`;
674
+ ].join("\n")}\n\n# ${titleText(projection.result ?? operation.goal)}\n\nOpt-in, non-authoritative Immune-Brain Task Issue. Kernel TaskIntent, TaskRecord, and Assurance remain the execution authority.\n\n## Parent\n\n| Initiative | \`${operation.initiative_id}\` |\n| Parent Issue | [#${parent.number}](${parent.url}) |\n| Slice | \`${operation.slice_id}\` |\n| Risk | \`${operation.risk}\` |\n\n## What to build\n\n${publicText(projection.result ?? operation.goal, "projection.result")}\n\n## Current behavior\n\n${publicText(projection.current_behavior ?? "The current behavior is defined by the repository's existing contract.", "projection.current_behavior")}\n\n## Desired behavior\n\n${publicText(projection.desired_behavior ?? operation.goal, "projection.desired_behavior")}\n\n## Key interfaces\n\n${listText(projection.key_interfaces, "- Canonical TaskIntent acceptance and Kernel lifecycle remain authoritative.")}\n\n## Acceptance criteria\n\n${acceptance}\n\n## Verification\n\n${publicText(projection.verification ?? "Run the focused acceptance verification declared by the TaskIntent.", "projection.verification")}\n\n## Blocked by\n\n${projection.blocked_by?.length ? projection.blocked_by.map((id) => `- \`${identifier(id, "blocked_by task_id")}\``).join("\n") : "None"}\n\n## Out of scope\n\n${listText(projection.out_of_scope, "- Scope not declared by the validated TaskIntent.")}\n\n## Agent handoff\n\n${publicText(projection.agent_handoff ?? "Implement only the bounded TaskIntent result and run the focused checks. Do not widen scope or treat GitHub as authorization.", "projection.agent_handoff")}\n\n## Lifecycle\n\n- **Open** means this Task still needs attention; it does not mean the Task is authorized or executing.\n- Only a fresh claimless terminal projection can close this Issue: \`done\` becomes **Completed**, and \`stopped\` becomes **Not planned**.\n\n## Authority boundary\n\nThis Issue is outbound visibility only. GitHub state never changes TaskIntent, TaskRecord, QA, Review, authorization, or Kernel settlement. Internal role prompts, tool policies, review gates, model reservations, and prompt digests are not part of this external handoff.\n`;
624
675
  }
625
676
 
626
677
  async function upsertTask(
@@ -655,7 +706,7 @@ async function upsertTask(
655
706
  if (!("owned" in ownership)) return ownership;
656
707
  blockers.push(blocker.issue);
657
708
  }
658
- const body = childBody(source.repository, operation);
709
+ const body = childBody(source.repository, operation, parent.issue);
659
710
  const oversized = bodyLimitFailure(operation.op, body, MAX_TERMINAL_SUFFIX_BYTES);
660
711
  if (oversized) return oversized;
661
712
  const title = issueTitle(`${operation.initiative_id}/${operation.slice_id}`, operation.projection?.result ?? operation.goal);
@@ -870,6 +921,7 @@ function normalizeInitiativeProjection(value: InitiativeProjection | undefined):
870
921
  return {
871
922
  problem: value.problem === undefined ? undefined : projectionText(value.problem, "projection.problem"),
872
923
  result: value.result === undefined ? undefined : projectionText(value.result, "projection.result"),
924
+ design: value.design === undefined ? undefined : projectionText(value.design, "projection.design"),
873
925
  decisions: normalizedProjectionList(value.decisions, "projection.decisions", 500),
874
926
  testing_strategy: value.testing_strategy === undefined ? undefined : projectionText(value.testing_strategy, "projection.testing_strategy"),
875
927
  out_of_scope: normalizedProjectionList(value.out_of_scope, "projection.out_of_scope", 500),
@@ -949,12 +1001,265 @@ export async function runGithubTrackerOperation(
949
1001
  }
950
1002
  }
951
1003
 
952
- function valueAfter(args: string[], name: string): string | undefined {
953
- const index = args.indexOf(name);
954
- return index === -1 ? undefined : args[index + 1];
1004
+ function publicationResult(
1005
+ status: TrackerStatus,
1006
+ message: string,
1007
+ initiative?: GithubTrackerResult,
1008
+ tasks: GithubInitiativePublicationResult["tasks"] = [],
1009
+ execution?: GithubInitiativePublicationResult["execution"],
1010
+ ): GithubInitiativePublicationResult {
1011
+ return {
1012
+ contract: "immune_brain/github_initiative_publication/v1",
1013
+ operation: "publish-initiative",
1014
+ status,
1015
+ ...(initiative ? { initiative } : {}),
1016
+ tasks,
1017
+ ...(execution ? { execution } : {}),
1018
+ message: redactGithubDiagnostic(message),
1019
+ };
1020
+ }
1021
+
1022
+ interface PreparedPublicationTask {
1023
+ operation: Extract<TrackerOperation, { op: "upsert-task" }>;
1024
+ intent_path: string;
1025
+ intent_content_hash: string;
1026
+ }
1027
+
1028
+ function publicationPlan(operations: Array<Extract<TrackerOperation, { op: "upsert-task" }>>): {
1029
+ order: Array<Extract<TrackerOperation, { op: "upsert-task" }>>;
1030
+ parallel_groups: string[][];
1031
+ } {
1032
+ const remaining = new Set(operations.map((operation) => operation.task_id));
1033
+ const done = new Set<string>();
1034
+ const order: Array<Extract<TrackerOperation, { op: "upsert-task" }>> = [];
1035
+ const parallelGroups: string[][] = [];
1036
+ while (remaining.size) {
1037
+ const ready = operations.filter((operation) => remaining.has(operation.task_id)
1038
+ && (operation.projection?.blocked_by ?? []).every((taskId) => done.has(taskId)));
1039
+ if (!ready.length) throw new Error("Initiative Task dependencies must form an acyclic graph");
1040
+ parallelGroups.push(ready.map((operation) => operation.task_id));
1041
+ for (const operation of ready) {
1042
+ remaining.delete(operation.task_id);
1043
+ done.add(operation.task_id);
1044
+ order.push(operation);
1045
+ }
1046
+ }
1047
+ return { order, parallel_groups: parallelGroups };
1048
+ }
1049
+
1050
+ function preflightPublication(root: string, input: InitiativePublicationInput): {
1051
+ initiative: Extract<TrackerOperation, { op: "create-initiative" }>;
1052
+ order: Array<Extract<TrackerOperation, { op: "upsert-task" }>>;
1053
+ parallel_groups: string[][];
1054
+ intent_bindings: Map<string, Pick<PreparedPublicationTask, "intent_path" | "intent_content_hash">>;
1055
+ } {
1056
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("publication must be an object");
1057
+ if (!Array.isArray(input.tasks) || input.tasks.length < 2)
1058
+ throw new Error("a complete Initiative publication requires at least two Tasks");
1059
+ if (!input.projection || typeof input.projection !== "object" || Array.isArray(input.projection))
1060
+ throw new Error("publication projection must be an object");
1061
+ for (const field of ["problem", "result", "design"] as const) {
1062
+ if (input.projection[field] === undefined)
1063
+ throw new Error(`a complete Initiative publication requires projection.${field}`);
1064
+ }
1065
+ const publications = input.tasks.map((task, index) => {
1066
+ if (!task || typeof task !== "object" || Array.isArray(task)) throw new Error(`tasks[${index}] must be an object`);
1067
+ if (typeof task.intent !== "string") throw new Error(`tasks[${index}].intent must be a string`);
1068
+ return taskPublication(root, input.initiative_id, task.slice_id, task.intent, task.projection);
1069
+ });
1070
+ const operations = publications.map((publication) => publication.operation);
1071
+ const taskIds = new Set<string>();
1072
+ const sliceIds = new Set<string>();
1073
+ for (const operation of operations) {
1074
+ if (taskIds.has(operation.task_id)) throw new Error(`duplicate Task id: ${operation.task_id}`);
1075
+ if (sliceIds.has(operation.slice_id)) throw new Error(`duplicate Slice id: ${operation.slice_id}`);
1076
+ taskIds.add(operation.task_id);
1077
+ sliceIds.add(operation.slice_id);
1078
+ }
1079
+ for (const operation of operations) {
1080
+ for (const blocker of operation.projection?.blocked_by ?? []) {
1081
+ if (!taskIds.has(blocker)) throw new Error(`Task ${operation.task_id} depends on ${blocker}, which is outside the complete Initiative batch`);
1082
+ }
1083
+ }
1084
+ const plan = publicationPlan(operations);
1085
+ const initiative = validateOperation({
1086
+ op: "create-initiative",
1087
+ initiative_id: input.initiative_id,
1088
+ goal: input.goal,
1089
+ projection: input.projection,
1090
+ slices: operations.map((operation) => ({
1091
+ id: operation.slice_id,
1092
+ goal: operation.goal,
1093
+ result: operation.projection?.result,
1094
+ blocked_by: operation.projection?.blocked_by,
1095
+ })),
1096
+ }) as Extract<TrackerOperation, { op: "create-initiative" }>;
1097
+ const intentBindings = new Map(publications.map((publication) => [publication.operation.task_id, {
1098
+ intent_path: publication.intent_path,
1099
+ intent_content_hash: publication.intent_content_hash,
1100
+ }]));
1101
+ return { initiative, ...plan, intent_bindings: intentBindings };
1102
+ }
1103
+
1104
+ function publicationIntentDrift(
1105
+ root: string,
1106
+ bindings: Map<string, Pick<PreparedPublicationTask, "intent_path" | "intent_content_hash">>,
1107
+ taskIds: Iterable<string> = bindings.keys(),
1108
+ ): string | null {
1109
+ for (const taskId of taskIds) {
1110
+ const expected = bindings.get(taskId);
1111
+ if (!expected) return `TaskIntent ${taskId} is missing its publication binding`;
1112
+ try {
1113
+ const current = readTaskIntent(root, taskId);
1114
+ if (current.intent_ref.path !== expected.intent_path || current.content_hash !== expected.intent_content_hash)
1115
+ return `TaskIntent ${taskId} changed during Initiative publication`;
1116
+ } catch (error) {
1117
+ return `TaskIntent ${taskId} became unreadable during Initiative publication: ${error instanceof Error ? error.message : String(error)}`;
1118
+ }
1119
+ }
1120
+ return null;
1121
+ }
1122
+
1123
+ function publicationIssueDrift(expected: {
1124
+ issue_number?: number;
1125
+ issue_url?: string;
1126
+ node_id?: string;
1127
+ }, actual: GithubIssue, title: string, body: string, label: string): string | null {
1128
+ if (expected.issue_number !== actual.number || expected.issue_url !== actual.url || expected.node_id !== String(actual.id))
1129
+ return `${label} identity changed during Initiative publication`;
1130
+ if (actual.state !== "open") return `${label} is no longer open`;
1131
+ if (actual.title !== title || actual.body !== body) return `${label} content changed during Initiative publication`;
1132
+ return null;
955
1133
  }
956
1134
 
957
- function taskPublication(root: string, initiativeId: string, sliceId: string, intentPath: string, projection?: TaskProjection): Extract<TrackerOperation, { op: "upsert-task" }> {
1135
+ export async function runGithubInitiativePublication(
1136
+ root: string,
1137
+ input: InitiativePublicationInput,
1138
+ gh: GhTransport = createGhTransport(),
1139
+ ): Promise<GithubInitiativePublicationResult> {
1140
+ const absoluteRoot = resolve(root);
1141
+ let prepared: ReturnType<typeof preflightPublication>;
1142
+ try {
1143
+ prepared = preflightPublication(absoluteRoot, input);
1144
+ } catch (error) {
1145
+ return publicationResult("permanent_failure", error instanceof Error ? error.message : String(error));
1146
+ }
1147
+ const conflict = carrierConflict(absoluteRoot, "create-initiative", prepared.initiative.initiative_id);
1148
+ if (conflict) return publicationResult(conflict.status, conflict.message, conflict);
1149
+ const initial = await snapshot(absoluteRoot, gh, "create-initiative");
1150
+ if ("contract" in initial) return publicationResult(initial.status, initial.message, initial);
1151
+ const initialParent = initiativeLookup(initial.issues, initial.repository.id, prepared.initiative.initiative_id);
1152
+ if (initialParent.kind === "ambiguous") return publicationResult("ambiguous_remote_state", initialParent.message);
1153
+ const parentForPreflight = initialParent.kind === "found" ? initialParent.issue : {
1154
+ id: Number.MAX_SAFE_INTEGER,
1155
+ number: Number.MAX_SAFE_INTEGER,
1156
+ url: `https://github.com/${initial.repository.name_with_owner}/issues/${Number.MAX_SAFE_INTEGER}`,
1157
+ title: "",
1158
+ body: "",
1159
+ state: "open" as const,
1160
+ state_reason: null,
1161
+ };
1162
+ const parentBodyFailure = bodyLimitFailure("create-initiative", createInitiativeBody(initial.repository, prepared.initiative));
1163
+ if (parentBodyFailure) return publicationResult(parentBodyFailure.status, parentBodyFailure.message, parentBodyFailure);
1164
+ for (const operation of prepared.order) {
1165
+ const childFailure = bodyLimitFailure("upsert-task", childBody(initial.repository, operation, parentForPreflight), MAX_TERMINAL_SUFFIX_BYTES);
1166
+ if (childFailure) return publicationResult(childFailure.status, childFailure.message, childFailure);
1167
+ }
1168
+
1169
+ const beforeParentWrite = publicationIntentDrift(absoluteRoot, prepared.intent_bindings);
1170
+ if (beforeParentWrite) return publicationResult("ambiguous_remote_state", beforeParentWrite);
1171
+ const parentResult = await runGithubTrackerOperation(absoluteRoot, prepared.initiative, gh);
1172
+ if (!isSuccessfulTrackerStatus(parentResult.status))
1173
+ return publicationResult(parentResult.status, parentResult.message, parentResult);
1174
+ const taskResults: GithubInitiativePublicationResult["tasks"] = [];
1175
+ for (const operation of prepared.order) {
1176
+ const intentDrift = publicationIntentDrift(absoluteRoot, prepared.intent_bindings, [operation.task_id]);
1177
+ if (intentDrift) return publicationResult("ambiguous_remote_state", intentDrift, parentResult, taskResults);
1178
+ const taskResult = await runGithubTrackerOperation(absoluteRoot, operation, gh);
1179
+ taskResults.push({
1180
+ task_id: operation.task_id,
1181
+ slice_id: operation.slice_id,
1182
+ status: taskResult.status,
1183
+ ...(taskResult.issue_number === undefined ? {} : { issue_number: taskResult.issue_number }),
1184
+ ...(taskResult.issue_url === undefined ? {} : { issue_url: taskResult.issue_url }),
1185
+ ...(taskResult.node_id === undefined ? {} : { node_id: taskResult.node_id }),
1186
+ });
1187
+ if (!isSuccessfulTrackerStatus(taskResult.status))
1188
+ return publicationResult(taskResult.status, taskResult.message, parentResult, taskResults);
1189
+ }
1190
+
1191
+ const finalIntentDrift = publicationIntentDrift(absoluteRoot, prepared.intent_bindings);
1192
+ if (finalIntentDrift) return publicationResult("ambiguous_remote_state", finalIntentDrift, parentResult, taskResults);
1193
+ const finalSource = await snapshot(absoluteRoot, gh, "upsert-task");
1194
+ if ("contract" in finalSource) return publicationResult(finalSource.status, finalSource.message, parentResult, taskResults);
1195
+ const parent = initiativeLookup(finalSource.issues, finalSource.repository.id, prepared.initiative.initiative_id);
1196
+ if (parent.kind !== "found") return publicationResult("ambiguous_remote_state", parent.kind === "ambiguous" ? parent.message : "published Initiative Parent disappeared", parentResult, taskResults);
1197
+ const parentDrift = publicationIssueDrift(
1198
+ parentResult,
1199
+ parent.issue,
1200
+ issueTitle(prepared.initiative.initiative_id, prepared.initiative.projection?.result ?? prepared.initiative.goal),
1201
+ createInitiativeBody(finalSource.repository, prepared.initiative),
1202
+ "Initiative Parent",
1203
+ );
1204
+ if (parentDrift) return publicationResult("ambiguous_remote_state", parentDrift, parentResult, taskResults);
1205
+ const resultByTask = new Map(taskResults.map((task) => [task.task_id, task]));
1206
+ const expectedNumbers: number[] = [];
1207
+ for (const operation of prepared.order) {
1208
+ const child = ownedTaskLookup(finalSource.issues, finalSource.repository.id, operation.task_id, operation.initiative_id, operation.slice_id);
1209
+ if (child.kind !== "found") return publicationResult("ambiguous_remote_state", child.kind === "ambiguous" ? child.message : `published Task ${operation.task_id} disappeared`, parentResult, taskResults);
1210
+ const childResult = resultByTask.get(operation.task_id);
1211
+ if (!childResult) return publicationResult("ambiguous_remote_state", `published Task ${operation.task_id} has no batch result`, parentResult, taskResults);
1212
+ const childDrift = publicationIssueDrift(
1213
+ childResult,
1214
+ child.issue,
1215
+ issueTitle(`${operation.initiative_id}/${operation.slice_id}`, operation.projection?.result ?? operation.goal),
1216
+ childBody(finalSource.repository, operation, parent.issue),
1217
+ `Task ${operation.task_id}`,
1218
+ );
1219
+ if (childDrift) return publicationResult("ambiguous_remote_state", childDrift, parentResult, taskResults);
1220
+ expectedNumbers.push(child.issue.number);
1221
+ const ownership = await confirmTerminalOwnership(absoluteRoot, gh, "upsert-task", finalSource, child.issue);
1222
+ if (!("owned" in ownership)) return publicationResult(ownership.status, ownership.message, parentResult, taskResults);
1223
+ const blockers: GithubIssue[] = [];
1224
+ for (const blockerId of operation.projection?.blocked_by ?? []) {
1225
+ const blocker = taskLookup(finalSource.issues, finalSource.repository.id, blockerId);
1226
+ if (blocker.kind !== "found") return publicationResult("ambiguous_remote_state", `published blocker ${blockerId} disappeared`, parentResult, taskResults);
1227
+ blockers.push(blocker.issue);
1228
+ }
1229
+ const dependencies = await confirmBlockedBy(absoluteRoot, gh, "upsert-task", finalSource.repository, child.issue.number, blockers);
1230
+ if (!("complete" in dependencies)) return publicationResult(dependencies.status, dependencies.message, parentResult, taskResults);
1231
+ if (!dependencies.complete) return publicationResult("ambiguous_remote_state", `Task ${operation.task_id} has incomplete blocking relations`, parentResult, taskResults);
1232
+ }
1233
+ const attached = await readSubIssueNumbers(absoluteRoot, gh, "upsert-task", finalSource.repository, parent.issue.number);
1234
+ if (!Array.isArray(attached)) return publicationResult(attached.status, attached.message, parentResult, taskResults);
1235
+ const sortedAttached = [...attached].sort((left, right) => left - right);
1236
+ const sortedExpected = [...expectedNumbers].sort((left, right) => left - right);
1237
+ if (sortedAttached.length !== sortedExpected.length || sortedAttached.some((number, index) => number !== sortedExpected[index]))
1238
+ return publicationResult("ambiguous_remote_state", "Initiative Parent Sub-issues do not match the complete publication batch", parentResult, taskResults);
1239
+ const statuses = [parentResult.status, ...taskResults.map((task) => task.status)];
1240
+ const status: TrackerStatus = statuses.every((item) => item === "created")
1241
+ ? "created"
1242
+ : statuses.every((item) => item === "already_current") ? "already_current" : "updated";
1243
+ const issueByTask = new Map(taskResults.map((task) => [task.task_id, task.issue_number]));
1244
+ if ([...issueByTask.values()].some((number) => number === undefined))
1245
+ return publicationResult("ambiguous_remote_state", "published Task result is missing an Issue number", parentResult, taskResults);
1246
+ const issueNumber = (taskId: string): number => issueByTask.get(taskId)!;
1247
+ const firstTaskId = prepared.order[0].task_id;
1248
+ return publicationResult(status, "complete Initiative Parent, Children, and dependency graph published", parentResult, taskResults, {
1249
+ recommended_first_task_id: firstTaskId,
1250
+ recommended_first_issue_number: issueNumber(firstTaskId),
1251
+ order: prepared.order.map((operation) => operation.task_id),
1252
+ issue_order: prepared.order.map((operation) => issueNumber(operation.task_id)),
1253
+ parallel_groups: prepared.parallel_groups,
1254
+ parallel_issue_groups: prepared.parallel_groups.map((group) => group.map(issueNumber)),
1255
+ });
1256
+ }
1257
+
1258
+ function isSuccessfulTrackerStatus(status: TrackerStatus): boolean {
1259
+ return status === "created" || status === "updated" || status === "already_current";
1260
+ }
1261
+
1262
+ function taskPublication(root: string, initiativeId: string, sliceId: string, intentPath: string, projection?: TaskProjection): PreparedPublicationTask {
958
1263
  const absoluteRoot = resolve(root);
959
1264
  const absolutePath = resolve(absoluteRoot, intentPath);
960
1265
  const rel = relative(absoluteRoot, absolutePath);
@@ -966,14 +1271,18 @@ function taskPublication(root: string, initiativeId: string, sliceId: string, in
966
1271
  if (read.intent_ref.path !== rel) throw new Error("TaskIntent path must match its canonical sidecar path");
967
1272
  const intent = read.intent;
968
1273
  return {
969
- op: "upsert-task",
970
- initiative_id: initiativeId,
971
- task_id: intent.task_id,
972
- slice_id: sliceId,
973
- goal: intent.goal,
974
- risk: intent.risk,
975
- acceptance: intent.acceptance.map((item) => ({ id: item.id, summary: item.assertion })),
976
- projection,
1274
+ operation: validateOperation({
1275
+ op: "upsert-task",
1276
+ initiative_id: initiativeId,
1277
+ task_id: intent.task_id,
1278
+ slice_id: sliceId,
1279
+ goal: intent.goal,
1280
+ risk: intent.risk,
1281
+ acceptance: intent.acceptance.map((item) => ({ id: item.id, summary: item.assertion })),
1282
+ projection,
1283
+ }) as Extract<TrackerOperation, { op: "upsert-task" }>,
1284
+ intent_path: read.intent_ref.path,
1285
+ intent_content_hash: read.content_hash,
977
1286
  };
978
1287
  }
979
1288
 
@@ -985,29 +1294,16 @@ export async function runGithubTrackerCli(
985
1294
  const op = args[0];
986
1295
  if (!args.includes("--json"))
987
1296
  return { stdout: "", stderr: "invalid_tracker_command: --json is required\n", returncode: 2 };
988
- let operation: TrackerOperation;
989
1297
  try {
990
- if (op === "create-initiative" && args.length === 3 && args[1] === "--stdin") {
991
- const raw = JSON.parse((options.stdin ?? (() => readFileSync(0, "utf8")))()) as Record<string, unknown>;
992
- operation = {
993
- op: "create-initiative",
994
- initiative_id: raw.initiative_id as string,
995
- goal: raw.goal as string,
996
- slices: raw.slices as InitiativeSlice[],
997
- projection: raw.projection as InitiativeProjection | undefined,
998
- };
999
- } else if (op === "upsert-task" && (args.length === 8 || args.length === 10)) {
1000
- const initiative = valueAfter(args, "--initiative-id");
1001
- const slice = valueAfter(args, "--slice-id");
1002
- const intent = valueAfter(args, "--intent");
1003
- if (!initiative || !slice || !intent)
1004
- throw new Error("upsert-task requires --initiative-id, --slice-id, and --intent");
1005
- const rawProjection = valueAfter(args, "--projection-json");
1006
- const projection = rawProjection ? JSON.parse(rawProjection) as TaskProjection : undefined;
1007
- operation = taskPublication(root, initiative, slice, intent, projection);
1008
- } else {
1009
- throw new Error("use create-initiative --stdin --json or upsert-task --initiative-id <id> --slice-id <id> --intent <path> [--projection-json <json>] --json");
1010
- }
1298
+ if (op !== "publish-initiative" || args.length !== 3 || args[1] !== "--stdin")
1299
+ throw new Error("use publish-initiative --stdin --json");
1300
+ const raw = JSON.parse((options.stdin ?? (() => readFileSync(0, "utf8")))()) as InitiativePublicationInput;
1301
+ const published = await runGithubInitiativePublication(root, raw, options.gh);
1302
+ return {
1303
+ stdout: `${JSON.stringify(published, null, 2)}\n`,
1304
+ stderr: "",
1305
+ returncode: isSuccessfulTrackerStatus(published.status) ? 0 : 1,
1306
+ };
1011
1307
  } catch (error) {
1012
1308
  return {
1013
1309
  stdout: "",
@@ -1015,10 +1311,4 @@ export async function runGithubTrackerCli(
1015
1311
  returncode: 2,
1016
1312
  };
1017
1313
  }
1018
- const projected = await runGithubTrackerOperation(root, operation, options.gh);
1019
- return {
1020
- stdout: `${JSON.stringify(projected, null, 2)}\n`,
1021
- stderr: "",
1022
- returncode: projected.status === "permanent_failure" || projected.status === "ambiguous_remote_state" ? 1 : 0,
1023
- };
1024
1314
  }
@@ -261,11 +261,10 @@ async function main(argv: string[]): Promise<number> {
261
261
  {
262
262
  name: "imm-tracker",
263
263
  description:
264
- "Opt-in, one-way GitHub Issue projection. Creates a Parent once, never rewrites or closes it. Never grants or consumes Kernel authority.",
264
+ "Opt-in, one-way GitHub Issue projection. Publishes one complete Parent/Child graph and execution recommendation. Never grants or consumes Kernel authority.",
265
265
  json_output: true,
266
266
  examples: [
267
- "imm-tracker create-initiative --stdin --json",
268
- "imm-tracker upsert-task --initiative-id <id> --slice-id <id> --intent docs/plans/<task-id>.intent.json --json",
267
+ "imm-tracker publish-initiative --stdin --json",
269
268
  ],
270
269
  },
271
270
  ],
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: imm-agent-doc-maintain
3
+ description: Use to minimize tracked AGENTS.md, CLAUDE.md, and GEMINI.md files to necessary non-discoverable context after an explicit, hash-bound, user-approved manifest; never installs or validates project contracts.
4
+ ---
5
+
6
+ # Immune-Brain: Agent Doc Maintain
7
+
8
+ Load [`../../dist/imm-agent-doc-maintain.md`](../../dist/imm-agent-doc-maintain.md),
9
+ then minimize tracked agent instruction files to necessary non-discoverable
10
+ context. This is a standalone host-native maintenance entry, not a Managed Path
11
+ continuation and not an `imm-loop` internal-role dispatch.
@@ -14,7 +14,7 @@ Agent envelope exactly. Brainstorm and Planner use the same Tool for bounded
14
14
  `arch-explorer` and explicit-lens `advisory-reviewer` dispatches. Loop may
15
15
  dispatch `compounder` only when a closed Step supplies structured evidence for
16
16
  a reusable Learning; routine work without that evidence returns `next: none`
17
- and creates no Learning. Do not discover or load a Pi Skill for these roles. The Managed Path public entries remain `imm-brainstorm`, `imm-planner`, and `imm-loop`; standalone `imm-pr-fix` and `imm-doc-prune` are host-native and are never dispatched as the Loop role.
17
+ and creates no Learning. Do not discover or load a Pi Skill for these roles. The Managed Path public entries remain `imm-brainstorm`, `imm-planner`, and `imm-loop`; standalone `imm-pr-fix`, `imm-doc-prune`, and `imm-agent-doc-maintain` are host-native and are never dispatched as the Loop role.
18
18
  Subagent Dispatch Protocol](../../dist/docs/reference/subagent-dispatch-protocol.md#authorization-authority).
19
19
  All internal Agent dispatch envelopes use `run_in_background: false` and
20
20
  return a direct result to the Parent before any workflow mutation.
@@ -146,56 +146,65 @@ A repository directive overrides the global directive. Report an invalid value
146
146
  and ask instead of guessing. After resolving it, display one non-blocking line
147
147
  with the selected carrier and its source. A configured `github` default is
148
148
  standing opt-in for GitHub projection, but the literal user must still confirm
149
- the named Initiative and its immutable slug before the first remote mutation.
150
- Surface that name, slug, and the proposed Parent/Child creation together as soon
151
- as decomposition establishes multiple TaskIntents. Recommend one answer so the
152
- user may adopt the complete current decision frontier in bulk. A prior bulk
153
- approval cannot confirm a name or slug that had not yet been shown.
154
- Resolve `../../bin/imm-tracker` from this Skill location; do not assume a bare
155
- command is on `PATH`. After the first TaskIntent has been authored, staged, and
156
- validated with `valid: true` and `enrollment_ready: true`, and both the named
157
- Initiative and its immutable slug are confirmed, attempt the GitHub projection
158
- before returning the final Planner result or invoking Enrollment: call
159
- `imm-tracker create-initiative --stdin --json` once with the confirmed goal,
160
- stable Slice summaries, and the public Parent projection fields. `create-initiative`
161
- receives the stable Initiative goal and Slice summaries plus the
162
- public Parent projection fields `problem`, `result`, `decisions`,
163
- `testing_strategy`, and `out_of_scope`. It creates a result-oriented Parent title
164
- `[<initiative>] <result>` and never rewrites an existing Parent.
165
-
166
- Then call
167
- `imm-tracker upsert-task --initiative-id <slug> --slice-id <id> --intent <path> --projection-json <json> --json`
168
- to create one neutral open Child Issue and attach it to the Parent as a native
169
- Sub-issue. The projection JSON is public planning context only and may contain
170
- `result`, `current_behavior`, `desired_behavior`, `key_interfaces`,
171
- `verification`, `blocked_by` Task IDs, `out_of_scope`, and `agent_handoff`.
172
- The tracker rereads the canonical TaskIntent for identity, risk, and acceptance;
173
- projection fields never widen TaskIntent scope or authority. The Child title is
174
- `[<initiative>/<slice>] <result>` with no `IB:` prefix or Task ID. Its body is an
175
- Agent Brief with Parent, What to build, Current behavior, Desired behavior, Key
176
- interfaces, Acceptance criteria, Verification, Blocked by, Out of scope, Agent
177
- handoff, and Authority boundary sections. Native `blocked_by` relations are
178
- created only for exact marker-owned Task Issues and are idempotently observed.
149
+ the named Initiative, its immutable slug, and the complete Parent/Child
150
+ decomposition before the first remote mutation. A prior bulk approval cannot
151
+ confirm a name, slug, Child, or dependency that had not yet been shown.
152
+
153
+ Once decomposition is complete, present one review table containing the Parent
154
+ result and every Child's stable Slice ID, result, scope boundary, risk, blockers,
155
+ and proposed execution order. Ask one focused question: whether the coverage,
156
+ granularity, and dependencies are correct. Recommend the complete current
157
+ frontier so the user can approve it in one response. Before that approval,
158
+ perform zero GitHub mutations. A partial or progressively disclosed issue set is
159
+ not eligible for publication.
160
+
161
+ After approval, author, stage, and validate every TaskIntent in the decomposition
162
+ with `valid: true` and `enrollment_ready: true`. Resolve `../../bin/imm-tracker` from this Skill location; do not assume a bare command is on `PATH`. Submit the entire approved set once through
163
+ `imm-tracker publish-initiative --stdin --json`. Its input contains the confirmed
164
+ Initiative slug and goal, Parent projection, and every Child's `slice_id`,
165
+ canonical TaskIntent path, and public projection. The Parent projection requires
166
+ `problem`, `result`, and `design`, and may include `decisions`,
167
+ `testing_strategy`, and `out_of_scope`. `design` records Initiative-level
168
+ invariants, Slice boundaries and ordering, shared interfaces or state flow, and
169
+ material compatibility decisions. Every Parent Slice must correspond to one
170
+ published Child; future checklist-only Slices are not allowed in the batch.
171
+
172
+ Each Child projection may contain `result`, `current_behavior`,
173
+ `desired_behavior`, `key_interfaces`, `verification`, `blocked_by` Task IDs,
174
+ `out_of_scope`, and `agent_handoff`. The tracker rereads every canonical
175
+ TaskIntent for identity, risk, and acceptance; projection fields never widen
176
+ TaskIntent scope or authority. It validates the complete dependency graph before
177
+ remote writes, creates the Parent once, creates all Children, attaches every
178
+ Child as a native Sub-issue, creates native `blocked_by` relations, and rereads
179
+ the complete topology. The Child Agent Brief includes a direct Parent Issue link.
179
180
  Internal role prompts, tool policies, review gates, model reservations, and
180
- prompt digests never belong in this external handoff.
181
- If `docs/initiatives/<slug>.md` exists, the tracker fails with a
182
- carrier conflict; Local mode performs zero GitHub operations. A future Slice remains a parent checklist entry until its own TaskIntent is
183
- canonically authored and validated.
181
+ prompt digests never belong in this external handoff. If
182
+ `docs/initiatives/<slug>.md` exists, publication fails with a carrier conflict;
183
+ Local mode performs zero GitHub operations.
184
+
185
+ The batch result includes an execution recommendation: the first unblocked Task,
186
+ a stable dependency order, and parallel groups. For a plan-only request, report
187
+ that recommendation and stop. For a request that includes execution, invoke the
188
+ native Enrollment gate for the recommended first TaskIntent after successful
189
+ publication; do not ask for another chat confirmation. GitHub selection never
190
+ bypasses Enrollment.
184
191
 
185
192
  Tracker output is observation, never authority. Before the Planner returns, its
186
- GitHub carrier outcome must be exactly one of: `tracker_associated` after both
187
- operations return `created`, `updated`, or `already_current`;
188
- `awaiting_user_initiative_confirmation` with the single pending Initiative
189
- name-and-slug decision; or `tracker_projection_failed` with the returned failure
190
- and exact retry action.
191
- A candidate Initiative name or slug recorded only in the Spec or final summary
192
- is neither user confirmation nor a completed carrier outcome. Report `retryable_failure`,
193
- `permanent_failure`, or `ambiguous_remote_state` and the exact retry action, but
194
- do not block planning, Enrollment, execution, QA, Review, settlement, or another
195
- association. Do not infer opt-in from tracker output or Issue state,
196
- auto-close the parent, import Issue state, create a TaskIntent from an Issue, or store Issue identity in TaskIntent or
197
- TaskRecord. Existing Issue markers grant permission only for later one-way
198
- projection updates to that same Initiative; they never grant execution authority.
193
+ GitHub carrier outcome must be exactly one of: `tracker_associated` after the
194
+ complete batch returns `created`, `updated`, or `already_current`;
195
+ `awaiting_user_initiative_confirmation` with the single pending name, slug, and
196
+ complete-decomposition decision; or `tracker_projection_failed` with the returned
197
+ failure and exact retry action. A candidate Initiative or partial Issue set
198
+ recorded only in the Spec or final summary is neither user confirmation nor a
199
+ completed carrier outcome. Report `retryable_failure`, `permanent_failure`, or
200
+ `ambiguous_remote_state` and the exact batch retry action. This does not invalidate
201
+ already-authored planning files, but it blocks `tracker_associated` and every
202
+ Enrollment or execution handoff for that Initiative until the same complete
203
+ batch succeeds. Do not infer opt-in from tracker output or Issue state, auto-close the Parent,
204
+ import Issue state, create a TaskIntent from an Issue, or store Issue identity in
205
+ TaskIntent or TaskRecord. Existing Issue markers grant permission only for
206
+ idempotent retry of that same approved Initiative; they never grant execution
207
+ authority.
199
208
 
200
209
  ### Verification Descriptor Discipline
201
210
 
@@ -47,3 +47,12 @@ skills:
47
47
  output_artifacts: [prune_report]
48
48
  next_actions: []
49
49
  boundary: Prune stale current documentation after explicit manifest approval; no Managed authority mutation or authority-artifact deletion.
50
+ - name: imm-agent-doc-maintain
51
+ path: skills/imm-agent-doc-maintain/SKILL.md
52
+ role: execute
53
+ title: Agent Doc Maintain
54
+ role_class: repair
55
+ canonical: true
56
+ output_artifacts: [maintain_report]
57
+ next_actions: []
58
+ boundary: Minimize tracked agent-instruction context after explicit manifest approval; no Managed authority mutation, contract installation, or reference-document creation.