@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
@@ -0,0 +1,134 @@
1
+ /**
2
+ * commands/detect-runtime.mjs — `apt-tools detect-runtime <projectDir>`.
3
+ *
4
+ * Exposes runtime-detect.mjs at the CLI surface so adopters can preview
5
+ * which driver Aperant will pick for /apt:verify-proof.
6
+ *
7
+ * Returns:
8
+ * { status: 'ok', command: 'detect-runtime', projectDir,
9
+ * runtime: { is_electron, is_tauri, is_nextjs, is_web_frontend, ... },
10
+ * selected_driver_id: string | null,
11
+ * fallback_attempts: [{ driver_id, reason }] }
12
+ */
13
+
14
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
15
+ import { join, resolve } from 'node:path'
16
+ import { fileURLToPath } from 'node:url'
17
+ import { err, ok } from '../util/result.mjs'
18
+ import { resolveDriver } from '../verify-proof/resolver.mjs'
19
+ import { loadRuntimeCapabilities } from '../verify-proof/runtime-detect.mjs'
20
+
21
+ /** Default required capabilities — the smallest set that proves a driver is usable. */
22
+ const DEFAULT_REQUIRED_CAPABILITIES = ['screenshot']
23
+
24
+ /**
25
+ * Discover every driver manifest bundled with @aperant/framework.
26
+ * @param {string} frameworkRoot — absolute path to packages/framework or .aperant/deps/.../@aperant/framework
27
+ * @returns {Array<object>}
28
+ */
29
+ function discoverBundledDrivers(frameworkRoot) {
30
+ const dir = join(frameworkRoot, 'drivers')
31
+ if (!existsSync(dir)) return []
32
+ const manifests = []
33
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
34
+ if (!entry.isDirectory()) continue
35
+ const manifestPath = join(dir, entry.name, 'manifest.json')
36
+ if (!existsSync(manifestPath)) continue
37
+ try {
38
+ const parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'))
39
+ manifests.push({ ...parsed, locality: 'bundled' })
40
+ } catch {
41
+ /* skip malformed manifests */
42
+ }
43
+ }
44
+ return manifests
45
+ }
46
+
47
+ /**
48
+ * Discover every user-installed driver manifest at .aperant/drivers/.
49
+ * @param {string} projectDir
50
+ * @returns {Array<object>}
51
+ */
52
+ function discoverUserDrivers(projectDir) {
53
+ const dir = join(projectDir, '.aperant/drivers')
54
+ if (!existsSync(dir)) return []
55
+ const manifests = []
56
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
57
+ if (!entry.isDirectory()) continue
58
+ const manifestPath = join(dir, entry.name, 'manifest.json')
59
+ if (!existsSync(manifestPath)) continue
60
+ try {
61
+ const parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'))
62
+ manifests.push({ ...parsed, locality: 'user' })
63
+ } catch {
64
+ /* skip malformed manifests */
65
+ }
66
+ }
67
+ return manifests
68
+ }
69
+
70
+ /**
71
+ * @param {string} projectDir
72
+ * @param {string[]} [_extra]
73
+ */
74
+ export function cmdDetectRuntime(projectDir, _extra = []) {
75
+ if (!projectDir) return err('Usage: apt-tools detect-runtime <project-dir>')
76
+ const target = resolve(projectDir)
77
+
78
+ // Detect runtime capabilities from project's package.json.
79
+ const runtime = loadRuntimeCapabilities(target)
80
+
81
+ // Discover bundled + user drivers.
82
+ // __dirname-equivalent under ESM: walk up from this file to the framework
83
+ // package root. fileURLToPath is portable across POSIX + Windows;
84
+ // new URL(...).pathname returns '/C:/...' on Windows which resolve()
85
+ // does not handle correctly.
86
+ const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
87
+ const bundled = discoverBundledDrivers(frameworkRoot)
88
+ const user = discoverUserDrivers(target)
89
+ const drivers = [...bundled, ...user]
90
+
91
+ // Read user override from .aperant/config.json:verification.runtimes[].
92
+ let override = []
93
+ const configPath = join(target, '.aperant/config.json')
94
+ if (existsSync(configPath)) {
95
+ try {
96
+ const cfg = JSON.parse(readFileSync(configPath, 'utf-8'))
97
+ if (Array.isArray(cfg?.verification?.runtimes)) {
98
+ override = cfg.verification.runtimes.filter((x) => typeof x === 'string')
99
+ }
100
+ } catch {
101
+ /* malformed config — ignore */
102
+ }
103
+ }
104
+
105
+ // Try resolving with the default minimum required capability.
106
+ let selected = null
107
+ let fallback_attempts = []
108
+ try {
109
+ const result = resolveDriver({
110
+ drivers,
111
+ runtime,
112
+ required_capabilities: DEFAULT_REQUIRED_CAPABILITIES,
113
+ override,
114
+ })
115
+ selected = result.selected.driverId
116
+ fallback_attempts = result.fallback_attempts
117
+ } catch (resolveErr) {
118
+ fallback_attempts = resolveErr && Array.isArray(resolveErr.attempts) ? resolveErr.attempts : []
119
+ }
120
+
121
+ return ok({
122
+ status: 'ok',
123
+ command: 'detect-runtime',
124
+ projectDir: target,
125
+ runtime,
126
+ discovered_drivers: drivers.map((d) => ({
127
+ driverId: d.driverId,
128
+ version: d.version,
129
+ locality: d.locality,
130
+ })),
131
+ selected_driver_id: selected,
132
+ fallback_attempts,
133
+ })
134
+ }
@@ -0,0 +1,294 @@
1
+ /**
2
+ * commands/driver-doctor.mjs — `apt-tools driver doctor <projectDir>`.
3
+ *
4
+ * Validates every discovered driver against:
5
+ * 1. The manifest JSON-Schema (manifest-validator.mjs).
6
+ * 2. IDL conformance — every declared capability has a function export.
7
+ * NOTE: this step necessarily imports driver.mjs, which RUNS the
8
+ * driver's top-level module code. For that reason we trust-gate the
9
+ * import: bundled drivers (first-party signed, sitting under
10
+ * packages/framework/drivers/) are always imported; user drivers
11
+ * under .aperant/drivers/ are imported only when policy + TOFU say
12
+ * it is safe. A user driver that fails the gate is reported with
13
+ * `idl_conformant: false` and `idl_skipped_reason: <decision>`
14
+ * rather than executed.
15
+ * 3. Preflight commands (declared as structured local_command per
16
+ * ID-03). For each preflight whose declared transport is the
17
+ * structured `{ binary, args[] }` shape, we run it via execFile
18
+ * (exec.mjs — no shell) and record pass/fail + missing_deps.
19
+ * Preflights without a structured `command` (declaration-only) are
20
+ * marked `pending` — there is nothing to execute.
21
+ *
22
+ * Emits a JSON envelope listing pass/fail per driver. Third-party driver
23
+ * authors invoke this before publishing.
24
+ */
25
+
26
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
27
+ import { join, resolve } from 'node:path'
28
+ import { fileURLToPath } from 'node:url'
29
+ import { err, ok } from '../util/result.mjs'
30
+ import { execLocalCommand } from '../verify-proof/exec.mjs'
31
+ import { IDL_VERB_NAMES } from '../verify-proof/idl/index.mjs'
32
+ import { validateManifest } from '../verify-proof/manifest-validator.mjs'
33
+ import { decide, readPolicy, readTofuLock } from '../verify-proof/trust.mjs'
34
+
35
+ /**
36
+ * @typedef {Object} DriverDoctorReport
37
+ * @property {string} driverId
38
+ * @property {string} version
39
+ * @property {string} locality
40
+ * @property {boolean} manifest_valid
41
+ * @property {Array<{path: string, message: string}>} manifest_errors
42
+ * @property {boolean} idl_conformant
43
+ * @property {string[]} missing_exports
44
+ * @property {string[]} unknown_capabilities
45
+ * @property {string} [idl_skipped_reason] — present when conformance was skipped because the trust gate blocked driver.mjs import
46
+ * @property {'pending' | 'pass' | 'fail' | 'skipped'} preflight_status
47
+ * @property {string[]} missing_deps
48
+ * @property {string} [publisher_warning] — surfaced when a non-first-party manifest claims first_party_signed (v1 has no signature verification)
49
+ */
50
+
51
+ /**
52
+ * Scan a drivers directory for manifest.json files. Returns
53
+ * (manifest, dir, locality) tuples.
54
+ * @param {string} rootDir
55
+ * @param {'bundled' | 'user'} locality
56
+ */
57
+ function scanDriversDir(rootDir, locality) {
58
+ const out = []
59
+ if (!existsSync(rootDir)) return out
60
+ for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
61
+ if (!entry.isDirectory()) continue
62
+ const driverDir = join(rootDir, entry.name)
63
+ const manifestPath = join(driverDir, 'manifest.json')
64
+ if (!existsSync(manifestPath)) continue
65
+ let manifest
66
+ try {
67
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
68
+ } catch (e) {
69
+ out.push({ malformed: true, dir: driverDir, locality, parse_error: String(e) })
70
+ continue
71
+ }
72
+ out.push({ manifest, dir: driverDir, locality })
73
+ }
74
+ return out
75
+ }
76
+
77
+ /**
78
+ * @param {object} manifest
79
+ * @param {string} driverDir
80
+ * @returns {Promise<{idl_conformant: boolean, missing_exports: string[], unknown_capabilities: string[]}>}
81
+ */
82
+ async function checkIdlConformance(manifest, driverDir) {
83
+ const driverPath = join(driverDir, 'driver.mjs')
84
+ if (!existsSync(driverPath)) {
85
+ return {
86
+ idl_conformant: false,
87
+ missing_exports: ['driver.mjs not found'],
88
+ unknown_capabilities: [],
89
+ }
90
+ }
91
+ const missing_exports = []
92
+ const unknown_capabilities = []
93
+ const idlSet = new Set(IDL_VERB_NAMES)
94
+ try {
95
+ const mod = await import(driverPath)
96
+ for (const cap of manifest.capabilities ?? []) {
97
+ if (!idlSet.has(cap)) {
98
+ unknown_capabilities.push(cap)
99
+ continue
100
+ }
101
+ if (typeof mod[cap] !== 'function') {
102
+ missing_exports.push(cap)
103
+ }
104
+ }
105
+ } catch (e) {
106
+ return {
107
+ idl_conformant: false,
108
+ missing_exports: [`driver.mjs import failed: ${String(e)}`],
109
+ unknown_capabilities: [],
110
+ }
111
+ }
112
+ return {
113
+ idl_conformant: missing_exports.length === 0 && unknown_capabilities.length === 0,
114
+ missing_exports,
115
+ unknown_capabilities,
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Run a driver's preflight checks. Each declared preflight with a
121
+ * structured `command: { binary, args[] }` is executed via execFile
122
+ * (no shell). Preflights without a structured command are reported as
123
+ * `pending` — there is nothing to execute. Empty preflight list is
124
+ * `skipped`.
125
+ *
126
+ * @param {object} manifest
127
+ * @returns {Promise<{preflight_status: 'pending'|'pass'|'fail'|'skipped', missing_deps: string[]}>}
128
+ */
129
+ async function runPreflight(manifest) {
130
+ const preflight = Array.isArray(manifest.preflight) ? manifest.preflight : []
131
+ if (preflight.length === 0) return { preflight_status: 'skipped', missing_deps: [] }
132
+
133
+ const missing_deps = []
134
+ let executed = 0
135
+ let failed = false
136
+
137
+ for (const check of preflight) {
138
+ // Only execute when the preflight declares a structured local-command
139
+ // shape. Anything else (no command, or non-structured) is left as
140
+ // declaration-only — caller can drive it explicitly.
141
+ const cmd = check?.command
142
+ if (
143
+ cmd === undefined ||
144
+ cmd === null ||
145
+ typeof cmd !== 'object' ||
146
+ typeof cmd.binary !== 'string' ||
147
+ !Array.isArray(cmd.args) ||
148
+ cmd.args.some((a) => typeof a !== 'string')
149
+ ) {
150
+ continue
151
+ }
152
+ executed++
153
+ try {
154
+ await execLocalCommand({ transport: { local_command: cmd } }, [], { timeout: 5000 })
155
+ } catch {
156
+ failed = true
157
+ if (check.required !== false) {
158
+ missing_deps.push(check.name ?? cmd.binary)
159
+ }
160
+ }
161
+ }
162
+
163
+ if (executed === 0) return { preflight_status: 'pending', missing_deps: [] }
164
+ return { preflight_status: failed ? 'fail' : 'pass', missing_deps }
165
+ }
166
+
167
+ /**
168
+ * Trust-gate the driver.mjs import. Bundled drivers (locality='bundled')
169
+ * always pass — they live in packages/framework/drivers/ and are first-
170
+ * party. User drivers go through the policy + TOFU decision; only
171
+ * `allow` permits the dynamic import.
172
+ *
173
+ * @param {object} manifest
174
+ * @param {'bundled'|'user'} locality
175
+ * @param {string} projectDir
176
+ * @returns {{ok: true} | {ok: false, reason: string}}
177
+ */
178
+ function trustGate(manifest, locality, projectDir) {
179
+ if (locality === 'bundled') return { ok: true }
180
+ const policy = readPolicy(projectDir)
181
+ const tofu = readTofuLock(projectDir)
182
+ const decision = decide(manifest, tofu, policy)
183
+ if (decision.decision === 'allow') return { ok: true }
184
+ return { ok: false, reason: decision.reason }
185
+ }
186
+
187
+ /**
188
+ * v1 has no signature verification (spec §Out of Scope). A non-Aperant
189
+ * publisher claiming `first_party_signed` is suspicious — surface a
190
+ * warning so the operator notices.
191
+ *
192
+ * @param {object} manifest
193
+ * @returns {string | undefined}
194
+ */
195
+ function publisherWarning(manifest) {
196
+ const tier = manifest?.trust?.tier
197
+ if (tier !== 'first_party_signed') return undefined
198
+ const npmScope = manifest?.trust?.publisher?.npm_scope
199
+ const githubOrg = manifest?.trust?.publisher?.github_org
200
+ const looksLikeAperant = npmScope === '@aperant' || githubOrg === 'Mikalsen-AI'
201
+ if (looksLikeAperant) return undefined
202
+ return `trust.tier=first_party_signed but publisher is not @aperant — v1 does not verify signatures; treat tier as advisory`
203
+ }
204
+
205
+ /**
206
+ * @param {string} projectDir
207
+ * @param {string[]} [_extra]
208
+ */
209
+ export async function cmdDriverDoctor(projectDir, _extra = []) {
210
+ if (!projectDir) return err('Usage: apt-tools driver doctor <project-dir>')
211
+ const target = resolve(projectDir)
212
+
213
+ // fileURLToPath is portable across POSIX + Windows; new URL(...).pathname
214
+ // returns '/C:/...' on Windows which resolve() does not handle correctly.
215
+ const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
216
+ const bundled = scanDriversDir(join(frameworkRoot, 'drivers'), 'bundled')
217
+ const user = scanDriversDir(join(target, '.aperant/drivers'), 'user')
218
+ const discovered = [...bundled, ...user]
219
+
220
+ const drivers = []
221
+ for (const d of discovered) {
222
+ if (d.malformed) {
223
+ drivers.push({
224
+ driverId: '(malformed)',
225
+ version: '?',
226
+ locality: d.locality,
227
+ manifest_valid: false,
228
+ manifest_errors: [{ path: '/', message: d.parse_error }],
229
+ idl_conformant: false,
230
+ missing_exports: [],
231
+ unknown_capabilities: [],
232
+ preflight_status: 'skipped',
233
+ missing_deps: [],
234
+ })
235
+ continue
236
+ }
237
+
238
+ const manifest = d.manifest
239
+ const schemaResult = validateManifest(manifest)
240
+ const manifest_valid = schemaResult.valid === true
241
+ const manifest_errors = schemaResult.valid === false ? schemaResult.errors : []
242
+
243
+ let idlResult = { idl_conformant: false, missing_exports: [], unknown_capabilities: [] }
244
+ let idl_skipped_reason
245
+ if (manifest_valid) {
246
+ const gate = trustGate(manifest, d.locality, target)
247
+ if (gate.ok) {
248
+ idlResult = await checkIdlConformance(manifest, d.dir)
249
+ } else {
250
+ // Trust gate denied. Do NOT dynamic-import the driver — that
251
+ // would execute arbitrary third-party code under the audit
252
+ // command. Report idl_conformant=false with the gate reason
253
+ // so the operator sees exactly why conformance was not run.
254
+ idl_skipped_reason = `trust gate denied: ${gate.reason}`
255
+ }
256
+ }
257
+
258
+ let preflightResult = { preflight_status: 'skipped', missing_deps: [] }
259
+ if (manifest_valid) {
260
+ preflightResult = await runPreflight(manifest)
261
+ }
262
+
263
+ const entry = {
264
+ driverId: manifest.driverId ?? '(unknown)',
265
+ version: manifest.version ?? '?',
266
+ locality: d.locality,
267
+ manifest_valid,
268
+ manifest_errors,
269
+ idl_conformant: idlResult.idl_conformant,
270
+ missing_exports: idlResult.missing_exports,
271
+ unknown_capabilities: idlResult.unknown_capabilities,
272
+ preflight_status: preflightResult.preflight_status,
273
+ missing_deps: preflightResult.missing_deps,
274
+ }
275
+ if (idl_skipped_reason !== undefined) entry.idl_skipped_reason = idl_skipped_reason
276
+ const warning = publisherWarning(manifest)
277
+ if (warning !== undefined) entry.publisher_warning = warning
278
+ drivers.push(entry)
279
+ }
280
+
281
+ const all_pass = drivers.every((d) => d.manifest_valid && d.idl_conformant)
282
+ return ok({
283
+ status: 'ok',
284
+ command: 'driver-doctor',
285
+ projectDir: target,
286
+ drivers,
287
+ summary: {
288
+ total: drivers.length,
289
+ pass: drivers.filter((d) => d.manifest_valid && d.idl_conformant).length,
290
+ fail: drivers.filter((d) => !d.manifest_valid || !d.idl_conformant).length,
291
+ overall: all_pass ? 'pass' : 'fail',
292
+ },
293
+ })
294
+ }
@@ -135,6 +135,20 @@ async function cmdGateCheck(projectDir, extraArgs) {
135
135
  const cache = cacheMod ? cacheMod.readCache(ctx.taskDir) : {}
136
136
 
137
137
  for (const gate of gates) {
138
+ // Applicability check fires BEFORE cache lookup or check() execution.
139
+ // Gates that don't apply to the active profile (lite vs full-pipeline)
140
+ // short-circuit with a skip verdict. Skip results are intentionally
141
+ // NOT cached (see status: 'skip' branch below), so a track change on
142
+ // the same task immediately reflects in the next gate-check.
143
+ if (typeof gate.applies_to_profile === 'function' && gate.applies_to_profile(ctx) === false) {
144
+ skipped.push({
145
+ id: gate.id,
146
+ severity: gate.severity,
147
+ reason: `profile-not-applicable: ${ctx.profile}`,
148
+ })
149
+ continue
150
+ }
151
+
138
152
  let verdict
139
153
  let cached = false
140
154
  let inputHash = null
@@ -244,6 +258,7 @@ async function cmdGateCheck(projectDir, extraArgs) {
244
258
  command: 'gate-check',
245
259
  skill,
246
260
  task_id: taskId,
261
+ profile: ctx.profile,
247
262
  passed: passed.map((p) => p.id),
248
263
  warned: warned.map((w) => ({ id: w.id, reason: w.reason })),
249
264
  skipped: skipped.map((s) => ({ id: s.id, reason: s.reason })),
@@ -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).