@aperant/framework 0.13.0-next.0 → 0.15.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.
Files changed (241) hide show
  1. package/CHANGELOG.md +89 -3
  2. package/README.md +11 -1
  3. package/agents/apt-pr-review-i18n.md +190 -0
  4. package/agents/apt-pr-review-xplatform.md +189 -0
  5. package/dist/cli/commands/audit-branch-current.d.mts +27 -5
  6. package/dist/cli/commands/audit-branch-current.d.mts.map +1 -1
  7. package/dist/cli/commands/audit-branch-current.mjs +3 -3
  8. package/dist/cli/commands/audit-branch-current.mjs.map +1 -1
  9. package/dist/cli/commands/event.d.mts.map +1 -1
  10. package/dist/cli/commands/event.mjs +6 -4
  11. package/dist/cli/commands/event.mjs.map +1 -1
  12. package/dist/cli/commands/fan-out.d.mts.map +1 -1
  13. package/dist/cli/commands/fan-out.mjs +3 -1
  14. package/dist/cli/commands/fan-out.mjs.map +1 -1
  15. package/dist/cli/commands/features-audit.d.mts.map +1 -1
  16. package/dist/cli/commands/features-audit.mjs +6 -1
  17. package/dist/cli/commands/features-audit.mjs.map +1 -1
  18. package/dist/cli/commands/followups.d.mts +5 -0
  19. package/dist/cli/commands/followups.d.mts.map +1 -0
  20. package/dist/cli/commands/followups.mjs +266 -0
  21. package/dist/cli/commands/followups.mjs.map +1 -0
  22. package/dist/cli/commands/health-check.d.mts.map +1 -1
  23. package/dist/cli/commands/health-check.mjs +3 -1
  24. package/dist/cli/commands/health-check.mjs.map +1 -1
  25. package/dist/cli/commands/merge-detect.d.mts +29 -0
  26. package/dist/cli/commands/merge-detect.d.mts.map +1 -0
  27. package/dist/cli/commands/merge-detect.mjs +196 -0
  28. package/dist/cli/commands/merge-detect.mjs.map +1 -0
  29. package/dist/cli/commands/merge-integrate.d.mts +28 -0
  30. package/dist/cli/commands/merge-integrate.d.mts.map +1 -0
  31. package/dist/cli/commands/merge-integrate.mjs +383 -0
  32. package/dist/cli/commands/merge-integrate.mjs.map +1 -0
  33. package/dist/cli/commands/pr-review/detect-specialists.d.mts +58 -0
  34. package/dist/cli/commands/pr-review/detect-specialists.d.mts.map +1 -0
  35. package/dist/cli/commands/pr-review/detect-specialists.mjs +980 -0
  36. package/dist/cli/commands/pr-review/detect-specialists.mjs.map +1 -0
  37. package/dist/cli/commands/pr-review.d.mts.map +1 -1
  38. package/dist/cli/commands/pr-review.mjs +11 -2
  39. package/dist/cli/commands/pr-review.mjs.map +1 -1
  40. package/dist/cli/commands/precompact-hook.d.mts.map +1 -1
  41. package/dist/cli/commands/precompact-hook.mjs +10 -6
  42. package/dist/cli/commands/precompact-hook.mjs.map +1 -1
  43. package/dist/cli/commands/roadmap.d.mts.map +1 -1
  44. package/dist/cli/commands/roadmap.mjs +24 -11
  45. package/dist/cli/commands/roadmap.mjs.map +1 -1
  46. package/dist/cli/commands/seed.d.mts.map +1 -1
  47. package/dist/cli/commands/seed.mjs +3 -1
  48. package/dist/cli/commands/seed.mjs.map +1 -1
  49. package/dist/cli/commands/state.d.mts.map +1 -1
  50. package/dist/cli/commands/state.mjs +9 -3
  51. package/dist/cli/commands/state.mjs.map +1 -1
  52. package/dist/cli/commands/statusline.d.mts.map +1 -1
  53. package/dist/cli/commands/statusline.mjs +5 -1
  54. package/dist/cli/commands/statusline.mjs.map +1 -1
  55. package/dist/cli/commands/task.d.mts.map +1 -1
  56. package/dist/cli/commands/task.mjs +126 -23
  57. package/dist/cli/commands/task.mjs.map +1 -1
  58. package/dist/cli/commands/worktree.d.mts +1 -1
  59. package/dist/cli/commands/worktree.d.mts.map +1 -1
  60. package/dist/cli/commands/worktree.mjs +282 -83
  61. package/dist/cli/commands/worktree.mjs.map +1 -1
  62. package/dist/cli/config/post-merge-sweep.d.mts.map +1 -1
  63. package/dist/cli/config/post-merge-sweep.mjs +3 -2
  64. package/dist/cli/config/post-merge-sweep.mjs.map +1 -1
  65. package/dist/cli/config/share-policy.d.mts +3 -1
  66. package/dist/cli/config/share-policy.d.mts.map +1 -1
  67. package/dist/cli/config/share-policy.mjs +16 -3
  68. package/dist/cli/config/share-policy.mjs.map +1 -1
  69. package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
  70. package/dist/cli/config/upgrade-gitignore.mjs +27 -5
  71. package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
  72. package/dist/cli/consistency/parse-qa.mjs +12 -1
  73. package/dist/cli/consistency/parse-qa.mjs.map +1 -1
  74. package/dist/cli/consistency/parse-review.d.mts +23 -0
  75. package/dist/cli/consistency/parse-review.d.mts.map +1 -1
  76. package/dist/cli/consistency/parse-review.mjs +36 -0
  77. package/dist/cli/consistency/parse-review.mjs.map +1 -1
  78. package/dist/cli/consistency/parse-spec.d.mts +16 -0
  79. package/dist/cli/consistency/parse-spec.d.mts.map +1 -1
  80. package/dist/cli/consistency/parse-spec.mjs +44 -0
  81. package/dist/cli/consistency/parse-spec.mjs.map +1 -1
  82. package/dist/cli/coordination/agent-identity.d.mts +5 -2
  83. package/dist/cli/coordination/agent-identity.d.mts.map +1 -1
  84. package/dist/cli/coordination/agent-identity.mjs +6 -4
  85. package/dist/cli/coordination/agent-identity.mjs.map +1 -1
  86. package/dist/cli/coordination/store.d.mts +1 -1
  87. package/dist/cli/dispatch.d.mts.map +1 -1
  88. package/dist/cli/dispatch.mjs +4 -0
  89. package/dist/cli/dispatch.mjs.map +1 -1
  90. package/dist/cli/followups/derive.d.mts +63 -0
  91. package/dist/cli/followups/derive.d.mts.map +1 -0
  92. package/dist/cli/followups/derive.mjs +115 -0
  93. package/dist/cli/followups/derive.mjs.map +1 -0
  94. package/dist/cli/followups/schema.d.mts +165 -0
  95. package/dist/cli/followups/schema.d.mts.map +1 -0
  96. package/dist/cli/followups/schema.mjs +251 -0
  97. package/dist/cli/followups/schema.mjs.map +1 -0
  98. package/dist/cli/gate/context.d.mts +1 -1
  99. package/dist/cli/gate/gates/followups-adjudicated.d.mts +69 -0
  100. package/dist/cli/gate/gates/followups-adjudicated.d.mts.map +1 -0
  101. package/dist/cli/gate/gates/followups-adjudicated.mjs +141 -0
  102. package/dist/cli/gate/gates/followups-adjudicated.mjs.map +1 -0
  103. package/dist/cli/gate/gates/index.mjs +1 -0
  104. package/dist/cli/gate/gates/index.mjs.map +1 -1
  105. package/dist/cli/git/materialize-env.d.mts +39 -0
  106. package/dist/cli/git/materialize-env.d.mts.map +1 -0
  107. package/dist/cli/git/materialize-env.mjs +217 -0
  108. package/dist/cli/git/materialize-env.mjs.map +1 -0
  109. package/dist/cli/help.d.mts.map +1 -1
  110. package/dist/cli/help.mjs +9 -1
  111. package/dist/cli/help.mjs.map +1 -1
  112. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  113. package/dist/cli/install/legacy-paths.mjs +4 -0
  114. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  115. package/dist/cli/roadmap/lifecycle.d.mts.map +1 -1
  116. package/dist/cli/roadmap/lifecycle.mjs +4 -1
  117. package/dist/cli/roadmap/lifecycle.mjs.map +1 -1
  118. package/dist/cli/task/ids.d.mts +9 -0
  119. package/dist/cli/task/ids.d.mts.map +1 -1
  120. package/dist/cli/task/ids.mjs +9 -0
  121. package/dist/cli/task/ids.mjs.map +1 -1
  122. package/dist/cli/task/index-md.d.mts +5 -0
  123. package/dist/cli/task/index-md.d.mts.map +1 -1
  124. package/dist/cli/task/index-md.mjs +12 -4
  125. package/dist/cli/task/index-md.mjs.map +1 -1
  126. package/dist/cli/util/io.d.mts +6 -0
  127. package/dist/cli/util/io.d.mts.map +1 -1
  128. package/dist/cli/util/io.mjs +16 -1
  129. package/dist/cli/util/io.mjs.map +1 -1
  130. package/dist/cli/util/project.d.mts +7 -2
  131. package/dist/cli/util/project.d.mts.map +1 -1
  132. package/dist/cli/util/project.mjs +11 -14
  133. package/dist/cli/util/project.mjs.map +1 -1
  134. package/dist/cli/util/state-paths.d.mts +19 -0
  135. package/dist/cli/util/state-paths.d.mts.map +1 -0
  136. package/dist/cli/util/state-paths.mjs +45 -0
  137. package/dist/cli/util/state-paths.mjs.map +1 -0
  138. package/dist/plugin/.claude-plugin/plugin.json +4 -1
  139. package/dist/plugin/agents/apt-pr-review-i18n.md +190 -0
  140. package/dist/plugin/agents/apt-pr-review-xplatform.md +189 -0
  141. package/dist/plugin/skills/apt/SKILL.md +13 -6
  142. package/dist/plugin/skills/apt-close-task/SKILL.md +48 -0
  143. package/dist/plugin/skills/apt-debug/SKILL.md +19 -9
  144. package/dist/plugin/skills/apt-execute/SKILL.md +3 -1
  145. package/dist/plugin/skills/apt-fan-out/SKILL.md +65 -1
  146. package/dist/plugin/skills/apt-merge-conflict-resolution/SKILL.md +227 -0
  147. package/dist/plugin/skills/apt-pr-review/SCHEMA.md +4 -2
  148. package/dist/plugin/skills/apt-pr-review/SKILL.md +117 -29
  149. package/dist/plugin/skills/apt-quick/SKILL.md +24 -1
  150. package/dist/plugin/skills/apt-review/SKILL.md +6 -0
  151. package/dist/plugin/skills/apt-run/SKILL.md +1 -1
  152. package/dist/plugin/skills/apt-setup/SKILL.md +21 -3
  153. package/dist/plugin/skills/apt-ship/SKILL.md +32 -11
  154. package/dist/plugin/skills/apt-terminal/SKILL.md +8 -0
  155. package/dist/plugin/skills/apt-triage/SKILL.md +4 -0
  156. package/dist/plugin/skills/apt-verify/SKILL.md +24 -0
  157. package/dist/plugin/skills/apt-watch-ci/SKILL.md +3 -1
  158. package/dist/types/config.d.ts +33 -0
  159. package/dist/types/config.d.ts.map +1 -1
  160. package/package.json +138 -138
  161. package/prompts/conductor-framework-context.md +1 -1
  162. package/prompts/github/pr_i18n_agent.md +279 -0
  163. package/prompts/github/pr_xplatform_agent.md +283 -0
  164. package/prompts/inbox-clarification.md +18 -11
  165. package/skills/apt/SKILL.md +9 -3
  166. package/skills/apt-close-task/SKILL.md +48 -0
  167. package/skills/apt-debug/SKILL.md +19 -9
  168. package/skills/apt-execute/SKILL.md +3 -1
  169. package/skills/apt-fan-out/SKILL.md +65 -1
  170. package/skills/apt-merge-conflict-resolution/SKILL.md +227 -0
  171. package/skills/apt-pr-review/SCHEMA.md +4 -2
  172. package/skills/apt-pr-review/SKILL.md +117 -29
  173. package/skills/apt-quick/SKILL.md +24 -1
  174. package/skills/apt-review/SKILL.md +6 -0
  175. package/skills/apt-run/SKILL.md +1 -1
  176. package/skills/apt-setup/SKILL.md +21 -3
  177. package/skills/apt-ship/SKILL.md +32 -11
  178. package/skills/apt-terminal/SKILL.md +8 -0
  179. package/skills/apt-triage/SKILL.md +4 -0
  180. package/skills/apt-verify/SKILL.md +24 -0
  181. package/skills/apt-watch-ci/SKILL.md +3 -1
  182. package/src/cli/commands/audit-branch-current.mjs +3 -3
  183. package/src/cli/commands/event.mjs +6 -4
  184. package/src/cli/commands/fan-out.mjs +3 -1
  185. package/src/cli/commands/features-audit.mjs +6 -1
  186. package/src/cli/commands/followups.mjs +303 -0
  187. package/src/cli/commands/health-check.mjs +3 -1
  188. package/src/cli/commands/merge-detect.mjs +203 -0
  189. package/src/cli/commands/merge-integrate.mjs +392 -0
  190. package/src/cli/commands/pr-review/__fixtures__/android-strings/app/src/main/res/values-fr/strings.xml +3 -0
  191. package/src/cli/commands/pr-review/__fixtures__/babel-js/package.json +4 -0
  192. package/src/cli/commands/pr-review/__fixtures__/c-ifdef/src/main.c +4 -0
  193. package/src/cli/commands/pr-review/__fixtures__/ci-matrix/.github/workflows/ci.yml +10 -0
  194. package/src/cli/commands/pr-review/__fixtures__/cmake-win/CMakeLists.txt +5 -0
  195. package/src/cli/commands/pr-review/__fixtures__/diffs/add-arb.patch +9 -0
  196. package/src/cli/commands/pr-review/__fixtures__/diffs/ci-single-os-dep.patch +13 -0
  197. package/src/cli/commands/pr-review/__fixtures__/diffs/ci-single-os-list-item-dep.patch +17 -0
  198. package/src/cli/commands/pr-review/__fixtures__/diffs/lone-weak-t.patch +8 -0
  199. package/src/cli/commands/pr-review/__fixtures__/diffs/monorepo-b.patch +7 -0
  200. package/src/cli/commands/pr-review/__fixtures__/diffs/two-weak-xpl.patch +12 -0
  201. package/src/cli/commands/pr-review/__fixtures__/dropped-underscore/src/app.js +2 -0
  202. package/src/cli/commands/pr-review/__fixtures__/electron-app/package.json +4 -0
  203. package/src/cli/commands/pr-review/__fixtures__/i18n-locale-file/src/locales/fr.json +1 -0
  204. package/src/cli/commands/pr-review/__fixtures__/monorepo/packages/a/locales/fr.json +1 -0
  205. package/src/cli/commands/pr-review/__fixtures__/monorepo/packages/b/src/index.ts +1 -0
  206. package/src/cli/commands/pr-review/__fixtures__/py-babel/pyproject.toml +3 -0
  207. package/src/cli/commands/pr-review/__fixtures__/qt-i18n-dir/i18n/app_fr.ts +11 -0
  208. package/src/cli/commands/pr-review/__fixtures__/rust-cli/Cargo.toml +7 -0
  209. package/src/cli/commands/pr-review/__fixtures__/rust-cli/src/main.rs +1 -0
  210. package/src/cli/commands/pr-review/__fixtures__/ts-dir/ts/foo.ts +3 -0
  211. package/src/cli/commands/pr-review/detect-specialists.mjs +1023 -0
  212. package/src/cli/commands/pr-review.mjs +12 -2
  213. package/src/cli/commands/precompact-hook.mjs +10 -6
  214. package/src/cli/commands/roadmap.mjs +24 -11
  215. package/src/cli/commands/seed.mjs +3 -1
  216. package/src/cli/commands/state.mjs +9 -3
  217. package/src/cli/commands/statusline.mjs +5 -1
  218. package/src/cli/commands/task.mjs +135 -23
  219. package/src/cli/commands/worktree.mjs +298 -81
  220. package/src/cli/config/post-merge-sweep.mjs +3 -2
  221. package/src/cli/config/share-policy.mjs +16 -3
  222. package/src/cli/config/upgrade-gitignore.mjs +27 -5
  223. package/src/cli/consistency/parse-qa.mjs +11 -1
  224. package/src/cli/consistency/parse-review.mjs +36 -0
  225. package/src/cli/consistency/parse-spec.mjs +43 -0
  226. package/src/cli/coordination/agent-identity.mjs +6 -4
  227. package/src/cli/dispatch.mjs +4 -0
  228. package/src/cli/followups/derive.mjs +123 -0
  229. package/src/cli/followups/schema.mjs +264 -0
  230. package/src/cli/gate/gates/followups-adjudicated.mjs +158 -0
  231. package/src/cli/gate/gates/index.mjs +1 -0
  232. package/src/cli/git/materialize-env.mjs +212 -0
  233. package/src/cli/help.mjs +9 -1
  234. package/src/cli/install/legacy-paths.mjs +4 -0
  235. package/src/cli/roadmap/lifecycle.mjs +4 -1
  236. package/src/cli/task/ids.mjs +9 -0
  237. package/src/cli/task/index-md.mjs +12 -4
  238. package/src/cli/util/io.mjs +15 -1
  239. package/src/cli/util/project.mjs +11 -14
  240. package/src/cli/util/state-paths.mjs +45 -0
  241. package/prompts/followup_planner.md +0 -399
@@ -0,0 +1,283 @@
1
+ # Cross-Platform Review Agent
2
+
3
+ You are a focused cross-platform portability review agent. You have been spawned by the orchestrating agent — only because a deterministic detector found this project targets more than one OS/architecture (a multi-OS CI matrix, a cross-platform app framework, or platform-conditional build code) — to perform a deep portability audit of specific files.
4
+
5
+ ## Your Mission
6
+
7
+ Perform a thorough cross-platform review of the provided code changes, focusing ONLY on portability defects. Do not review security, code quality, logic correctness, performance, or internationalization.
8
+
9
+ ## Phase 1: Understand the PR Intent (BEFORE Looking for Issues)
10
+
11
+ **MANDATORY** - Before searching for issues, understand what this PR is trying to accomplish.
12
+
13
+ 1. **Read the provided context**
14
+ - PR description: What does the author say this does?
15
+ - Changed files: What areas of code are affected?
16
+ - Commits: How did the PR evolve?
17
+
18
+ 2. **Identify the change type**
19
+ - Bug fix: Correcting broken behavior
20
+ - New feature: Adding new capability
21
+ - Refactor: Restructuring without behavior change
22
+ - Performance: Optimizing existing code
23
+ - Cleanup: Removing dead code or improving organization
24
+
25
+ 3. **State your understanding** (include in your analysis)
26
+ ```
27
+ PR INTENT: This PR [verb] [what] by [how].
28
+ PORTABILITY RISK AREAS: [what could break on another supported OS/arch]
29
+ ```
30
+
31
+ **Only AFTER completing Phase 1, proceed to looking for issues.**
32
+
33
+ Why this matters: Understanding the project's target set prevents flagging unix-flavored code in a unix-only project.
34
+
35
+ ## TRIGGER-DRIVEN EXPLORATION (CHECK YOUR DELEGATION PROMPT)
36
+
37
+ **FIRST**: Check if your delegation prompt contains a `TRIGGER:` instruction.
38
+
39
+ - **If TRIGGER is present** → Exploration is **MANDATORY**, even if the diff looks correct
40
+ - **If no TRIGGER** → Use your judgment to explore or not
41
+
42
+ ### How to Explore (Bounded)
43
+
44
+ 1. **Read the trigger** - What pattern did the orchestrator identify?
45
+ 2. **Form the specific question** - "Does this new filesystem path use a portable join, or a hardcoded separator?"
46
+ 3. **Use Grep** to find any existing platform guard / path-join helper / portable wrapper
47
+ 4. **Use Read** to examine the relevant call sites and the CI/build config that proves the target set
48
+ 5. **Answer the question** - Yes (report issue) or No (move on)
49
+ 6. **Stop** - Do not explore beyond depth 1
50
+
51
+ ### Cross-Platform-Specific Trigger Questions
52
+
53
+ | Trigger | Portability Question to Answer |
54
+ |---------|--------------------------------|
55
+ | **Side effect contract changed** | Is the new filesystem/shell/process behavior platform-specific without a guard? |
56
+ | **Input contract changed** | Does new path / byte-stream handling assume one OS (separators, line endings, endianness)? |
57
+ | **Behavioral contract changed** | Did path/line-ending formatting regress to a single-OS form? |
58
+
59
+ ## CRITICAL: PR Scope and Context
60
+
61
+ ### What IS in scope (report these issues):
62
+ 1. **Portability assumptions in changed code** that break on another supported OS/arch
63
+ 2. **Platform-specific behavior added without a guard** or a portable abstraction
64
+ 3. **Broken portability assumptions** — a change that breaks an existing cross-platform contract
65
+
66
+ ### What is NOT in scope (do NOT report):
67
+ 1. **Pre-existing portability issues** in code this PR didn't touch
68
+ 2. **Portability improvements to untouched code**
69
+ 3. **Non-portability concerns** (security, logic, quality, performance, i18n)
70
+ 4. **Unix-flavored code in a project that only ships on unix** (verify the target set first)
71
+ 5. **A platform-specific code path that already sits behind a guard**
72
+
73
+ **Key distinction:**
74
+ - ✅ "Your new temp-file write hardcodes `/tmp`, which breaks on the Windows CI runner" - GOOD
75
+ - ✅ "This `exec('rm -rf')` has no Windows fallback and the project ships a Windows build" - GOOD
76
+ - ❌ "The old `unix_only_helper.sh` assumes bash" - BAD (pre-existing, not this PR)
77
+
78
+ ## Cross-Platform Focus Areas
79
+
80
+ ### 1. Path & Filesystem Assumptions
81
+ - Hardcoded path separators (`/` or `\`) instead of the platform path-join API
82
+ - Hardcoded absolute paths (`/tmp`, `/usr/...`, `C:\...`) instead of a portable temp/home resolver
83
+ - Case-sensitive-vs-insensitive FS assumptions (two files differing only by case; a lookup that assumes case folding)
84
+ - Line-ending assumptions (`\n` vs `\r\n`) in file read/write or string-split logic
85
+
86
+ ### 2. Platform-Specific APIs & Syscalls
87
+ - A platform-only API/syscall used without a `process.platform` / `#[cfg(target_os)]` / `#ifdef` guard
88
+ - Shell/exec portability: invoking `sh`/`bash`/`cmd` directly, relying on a unix-only binary, shell-builtin assumptions
89
+ - Environment-variable assumptions that differ by OS (`HOME` vs `USERPROFILE`, `PATH` separator `:` vs `;`)
90
+
91
+ ### 3. Architecture & Endianness
92
+ - Byte-order (endianness) assumptions in binary read/write or buffer parsing
93
+ - Word-size / pointer-size assumptions; integer-width assumptions in serialization
94
+ - Architecture-specific code paths added without a guard
95
+
96
+ ### 4. Build & Native Portability
97
+ - A native module / FFI / build step that only works on one OS without a fallback or guard
98
+ - Platform-conditional build config (CMake/Gradle/cargo cfg) added for one OS but missing the others the project supports
99
+ - A new dependency that is platform-locked in a project that ships on multiple OSes
100
+
101
+ ## Review Guidelines
102
+
103
+ ### High Confidence Only
104
+ - Only report findings with **>80% confidence**
105
+ - If you're unsure, don't report it
106
+ - Prefer false negatives over false positives
107
+
108
+ ### Verify Before Claiming "Will Break On X"
109
+
110
+ When your finding claims code breaks on a specific OS/arch:
111
+
112
+ **Ask yourself**: "Have I confirmed that OS/arch is actually a target, and that no guard already handles this?"
113
+
114
+ - Confirm that OS/arch is actually a target (CI matrix, cross-platform framework, build config)
115
+ - Read the complete code path; check whether a guard or portable helper already handles it elsewhere
116
+ - Search for related patterns: `platform`, `os.`, `cfg(`, `ifdef`, `sep`, `path.join`, `EOL`
117
+
118
+ **Your evidence must prove the break — not just that the code looks unix-flavored.**
119
+
120
+ ❌ **Weak**: "This path will break on Windows"
121
+ ✅ **Strong**: "The CI matrix includes windows-latest. This `'/tmp/' + name` at line 30 hardcodes a unix path with no `os.tmpdir()` / `path.join` and no platform guard."
122
+
123
+ ### Severity Classification (All block merge except LOW)
124
+ - **CRITICAL** (Blocker): A change that breaks the build or crashes at runtime on a supported OS/arch
125
+ - Example: a native build step that only works on macOS while the project ships Windows
126
+ - **Blocks merge: YES**
127
+ - **HIGH** (Required): A platform assumption that produces wrong behavior on a supported target
128
+ - Example: hardcoded `/tmp` path; a unix-only syscall with no guard
129
+ - **Blocks merge: YES**
130
+ - **MEDIUM** (Recommended): A portability weakness with limited blast radius
131
+ - Example: line-ending split assuming `\n`; `PATH`-separator assumption
132
+ - **Blocks merge: YES**
133
+ - **LOW** (Suggestion): Best-practice violation, minimal risk on the actual target set
134
+ - Example: `left`/`right` string literal in a comment
135
+ - **Blocks merge: NO** (optional polish)
136
+
137
+ <!-- SYNC: This section is shared. See partials/full_context_analysis.md for canonical version -->
138
+ ## CRITICAL: Full Context Analysis
139
+
140
+ Before reporting ANY finding, you MUST:
141
+
142
+ 1. **USE the Read tool** to examine the actual code at the finding location
143
+ - Never report based on diff alone
144
+ - Get ±20 lines of context around the flagged line
145
+ - Verify the line number actually exists in the file
146
+
147
+ 2. **Verify the issue exists** - Not assume it does
148
+ - Is the path/syscall actually unguarded, or is there a `process.platform` / `#ifdef` nearby?
149
+ - Is the OS you claim it breaks on actually a target?
150
+ - Does the framework provide a portable abstraction here?
151
+
152
+ 3. **Provide code evidence** - Copy-paste the actual code
153
+ - Your `code_examined` field must contain real non-portable code from the file
154
+
155
+ 4. **Check for mitigations** - Use Grep to search for:
156
+ - An existing platform guard / path-join helper / portable wrapper
157
+ - The CI matrix / build config that proves the target set
158
+
159
+ **Your evidence must prove the issue exists - not just that you suspect it.**
160
+
161
+ ## Evidence Requirements (MANDATORY)
162
+
163
+ Every finding you report MUST include a `verification` object with ALL of these fields:
164
+
165
+ ### Required Fields
166
+
167
+ **code_examined** (string, min 1 character)
168
+ The **exact code snippet** you examined. Copy-paste directly from the file:
169
+ ```
170
+ CORRECT: "const tmp = '/tmp/' + name;"
171
+ WRONG: "a hardcoded temp path"
172
+ ```
173
+
174
+ **line_range_examined** (array of 2 integers)
175
+ The exact line numbers [start, end] where the issue exists:
176
+ ```
177
+ CORRECT: [30, 30]
178
+ WRONG: [1, 100] // Too broad - you didn't examine all 100 lines
179
+ ```
180
+
181
+ **verification_method** (one of these exact values)
182
+ - `"direct_code_inspection"` - Found the issue directly in the code at the location
183
+ - `"cross_file_trace"` - Traced through callers / the CI/build config to confirm
184
+ - `"test_verification"` - Verified through examination of test code
185
+ - `"dependency_analysis"` - Verified through analyzing dependencies
186
+
187
+ ### Conditional Fields
188
+
189
+ **is_impact_finding** (boolean, default false)
190
+ Set to `true` ONLY if this finding is about impact on OTHER files (not the changed file).
191
+
192
+ **checked_for_handling_elsewhere** (boolean, default false)
193
+ For ANY "no guard" / "non-portable" claim:
194
+ - Set `true` ONLY if you used Grep/Read to verify no guard/abstraction handles it
195
+ - **When true, include the search in your description:**
196
+ - "Grep'd for `process.platform|os.tmpdir|path.join` near line 30 - none; `/tmp` is used directly"
197
+ - "Read `.github/workflows/ci.yml` - matrix includes windows-latest, so this unix path is a real break"
198
+
199
+ **If you cannot provide real evidence, you do not have a verified finding - do not report it.**
200
+
201
+ **Search Before Claiming Absence:** Never claim a guard is "missing" without searching for it first.
202
+
203
+ ## Valid Outputs
204
+
205
+ Finding issues is NOT the goal. Accurate review is the goal.
206
+
207
+ ### Valid: No Significant Issues Found
208
+ ```json
209
+ {
210
+ "findings": [],
211
+ "summary": "Reviewed [files]. No cross-platform issues found. Filesystem paths use path.join and platform-specific code sits behind process.platform guards consistent with the project's OS matrix."
212
+ }
213
+ ```
214
+
215
+ ### INVALID: Forced Issues
216
+ Do NOT report issues just to have something to say:
217
+ - Unix-flavored code flagged in a project that only ships on unix (verify the target set first)
218
+ - A platform path that already sits behind a guard
219
+ - Theoretical endianness concerns with no binary I/O in the diff
220
+ - Pre-existing portability issues not introduced by this PR
221
+
222
+ **Reporting nothing is better than reporting noise.** False positives erode trust faster than false negatives.
223
+
224
+ ## Code Patterns to Flag
225
+
226
+ ```js
227
+ // HIGH: Hardcoded unix temp path (CI matrix includes windows)
228
+ const tmp = '/tmp/' + name; // use os.tmpdir() + path.join
229
+
230
+ // HIGH: Hardcoded path separator
231
+ const p = dir + '/' + file; // use path.join(dir, file)
232
+
233
+ // HIGH: Unix-only shell with no guard / fallback
234
+ exec('rm -rf ' + dir); // use fs.rm; no Windows fallback
235
+
236
+ // MEDIUM: Line-ending assumption
237
+ const lines = text.split('\n'); // breaks on \r\n input
238
+
239
+ // MEDIUM: Env-var assumption
240
+ const home = process.env.HOME; // undefined on Windows (USERPROFILE)
241
+ ```
242
+
243
+ ```rust
244
+ // HIGH: Platform-only code path added without a cfg guard
245
+ // (the crate's CI matrix builds windows + macos + linux)
246
+ std::os::unix::fs::symlink(a, b)?; // no #[cfg(unix)]
247
+ ```
248
+
249
+ ## Output Format
250
+
251
+ Provide findings in JSON format:
252
+
253
+ ```json
254
+ [
255
+ {
256
+ "file": "src/cache.ts",
257
+ "line": 30,
258
+ "title": "Hardcoded /tmp path breaks the Windows CI runner",
259
+ "description": "The cache file path hardcodes '/tmp/'. The project's CI matrix (.github/workflows/ci.yml) includes windows-latest, where /tmp does not exist, so the cache write will fail on Windows.",
260
+ "category": "quality",
261
+ "severity": "high",
262
+ "verification": {
263
+ "code_examined": "const tmp = '/tmp/' + name;",
264
+ "line_range_examined": [30, 30],
265
+ "verification_method": "cross_file_trace"
266
+ },
267
+ "is_impact_finding": false,
268
+ "checked_for_handling_elsewhere": true,
269
+ "suggested_fix": "Use a portable temp path: const tmp = path.join(os.tmpdir(), name);",
270
+ "confidence": 90
271
+ }
272
+ ]
273
+ ```
274
+
275
+ ## Important Notes
276
+
277
+ 1. **Be Specific**: Include exact file path and line number
278
+ 2. **Explain Impact**: Describe the break ("build fails on Windows CI", "crashes on macOS arm64")
279
+ 3. **Provide Fix**: Give an actionable suggested_fix (use path.join, add a platform guard, use the portable API)
280
+ 4. **Check Context**: Confirm the target set before flagging; don't flag guarded code or test fixtures
281
+ 5. **Focus on NEW Code**: Prioritize reviewing additions over deletions
282
+
283
+ Focus on **cross-platform portability defects** only. High confidence, high impact findings.
@@ -1,17 +1,24 @@
1
- Run one read-only clarification turn using Work Record context, prior chat history, and the latest user response.
2
- Return semantic readiness updates and either the next single focused clarification question or ready status, without mutating project files or external systems.
1
+ Run one read-only clarification turn using Work Record context, the prior conversation history, and the latest user reply.
2
+ Return semantic readiness updates plus a conversational reply, without mutating project files or external systems.
3
3
 
4
- You are the Aperant clarification agent.
4
+ You are the Aperant clarification agent: a collaborative senior engineer helping the user make this work item ready for planning. You are a partner working the problem with them, not a form collecting fields.
5
5
  First call readPhaseOutput with phase="triage" to inspect the triage output.
6
- If triage is already specific and high-confidence, set status="ready" and omit nextChatMessage.
7
- Otherwise, populate nextChatMessage with EXACTLY ONE focused, concrete question that addresses the highest-impact remaining readiness gap. Do not batch multiple questions into one turn.
8
- Ground the question in the triage findings, the inbox title and description, and the prior message history. Acknowledge what the user already said when relevant.
9
- Set nextChatMessage.dimensionKey to the readiness dimension the question targets, and nextChatMessage.cardId to the matching open clarification card when one exists.
10
- Keep nextClarificationQuestions as the ordered list of remaining open questions (one per open dimension); the UI renders this list as a chip row alongside the chat.
6
+ Read the full messageHistory before composing your reply. The latest user reply arrives in submittedResponses; messageHistory is everything before it. Never re-ask something the user already answered, and acknowledge what they already told you.
7
+ Respond to what the user actually said FIRST. If they asked you a question, answer it directly. If they asked for your opinion or a recommendation (for example "what do you think?"), give ONE concrete recommendation grounded in the triage evidence and the repository, then ask them to confirm or correct it.
8
+ Propose-then-confirm: when the user explicitly confirms a proposal you made, treat the confirmed content as their answer and emit the slot patches with source:'user_answer' and groundedness:'grounded'.
9
+ When the user delegates a decision to you (for example "you decide" or "whatever you think is best"), choose a sensible value, fill the slot with source:'agent_assume' and groundedness:'tentative', and STATE the assumption plainly in nextChatMessage so the user can veto it.
10
+ Classify the intent of each user reply in consumedResponses[].mode: 'answer' when it supplies information, 'not_applicable' when the user says the question does not apply, 'agent_assume' when they delegate the decision to you, 'unknown' when they say they cannot answer.
11
+ Weave AT MOST ONE primary ask into each reply, placed after you have responded to the user. A purely conversational reply with no new ask is allowed when answering the user is the right move this turn.
12
+ If the user goes off on a tangent, answer briefly and politely steer back to the highest-impact open readiness gap.
13
+ nextChatMessage.text is YOUR reply shown in the chat. Make it specific to this work item and this conversation; never emit a generic template. Set nextChatMessage.dimensionKey to the readiness dimension your primary ask targets (when there is one), and nextChatMessage.cardId to the matching open clarification card when one exists.
14
+ When messageHistory is empty (the opening turn), nextChatMessage is your opener: say in one sentence what you understood from triage, then ask the single highest-impact question.
15
+ When status="ready", you may still populate nextChatMessage with a short close-out, including a one-line statement of any assumptions you made; omit it only when there is genuinely nothing left to say.
16
+ Keep nextClarificationQuestions as the ordered list of remaining open questions (one per open dimension); it tracks what is left to resolve and is not shown directly in the chat.
11
17
  If the user's prior answer did not raise the targeted dimension's readinessAssessment score above its scoreThreshold, the next question MUST target the same dimensionKey with a sharper or more concrete follow-up. Otherwise, advance to a different open dimension.
12
18
  Target the gating dimension whose acceptance-criteria slots are still open; do not jump to a dimension whose slots are already grounded.
19
+ Do not re-grill the same dimension more than three turns.
13
20
  Emit proposedAcceptanceCriteriaUpdates as field-level patches ({ acIndex, fieldPath, value, source, groundedness }) that fill the targeted acceptance-criteria slots from the user's answer; fieldPath is one of statement, given, when, thenOutcome, verificationMethod.
14
21
  Mark a slot patch groundedness:'tentative' when you are filling it from a vague answer or your own assumption, and groundedness:'grounded' only when the user's answer concretely specifies the field.
15
- On an explicit not_applicable reply, do not write a value; on an explicit agent_assume reply, fill the slot with source:'agent_assume'. Otherwise use source:'user_answer'.
16
- Ask EXACTLY ONE question per turn, and do not re-grill the same dimension more than three turns.
17
- Use repository tools only when needed to sharpen the question.
22
+ When the user's reply means a question does not apply, do not write a value for that slot; when they delegate the decision, fill the slot with source:'agent_assume'. Otherwise use source:'user_answer'.
23
+ If triage is already specific and high-confidence and every slot is grounded, set status="ready".
24
+ Use repository tools only when needed to ground a recommendation or sharpen a question.
@@ -481,9 +481,15 @@ Print `[APT] Tokens: {input}+{output} = {total} (task cumulative)` using the
481
481
  tally output. Skip silently if tokens tally errors — never block the
482
482
  pipeline on telemetry.
483
483
 
484
- **If `spawn_agent` is false (QUICK):**
485
- Invoke `apt:quick` directly with task_dir context. No phase marker QUICK is
486
- single-shot.
484
+ **If `spawn_agent` is false (QUICK or DEBUG):**
485
+ Both QUICK and DEBUG resolve here (single-shot, no spawned phase agent). Invoke
486
+ the picked skill (`apt:quick` for QUICK, `apt:debug` for DEBUG, per
487
+ `primary_skill`) directly with `task_id`, `task_dir`, AND `worktree_path` in its
488
+ skill context — Step 2 already created the worktree + printed the banner for this
489
+ track, so thread `worktree_path` the same way the `spawn_agent: true` branch
490
+ threads it into the planner. No phase marker — QUICK and DEBUG are both
491
+ single-shot here. (If Step 2 returned no `worktree` block, pass `worktree_path` as
492
+ "none" and the skill proceeds in the project root.)
487
493
 
488
494
  ### Step 4: Chain Pipeline (based on selected option)
489
495
 
@@ -17,6 +17,8 @@ execution_modes:
17
17
  allowed-tools: "Bash, Read, Grep, Glob, Write"
18
18
  argument-hint: "apt:close-task [--all | --task <id>] [--narrate-only]"
19
19
  gates: []
20
+ config_keys:
21
+ - followups.after_close
20
22
  ---
21
23
  <objective>
22
24
  Confirm a shipped PR has merged on GitHub, then run the closer side of the task lifecycle: flip `lifecycle_phase` from `shipped-pending-merge` to `completed`, strip stale per-PR overrides from `.aperant/config.json`, clean up worktree per policy, and emit `task.closed.merged` events for the audit trail. This is the post-merge counterpart to `/apt:ship`.
@@ -54,6 +56,50 @@ Run `apt-tools task close-merged . --all --dry-run` first to see what would happ
54
56
 
55
57
  If `--task <id>` is passed, narrow to that task.
56
58
 
59
+ ## 1.5 Disposition follow-ups (autonomy-aware — G45)
60
+
61
+ Run this BEFORE §2. The `task close` path that §2 invokes carries a CLI-level disposition gate (G45): it refuses to close while `{task_dir}/followups.json` has any open row, or any `dispositioned` row missing a valid `disposition.kind` + `ref`. Disposition every open row here so §2 closes cleanly without `--force`.
62
+
63
+ For each candidate the dry-run says `close`, read the effective policy:
64
+
65
+ ```bash
66
+ node packages/framework/bin/apt-tools.mjs followups policy . --task-id <id>
67
+ ```
68
+
69
+ The envelope returns `{ after_close: 'ask' | 'file' | 'chain', max_chain_depth, source }`. `after_close` derives from `config.followups.after_close` when set, else from the task autonomy level (≤1 `ask` / 2 `file` / 3 `chain`). Then enumerate the open rows from `{task_dir}/followups.json` and route each by class + policy:
70
+
71
+ **`ask` (autonomy ≤ 1).** Present each open row to the user with its `class_proposed`, `adjudication.verdict`, and `files`. Offer fix / file / drop:
72
+ - fix → execute the change in this session (only if it is genuinely small + safe), commit, flip the row `status: 'fixed'`, optionally record `executed_commit`.
73
+ - file → the `new-task` flow below.
74
+ - drop → `apt-tools followups disposition . --task-id <id> --row <row-id> --kind dropped_by_user --ref <reason-slug> --reason "<text>"`.
75
+
76
+ **`file` (autonomy 2).** Auto-file every `new-task` row through the EXISTING triage machine (report, do NOT execute):
77
+
78
+ ```bash
79
+ # 1) create the task seeded from the row
80
+ node packages/framework/bin/apt-tools.mjs task create . --description "<row title>" --scope <scope> [...linkage]
81
+ # 2) enter it at needs-triage; the configured task_tracking.backend mirrors it
82
+ # (github-issues creates/updates a real labeled issue idempotently)
83
+ node packages/framework/bin/apt-tools.mjs triage init . --id <new-task-id>
84
+ # 3) record the disposition (ref = the filed task id; the triage mirror carries the issue URL)
85
+ node packages/framework/bin/apt-tools.mjs followups disposition . --task-id <id> --row <row-id> --kind filed_task --ref <new-task-id>
86
+ ```
87
+
88
+ There is **NO new `gh issue create` logic** — filing goes through `task create` + `triage init`, and the backend mirror does the GitHub work (ID-08). This works with the local-only default (no `task_tracking` key) AND with `github-issues` when configured; never hard-require gh.
89
+
90
+ **`polish` rows** (any policy) → park in the committable Deferred Ideas section (ID-09): append `- {title} -- deferred because {reason}` under `## Deferred Ideas` in `context/notes/{task-id}.md` (create the section/file if absent), then `apt-tools followups disposition . --task-id <id> --row <row-id> --kind parked_note --ref .aperant/context/notes/{task-id}.md`. Polish rows must NOT linger in the ledger — the ledger is ephemeral.
91
+
92
+ **`chain` (autonomy 3).** Do the `file` flow, THEN auto-chain depth-1 ONLY the filed tasks that triage to `ready-for-agent` (run the chained task through the full pipeline gates). Pass the depth marker on the chained task so the next-level policy degrades `chain → file`:
93
+
94
+ ```bash
95
+ node packages/framework/bin/apt-tools.mjs triage init . --id <new-task-id> \
96
+ --extra '{"followups_chain": {"origin_task": "<id>", "depth": 1}}'
97
+ ```
98
+
99
+ `ready-for-human` items stop there by design — they are filed + reported, never auto-executed. `max_chain_depth` is clamped to 1 in v1 (anti-perpetual-motion); `followups policy` already degrades `chain → file` when the current task's `triage.json` carries `extra.followups_chain.depth >= max_chain_depth`.
100
+
101
+ After this step, every open row carries a durable disposition, so §2's `task close` clears the gate.
102
+
57
103
  ## 2. Confirm merge
58
104
 
59
105
  For each candidate the dry-run says `close`:
@@ -124,6 +170,8 @@ close-merged emits an envelope with:
124
170
 
125
171
  Report the summary verbatim. If anything landed in `skipped[]` with a recoverable reason (`offline`, `rate_limited`), suggest the user re-run once the network clears.
126
172
 
173
+ **Follow-ups-gate recovery (G45).** The passive post-merge sweep (and any bare `task close-merged` that skips §1.5) inherits the CLI close gate: a merged task whose `followups.json` still has open/undispositioned rows fails its spawned `task close` and lands in `errors[]` with a `task-close-failed` reason — the task STAYS in `shipped-pending-merge`. This is the designed backstop, not a bug. Recover by running `/apt:close-task --task <id>` (which runs §1.5 first to disposition every open row), then the close clears the gate. As a last resort, `apt-tools task close . --id <id> --force --reason "<text>"` bypasses the gate with an audited `task.close.followups-override` event.
174
+
127
175
  **Local cleanup recap (best-effort).** On confirmed merge, `task close-merged` runs `computeWorktreeCleanup` against the primary repo (`packages/framework/src/cli/task/worktree-cleanup.mjs:140-230`): `git fetch origin <base>` (failure → recorded as `fetch_failed` warning, cleanup proceeds), `git checkout <base>` (failure → returns `action: 'checkout_failed'` and preserves BOTH the worktree and the task branch — nothing is removed), `git merge --ff-only origin/<base>` (failure → `ff_only_failed` warning, GC still runs), `git worktree remove <wtPath>`, and `git branch -d <taskBranch>` (failure on squash/rebase merges → `branch_delete_failed` warning, `branch_deleted: false` in the envelope; user can `git branch -D` manually). This is why `/apt:watch-ci` auto-merge MUST NOT pass `gh pr merge --delete-branch` — that flag would try to do the same work from inside the worktree where `git switch <base>` cannot succeed (see FRAMEWORK-BUG-019).
128
176
 
129
177
  ## 4. Narrate-only mode
@@ -42,6 +42,7 @@ reproduction, so loading this appendix is non-trivial.
42
42
  - **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` or the locally installed `apt-tools` binary
43
43
  - **Constitution:** Read `AGENTS.md` in the project root if it exists
44
44
  - **Debug sessions:** `.aperant/debug/{session-id}/DEBUG.md`
45
+ - **Task-level worktree isolation:** A worktree is provisioned at session start by `task create` (track-agnostic — DEBUG gets one too; parsed + banner-announced in §1a) when `task_isolation.worktree_per_task` is enabled. Read-only investigation (Sections 2-3 Observe / Hypothesize — `git log`, `git diff`, Grep, Read) may run from EITHER cwd; they only read. But once code changes begin — Section 4 (Checkpoint), Section 5b (Execute Test — the minimal change), Section 6b (Implement Fix + commit) — run those Bash commands with `cd {worktree_path} &&` so the fix + checkpoint commits land on the worktree's task branch. Use the `worktree_path` the router passed in skill context or that §1a parsed from `task create`'s envelope. `apt-tools` calls always take the project root as `<project-dir>`. If no `worktree` block was returned, proceed in the project root as before.
45
46
  </your_environment>
46
47
 
47
48
  <state_files>
@@ -98,6 +99,15 @@ Sequence:
98
99
 
99
100
  The debug working dir at `.aperant/debug/{session-id}/` stays — that working artifact is preserved (per spec §3 R7). The canonical task record at `.aperant/tasks/{task-id}/` is created in parallel by `task create`, and both share the same id.
100
101
 
102
+ **Worktree isolation (both paths).** `task create` provisions a worktree when `task_isolation.worktree_per_task` is enabled (track-agnostic — DEBUG gets one too). Parse `worktree.worktree_path`, `worktree.branch`, and `worktree.base_branch` from the `task create` envelope (direct path); on the router path the orchestrator already passed `worktree_path` into your skill context — use that. When a `worktree` block is present, print the banner:
103
+
104
+ ```
105
+ [APT] Working in isolated worktree: {worktree_path}
106
+ [APT] Task branch: {branch} (from {base_branch})
107
+ ```
108
+
109
+ Investigation (Sections 2-3) may run from either cwd — but once you reach the fix-applying phases (Section 4 Checkpoint, Section 5b Execute Test, Section 6b Implement Fix), prefix those Bash commands with `cd {worktree_path} &&` so commits land on the task branch. If no `worktree` block is returned, proceed in the project root as before.
110
+
101
111
  ### 1b. Create debug working directory
102
112
 
103
113
  ```bash
@@ -202,11 +212,11 @@ Update DEBUG.md:
202
212
 
203
213
  ## 4. Checkpoint — Save State Before Changes
204
214
 
205
- Before making any code changes, create a git checkpoint:
215
+ Before making any code changes, create a git checkpoint. From here on, code-change Bash commands run in the task worktree when one was provisioned — prefix them with `cd {worktree_path} &&` (omit the prefix if no `worktree` block was returned in §1a):
206
216
 
207
217
  ```bash
208
- git stash push -m "debug-checkpoint-{session-id}-pre-test" || true
209
- git rev-parse HEAD > .aperant/debug/{session-id}/checkpoints/pre-test.ref
218
+ cd {worktree_path} && git stash push -m "debug-checkpoint-{session-id}-pre-test" || true
219
+ cd {worktree_path} && git rev-parse HEAD > .aperant/debug/{session-id}/checkpoints/pre-test.ref
210
220
  ```
211
221
 
212
222
  Record the checkpoint in DEBUG.md:
@@ -215,9 +225,9 @@ Record the checkpoint in DEBUG.md:
215
225
  - {timestamp} — pre-test: {commit-sha} (before testing hypotheses)
216
226
  ```
217
227
 
218
- If a hypothesis test fails badly, revert to checkpoint:
228
+ If a hypothesis test fails badly, revert to checkpoint (in the worktree):
219
229
  ```bash
220
- git checkout $(cat .aperant/debug/{session-id}/checkpoints/pre-test.ref) -- .
230
+ cd {worktree_path} && git checkout $(cat .aperant/debug/{session-id}/checkpoints/pre-test.ref) -- .
221
231
  ```
222
232
 
223
233
  ## 5. Test — Validate Hypotheses
@@ -229,7 +239,7 @@ For each hypothesis (starting with most likely):
229
239
  - What is the expected outcome if the hypothesis is correct?
230
240
 
231
241
  ### 5b. Execute Test
232
- - Make the minimal change needed to test the hypothesis
242
+ - Make the minimal change needed to test the hypothesis — apply it in the task worktree (edit files under `{worktree_path}` and run any verification command with `cd {worktree_path} &&`) when a `worktree` block was provisioned in §1a; otherwise edit in the project root.
233
243
  - Run the relevant command/test
234
244
  - Record the actual outcome
235
245
 
@@ -283,12 +293,12 @@ Document the root cause clearly:
283
293
  ```
284
294
 
285
295
  ### 6b. Implement Fix
286
- - Apply the minimal fix that addresses the root cause
296
+ - Apply the minimal fix that addresses the root cause — in the task worktree (edit files under `{worktree_path}`) when one was provisioned in §1a, so the commit lands on the task branch.
287
297
  - Run verification (tests, typecheck, lint)
288
- - If verification passes, commit:
298
+ - If verification passes, commit (prefix with `cd {worktree_path} &&` when a worktree was provisioned so `git` stages + commits on the worktree's task branch; `apt-tools commit` takes no `<project-dir>` and operates on the current cwd):
289
299
 
290
300
  ```bash
291
- node packages/framework/bin/apt-tools.mjs commit "fix: {concise description of fix}" --files {changed-files}
301
+ cd {worktree_path} && node packages/framework/bin/apt-tools.mjs commit "fix: {concise description of fix}" --files {changed-files}
292
302
  ```
293
303
 
294
304
  ### 6c. Update Status
@@ -555,10 +555,12 @@ Read `state.active_tasks[<task-id>].install_status`. Branch on the value:
555
555
  ```
556
556
  Do NOT let the failure surface as a downstream "command not found" — the executor's job here is to make the install failure legible. The caller can re-run the task after fixing the install (or invoke with `--no-bootstrap` to skip the install entirely).
557
557
 
558
+ > **State resolves to the main repo from any cwd (state-fork-fix).** You no longer need to pass the main project root to keep the board coherent: ephemeral `.aperant/` state — `state.json`, `tasks/<id>/` (spec/plan/build-progress), locks, events — resolves to the **one main-repo board** even when `apt-tools` is invoked with `.` from a worktree cwd. The board never forks. (Committable `roadmap/` + `features/` stay worktree-local so they ride the PR branch.) `worktree create --task` likewise anchors at main from any cwd — invoking the **task-mode** create from inside a worktree retargets to the main repo instead of erroring. The **subtask-mode** worktree path below keeps its nested-worktree skip (back-compat only).
559
+
558
560
  **2b. Continue with the existing worktree-mode logic:**
559
561
  - Run `node packages/framework/bin/apt-tools.mjs detect-worktree .` — if `is_worktree: true`:
560
562
  - Log: "Skipping worktree isolation (already in worktree)"
561
- - Do NOT create worktrees (prevents nested worktrees)
563
+ - Do NOT create SUBTASK-mode worktrees from here (the legacy subtask-mode path refuses nesting; task-mode `worktree create --task` is safe — it anchors at main)
562
564
  - Agents execute in the current working directory directly
563
565
  - Note: locks still coordinate correctly (apt-tools uses shared lock dir via git common dir)
564
566
  - If `use_worktrees` is `true` AND `is_worktree` is `false` AND no task-level `worktree_path` is present: set `WORKTREE_MODE=true`
@@ -85,6 +85,15 @@ node packages/framework/bin/apt-tools.mjs worktree create --task ${task_id}
85
85
 
86
86
  (Skip if `state.active_tasks[task_id].worktree_path` is already set — the create command is idempotent.)
87
87
 
88
+ `worktree create` also materializes the project's stored env blobs (the
89
+ per-project encrypted env store, #354) into the fresh worktree as a best-effort,
90
+ traversal-guarded step — so each fan-out worker's checkout has the real `.env*`
91
+ files instead of crashing in no-cloud mode. The result is surfaced on the
92
+ envelope as `env_materialized` (null when no env is stored). For a long-lived
93
+ worktree whose stored env later drifted, re-copy on demand with
94
+ `node packages/framework/bin/apt-tools.mjs worktree sync-env <dir> --task ${task_id}`
95
+ (the store is the source of truth — sync-env overwrites, never merges).
96
+
88
97
  ## 3. Spawn N apt-executor subagents
89
98
 
90
99
  Read the `parallelization.max_tasks` cap from `.aperant/config.json` (or default 4). Slice `task_ids` into batches of size `min(cap, task_ids.length)`. For each task in the current batch, dispatch via the host CLI's `Task` tool with `run_in_background: true`:
@@ -129,7 +138,62 @@ Churned for {Xm Ys} · {K} local agents still running
129
138
 
130
139
  The first panel uses `┌─ ... │` (top-with-stem); the LAST panel uses `└─ ... ` (bottom corner). Footer line: "Churned" = elapsed since the most recent lifecycle event across all in-flight workers; "K local agents still running" = count of workers whose `lifecycle_phase` is non-terminal AND `assigned_to` is non-null.
131
140
 
132
- ## 5. Report
141
+ ## 5. Integration phase — serialize N PRs against the moving base (opt-in)
142
+
143
+ The N workers each opened an independent PR against the same base. Nothing
144
+ ordered or reconciled them — two same-milestone tasks touching overlapping code
145
+ are otherwise only reconciled when a human merges each PR. This phase closes
146
+ that gap. It is **opt-in** via `pipeline.resolve_merge_conflicts` (default
147
+ false) in `.aperant/config.json`; when disabled, skip straight to §6 and let
148
+ the human serialize the PRs (today's report-only behavior — byte-unchanged).
149
+
150
+ When enabled, after all workers reach a terminal state, run the
151
+ `apt-tools merge integrate` command — it resolves the batch's task → branch/PR
152
+ set, computes a **merge order** (topological when `depends_on` edges exist
153
+ among the batch tasks, else a stable alphabetical task-id order), and probes
154
+ each PR's mergeability against the current base via the same `git merge-tree`
155
+ detection `apt:merge-conflict-resolution` uses. It is **read-only** — it never
156
+ merges, rebases, or auto-pushes; it ORDERS and PROBES, you RESOLVE.
157
+
158
+ First fetch the latest base tips so the probes are against the moving base, then
159
+ call `integrate` with the batch's task list (or `--batch ${batch_id}` to recover
160
+ it from the worker rows):
161
+
162
+ ```bash
163
+ git -C "$conductor_root" fetch origin "$base_branch"
164
+ node packages/framework/bin/apt-tools.mjs merge integrate . --tasks ${task_ids_csv}
165
+ # OR recover the task list from the batch's worker rows:
166
+ node packages/framework/bin/apt-tools.mjs merge integrate . --batch ${batch_id}
167
+ ```
168
+
169
+ Returns a structured integration report:
170
+ ```json
171
+ {
172
+ "status": "ok",
173
+ "command": "merge-integrate",
174
+ "order_strategy": "topological",
175
+ "order": ["task-a", "task-b"],
176
+ "summary": { "total": 2, "mergeable": 1, "conflicted": 1, "needs_human": 0 },
177
+ "tasks": [
178
+ { "task_id": "task-a", "status": "mergeable", "action": "advance", "mergeable": true },
179
+ { "task_id": "task-b", "status": "conflicted", "action": "resolve-conflict",
180
+ "conflicts_preview": ["src/x.ts"],
181
+ "resolution": { "skill": "apt:merge-conflict-resolution", "worktree_path": "…", "base": "origin/main" } }
182
+ ]
183
+ }
184
+ ```
185
+
186
+ Walk the `tasks` array **in `order`** (the command already serialized it):
187
+
188
+ 1. **`status: "mergeable"`** (`action: "advance"`) → no inter-PR conflict introduced by the branches ordered before it. Nothing to do; continue.
189
+ 2. **`status: "conflicted"`** (`action: "resolve-conflict"`) → an earlier-ordered branch conflicts with this one. Hand off to **`/apt:merge-conflict-resolution`** using the entry's `resolution` block (`worktree_path` + `base`); it resolves the markers inline and re-probes. On its non-convergence STOP, leave that branch for the human and note it in the §6 report — do **not** block the remaining branches. After it converges, re-run `merge integrate` to re-probe the rest against the now-updated base.
190
+ 3. **`status: "needs-human"` / `"skipped"` / `"unknown"`** → not integrable by the agent (no `pr_url`, missing worktree, or probe-failed). Carry the `reason` into the §6 report and move on.
191
+
192
+ The resolved branches still flow through PR → review → CI — this phase never
193
+ auto-merges. It only ensures each PR is mergeable against the base the prior
194
+ PRs established, so the human's final merge is conflict-free.
195
+
196
+ ## 6. Report
133
197
 
134
198
  When all workers reach a terminal state, render the rail one final time. For each shipped worker (lifecycle `shipped-pending-merge` + `pr_url` set), collapse that panel to the SHIPPED variant:
135
199