@aperant/framework 0.8.1 → 0.8.4

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 (248) hide show
  1. package/CHANGELOG.md +287 -1
  2. package/agents/apt-planner.md +5 -4
  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/detect-runtime.d.mts +6 -0
  17. package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
  18. package/dist/cli/commands/detect-runtime.mjs +136 -0
  19. package/dist/cli/commands/detect-runtime.mjs.map +1 -0
  20. package/dist/cli/commands/driver-doctor.d.mts +29 -0
  21. package/dist/cli/commands/driver-doctor.d.mts.map +1 -0
  22. package/dist/cli/commands/driver-doctor.mjs +291 -0
  23. package/dist/cli/commands/driver-doctor.mjs.map +1 -0
  24. package/dist/cli/commands/gate.mjs +14 -0
  25. package/dist/cli/commands/gate.mjs.map +1 -1
  26. package/dist/cli/commands/init.d.mts.map +1 -1
  27. package/dist/cli/commands/init.mjs +9 -6
  28. package/dist/cli/commands/init.mjs.map +1 -1
  29. package/dist/cli/commands/install-from-source.d.mts +2 -0
  30. package/dist/cli/commands/install-from-source.d.mts.map +1 -0
  31. package/dist/cli/commands/install-from-source.mjs +2 -0
  32. package/dist/cli/commands/install-from-source.mjs.map +1 -0
  33. package/dist/cli/commands/modes.mjs +1 -1
  34. package/dist/cli/commands/route.d.mts.map +1 -1
  35. package/dist/cli/commands/route.mjs +38 -1
  36. package/dist/cli/commands/route.mjs.map +1 -1
  37. package/dist/cli/commands/triage.mjs +1 -1
  38. package/dist/cli/config/load.d.mts.map +1 -1
  39. package/dist/cli/config/load.mjs +9 -3
  40. package/dist/cli/config/load.mjs.map +1 -1
  41. package/dist/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  42. package/dist/cli/coordination/event-schema.d.mts.map +1 -1
  43. package/dist/cli/coordination/event-schema.mjs +18 -0
  44. package/dist/cli/coordination/event-schema.mjs.map +1 -1
  45. package/dist/cli/dispatch.d.mts.map +1 -1
  46. package/dist/cli/dispatch.mjs +18 -1
  47. package/dist/cli/dispatch.mjs.map +1 -1
  48. package/dist/cli/gate/context.d.mts +1 -0
  49. package/dist/cli/gate/context.d.mts.map +1 -1
  50. package/dist/cli/gate/context.mjs +14 -1
  51. package/dist/cli/gate/context.mjs.map +1 -1
  52. package/dist/cli/gate/gates/consistency-check.d.mts +1 -0
  53. package/dist/cli/gate/gates/consistency-check.d.mts.map +1 -1
  54. package/dist/cli/gate/gates/consistency-check.mjs +1 -0
  55. package/dist/cli/gate/gates/consistency-check.mjs.map +1 -1
  56. package/dist/cli/gate/gates/review-clean.d.mts +1 -0
  57. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  58. package/dist/cli/gate/gates/review-clean.mjs +1 -0
  59. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  60. package/dist/cli/gate/gates/verify-approved.d.mts +1 -0
  61. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  62. package/dist/cli/gate/gates/verify-approved.mjs +1 -0
  63. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  64. package/dist/cli/gate/registry.d.mts.map +1 -1
  65. package/dist/cli/gate/registry.mjs +9 -1
  66. package/dist/cli/gate/registry.mjs.map +1 -1
  67. package/dist/cli/install/find-workspace-framework-version.d.mts +16 -0
  68. package/dist/cli/install/find-workspace-framework-version.d.mts.map +1 -0
  69. package/dist/cli/install/find-workspace-framework-version.mjs +85 -0
  70. package/dist/cli/install/find-workspace-framework-version.mjs.map +1 -0
  71. package/dist/cli/install/install-from-source.d.mts +10 -0
  72. package/dist/cli/install/install-from-source.d.mts.map +1 -0
  73. package/dist/cli/install/install-from-source.mjs +271 -0
  74. package/dist/cli/install/install-from-source.mjs.map +1 -0
  75. package/dist/cli/install/install-kernel.d.mts.map +1 -1
  76. package/dist/cli/install/install-kernel.mjs +24 -0
  77. package/dist/cli/install/install-kernel.mjs.map +1 -1
  78. package/dist/cli/install/legacy-paths.d.mts +33 -11
  79. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  80. package/dist/cli/install/legacy-paths.mjs +161 -39
  81. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  82. package/dist/cli/install/runtime-detect.mjs +1 -1
  83. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  84. package/dist/cli/install/runtime-migrate.d.mts +34 -14
  85. package/dist/cli/install/runtime-migrate.d.mts.map +1 -1
  86. package/dist/cli/install/runtime-migrate.mjs +63 -30
  87. package/dist/cli/install/runtime-migrate.mjs.map +1 -1
  88. package/dist/cli/install/transforms/codex.d.mts.map +1 -1
  89. package/dist/cli/install/transforms/codex.mjs +28 -17
  90. package/dist/cli/install/transforms/codex.mjs.map +1 -1
  91. package/dist/cli/skill-author/contract.mjs +2 -2
  92. package/dist/cli/skill-author/contract.mjs.map +1 -1
  93. package/dist/cli/verify-proof/audit.d.mts +34 -0
  94. package/dist/cli/verify-proof/audit.d.mts.map +1 -0
  95. package/dist/cli/verify-proof/audit.mjs +53 -0
  96. package/dist/cli/verify-proof/audit.mjs.map +1 -0
  97. package/dist/cli/verify-proof/exec.d.mts +20 -0
  98. package/dist/cli/verify-proof/exec.d.mts.map +1 -0
  99. package/dist/cli/verify-proof/exec.mjs +74 -0
  100. package/dist/cli/verify-proof/exec.mjs.map +1 -0
  101. package/dist/cli/verify-proof/idl/index.d.mts +2 -0
  102. package/dist/cli/verify-proof/idl/index.d.mts.map +1 -0
  103. package/dist/cli/verify-proof/idl/index.mjs +14 -0
  104. package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
  105. package/dist/cli/verify-proof/idl/types.d.ts +9 -0
  106. package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
  107. package/dist/cli/verify-proof/idl/types.js +9 -0
  108. package/dist/cli/verify-proof/idl/types.js.map +1 -0
  109. package/dist/cli/verify-proof/manifest-validator.d.mts +19 -0
  110. package/dist/cli/verify-proof/manifest-validator.d.mts.map +1 -0
  111. package/dist/cli/verify-proof/manifest-validator.mjs +182 -0
  112. package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -0
  113. package/dist/cli/verify-proof/resolver.d.mts +43 -0
  114. package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
  115. package/dist/cli/verify-proof/resolver.mjs +160 -0
  116. package/dist/cli/verify-proof/resolver.mjs.map +1 -0
  117. package/dist/cli/verify-proof/runtime-detect.d.mts +27 -0
  118. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
  119. package/dist/cli/verify-proof/runtime-detect.mjs +116 -0
  120. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -0
  121. package/dist/cli/verify-proof/trust.d.mts +70 -0
  122. package/dist/cli/verify-proof/trust.d.mts.map +1 -0
  123. package/dist/cli/verify-proof/trust.mjs +174 -0
  124. package/dist/cli/verify-proof/trust.mjs.map +1 -0
  125. package/dist/plugin/.claude-plugin/plugin.json +3 -1
  126. package/dist/plugin/agents/apt-planner.md +5 -4
  127. package/dist/plugin/skills/apt/SKILL.md +75 -6
  128. package/dist/plugin/skills/apt-caveman/SKILL.md +64 -0
  129. package/dist/plugin/skills/apt-debug/SKILL.md +36 -4
  130. package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  131. package/dist/plugin/skills/apt-discuss/SKILL.md +4 -4
  132. package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  133. package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  134. package/dist/plugin/skills/apt-execute/SKILL.md +27 -4
  135. package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
  136. package/dist/plugin/skills/apt-fan-out/SKILL.md +36 -1
  137. package/dist/plugin/skills/apt-handoff/SKILL.md +228 -0
  138. package/dist/plugin/skills/apt-improve/DEEPENING.md +0 -8
  139. package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  140. package/dist/plugin/skills/apt-improve/LANGUAGE.md +0 -8
  141. package/dist/plugin/skills/apt-improve/SKILL.md +4 -4
  142. package/dist/plugin/skills/apt-plan/SKILL.md +23 -5
  143. package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
  144. package/dist/plugin/skills/apt-prototype/LOGIC.md +0 -8
  145. package/dist/plugin/skills/apt-prototype/SKILL.md +4 -4
  146. package/dist/plugin/skills/apt-prototype/UI.md +0 -8
  147. package/dist/plugin/skills/apt-quick/SKILL.md +31 -1
  148. package/dist/plugin/skills/apt-resume/SKILL.md +54 -31
  149. package/dist/plugin/skills/apt-run/SKILL.md +114 -16
  150. package/dist/plugin/skills/apt-setup/SKILL.md +97 -8
  151. package/dist/plugin/skills/apt-ship/SKILL.md +165 -9
  152. package/dist/plugin/skills/apt-spar/SKILL.md +65 -16
  153. package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +0 -8
  154. package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  155. package/dist/plugin/skills/apt-triage/SKILL.md +2 -2
  156. package/dist/plugin/skills/apt-update/SKILL.md +74 -17
  157. package/dist/plugin/skills/apt-verify-proof/SKILL.md +48 -1
  158. package/dist/plugin/skills/apt-zoom-out/SKILL.md +3 -3
  159. package/dist/schemas/quick-task.d.ts.map +1 -1
  160. package/dist/schemas/quick-task.js +2 -2
  161. package/dist/schemas/quick-task.js.map +1 -1
  162. package/dist/types/config.d.ts +33 -0
  163. package/dist/types/config.d.ts.map +1 -1
  164. package/dist/types/state.d.ts +1 -1
  165. package/dist/types/state.d.ts.map +1 -1
  166. package/dist/types/task-record.d.ts +1 -1
  167. package/dist/types/task-record.d.ts.map +1 -1
  168. package/package.json +134 -133
  169. package/prompts/planner.md +1 -1
  170. package/prompts/spec_writer.md +1 -1
  171. package/skills/apt/SKILL.md +75 -6
  172. package/skills/apt-caveman/SKILL.md +64 -0
  173. package/skills/apt-debug/SKILL.md +36 -4
  174. package/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  175. package/skills/apt-discuss/SKILL.md +4 -4
  176. package/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  177. package/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  178. package/skills/apt-execute/SKILL.md +27 -4
  179. package/skills/apt-execute/appendices/tdd-mode.md +2 -10
  180. package/skills/apt-fan-out/SKILL.md +36 -1
  181. package/skills/apt-handoff/SKILL.md +228 -0
  182. package/skills/apt-improve/DEEPENING.md +0 -8
  183. package/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  184. package/skills/apt-improve/LANGUAGE.md +0 -8
  185. package/skills/apt-improve/SKILL.md +4 -4
  186. package/skills/apt-plan/SKILL.md +23 -5
  187. package/skills/apt-plan/adapters/conductor.md +1 -1
  188. package/skills/apt-planner.md +1 -1
  189. package/skills/apt-prototype/LOGIC.md +0 -8
  190. package/skills/apt-prototype/SKILL.md +4 -4
  191. package/skills/apt-prototype/UI.md +0 -8
  192. package/skills/apt-quick/SKILL.md +31 -1
  193. package/skills/apt-resume/SKILL.md +54 -31
  194. package/skills/apt-run/SKILL.md +114 -16
  195. package/skills/apt-setup/SKILL.md +97 -8
  196. package/skills/apt-ship/SKILL.md +165 -9
  197. package/skills/apt-spar/SKILL.md +65 -16
  198. package/skills/apt-triage/AGENT-BRIEF.md +0 -8
  199. package/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  200. package/skills/apt-triage/SKILL.md +2 -2
  201. package/skills/apt-update/SKILL.md +74 -17
  202. package/skills/apt-verify-proof/SKILL.md +48 -1
  203. package/skills/apt-zoom-out/SKILL.md +3 -3
  204. package/src/cli/commands/adr.mjs +1 -1
  205. package/src/cli/commands/audit-branch-current.mjs +250 -0
  206. package/src/cli/commands/audit.mjs +13 -2
  207. package/src/cli/commands/check-version.mjs +20 -1
  208. package/src/cli/commands/context.mjs +3 -3
  209. package/src/cli/commands/detect-runtime.mjs +134 -0
  210. package/src/cli/commands/driver-doctor.mjs +294 -0
  211. package/src/cli/commands/gate.mjs +15 -0
  212. package/src/cli/commands/init.mjs +9 -6
  213. package/src/cli/commands/install-from-source.mjs +1 -0
  214. package/src/cli/commands/modes.mjs +1 -1
  215. package/src/cli/commands/route.mjs +42 -1
  216. package/src/cli/commands/triage.mjs +1 -1
  217. package/src/cli/config/load.mjs +9 -3
  218. package/src/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  219. package/src/cli/coordination/event-schema.mjs +19 -0
  220. package/src/cli/dispatch.mjs +17 -1
  221. package/src/cli/gate/context.mjs +20 -1
  222. package/src/cli/gate/gates/consistency-check.mjs +1 -0
  223. package/src/cli/gate/gates/review-clean.mjs +1 -0
  224. package/src/cli/gate/gates/verify-approved.mjs +1 -0
  225. package/src/cli/gate/registry.mjs +11 -1
  226. package/src/cli/install/find-workspace-framework-version.mjs +88 -0
  227. package/src/cli/install/install-from-source.mjs +276 -0
  228. package/src/cli/install/install-kernel.mjs +24 -0
  229. package/src/cli/install/legacy-paths.mjs +162 -39
  230. package/src/cli/install/runtime-detect.mjs +1 -1
  231. package/src/cli/install/runtime-migrate.mjs +63 -30
  232. package/src/cli/install/transforms/codex.mjs +26 -17
  233. package/src/cli/skill-author/contract.mjs +2 -2
  234. package/src/cli/verify-proof/.gitkeep +0 -0
  235. package/src/cli/verify-proof/audit.mjs +55 -0
  236. package/src/cli/verify-proof/exec.mjs +74 -0
  237. package/src/cli/verify-proof/idl/index.mjs +24 -0
  238. package/src/cli/verify-proof/idl/types.ts +41 -0
  239. package/src/cli/verify-proof/manifest-schema.json +211 -0
  240. package/src/cli/verify-proof/manifest-validator.mjs +184 -0
  241. package/src/cli/verify-proof/resolver.mjs +163 -0
  242. package/src/cli/verify-proof/runtime-detect.mjs +122 -0
  243. package/src/cli/verify-proof/trust.mjs +187 -0
  244. package/templates/adr-format.md +0 -8
  245. package/templates/aperant-claude-md-appendix.md +1 -1
  246. package/templates/context-format.md +1 -9
  247. package/templates/proof-verification.md +19 -5
  248. package/workflows/verify-proof.md +56 -14
@@ -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
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * install/find-workspace-framework-version.mjs — Bundle A.
3
+ *
4
+ * Locates the workspace `packages/framework/package.json` above `targetDir`
5
+ * so the SessionStart worker and `apt-tools check-version` can detect the
6
+ * "self-host contributor's workspace is ahead of the installed kernel" case
7
+ * WITHOUT requiring the user to remember the `--from-source` flag.
8
+ *
9
+ * Mirrors the bounded-walk + semver-validation contract of the route-side
10
+ * `detectSourceWorkspaceDrift` (route/drift-detect.mjs:32-107), but returns
11
+ * just the source workspace metadata — the caller decides whether to flip
12
+ * `update_available` (worker / check-version branch on the comparison).
13
+ *
14
+ * Safe for hot paths: synchronous, file-existence + JSON parse only,
15
+ * bounded walk (6 segments max), no network, no spawn.
16
+ *
17
+ * Security: workspace `package.json:version` is validated against the same
18
+ * `SEMVER_RE` as `update-chips.mjs` so a poisoned `package.json` cannot
19
+ * propagate a non-semver value into downstream envelopes.
20
+ */
21
+
22
+ import { existsSync, readFileSync } from 'node:fs'
23
+ import { dirname, join } from 'node:path'
24
+ import { SEMVER_RE } from '../util/semver.mjs'
25
+
26
+ /** Max directory frames to walk before giving up. Mirrors route/drift-detect.mjs:32. */
27
+ const MAX_WALK_DEPTH = 6
28
+
29
+ /**
30
+ * Walk up from `start`, returning every directory frame including `start`
31
+ * itself. Stops at the filesystem root or at depth `MAX_WALK_DEPTH`
32
+ * (whichever comes first).
33
+ *
34
+ * @param {string} start
35
+ * @returns {string[]}
36
+ */
37
+ function walkUp(start) {
38
+ const frames = []
39
+ let cur = start
40
+ for (let i = 0; i < MAX_WALK_DEPTH; i++) {
41
+ frames.push(cur)
42
+ const parent = dirname(cur)
43
+ if (parent === cur) break
44
+ cur = parent
45
+ }
46
+ return frames
47
+ }
48
+
49
+ /**
50
+ * Find the workspace `@aperant/framework` package above `targetDir`.
51
+ *
52
+ * @param {string} targetDir Absolute path to start the walk from.
53
+ * @returns {null | { source_path: string, source_version: string }}
54
+ * `source_path` is the absolute path to `packages/framework/package.json`.
55
+ * `source_version` is the semver-validated `version` field. Returns null
56
+ * when no workspace package is found, when the workspace package name is
57
+ * not `@aperant/framework`, or when the workspace version fails the
58
+ * semver-shape check.
59
+ */
60
+ export function findWorkspaceFrameworkVersion(targetDir) {
61
+ if (!targetDir || typeof targetDir !== 'string') return null
62
+
63
+ for (const dir of walkUp(targetDir)) {
64
+ const candidate = join(dir, 'packages', 'framework', 'package.json')
65
+ if (!existsSync(candidate)) continue
66
+
67
+ let pkg
68
+ try {
69
+ pkg = JSON.parse(readFileSync(candidate, 'utf-8'))
70
+ } catch {
71
+ continue
72
+ }
73
+
74
+ // Guard against false matches on unrelated monorepos that happen to
75
+ // have a `packages/framework/` directory.
76
+ if (pkg?.name !== '@aperant/framework') continue
77
+
78
+ const sourceVersion = pkg?.version
79
+ if (typeof sourceVersion !== 'string' || !SEMVER_RE.test(sourceVersion)) continue
80
+
81
+ return {
82
+ source_path: candidate,
83
+ source_version: sourceVersion,
84
+ }
85
+ }
86
+
87
+ return null
88
+ }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * install/install-from-source.mjs — Bundle D part 1.
3
+ *
4
+ * `apt-tools install-from-source <project-dir>` for the Aperant-contributor
5
+ * self-host case. Resolves the workspace `@aperant/framework` chain — packs
6
+ * the framework itself AND every `workspace:*` dependency (currently
7
+ * `@aperant/driver-sdk`, but the walker is general). Hands the full tarball
8
+ * list to a single `npm install --prefix` invocation so the same-PR
9
+ * unpublished-pair case stops blowing up with `EUNSUPPORTEDPROTOCOL`.
10
+ *
11
+ * Stable envelope:
12
+ * { status: 'ok', tarballs: string[], kernel_path: string }
13
+ * { status: 'error', error: string, ...optional context }
14
+ *
15
+ * Test seams: `APT_PNPM_BIN` + `APT_NPM_BIN` mirror the existing
16
+ * `install-kernel.mjs` + worker patterns.
17
+ */
18
+
19
+ import { spawnSync } from 'node:child_process'
20
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs'
21
+ import { tmpdir } from 'node:os'
22
+ import { dirname, isAbsolute, join, resolve } from 'node:path'
23
+ import { err, ok } from '../util/result.mjs'
24
+ import { findWorkspaceFrameworkVersion } from './find-workspace-framework-version.mjs'
25
+ import { KERNEL_BIN_REL_PATH } from './install-kernel.mjs'
26
+
27
+ /**
28
+ * Locate the monorepo root given the workspace framework package.json path.
29
+ * `<root>/packages/framework/package.json` → `<root>`.
30
+ *
31
+ * @param {string} frameworkPkgPath
32
+ * @returns {string}
33
+ */
34
+ function monorepoRootFrom(frameworkPkgPath) {
35
+ // `<root>/packages/framework/package.json` — go up THREE levels.
36
+ return dirname(dirname(dirname(frameworkPkgPath)))
37
+ }
38
+
39
+ /**
40
+ * Scan `<monorepoRoot>/packages/` for a directory whose `package.json:name`
41
+ * matches `pkgName`. Returns the directory path (containing package.json),
42
+ * or null if no match.
43
+ *
44
+ * @param {string} monorepoRoot
45
+ * @param {string} pkgName
46
+ * @returns {string|null}
47
+ */
48
+ function findWorkspacePackage(monorepoRoot, pkgName) {
49
+ const packagesDir = join(monorepoRoot, 'packages')
50
+ if (!existsSync(packagesDir)) return null
51
+ let entries
52
+ try {
53
+ entries = readdirSync(packagesDir, { withFileTypes: true })
54
+ } catch {
55
+ return null
56
+ }
57
+ for (const entry of entries) {
58
+ if (!entry.isDirectory()) continue
59
+ const candidate = join(packagesDir, entry.name, 'package.json')
60
+ if (!existsSync(candidate)) continue
61
+ try {
62
+ const pkg = JSON.parse(readFileSync(candidate, 'utf-8'))
63
+ if (pkg?.name === pkgName) return join(packagesDir, entry.name)
64
+ } catch {
65
+ // Skip unreadable package.json files.
66
+ }
67
+ }
68
+ return null
69
+ }
70
+
71
+ /**
72
+ * pnpm-pack a workspace package into `tmpDir` and return the resulting
73
+ * tarball path. Uses `pnpm --filter <name> pack --pack-destination <tmp>`
74
+ * so workspace protocols resolve to concrete versions inside the tarball.
75
+ *
76
+ * `knownBefore` is the set of .tgz filenames already present in `tmpDir`
77
+ * before this call; the mtime fallback uses it to find only the NEW file,
78
+ * avoiding coarse-timestamp ambiguity when multiple packs land in the same
79
+ * directory.
80
+ *
81
+ * @param {string} monorepoRoot
82
+ * @param {string} pkgName
83
+ * @param {string} tmpDir
84
+ * @param {Set<string>} knownBefore Filenames (not paths) present before this pack.
85
+ * @returns {{ tarball: string } | { error: string }}
86
+ */
87
+ function packWorkspace(monorepoRoot, pkgName, tmpDir, knownBefore) {
88
+ const pnpmBin = process.env.APT_PNPM_BIN || 'pnpm'
89
+ const result = spawnSync(pnpmBin, ['--filter', pkgName, 'pack', '--pack-destination', tmpDir], {
90
+ cwd: monorepoRoot,
91
+ stdio: ['ignore', 'pipe', 'pipe'],
92
+ encoding: 'utf-8',
93
+ timeout: 120_000,
94
+ })
95
+ if (result.error) {
96
+ return { error: `pnpm spawn failed for ${pkgName}: ${result.error.message}` }
97
+ }
98
+ if (result.status !== 0) {
99
+ const stderr = (result.stderr || '').trim().slice(-600)
100
+ return { error: `pnpm pack ${pkgName} exited with ${result.status}: ${stderr || 'no stderr'}` }
101
+ }
102
+ // pnpm pack prints the resulting tarball path on stdout. Take the last
103
+ // `.tgz` line (pnpm sometimes emits warnings above the path line).
104
+ const tarballLine = (result.stdout || '')
105
+ .split('\n')
106
+ .map((s) => s.trim())
107
+ .reverse()
108
+ .find((s) => s.endsWith('.tgz'))
109
+ if (tarballLine) {
110
+ // `tarballLine` may be a bare filename (older pnpm) or an absolute path.
111
+ // Use `isAbsolute` so Windows backslash + drive-letter paths are also
112
+ // recognized — `startsWith('/')` was POSIX-only.
113
+ const tarball = isAbsolute(tarballLine) ? tarballLine : join(tmpDir, tarballLine)
114
+ // SEC-001: containment check — tarball must resolve to a child of tmpDir.
115
+ const resolvedTmpDir = resolve(tmpDir)
116
+ const resolvedTarball = resolve(tarball)
117
+ if (!resolvedTarball.startsWith(`${resolvedTmpDir}/`) && resolvedTarball !== resolvedTmpDir) {
118
+ return { error: `pnpm pack ${pkgName}: tarball path escapes tmpDir: ${tarball}` }
119
+ }
120
+ return { tarball }
121
+ }
122
+ // Fall back to scanning tmpDir for the NEW tgz when stdout was
123
+ // uninformative (some pnpm builds print only progress to stderr).
124
+ // Use `knownBefore` to find only files added by this pack, avoiding
125
+ // mtime coarse-timestamp ambiguity on FAT32/network filesystems.
126
+ try {
127
+ for (const f of readdirSync(tmpDir)) {
128
+ if (!f.endsWith('.tgz')) continue
129
+ if (knownBefore.has(f)) continue
130
+ return { tarball: join(tmpDir, f) }
131
+ }
132
+ } catch {
133
+ // fall through
134
+ }
135
+ return {
136
+ error: `pnpm pack ${pkgName}: no .tgz produced (stdout: ${result.stdout?.slice(-200)})`,
137
+ }
138
+ }
139
+
140
+ /**
141
+ * `apt-tools install-from-source <project-dir>` — pack the workspace
142
+ * framework + every `workspace:*` dep, then npm-install the full tarball
143
+ * list into `<project-dir>/.aperant/deps/`.
144
+ *
145
+ * @param {string} projectDir
146
+ * @returns {import('../util/result.mjs').CommandResult}
147
+ */
148
+ export function cmdInstallFromSource(projectDir) {
149
+ if (!projectDir) {
150
+ return err('Usage: apt-tools install-from-source <project-dir>')
151
+ }
152
+ const targetDir = resolve(projectDir)
153
+
154
+ const workspace = findWorkspaceFrameworkVersion(targetDir)
155
+ if (!workspace) {
156
+ return err('install-from-source: no @aperant/framework workspace found above project-dir')
157
+ }
158
+ const monorepoRoot = monorepoRootFrom(workspace.source_path)
159
+
160
+ // QUA-001: re-read the workspace package.json with a try/catch — the file
161
+ // was validated by findWorkspaceFrameworkVersion but could change between
162
+ // the two reads (active monorepo, contributor machine).
163
+ let frameworkPkg
164
+ try {
165
+ frameworkPkg = JSON.parse(readFileSync(workspace.source_path, 'utf-8'))
166
+ } catch (e) {
167
+ return err(
168
+ `install-from-source: failed to read ${workspace.source_path}: ${e instanceof Error ? e.message : String(e)}`,
169
+ )
170
+ }
171
+
172
+ // Enumerate workspace:* deps from dependencies + peerDependencies.
173
+ const depSources = [frameworkPkg.dependencies, frameworkPkg.peerDependencies]
174
+ const workspaceDeps = []
175
+ for (const block of depSources) {
176
+ if (!block || typeof block !== 'object') continue
177
+ for (const [name, spec] of Object.entries(block)) {
178
+ if (typeof spec === 'string' && spec.startsWith('workspace:')) {
179
+ if (!workspaceDeps.includes(name)) workspaceDeps.push(name)
180
+ }
181
+ }
182
+ }
183
+
184
+ // Resolve each workspace dep to its `packages/<dir>` path. Reject hard
185
+ // when any dep cannot be located — we cannot pack what we cannot find.
186
+ const missingDeps = []
187
+ const resolvedDeps = []
188
+ for (const depName of workspaceDeps) {
189
+ const pkgDir = findWorkspacePackage(monorepoRoot, depName)
190
+ if (!pkgDir) {
191
+ missingDeps.push(depName)
192
+ continue
193
+ }
194
+ resolvedDeps.push({ name: depName, dir: pkgDir })
195
+ }
196
+ if (missingDeps.length > 0) {
197
+ return err(
198
+ `install-from-source: workspace:* dep(s) not found in packages/: ${missingDeps.join(', ')}`,
199
+ )
200
+ }
201
+
202
+ // PRF-001: include framework in the same pack loop — eliminates structural
203
+ // duplication and ensures per-pack knownBefore snapshots work uniformly.
204
+ const packTargets = [...resolvedDeps.map((d) => d.name), '@aperant/framework']
205
+
206
+ // Pack everything into a fresh tmp dir so accumulated stale tarballs
207
+ // from prior runs don't sneak in.
208
+ const tmpDir = mkdtempSync(join(tmpdir(), 'apt-install-from-source-'))
209
+ const tarballs = []
210
+
211
+ // QUA-002: wrap pack+install in try/finally so tmpDir is always removed.
212
+ try {
213
+ for (const pkgName of packTargets) {
214
+ // LOG-003: snapshot existing tgz filenames before each pack so the
215
+ // fallback can identify exactly the new file, even on coarse-timestamp
216
+ // filesystems (FAT32, some network mounts).
217
+ const knownBefore = new Set()
218
+ try {
219
+ for (const f of readdirSync(tmpDir)) {
220
+ if (f.endsWith('.tgz')) knownBefore.add(f)
221
+ }
222
+ } catch {
223
+ // tmpDir unreadable — proceed with empty set; fallback will still work.
224
+ }
225
+ const packResult = packWorkspace(monorepoRoot, pkgName, tmpDir, knownBefore)
226
+ if ('error' in packResult) return err(packResult.error)
227
+ tarballs.push(packResult.tarball)
228
+ }
229
+
230
+ // npm install --prefix .aperant/deps <tarball1> <tarball2> ...
231
+ const depsDir = join(targetDir, '.aperant', 'deps')
232
+ mkdirSync(depsDir, { recursive: true })
233
+
234
+ const npmBin = process.env.APT_NPM_BIN || 'npm'
235
+ const args = [
236
+ 'install',
237
+ '--prefix',
238
+ depsDir,
239
+ ...tarballs,
240
+ '--no-save',
241
+ '--no-package-lock',
242
+ '--no-audit',
243
+ '--no-fund',
244
+ '--silent',
245
+ ]
246
+ const npmResult = spawnSync(npmBin, args, {
247
+ cwd: targetDir,
248
+ stdio: ['ignore', 'pipe', 'pipe'],
249
+ encoding: 'utf-8',
250
+ timeout: 180_000,
251
+ })
252
+ if (npmResult.error) {
253
+ return err(`install-from-source: npm spawn failed: ${npmResult.error.message}`)
254
+ }
255
+ if (npmResult.status !== 0) {
256
+ const stderr = (npmResult.stderr || '').trim().slice(-600)
257
+ return err(
258
+ `install-from-source: npm install --prefix exited with ${npmResult.status}: ${stderr || 'no stderr'}`,
259
+ )
260
+ }
261
+
262
+ const kernelPath = join(targetDir, KERNEL_BIN_REL_PATH)
263
+ if (!existsSync(kernelPath)) {
264
+ return err(`install-from-source: npm reported success but kernel not found at ${kernelPath}`)
265
+ }
266
+
267
+ return ok({
268
+ status: 'ok',
269
+ command: 'install-from-source',
270
+ tarballs,
271
+ kernel_path: kernelPath,
272
+ })
273
+ } finally {
274
+ rmSync(tmpDir, { recursive: true, force: true })
275
+ }
276
+ }
@@ -120,6 +120,30 @@ export function installKernel(targetDir, version, opts = {}) {
120
120
  const kernelPath = join(targetDir, KERNEL_BIN_REL_PATH)
121
121
  const npmBin = resolveNpmBin()
122
122
 
123
+ // Bundle B — idempotency short-circuit. When the kernel package.json
124
+ // already reports the requested version AND the kernel binary exists,
125
+ // skip the npm spawn entirely. Returns the same `status: 'installed'`
126
+ // envelope shape that downstream consumers branch on (init.mjs:1441,
127
+ // init.mjs:1735, apt-update/SKILL.md Scenario D) — no new enum value
128
+ // per D-02.
129
+ const installedPkgPath = join(depsDir, 'node_modules', '@aperant', 'framework', 'package.json')
130
+ if (existsSync(installedPkgPath) && existsSync(kernelPath)) {
131
+ try {
132
+ const installedPkg = JSON.parse(readFileSync(installedPkgPath, 'utf-8'))
133
+ if (installedPkg?.version === version) {
134
+ return {
135
+ status: 'installed',
136
+ kernelPath,
137
+ reason: 'already at target version',
138
+ durationMs: 0,
139
+ }
140
+ }
141
+ } catch {
142
+ // Malformed package.json — fall through to the npm install path
143
+ // (npm will reconcile / repair).
144
+ }
145
+ }
146
+
123
147
  const t0 = Date.now()
124
148
  const args = [
125
149
  'install',