@aperant/framework 0.8.1 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/CHANGELOG.md +443 -1
  2. package/agents/apt-planner.md +39 -7
  3. package/dist/cli/commands/adr.mjs +1 -1
  4. package/dist/cli/commands/audit-branch-current.d.mts +25 -0
  5. package/dist/cli/commands/audit-branch-current.d.mts.map +1 -0
  6. package/dist/cli/commands/audit-branch-current.mjs +252 -0
  7. package/dist/cli/commands/audit-branch-current.mjs.map +1 -0
  8. package/dist/cli/commands/audit.d.mts.map +1 -1
  9. package/dist/cli/commands/audit.mjs +13 -2
  10. package/dist/cli/commands/audit.mjs.map +1 -1
  11. package/dist/cli/commands/check-version.d.mts.map +1 -1
  12. package/dist/cli/commands/check-version.mjs +19 -1
  13. package/dist/cli/commands/check-version.mjs.map +1 -1
  14. package/dist/cli/commands/context.mjs +3 -3
  15. package/dist/cli/commands/context.mjs.map +1 -1
  16. package/dist/cli/commands/coverage-check.d.mts.map +1 -1
  17. package/dist/cli/commands/coverage-check.mjs +74 -7
  18. package/dist/cli/commands/coverage-check.mjs.map +1 -1
  19. package/dist/cli/commands/detect-runtime.d.mts +6 -0
  20. package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
  21. package/dist/cli/commands/detect-runtime.mjs +142 -0
  22. package/dist/cli/commands/detect-runtime.mjs.map +1 -0
  23. package/dist/cli/commands/driver-doctor.d.mts +29 -0
  24. package/dist/cli/commands/driver-doctor.d.mts.map +1 -0
  25. package/dist/cli/commands/driver-doctor.mjs +291 -0
  26. package/dist/cli/commands/driver-doctor.mjs.map +1 -0
  27. package/dist/cli/commands/gate.mjs +14 -0
  28. package/dist/cli/commands/gate.mjs.map +1 -1
  29. package/dist/cli/commands/init.d.mts.map +1 -1
  30. package/dist/cli/commands/init.mjs +9 -6
  31. package/dist/cli/commands/init.mjs.map +1 -1
  32. package/dist/cli/commands/install-from-source.d.mts +2 -0
  33. package/dist/cli/commands/install-from-source.d.mts.map +1 -0
  34. package/dist/cli/commands/install-from-source.mjs +2 -0
  35. package/dist/cli/commands/install-from-source.mjs.map +1 -0
  36. package/dist/cli/commands/modes.mjs +1 -1
  37. package/dist/cli/commands/route.d.mts.map +1 -1
  38. package/dist/cli/commands/route.mjs +38 -1
  39. package/dist/cli/commands/route.mjs.map +1 -1
  40. package/dist/cli/commands/triage.mjs +1 -1
  41. package/dist/cli/config/load.d.mts.map +1 -1
  42. package/dist/cli/config/load.mjs +9 -3
  43. package/dist/cli/config/load.mjs.map +1 -1
  44. package/dist/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  45. package/dist/cli/coordination/event-schema.d.mts.map +1 -1
  46. package/dist/cli/coordination/event-schema.mjs +18 -0
  47. package/dist/cli/coordination/event-schema.mjs.map +1 -1
  48. package/dist/cli/coverage-check/user-outcomes.d.mts +84 -0
  49. package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -0
  50. package/dist/cli/coverage-check/user-outcomes.mjs +265 -0
  51. package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -0
  52. package/dist/cli/dispatch.d.mts.map +1 -1
  53. package/dist/cli/dispatch.mjs +18 -1
  54. package/dist/cli/dispatch.mjs.map +1 -1
  55. package/dist/cli/gate/context.d.mts +1 -0
  56. package/dist/cli/gate/context.d.mts.map +1 -1
  57. package/dist/cli/gate/context.mjs +14 -1
  58. package/dist/cli/gate/context.mjs.map +1 -1
  59. package/dist/cli/gate/gates/consistency-check.d.mts +1 -0
  60. package/dist/cli/gate/gates/consistency-check.d.mts.map +1 -1
  61. package/dist/cli/gate/gates/consistency-check.mjs +1 -0
  62. package/dist/cli/gate/gates/consistency-check.mjs.map +1 -1
  63. package/dist/cli/gate/gates/review-clean.d.mts +1 -0
  64. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  65. package/dist/cli/gate/gates/review-clean.mjs +1 -0
  66. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  67. package/dist/cli/gate/gates/verify-approved.d.mts +1 -0
  68. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  69. package/dist/cli/gate/gates/verify-approved.mjs +1 -0
  70. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  71. package/dist/cli/gate/registry.d.mts.map +1 -1
  72. package/dist/cli/gate/registry.mjs +9 -1
  73. package/dist/cli/gate/registry.mjs.map +1 -1
  74. package/dist/cli/host/detect.mjs +1 -1
  75. package/dist/cli/host/detect.mjs.map +1 -1
  76. package/dist/cli/install/find-workspace-framework-version.d.mts +16 -0
  77. package/dist/cli/install/find-workspace-framework-version.d.mts.map +1 -0
  78. package/dist/cli/install/find-workspace-framework-version.mjs +85 -0
  79. package/dist/cli/install/find-workspace-framework-version.mjs.map +1 -0
  80. package/dist/cli/install/install-from-source.d.mts +10 -0
  81. package/dist/cli/install/install-from-source.d.mts.map +1 -0
  82. package/dist/cli/install/install-from-source.mjs +271 -0
  83. package/dist/cli/install/install-from-source.mjs.map +1 -0
  84. package/dist/cli/install/install-kernel.d.mts.map +1 -1
  85. package/dist/cli/install/install-kernel.mjs +24 -0
  86. package/dist/cli/install/install-kernel.mjs.map +1 -1
  87. package/dist/cli/install/legacy-paths.d.mts +33 -11
  88. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  89. package/dist/cli/install/legacy-paths.mjs +163 -39
  90. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  91. package/dist/cli/install/runtime-detect.mjs +1 -1
  92. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  93. package/dist/cli/install/runtime-migrate.d.mts +34 -14
  94. package/dist/cli/install/runtime-migrate.d.mts.map +1 -1
  95. package/dist/cli/install/runtime-migrate.mjs +63 -30
  96. package/dist/cli/install/runtime-migrate.mjs.map +1 -1
  97. package/dist/cli/install/transforms/codex.d.mts.map +1 -1
  98. package/dist/cli/install/transforms/codex.mjs +28 -17
  99. package/dist/cli/install/transforms/codex.mjs.map +1 -1
  100. package/dist/cli/skill-author/contract.mjs +2 -2
  101. package/dist/cli/skill-author/contract.mjs.map +1 -1
  102. package/dist/cli/verify-proof/audit.d.mts +34 -0
  103. package/dist/cli/verify-proof/audit.d.mts.map +1 -0
  104. package/dist/cli/verify-proof/audit.mjs +53 -0
  105. package/dist/cli/verify-proof/audit.mjs.map +1 -0
  106. package/dist/cli/verify-proof/exec.d.mts +20 -0
  107. package/dist/cli/verify-proof/exec.d.mts.map +1 -0
  108. package/dist/cli/verify-proof/exec.mjs +74 -0
  109. package/dist/cli/verify-proof/exec.mjs.map +1 -0
  110. package/dist/cli/verify-proof/idl/index.d.mts +2 -0
  111. package/dist/cli/verify-proof/idl/index.d.mts.map +1 -0
  112. package/dist/cli/verify-proof/idl/index.mjs +13 -0
  113. package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
  114. package/dist/cli/verify-proof/idl/types.d.ts +10 -0
  115. package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
  116. package/dist/cli/verify-proof/idl/types.js +10 -0
  117. package/dist/cli/verify-proof/idl/types.js.map +1 -0
  118. package/dist/cli/verify-proof/manifest-validator.d.mts +19 -0
  119. package/dist/cli/verify-proof/manifest-validator.d.mts.map +1 -0
  120. package/dist/cli/verify-proof/manifest-validator.mjs +182 -0
  121. package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -0
  122. package/dist/cli/verify-proof/resolver.d.mts +105 -0
  123. package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
  124. package/dist/cli/verify-proof/resolver.mjs +320 -0
  125. package/dist/cli/verify-proof/resolver.mjs.map +1 -0
  126. package/dist/cli/verify-proof/runtime-detect.d.mts +30 -0
  127. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
  128. package/dist/cli/verify-proof/runtime-detect.mjs +209 -0
  129. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -0
  130. package/dist/cli/verify-proof/trust.d.mts +70 -0
  131. package/dist/cli/verify-proof/trust.d.mts.map +1 -0
  132. package/dist/cli/verify-proof/trust.mjs +174 -0
  133. package/dist/cli/verify-proof/trust.mjs.map +1 -0
  134. package/dist/driver-sdk/conformance.d.ts +41 -0
  135. package/dist/driver-sdk/conformance.d.ts.map +1 -0
  136. package/dist/driver-sdk/conformance.js +123 -0
  137. package/dist/driver-sdk/conformance.js.map +1 -0
  138. package/dist/driver-sdk/errors.d.ts +75 -0
  139. package/dist/driver-sdk/errors.d.ts.map +1 -0
  140. package/dist/driver-sdk/errors.js +80 -0
  141. package/dist/driver-sdk/errors.js.map +1 -0
  142. package/dist/driver-sdk/idl.d.ts +221 -0
  143. package/dist/driver-sdk/idl.d.ts.map +1 -0
  144. package/dist/driver-sdk/idl.js +140 -0
  145. package/dist/driver-sdk/idl.js.map +1 -0
  146. package/dist/driver-sdk/index.d.ts +28 -0
  147. package/dist/driver-sdk/index.d.ts.map +1 -0
  148. package/dist/driver-sdk/index.js +28 -0
  149. package/dist/driver-sdk/index.js.map +1 -0
  150. package/dist/driver-sdk/manifest.d.ts +93 -0
  151. package/dist/driver-sdk/manifest.d.ts.map +1 -0
  152. package/dist/driver-sdk/manifest.js +12 -0
  153. package/dist/driver-sdk/manifest.js.map +1 -0
  154. package/dist/driver-sdk/retry.d.ts +33 -0
  155. package/dist/driver-sdk/retry.d.ts.map +1 -0
  156. package/dist/driver-sdk/retry.js +50 -0
  157. package/dist/driver-sdk/retry.js.map +1 -0
  158. package/dist/plugin/.claude-plugin/plugin.json +4 -1
  159. package/dist/plugin/agents/apt-planner.md +39 -7
  160. package/dist/plugin/skills/apt/SKILL.md +75 -6
  161. package/dist/plugin/skills/apt-caveman/SKILL.md +64 -0
  162. package/dist/plugin/skills/apt-debug/SKILL.md +36 -4
  163. package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  164. package/dist/plugin/skills/apt-discuss/SKILL.md +4 -4
  165. package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  166. package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  167. package/dist/plugin/skills/apt-execute/SKILL.md +27 -4
  168. package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
  169. package/dist/plugin/skills/apt-fan-out/SKILL.md +36 -1
  170. package/dist/plugin/skills/apt-handoff/SKILL.md +228 -0
  171. package/dist/plugin/skills/apt-improve/DEEPENING.md +0 -8
  172. package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  173. package/dist/plugin/skills/apt-improve/LANGUAGE.md +0 -8
  174. package/dist/plugin/skills/apt-improve/SKILL.md +4 -4
  175. package/dist/plugin/skills/apt-plan/SKILL.md +73 -5
  176. package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
  177. package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -1
  178. package/dist/plugin/skills/apt-prototype/LOGIC.md +0 -8
  179. package/dist/plugin/skills/apt-prototype/SKILL.md +4 -4
  180. package/dist/plugin/skills/apt-prototype/UI.md +0 -8
  181. package/dist/plugin/skills/apt-quick/SKILL.md +31 -1
  182. package/dist/plugin/skills/apt-research/SKILL.md +526 -0
  183. package/dist/plugin/skills/apt-research/appendices/budget-loop.md +397 -0
  184. package/dist/plugin/skills/apt-research/appendices/claim-graph-schema.md +206 -0
  185. package/dist/plugin/skills/apt-research/appendices/domain-tools.md +236 -0
  186. package/dist/plugin/skills/apt-resume/SKILL.md +54 -31
  187. package/dist/plugin/skills/apt-run/SKILL.md +114 -16
  188. package/dist/plugin/skills/apt-setup/SKILL.md +97 -8
  189. package/dist/plugin/skills/apt-ship/SKILL.md +165 -9
  190. package/dist/plugin/skills/apt-spar/SKILL.md +84 -23
  191. package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +0 -8
  192. package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  193. package/dist/plugin/skills/apt-triage/SKILL.md +2 -2
  194. package/dist/plugin/skills/apt-update/SKILL.md +74 -17
  195. package/dist/plugin/skills/apt-verify-proof/SKILL.md +87 -1
  196. package/dist/plugin/skills/apt-zoom-out/SKILL.md +3 -3
  197. package/dist/schemas/quick-task.d.ts +17 -17
  198. package/dist/schemas/quick-task.d.ts.map +1 -1
  199. package/dist/schemas/quick-task.js +2 -2
  200. package/dist/schemas/quick-task.js.map +1 -1
  201. package/dist/types/config.d.ts +33 -0
  202. package/dist/types/config.d.ts.map +1 -1
  203. package/dist/types/state.d.ts +1 -1
  204. package/dist/types/state.d.ts.map +1 -1
  205. package/dist/types/task-record.d.ts +1 -1
  206. package/dist/types/task-record.d.ts.map +1 -1
  207. package/drivers/.gitkeep +0 -0
  208. package/drivers/api/README.md +40 -0
  209. package/drivers/api/driver.mjs +59 -0
  210. package/drivers/api/manifest.json +26 -0
  211. package/drivers/browser/README.md +105 -0
  212. package/drivers/browser/driver.mjs +134 -0
  213. package/drivers/browser/manifest.json +35 -0
  214. package/drivers/cli/README.md +44 -0
  215. package/drivers/cli/driver.mjs +62 -0
  216. package/drivers/cli/manifest.json +28 -0
  217. package/drivers/electron/README.md +64 -0
  218. package/drivers/electron/driver.mjs +87 -0
  219. package/drivers/electron/manifest.json +37 -0
  220. package/package.json +138 -133
  221. package/prompts/planner.md +1 -1
  222. package/prompts/spec_writer.md +1 -1
  223. package/skills/apt/SKILL.md +75 -6
  224. package/skills/apt-caveman/SKILL.md +64 -0
  225. package/skills/apt-debug/SKILL.md +36 -4
  226. package/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  227. package/skills/apt-discuss/SKILL.md +4 -4
  228. package/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  229. package/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  230. package/skills/apt-execute/SKILL.md +27 -4
  231. package/skills/apt-execute/appendices/tdd-mode.md +2 -10
  232. package/skills/apt-fan-out/SKILL.md +36 -1
  233. package/skills/apt-handoff/SKILL.md +228 -0
  234. package/skills/apt-improve/DEEPENING.md +0 -8
  235. package/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  236. package/skills/apt-improve/LANGUAGE.md +0 -8
  237. package/skills/apt-improve/SKILL.md +4 -4
  238. package/skills/apt-plan/SKILL.md +73 -5
  239. package/skills/apt-plan/adapters/conductor.md +1 -1
  240. package/skills/apt-planner.md +17 -1
  241. package/skills/apt-pr-review/SKILL.md +1 -1
  242. package/skills/apt-prototype/LOGIC.md +0 -8
  243. package/skills/apt-prototype/SKILL.md +4 -4
  244. package/skills/apt-prototype/UI.md +0 -8
  245. package/skills/apt-quick/SKILL.md +31 -1
  246. package/skills/apt-research/SKILL.md +526 -0
  247. package/skills/apt-research/appendices/budget-loop.md +397 -0
  248. package/skills/apt-research/appendices/claim-graph-schema.md +206 -0
  249. package/skills/apt-research/appendices/domain-tools.md +236 -0
  250. package/skills/apt-resume/SKILL.md +54 -31
  251. package/skills/apt-run/SKILL.md +114 -16
  252. package/skills/apt-setup/SKILL.md +97 -8
  253. package/skills/apt-ship/SKILL.md +165 -9
  254. package/skills/apt-spar/SKILL.md +84 -23
  255. package/skills/apt-triage/AGENT-BRIEF.md +0 -8
  256. package/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  257. package/skills/apt-triage/SKILL.md +2 -2
  258. package/skills/apt-update/SKILL.md +74 -17
  259. package/skills/apt-verify-proof/SKILL.md +87 -1
  260. package/skills/apt-zoom-out/SKILL.md +3 -3
  261. package/src/cli/commands/adr.mjs +1 -1
  262. package/src/cli/commands/audit-branch-current.mjs +250 -0
  263. package/src/cli/commands/audit.mjs +13 -2
  264. package/src/cli/commands/check-version.mjs +20 -1
  265. package/src/cli/commands/context.mjs +3 -3
  266. package/src/cli/commands/coverage-check.mjs +126 -44
  267. package/src/cli/commands/detect-runtime.mjs +143 -0
  268. package/src/cli/commands/driver-doctor.mjs +294 -0
  269. package/src/cli/commands/gate.mjs +15 -0
  270. package/src/cli/commands/init.mjs +9 -6
  271. package/src/cli/commands/install-from-source.mjs +1 -0
  272. package/src/cli/commands/modes.mjs +1 -1
  273. package/src/cli/commands/route.mjs +42 -1
  274. package/src/cli/commands/triage.mjs +1 -1
  275. package/src/cli/config/load.mjs +9 -3
  276. package/src/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  277. package/src/cli/coordination/event-schema.mjs +19 -0
  278. package/src/cli/coverage-check/user-outcomes.mjs +273 -0
  279. package/src/cli/dispatch.mjs +17 -1
  280. package/src/cli/gate/context.mjs +20 -1
  281. package/src/cli/gate/gates/consistency-check.mjs +1 -0
  282. package/src/cli/gate/gates/review-clean.mjs +1 -0
  283. package/src/cli/gate/gates/verify-approved.mjs +1 -0
  284. package/src/cli/gate/registry.mjs +11 -1
  285. package/src/cli/host/detect.mjs +1 -1
  286. package/src/cli/install/find-workspace-framework-version.mjs +88 -0
  287. package/src/cli/install/install-from-source.mjs +276 -0
  288. package/src/cli/install/install-kernel.mjs +24 -0
  289. package/src/cli/install/legacy-paths.mjs +164 -39
  290. package/src/cli/install/runtime-detect.mjs +1 -1
  291. package/src/cli/install/runtime-migrate.mjs +63 -30
  292. package/src/cli/install/transforms/codex.mjs +26 -17
  293. package/src/cli/skill-author/contract.mjs +2 -2
  294. package/src/cli/verify-proof/.gitkeep +0 -0
  295. package/src/cli/verify-proof/audit.mjs +55 -0
  296. package/src/cli/verify-proof/exec.mjs +74 -0
  297. package/src/cli/verify-proof/idl/index.mjs +23 -0
  298. package/src/cli/verify-proof/idl/types.ts +42 -0
  299. package/src/cli/verify-proof/manifest-schema.json +212 -0
  300. package/src/cli/verify-proof/manifest-validator.mjs +184 -0
  301. package/src/cli/verify-proof/resolver.mjs +330 -0
  302. package/src/cli/verify-proof/runtime-detect.mjs +211 -0
  303. package/src/cli/verify-proof/trust.mjs +187 -0
  304. package/templates/adr-format.md +0 -8
  305. package/templates/aperant-claude-md-appendix.md +1 -1
  306. package/templates/context-format.md +1 -9
  307. package/templates/proof-verification.md +51 -8
  308. package/workflows/verify-proof.md +130 -19
@@ -1250,12 +1250,15 @@ export async function cmdInit(projectDir, extraArgs = []) {
1250
1250
  }
1251
1251
  }
1252
1252
 
1253
- // FRAMEWORK-BUG-021 — sweep the legacy `.agents/skills/` codex tree
1253
+ // FRAMEWORK-BUG-021 — sweep the legacy `.codex/` codex tree
1254
1254
  // BEFORE the install pipeline runs so the install can re-stamp
1255
- // `.codex/` against a clean slate. Only fires when codex is in the
1256
- // requested runtime set and `--keep-legacy` was not passed; the
1257
- // helper itself is conservative (no-op on managed installs, no-op
1258
- // when no legacy framework files are present).
1255
+ // `.agents/skills/<folder>/SKILL.md` (Codex CLI's documented
1256
+ // surface, per https://developers.openai.com/codex/skills) against
1257
+ // a clean slate. Only fires when codex is in the requested runtime
1258
+ // set and `--keep-legacy` was not passed; the helper itself is
1259
+ // conservative (no-op on managed installs, no-op when no legacy
1260
+ // framework files are present). The v0.8.2 inversion is
1261
+ // documented in `runtime-migrate.mjs:7-12`.
1259
1262
  let legacyCleanup = null
1260
1263
  if (effectiveAnswers.runtimes.includes('codex')) {
1261
1264
  if (parsedFlags.keepLegacy) {
@@ -1265,7 +1268,7 @@ export async function cmdInit(projectDir, extraArgs = []) {
1265
1268
  legacyCleanup = { runtime: 'codex', ...cleanup }
1266
1269
  if (cleanup.ran && cleanup.retained.length > 0) {
1267
1270
  process.stderr.write(
1268
- `[apt-tools] legacy_cleanup retained ${cleanup.retained.length} user-added path(s) under .agents/skills/: ${cleanup.retained.join(', ')}\n`,
1271
+ `[apt-tools] legacy_cleanup retained ${cleanup.retained.length} user-added path(s) under .codex/: ${cleanup.retained.join(', ')}\n`,
1269
1272
  )
1270
1273
  }
1271
1274
  }
@@ -0,0 +1 @@
1
+ export { cmdInstallFromSource } from '../install/install-from-source.mjs'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * commands/modes.mjs — `apt-tools modes list` (Pocock adoption ST-13, AC12).
2
+ * commands/modes.mjs — `apt-tools modes list`.
3
3
  *
4
4
  * Power-user discoverability tool. Returns a JSON envelope with one
5
5
  * record per discovered skill, exposing modes / flags / content-formats /
@@ -28,7 +28,11 @@ import { existsSync, readFileSync } from 'node:fs'
28
28
  import { homedir } from 'node:os'
29
29
  import { dirname, join, resolve } from 'node:path'
30
30
  import { fileURLToPath } from 'node:url'
31
- import { getTaskIsolationConfig, getTrustedSkillSources } from '../config/load.mjs'
31
+ import {
32
+ getTaskIsolationConfig,
33
+ getTrustedSkillSources,
34
+ loadMergedProjectConfig,
35
+ } from '../config/load.mjs'
32
36
  import '../gate/gates/index.mjs'
33
37
  import * as gateRegistry from '../gate/registry.mjs'
34
38
  import { detectHost, SUPPORTED_HOSTS } from '../host/detect.mjs'
@@ -264,6 +268,19 @@ export function cmdRoute(projectDir, extraArgs) {
264
268
  else if (f === 'debug') track_override = 'DEBUG'
265
269
  }
266
270
 
271
+ // FRAMEWORK-RFC-001 — consent signal for spar pipeline auto-inject.
272
+ // Reads merged config (config.json ∪ config.local.json) via the same
273
+ // helper apt-setup writes through. AUDIT-001 — spar fields live in
274
+ // .aperant/config.local.json, not shared config. Resolved once and
275
+ // folded into the envelope by both classify branches below.
276
+ // `loadMergedProjectConfig` returns null when no shared config exists
277
+ // (first-time init path) — treat as consent-off + no partner.
278
+ const merged = loadMergedProjectConfig(targetDir) ?? {}
279
+ const sparEnabled = merged.spar?.enabled === true
280
+ const sparPartner = merged.spar?.partner ?? null
281
+ const userPassedExplicitSparGates =
282
+ rawInput.includes('--spar-gates') || flags.some((f) => f.startsWith('spar-gates'))
283
+
267
284
  const taskDescription = normalizeAptInvocation(rawInput.replace(/--\w+/g, '').trim())
268
285
 
269
286
  const configPath = join(targetDir, '.aperant', 'config.json')
@@ -399,6 +416,17 @@ export function cmdRoute(projectDir, extraArgs) {
399
416
  const agentName = spawnAgent ? index.agentOf(primarySkill) || 'apt-planner' : null
400
417
  const options = buildTrackOptions(track)
401
418
  const gate_preview = previewGatesFor(primarySkill)
419
+ // FRAMEWORK-RFC-001 — flag-override path: track is known deterministically,
420
+ // so we can resolve the spar gate string inline and emit it directly.
421
+ // QUICK/DEBUG are Fast-Path-exempt — no inject. User-passed --spar-gates
422
+ // wins over consent (no double-inject). Field omitted entirely when null
423
+ // so the envelope stays byte-equivalent for users who never opted in.
424
+ let spar_inject_gates = null
425
+ if (sparEnabled && !userPassedExplicitSparGates) {
426
+ if (track === 'STANDARD') spar_inject_gates = 'plan'
427
+ else if (track === 'DEEP') spar_inject_gates = 'plan,execute'
428
+ // QUICK / DEBUG → stays null
429
+ }
402
430
  return ok({
403
431
  status: 'ok',
404
432
  command: 'route',
@@ -417,6 +445,7 @@ export function cmdRoute(projectDir, extraArgs) {
417
445
  task_isolation,
418
446
  update_check,
419
447
  ...(gate_preview ? { gate_preview } : {}),
448
+ ...(spar_inject_gates ? { spar_inject_gates } : {}),
420
449
  classification: {
421
450
  source: 'flag-override',
422
451
  reason: `Explicit --${track_override.toLowerCase()} flag`,
@@ -467,6 +496,18 @@ export function cmdRoute(projectDir, extraArgs) {
467
496
  reason:
468
497
  'Track and skill classification deferred to the host LLM; see classification_meta.trust_notice.',
469
498
  },
499
+ // FRAMEWORK-RFC-001 — host model composes --spar-gates from these
500
+ // signals + its track classification per apt/SKILL.md MODE: classify
501
+ // Step 4. Field is absent when consent is off (envelope stays slim).
502
+ ...(sparEnabled
503
+ ? {
504
+ spar_consent: {
505
+ enabled: true,
506
+ partner: sparPartner,
507
+ user_passed_explicit_gates: userPassedExplicitSparGates,
508
+ },
509
+ }
510
+ : {}),
470
511
  },
471
512
  })
472
513
  } catch (e) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * commands/triage.mjs — `apt-tools triage` (Pocock adoption ST-10, AC9).
2
+ * commands/triage.mjs — `apt-tools triage`.
3
3
  *
4
4
  * 5-state triage state machine with extensible metadata for v2 OSS
5
5
  * contributor primitives. Backend-configured: writes to
@@ -361,11 +361,17 @@ export function getTrustedSkillSources(projectDir, cli) {
361
361
  )
362
362
  if (existsSync(consumerSkills)) defaults.push(consumerSkills)
363
363
  // 4. Host CLI's installed runtime skills — only when the router knows
364
- // which runtime it's in. Some hosts (codex, claude-code) install into
365
- // .codex/skills / .claude/skills etc; the router picks skills from
364
+ // which runtime it's in. Some hosts (claude-code, gemini, …) install into
365
+ // .claude/skills / .gemini/skills etc; the router picks skills from
366
366
  // the host-local directory so per-runtime transforms are honored.
367
+ //
368
+ // Codex is excluded: its transform strips frontmatter to {name, description}
369
+ // for codex validator compliance, and that stripped form fails the
370
+ // router's SkillFrontmatterSchema (contract.mjs). The canonical + kernel
371
+ // roots above still serve routing on Codex hosts (they carry the fat
372
+ // frontmatter the schema requires).
367
373
  const installRoot = cli ? cliToInstallRoot(cli) : null
368
- if (installRoot) {
374
+ if (installRoot && cli !== 'codex') {
369
375
  const runtimeSkills = join(targetDir, installRoot, 'skills')
370
376
  if (existsSync(runtimeSkills)) defaults.push(runtimeSkills)
371
377
  }
@@ -7,7 +7,7 @@
7
7
  * postcondition of `apt-tools commit`. This closes the Conductor v2 gap
8
8
  * exposed during live verification 2026-05-15: the planner's QUICK /
9
9
  * SIMPLE track is hard-exempt from "new interaction discipline"
10
- * (Pocock Fast Path Guarantee ID-05), so the markdown emit step was
10
+ * (Aperant Fast Path Guarantee ID-05), so the markdown emit step was
11
11
  * never executed for tasks router-classified as simple. The fleet
12
12
  * never saw artifact.ready, the PlanReadyCard never auto-promoted.
13
13
  *
@@ -42,6 +42,10 @@ const CONDUCTOR_OPS = new Set([
42
42
  'conductor.action.executed',
43
43
  'conductor.action.gate_blocked',
44
44
  'conductor.boot_reconciled',
45
+ // AC21 — MCP-server consent decisions audited to .aperant/events/*.jsonl
46
+ 'mcp-consent.approved',
47
+ 'mcp-consent.denied',
48
+ 'mcp-consent.tofu-reprompt',
45
49
  ])
46
50
 
47
51
  /** Allowed values for `phase.changed.to` — PTY busy/idle only. */
@@ -133,6 +137,21 @@ export function validateConductorEvent(op, data) {
133
137
  'blocked_reason',
134
138
  ])
135
139
 
140
+ // AC21 — MCP server consent audit. Three ops share the same
141
+ // payload shape: who approved/denied which server, with which
142
+ // configHash, at what time. session_id is a session-scoped string
143
+ // (chat-session row id).
144
+ case 'mcp-consent.approved':
145
+ case 'mcp-consent.denied':
146
+ case 'mcp-consent.tofu-reprompt':
147
+ return requireFields(op, d, [
148
+ 'server_id',
149
+ 'config_sha256',
150
+ 'decided_at',
151
+ 'decided_by',
152
+ 'session_id',
153
+ ])
154
+
136
155
  case 'conductor.boot_reconciled': {
137
156
  // Fleet-wide event — numeric fields only. All optional but if
138
157
  // present must be finite numbers (zero allowed).
@@ -0,0 +1,273 @@
1
+ /**
2
+ * coverage-check/user-outcomes.mjs — `## User Outcomes` PRD section parser.
3
+ *
4
+ * Pure, file-IO-free. Mirrors the `coverage-check/acceptance-criteria.mjs`
5
+ * pattern: takes spec.md content as a string and returns a structured
6
+ * `{header_present, body_kind, epic?, outcomes[], validation_warnings?}`
7
+ * envelope the coverage-check command consumes.
8
+ *
9
+ * The `## User Outcomes` section is the verify-proof input contract
10
+ * introduced in v0.8.6 (spec.md §ID-01 / §ID-07). The grammar:
11
+ *
12
+ * ## User Outcomes
13
+ *
14
+ * **Epic:** {one-sentence headline}
15
+ *
16
+ * - **ON** [surface]: {prose}
17
+ * - **ON** [surface]: {prose} *(requires ON N)*
18
+ *
19
+ * (refactor-only escape — INSTEAD of the outcome list:)
20
+ * _No user-observable changes — pure refactor._
21
+ *
22
+ * `surface` is the closed set: electron | web | cli | api | electron+web.
23
+ *
24
+ * **Positional ID synthesis (§ID-07 / M1).** The parser counts outcome
25
+ * rows in document order and synthesizes `id = "O" + (i+1)`. The literal
26
+ * `**ON**` regex stays — IDs are NOT author-written. The `requires N`
27
+ * digit captured from `*(requires ON N)*` is validated against the set
28
+ * of synthesized IDs; on mismatch the parser emits a
29
+ * `validation_warnings[]` entry of kind `unknown_requires_target`,
30
+ * skips the dep edge, but still includes the outcome row.
31
+ *
32
+ * See packages/framework/skills/apt-plan/SKILL.md Section 5.B for the
33
+ * authoring contract.
34
+ */
35
+
36
+ const VALID_SURFACES = new Set(['electron', 'web', 'cli', 'api', 'electron+web'])
37
+
38
+ const HEADING_RE = /^(#{1,6})\s+(.*)$/
39
+ const USER_OUTCOMES_HEADING_RE = /^user\s+outcomes\s*$/i
40
+ const EPIC_RE = /^\*\*Epic:\*\*\s+(.+)$/
41
+ const EMPTY_WITH_NOTE_RE = /^_No user-observable changes — pure refactor\._\s*$/
42
+ const OUTCOME_RE =
43
+ /^-\s+\*\*ON\*\*\s+\[(electron|web|cli|api|electron\+web)\]:\s+(.+?)(\s+\*\(requires\s+ON\s+(\d+)\)\*)?\s*$/
44
+
45
+ /**
46
+ * @typedef {Object} UserOutcome
47
+ * @property {string} id — synthesized positional ID (O1, O2, ...)
48
+ * @property {('electron'|'web'|'cli'|'api'|'electron+web')} surface
49
+ * @property {string} text — outcome prose (sans the surface tag and requires suffix)
50
+ * @property {number} [requires] — the digit captured from `*(requires ON N)*`, when present and valid
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} ValidationWarning
55
+ * @property {'unknown_requires_target'} kind
56
+ * @property {string} outcome_id — synthesized ID of the outcome carrying the bad `requires`
57
+ * @property {number} requires — the offending digit
58
+ * @property {string} reason — human-readable explanation
59
+ */
60
+
61
+ /**
62
+ * @typedef {Object} UserOutcomesParseResult
63
+ * @property {boolean} header_present
64
+ * @property {('absent'|'empty'|'empty-with-note'|'outcomes')} body_kind
65
+ * @property {string} [epic]
66
+ * @property {UserOutcome[]} outcomes
67
+ * @property {ValidationWarning[]} [validation_warnings]
68
+ */
69
+
70
+ /**
71
+ * Parse the `## User Outcomes` section from a spec.md string.
72
+ *
73
+ * @param {string} specContent
74
+ * @returns {UserOutcomesParseResult}
75
+ */
76
+ export function parseUserOutcomes(specContent) {
77
+ if (typeof specContent !== 'string' || specContent.length === 0) {
78
+ return { header_present: false, body_kind: 'absent', outcomes: [] }
79
+ }
80
+
81
+ const lines = specContent.split('\n')
82
+
83
+ // Find the `## User Outcomes` heading + scope to its body (until the next
84
+ // heading of equal or lower depth).
85
+ let headingLine = -1
86
+ let headingLevel = 0
87
+ for (let i = 0; i < lines.length; i++) {
88
+ const m = lines[i].trim().match(HEADING_RE)
89
+ if (m && USER_OUTCOMES_HEADING_RE.test(m[2])) {
90
+ headingLine = i
91
+ headingLevel = m[1].length
92
+ break
93
+ }
94
+ }
95
+
96
+ if (headingLine === -1) {
97
+ return { header_present: false, body_kind: 'absent', outcomes: [] }
98
+ }
99
+
100
+ // Collect body lines from heading+1 until the next heading of ANY
101
+ // depth. The User Outcomes section is intentionally flat — a
102
+ // downstream sub-heading (e.g. `### Acceptance Criteria` following
103
+ // directly without a `## ` separator) ends the body. This matches
104
+ // how authors write the section and avoids accidentally pulling
105
+ // stray prose from later sections into the outcomes pool.
106
+ const bodyLines = []
107
+ for (let i = headingLine + 1; i < lines.length; i++) {
108
+ const trimmed = lines[i].trim()
109
+ if (HEADING_RE.test(trimmed)) break
110
+ bodyLines.push(lines[i])
111
+ }
112
+
113
+ // Classify body shape.
114
+ const nonBlank = bodyLines.map((l) => l.trim()).filter((l) => l.length > 0)
115
+
116
+ if (nonBlank.length === 0) {
117
+ return { header_present: true, body_kind: 'empty', outcomes: [] }
118
+ }
119
+
120
+ // Empty-with-note escape — body MUST contain only the verbatim
121
+ // italic-underscore-em-dash-period sentinel (other lines may exist
122
+ // only if they are the Epic header or blank — but the spec says the
123
+ // sentinel REPLACES the outcome list, so we require it to be the only
124
+ // non-blank line OR the only non-Epic non-blank line).
125
+ let epic
126
+ const sentinelLines = []
127
+ const outcomeLines = []
128
+ for (const line of nonBlank) {
129
+ const epicMatch = line.match(EPIC_RE)
130
+ if (epicMatch) {
131
+ epic = epicMatch[1].trim()
132
+ continue
133
+ }
134
+ if (EMPTY_WITH_NOTE_RE.test(line)) {
135
+ sentinelLines.push(line)
136
+ continue
137
+ }
138
+ outcomeLines.push(line)
139
+ }
140
+
141
+ // If the sentinel is present and there are NO outcome lines, this is
142
+ // the empty-with-note branch. The sentinel may co-exist with an Epic
143
+ // header — that's not required but not forbidden either.
144
+ if (sentinelLines.length > 0 && outcomeLines.length === 0) {
145
+ /** @type {UserOutcomesParseResult} */
146
+ const result = {
147
+ header_present: true,
148
+ body_kind: 'empty-with-note',
149
+ outcomes: [],
150
+ }
151
+ if (epic) result.epic = epic
152
+ return result
153
+ }
154
+
155
+ // If neither the sentinel nor any outcome rows are present, the body
156
+ // is effectively empty (header present, no parseable content).
157
+ if (outcomeLines.length === 0) {
158
+ /** @type {UserOutcomesParseResult} */
159
+ const result = {
160
+ header_present: true,
161
+ body_kind: 'empty',
162
+ outcomes: [],
163
+ }
164
+ if (epic) result.epic = epic
165
+ return result
166
+ }
167
+
168
+ // Parse outcome rows in document order. Skip non-matching lines (e.g.
169
+ // stray prose between outcomes) — they don't count as outcomes.
170
+ /** @type {UserOutcome[]} */
171
+ const outcomes = []
172
+ const rawRequires = []
173
+ for (const line of outcomeLines) {
174
+ const m = line.match(OUTCOME_RE)
175
+ if (!m) continue
176
+ const surface = /** @type {UserOutcome['surface']} */ (m[1])
177
+ if (!VALID_SURFACES.has(surface)) continue
178
+ const text = m[2].trim()
179
+ const id = 'O' + (outcomes.length + 1)
180
+ const outcome = /** @type {UserOutcome} */ ({ id, surface, text })
181
+ if (m[4]) {
182
+ const n = Number.parseInt(m[4], 10)
183
+ rawRequires.push({ outcomeIndex: outcomes.length, requires: n })
184
+ }
185
+ outcomes.push(outcome)
186
+ }
187
+
188
+ if (outcomes.length === 0) {
189
+ // Header present, prose present, but no parseable `**ON**` rows —
190
+ // effectively empty.
191
+ /** @type {UserOutcomesParseResult} */
192
+ const result = {
193
+ header_present: true,
194
+ body_kind: 'empty',
195
+ outcomes: [],
196
+ }
197
+ if (epic) result.epic = epic
198
+ return result
199
+ }
200
+
201
+ // Validate `requires` cross-refs against the set of synthesized IDs.
202
+ /** @type {ValidationWarning[]} */
203
+ const warnings = []
204
+ for (const r of rawRequires) {
205
+ if (r.requires >= 1 && r.requires <= outcomes.length) {
206
+ outcomes[r.outcomeIndex].requires = r.requires
207
+ } else {
208
+ warnings.push({
209
+ kind: 'unknown_requires_target',
210
+ outcome_id: outcomes[r.outcomeIndex].id,
211
+ requires: r.requires,
212
+ reason: `requires ON ${r.requires} but only ${outcomes.length} outcome(s) parsed`,
213
+ })
214
+ }
215
+ }
216
+
217
+ /** @type {UserOutcomesParseResult} */
218
+ const result = {
219
+ header_present: true,
220
+ body_kind: 'outcomes',
221
+ outcomes,
222
+ }
223
+ if (epic) result.epic = epic
224
+ if (warnings.length > 0) result.validation_warnings = warnings
225
+ return result
226
+ }
227
+
228
+ /**
229
+ * Compute the coverage-check gate result for a parsed user-outcomes
230
+ * envelope. Per §ID-07 table — warn-not-block in v0.8.x, hard-block in
231
+ * v0.9.0.
232
+ *
233
+ * | header_present | body_kind | v0.8.x | v0.9.0 |
234
+ * |----------------|-------------------|--------|--------|
235
+ * | false | — | warn | block |
236
+ * | true | empty-with-note | pass | pass |
237
+ * | true | outcomes (≥1) | pass | pass |
238
+ * | true | empty | block | block |
239
+ *
240
+ * @param {UserOutcomesParseResult} parsed
241
+ * @param {string} frameworkVersion — semver string from the kernel's package.json (e.g. "0.8.6")
242
+ * @returns {('pass'|'warn'|'block')}
243
+ */
244
+ export function userOutcomesGateResult(parsed, frameworkVersion) {
245
+ if (parsed.body_kind === 'empty-with-note' || parsed.body_kind === 'outcomes') {
246
+ return 'pass'
247
+ }
248
+ if (parsed.body_kind === 'empty') {
249
+ // Header present, body empty — author started the section and forgot
250
+ // to fill it in. Block at every version.
251
+ return 'block'
252
+ }
253
+ // Missing header (body_kind: 'absent'). Warn in 0.8.x, block in 0.9.0+.
254
+ const major = parseFrameworkMajorMinor(frameworkVersion)
255
+ if (major === null) return 'warn'
256
+ const { major: maj, minor: min } = major
257
+ if (maj > 0 || (maj === 0 && min >= 9)) return 'block'
258
+ return 'warn'
259
+ }
260
+
261
+ /**
262
+ * Parse the leading `major.minor` of a semver string. Returns null on
263
+ * malformed input (caller treats as "warn" — fail-soft).
264
+ *
265
+ * @param {string} version
266
+ * @returns {{major: number, minor: number} | null}
267
+ */
268
+ function parseFrameworkMajorMinor(version) {
269
+ if (typeof version !== 'string') return null
270
+ const m = version.match(/^(\d+)\.(\d+)/)
271
+ if (!m) return null
272
+ return { major: Number.parseInt(m[1], 10), minor: Number.parseInt(m[2], 10) }
273
+ }
@@ -53,7 +53,9 @@ import { cmdDesignScan } from './commands/design-scan.mjs'
53
53
  import { cmdDesignTemplate } from './commands/design-template.mjs'
54
54
  import { cmdDesignValidate } from './commands/design-validate.mjs'
55
55
  import { cmdDetectProject } from './commands/detect-project.mjs'
56
+ import { cmdDetectRuntime } from './commands/detect-runtime.mjs'
56
57
  import { cmdDocsAudit } from './commands/docs-audit.mjs'
58
+ import { cmdDriverDoctor } from './commands/driver-doctor.mjs'
57
59
  import { cmdEditCheck } from './commands/edit-check.mjs'
58
60
  import { cmdEffects } from './commands/effects.mjs'
59
61
  import { cmdEvent } from './commands/event.mjs'
@@ -64,6 +66,7 @@ import { cmdHealthCheck } from './commands/health-check.mjs'
64
66
  import { cmdHostDetect, cmdReconcile } from './commands/host-detect.mjs'
65
67
  import { cmdInit } from './commands/init.mjs'
66
68
  import { cmdInitState } from './commands/init-state.mjs'
69
+ import { cmdInstallFromSource } from './commands/install-from-source.mjs'
67
70
  import { cmdLock } from './commands/lock.mjs'
68
71
  import { cmdMigratePrReviews } from './commands/migrate-pr-reviews.mjs'
69
72
  import { cmdPersonas } from './commands/personas.mjs'
@@ -213,6 +216,18 @@ export const DISPATCH = {
213
216
  'state-update': (args) => wrap(cmdStateUpdate)(args[1], args.slice(2)),
214
217
  'state-artifacts': (args) => wrap(cmdStateArtifacts)(args[1], args.slice(2)),
215
218
  'detect-project': (args) => wrap(cmdDetectProject)(args[1]),
219
+ 'detect-runtime': (args) => wrap(cmdDetectRuntime)(args[1], args.slice(2)),
220
+ driver: (args) => {
221
+ // `apt-tools driver doctor <project-dir>` — only subcommand for now.
222
+ if (args[1] === 'doctor') return wrap(cmdDriverDoctor)(args[2], args.slice(3))
223
+ return wrap(() => ({
224
+ envelope: {
225
+ status: 'error',
226
+ error: `Usage: apt-tools driver doctor <project-dir>. Unknown subcommand: ${args[1] ?? '(none)'}`,
227
+ },
228
+ exitCode: 1,
229
+ }))()
230
+ },
216
231
  'features-audit': (args) => wrap(cmdFeaturesAudit)(args[1], args.slice(2)),
217
232
  'init-state': (args) => wrap(cmdInitState)(args[1], args.slice(2)),
218
233
  'risk-check': (args) => wrap(cmdRiskCheck)(args[1], args.slice(2)),
@@ -232,6 +247,7 @@ export const DISPATCH = {
232
247
  'detect-worktree': (args) => wrap(cmdDetectWorktree)(args[1]),
233
248
  template: (args) => wrap(cmdTemplate)(args[1]),
234
249
  'check-version': (args) => wrap(cmdCheckVersion)(args[1]),
250
+ 'install-from-source': (args) => wrap(cmdInstallFromSource)(args[1]),
235
251
  'health-check': (args) => wrap(cmdHealthCheck)(args[1]),
236
252
  route: (args) => wrap(cmdRoute)(args[1], args.slice(2)),
237
253
  statusline: (args) => wrap(cmdStatusline)(args[1], args.slice(2)),
@@ -287,7 +303,7 @@ export const DISPATCH = {
287
303
  },
288
304
  'plan-approval': (args) => wrap(cmdPlanApproval)(args[1], args[2], args.slice(3)),
289
305
 
290
- // ---- Pocock adoption (ST-5, ST-6, ST-10, ST-13) artifact writers + discoverability ----
306
+ // ---- Aperant adoption (ST-5, ST-6, ST-10, ST-13) artifact writers + discoverability ----
291
307
  context: (args) => wrap(cmdContext)(args[1], args[2], args.slice(3)),
292
308
  adr: (args) => wrap(cmdAdr)(args[1], args[2], args.slice(3)),
293
309
  triage: (args) => wrap(cmdTriage)(args[1], args[2], args.slice(3)),
@@ -11,7 +11,13 @@
11
11
  * taskId: string,
12
12
  * taskDir: string, // <sharedRoot>/.aperant/tasks/<taskId>/
13
13
  * taskRecord: object | null, // state.active_tasks[taskId] (or null)
14
- * config: object // .aperant/config.json (empty {} if none)
14
+ * config: object, // .aperant/config.json (empty {} if none)
15
+ * profile: 'lite' | 'full-pipeline'
16
+ * // derived from taskRecord.track:
17
+ * // QUICK | DEBUG → 'lite'
18
+ * // STANDARD | DEEP → 'full-pipeline'
19
+ * // null taskRecord → 'full-pipeline'
20
+ * // (fail-closed)
15
21
  * }
16
22
  *
17
23
  * The builder is side-effect-free — it never writes state, never mutates the
@@ -24,6 +30,8 @@ import { createCoordinationStore } from '../coordination/store.mjs'
24
30
 
25
31
  const SAFE_TASK_ID_PATTERN = /^[a-zA-Z0-9_-]+$/
26
32
 
33
+ const KNOWN_TRACKS = new Set(['QUICK', 'STANDARD', 'DEEP', 'DEBUG'])
34
+
27
35
  function readJsonIfExists(path) {
28
36
  if (!existsSync(path)) return null
29
37
  try {
@@ -81,6 +89,16 @@ export function buildGateContext(projectRoot, taskId) {
81
89
  config = readJsonIfExists(join(absRoot, '.aperant', 'config.json')) ?? {}
82
90
  }
83
91
 
92
+ if (taskRecord?.track && !KNOWN_TRACKS.has(taskRecord.track)) {
93
+ process.stderr.write(
94
+ `[apt:gate] WARNING: unrecognized track "${taskRecord.track}" — defaulting to full-pipeline. ` +
95
+ `Expected one of: ${[...KNOWN_TRACKS].join(', ')}.\n`,
96
+ )
97
+ }
98
+
99
+ const profile =
100
+ taskRecord?.track === 'QUICK' || taskRecord?.track === 'DEBUG' ? 'lite' : 'full-pipeline'
101
+
84
102
  return {
85
103
  projectRoot: absRoot,
86
104
  sharedRoot,
@@ -88,5 +106,6 @@ export function buildGateContext(projectRoot, taskId) {
88
106
  taskDir,
89
107
  taskRecord,
90
108
  config,
109
+ profile,
91
110
  }
92
111
  }
@@ -23,6 +23,7 @@ const gate = {
23
23
  description: 'Cross-artifact consistency (spec/plan/review/qa/progress) has no error drift.',
24
24
  severity: 'blocking',
25
25
  skills: ['ship'],
26
+ applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
26
27
  async check(ctx) {
27
28
  const inputs = [
28
29
  join(ctx.taskDir, 'spec.md'),
@@ -26,6 +26,7 @@ const gate = {
26
26
  description: 'review.json has no unresolved critical/major findings (skipped if absent)',
27
27
  severity: 'blocking',
28
28
  skills: ['ship'],
29
+ applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
29
30
  async check(ctx) {
30
31
  const path = join(ctx.taskDir, 'review.json')
31
32
  const inputs = [path]
@@ -53,6 +53,7 @@ const gate = {
53
53
  'qa_signoff.json exists with verdict === "approved" (or "approved-with-notes" when configured)',
54
54
  severity: 'blocking',
55
55
  skills: ['ship'],
56
+ applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
56
57
  async check(ctx) {
57
58
  const path = join(ctx.taskDir, 'qa_signoff.json')
58
59
  const inputs = [path]
@@ -10,7 +10,12 @@
10
10
  * description: string, // human-readable one-liner
11
11
  * severity: 'blocking' | 'warn', // blocking gates halt the skill
12
12
  * skills: string[], // skills this gate fires for
13
- * check: async (ctx) => GateVerdict
13
+ * check: async (ctx) => GateVerdict,
14
+ * applies_to_profile?: (ctx) => boolean // optional; when present and
15
+ * // returns false, the gate is
16
+ * // skipped before cache lookup or
17
+ * // check() invocation. Omit to
18
+ * // apply universally (default).
14
19
  * }
15
20
  *
16
21
  * A GateVerdict is:
@@ -57,6 +62,11 @@ export function registerGate(gate) {
57
62
  if (typeof gate.check !== 'function') {
58
63
  throw new Error(`registerGate(${gate.id}): check must be a function`)
59
64
  }
65
+ if (gate.applies_to_profile !== undefined && typeof gate.applies_to_profile !== 'function') {
66
+ throw new Error(
67
+ `registerGate(${gate.id}): applies_to_profile, when present, must be a function`,
68
+ )
69
+ }
60
70
  registry.set(gate.id, gate)
61
71
  return gate
62
72
  }
@@ -345,6 +345,6 @@ function detectRawCli(partner, pathEntries, exists) {
345
345
  function rawCliInvocation(partner) {
346
346
  if (partner === 'codex') return 'codex exec'
347
347
  if (partner === 'claude') return 'claude -p'
348
- if (partner === 'gemini') return 'gemini -m'
348
+ if (partner === 'gemini') return 'gemini'
349
349
  return partner
350
350
  }