@productbrain/cli 0.1.0-beta.7 → 0.1.0-beta.70

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 (600) hide show
  1. package/README.md +98 -30
  2. package/dist/__tests__/adapters.test.d.ts +2 -0
  3. package/dist/__tests__/adapters.test.d.ts.map +1 -0
  4. package/dist/__tests__/adapters.test.js +417 -0
  5. package/dist/__tests__/adapters.test.js.map +1 -0
  6. package/dist/__tests__/audit.test.d.ts +2 -0
  7. package/dist/__tests__/audit.test.d.ts.map +1 -0
  8. package/dist/__tests__/audit.test.js +394 -0
  9. package/dist/__tests__/audit.test.js.map +1 -0
  10. package/dist/__tests__/batch-transformations.test.d.ts +2 -0
  11. package/dist/__tests__/batch-transformations.test.d.ts.map +1 -0
  12. package/dist/__tests__/batch-transformations.test.js +263 -0
  13. package/dist/__tests__/batch-transformations.test.js.map +1 -0
  14. package/dist/__tests__/capture.test.d.ts +2 -0
  15. package/dist/__tests__/capture.test.d.ts.map +1 -0
  16. package/dist/__tests__/capture.test.js +377 -0
  17. package/dist/__tests__/capture.test.js.map +1 -0
  18. package/dist/__tests__/config.test.d.ts +8 -0
  19. package/dist/__tests__/config.test.d.ts.map +1 -0
  20. package/dist/__tests__/config.test.js +166 -0
  21. package/dist/__tests__/config.test.js.map +1 -0
  22. package/dist/__tests__/constants.test.d.ts +2 -0
  23. package/dist/__tests__/constants.test.d.ts.map +1 -0
  24. package/dist/__tests__/constants.test.js +141 -0
  25. package/dist/__tests__/constants.test.js.map +1 -0
  26. package/dist/__tests__/constellation.test.d.ts +2 -0
  27. package/dist/__tests__/constellation.test.d.ts.map +1 -0
  28. package/dist/__tests__/constellation.test.js +254 -0
  29. package/dist/__tests__/constellation.test.js.map +1 -0
  30. package/dist/__tests__/context-strategy.test.d.ts +2 -0
  31. package/dist/__tests__/context-strategy.test.d.ts.map +1 -0
  32. package/dist/__tests__/context-strategy.test.js +79 -0
  33. package/dist/__tests__/context-strategy.test.js.map +1 -0
  34. package/dist/__tests__/errors.test.d.ts +2 -0
  35. package/dist/__tests__/errors.test.d.ts.map +1 -0
  36. package/dist/__tests__/errors.test.js +117 -0
  37. package/dist/__tests__/errors.test.js.map +1 -0
  38. package/dist/__tests__/experiment.test.d.ts +6 -0
  39. package/dist/__tests__/experiment.test.d.ts.map +1 -0
  40. package/dist/__tests__/experiment.test.js +69 -0
  41. package/dist/__tests__/experiment.test.js.map +1 -0
  42. package/dist/__tests__/fields.test.d.ts +2 -0
  43. package/dist/__tests__/fields.test.d.ts.map +1 -0
  44. package/dist/__tests__/fields.test.js +238 -0
  45. package/dist/__tests__/fields.test.js.map +1 -0
  46. package/dist/__tests__/glossary.test.d.ts +2 -0
  47. package/dist/__tests__/glossary.test.d.ts.map +1 -0
  48. package/dist/__tests__/glossary.test.js +32 -0
  49. package/dist/__tests__/glossary.test.js.map +1 -0
  50. package/dist/__tests__/handshake.test.d.ts +2 -0
  51. package/dist/__tests__/handshake.test.d.ts.map +1 -0
  52. package/dist/__tests__/handshake.test.js +196 -0
  53. package/dist/__tests__/handshake.test.js.map +1 -0
  54. package/dist/__tests__/ingest.test.js +98 -0
  55. package/dist/__tests__/ingest.test.js.map +1 -1
  56. package/dist/__tests__/init.test.d.ts +7 -0
  57. package/dist/__tests__/init.test.d.ts.map +1 -0
  58. package/dist/__tests__/init.test.js +146 -0
  59. package/dist/__tests__/init.test.js.map +1 -0
  60. package/dist/__tests__/login.test.d.ts +2 -0
  61. package/dist/__tests__/login.test.d.ts.map +1 -0
  62. package/dist/__tests__/login.test.js +167 -0
  63. package/dist/__tests__/login.test.js.map +1 -0
  64. package/dist/__tests__/onboarding-path-b.test.d.ts +2 -0
  65. package/dist/__tests__/onboarding-path-b.test.d.ts.map +1 -0
  66. package/dist/__tests__/onboarding-path-b.test.js +46 -0
  67. package/dist/__tests__/onboarding-path-b.test.js.map +1 -0
  68. package/dist/__tests__/onboarding.test.d.ts +6 -0
  69. package/dist/__tests__/onboarding.test.d.ts.map +1 -0
  70. package/dist/__tests__/onboarding.test.js +347 -0
  71. package/dist/__tests__/onboarding.test.js.map +1 -0
  72. package/dist/__tests__/orient.test.d.ts +2 -0
  73. package/dist/__tests__/orient.test.d.ts.map +1 -0
  74. package/dist/__tests__/orient.test.js +143 -0
  75. package/dist/__tests__/orient.test.js.map +1 -0
  76. package/dist/__tests__/profiles.test.d.ts +2 -0
  77. package/dist/__tests__/profiles.test.d.ts.map +1 -0
  78. package/dist/__tests__/profiles.test.js +168 -0
  79. package/dist/__tests__/profiles.test.js.map +1 -0
  80. package/dist/__tests__/promote.test.d.ts +2 -0
  81. package/dist/__tests__/promote.test.d.ts.map +1 -0
  82. package/dist/__tests__/promote.test.js +161 -0
  83. package/dist/__tests__/promote.test.js.map +1 -0
  84. package/dist/__tests__/prompts.test.d.ts +6 -0
  85. package/dist/__tests__/prompts.test.d.ts.map +1 -0
  86. package/dist/__tests__/prompts.test.js +146 -0
  87. package/dist/__tests__/prompts.test.js.map +1 -0
  88. package/dist/__tests__/proposals.test.d.ts +2 -0
  89. package/dist/__tests__/proposals.test.d.ts.map +1 -0
  90. package/dist/__tests__/proposals.test.js +167 -0
  91. package/dist/__tests__/proposals.test.js.map +1 -0
  92. package/dist/__tests__/relate.test.d.ts +2 -0
  93. package/dist/__tests__/relate.test.d.ts.map +1 -0
  94. package/dist/__tests__/relate.test.js +103 -0
  95. package/dist/__tests__/relate.test.js.map +1 -0
  96. package/dist/__tests__/repo-detect.test.d.ts +2 -0
  97. package/dist/__tests__/repo-detect.test.d.ts.map +1 -0
  98. package/dist/__tests__/repo-detect.test.js +215 -0
  99. package/dist/__tests__/repo-detect.test.js.map +1 -0
  100. package/dist/__tests__/runner.test.d.ts +2 -0
  101. package/dist/__tests__/runner.test.d.ts.map +1 -0
  102. package/dist/__tests__/runner.test.js +219 -0
  103. package/dist/__tests__/runner.test.js.map +1 -0
  104. package/dist/__tests__/session-touch.test.d.ts +2 -0
  105. package/dist/__tests__/session-touch.test.d.ts.map +1 -0
  106. package/dist/__tests__/session-touch.test.js +134 -0
  107. package/dist/__tests__/session-touch.test.js.map +1 -0
  108. package/dist/__tests__/session.test.d.ts +2 -0
  109. package/dist/__tests__/session.test.d.ts.map +1 -0
  110. package/dist/__tests__/session.test.js +46 -0
  111. package/dist/__tests__/session.test.js.map +1 -0
  112. package/dist/__tests__/setup.test.d.ts +2 -0
  113. package/dist/__tests__/setup.test.d.ts.map +1 -0
  114. package/dist/__tests__/setup.test.js +141 -0
  115. package/dist/__tests__/setup.test.js.map +1 -0
  116. package/dist/__tests__/spinner-labels.test.d.ts +2 -0
  117. package/dist/__tests__/spinner-labels.test.d.ts.map +1 -0
  118. package/dist/__tests__/spinner-labels.test.js +23 -0
  119. package/dist/__tests__/spinner-labels.test.js.map +1 -0
  120. package/dist/__tests__/strip.test.d.ts +2 -0
  121. package/dist/__tests__/strip.test.d.ts.map +1 -0
  122. package/dist/__tests__/strip.test.js +136 -0
  123. package/dist/__tests__/strip.test.js.map +1 -0
  124. package/dist/__tests__/surface-profiles.test.d.ts +2 -0
  125. package/dist/__tests__/surface-profiles.test.d.ts.map +1 -0
  126. package/dist/__tests__/surface-profiles.test.js +233 -0
  127. package/dist/__tests__/surface-profiles.test.js.map +1 -0
  128. package/dist/__tests__/update.test.d.ts +2 -0
  129. package/dist/__tests__/update.test.d.ts.map +1 -0
  130. package/dist/__tests__/update.test.js +228 -0
  131. package/dist/__tests__/update.test.js.map +1 -0
  132. package/dist/__tests__/workspace.test.d.ts +2 -0
  133. package/dist/__tests__/workspace.test.d.ts.map +1 -0
  134. package/dist/__tests__/workspace.test.js +308 -0
  135. package/dist/__tests__/workspace.test.js.map +1 -0
  136. package/dist/commands/accept.d.ts +18 -0
  137. package/dist/commands/accept.d.ts.map +1 -0
  138. package/dist/commands/accept.js +76 -0
  139. package/dist/commands/accept.js.map +1 -0
  140. package/dist/commands/admin/cockpit.d.ts +88 -0
  141. package/dist/commands/admin/cockpit.d.ts.map +1 -0
  142. package/dist/commands/admin/cockpit.js +409 -0
  143. package/dist/commands/admin/cockpit.js.map +1 -0
  144. package/dist/commands/admin/index.d.ts +21 -0
  145. package/dist/commands/admin/index.d.ts.map +1 -0
  146. package/dist/commands/admin/index.js +254 -0
  147. package/dist/commands/admin/index.js.map +1 -0
  148. package/dist/commands/admin/inspect.d.ts +21 -0
  149. package/dist/commands/admin/inspect.d.ts.map +1 -0
  150. package/dist/commands/admin/inspect.js +536 -0
  151. package/dist/commands/admin/inspect.js.map +1 -0
  152. package/dist/commands/admin/inspect.test.d.ts +7 -0
  153. package/dist/commands/admin/inspect.test.d.ts.map +1 -0
  154. package/dist/commands/admin/inspect.test.js +71 -0
  155. package/dist/commands/admin/inspect.test.js.map +1 -0
  156. package/dist/commands/admin/seed.d.ts +32 -0
  157. package/dist/commands/admin/seed.d.ts.map +1 -0
  158. package/dist/commands/admin/seed.js +527 -0
  159. package/dist/commands/admin/seed.js.map +1 -0
  160. package/dist/commands/admin/seed.test.d.ts +6 -0
  161. package/dist/commands/admin/seed.test.d.ts.map +1 -0
  162. package/dist/commands/admin/seed.test.js +65 -0
  163. package/dist/commands/admin/seed.test.js.map +1 -0
  164. package/dist/commands/audit.d.ts +25 -0
  165. package/dist/commands/audit.d.ts.map +1 -0
  166. package/dist/commands/audit.js +188 -0
  167. package/dist/commands/audit.js.map +1 -0
  168. package/dist/commands/brand-pack.d.ts +2 -0
  169. package/dist/commands/brand-pack.d.ts.map +1 -0
  170. package/dist/commands/brand-pack.js +25 -0
  171. package/dist/commands/brand-pack.js.map +1 -0
  172. package/dist/commands/brief.d.ts +28 -0
  173. package/dist/commands/brief.d.ts.map +1 -0
  174. package/dist/commands/brief.js +75 -0
  175. package/dist/commands/brief.js.map +1 -0
  176. package/dist/commands/capture.d.ts +30 -0
  177. package/dist/commands/capture.d.ts.map +1 -0
  178. package/dist/commands/capture.js +385 -0
  179. package/dist/commands/capture.js.map +1 -0
  180. package/dist/commands/chain-walk.d.ts +14 -0
  181. package/dist/commands/chain-walk.d.ts.map +1 -0
  182. package/dist/commands/chain-walk.js +38 -0
  183. package/dist/commands/chain-walk.js.map +1 -0
  184. package/dist/commands/changes.d.ts +11 -0
  185. package/dist/commands/changes.d.ts.map +1 -0
  186. package/dist/commands/changes.js +46 -0
  187. package/dist/commands/changes.js.map +1 -0
  188. package/dist/commands/codex-prep.d.ts +12 -0
  189. package/dist/commands/codex-prep.d.ts.map +1 -0
  190. package/dist/commands/codex-prep.js +122 -0
  191. package/dist/commands/codex-prep.js.map +1 -0
  192. package/dist/commands/collections.d.ts +22 -0
  193. package/dist/commands/collections.d.ts.map +1 -0
  194. package/dist/commands/collections.js +77 -0
  195. package/dist/commands/collections.js.map +1 -0
  196. package/dist/commands/connect-integration.test.d.ts +7 -0
  197. package/dist/commands/connect-integration.test.d.ts.map +1 -0
  198. package/dist/commands/connect-integration.test.js +200 -0
  199. package/dist/commands/connect-integration.test.js.map +1 -0
  200. package/dist/commands/connect.d.ts +21 -0
  201. package/dist/commands/connect.d.ts.map +1 -0
  202. package/dist/commands/connect.js +279 -0
  203. package/dist/commands/connect.js.map +1 -0
  204. package/dist/commands/connect.test.d.ts +6 -0
  205. package/dist/commands/connect.test.d.ts.map +1 -0
  206. package/dist/commands/connect.test.js +230 -0
  207. package/dist/commands/connect.test.js.map +1 -0
  208. package/dist/commands/constellation.d.ts +11 -0
  209. package/dist/commands/constellation.d.ts.map +1 -0
  210. package/dist/commands/constellation.js +33 -0
  211. package/dist/commands/constellation.js.map +1 -0
  212. package/dist/commands/context.d.ts +2 -1
  213. package/dist/commands/context.d.ts.map +1 -1
  214. package/dist/commands/context.js +20 -9
  215. package/dist/commands/context.js.map +1 -1
  216. package/dist/commands/cross-cut.d.ts +11 -0
  217. package/dist/commands/cross-cut.d.ts.map +1 -0
  218. package/dist/commands/cross-cut.js +23 -0
  219. package/dist/commands/cross-cut.js.map +1 -0
  220. package/dist/commands/doctor.d.ts +18 -0
  221. package/dist/commands/doctor.d.ts.map +1 -0
  222. package/dist/commands/doctor.js +232 -0
  223. package/dist/commands/doctor.js.map +1 -0
  224. package/dist/commands/doctor.test.d.ts +8 -0
  225. package/dist/commands/doctor.test.d.ts.map +1 -0
  226. package/dist/commands/doctor.test.js +311 -0
  227. package/dist/commands/doctor.test.js.map +1 -0
  228. package/dist/commands/fields.d.ts +9 -0
  229. package/dist/commands/fields.d.ts.map +1 -0
  230. package/dist/commands/fields.js +30 -0
  231. package/dist/commands/fields.js.map +1 -0
  232. package/dist/commands/get.d.ts +8 -1
  233. package/dist/commands/get.d.ts.map +1 -1
  234. package/dist/commands/get.js +60 -7
  235. package/dist/commands/get.js.map +1 -1
  236. package/dist/commands/handshake.d.ts +28 -0
  237. package/dist/commands/handshake.d.ts.map +1 -0
  238. package/dist/commands/handshake.js +617 -0
  239. package/dist/commands/handshake.js.map +1 -0
  240. package/dist/commands/ingest.d.ts +8 -2
  241. package/dist/commands/ingest.d.ts.map +1 -1
  242. package/dist/commands/ingest.js +148 -25
  243. package/dist/commands/ingest.js.map +1 -1
  244. package/dist/commands/init.d.ts +14 -0
  245. package/dist/commands/init.d.ts.map +1 -0
  246. package/dist/commands/init.js +117 -0
  247. package/dist/commands/init.js.map +1 -0
  248. package/dist/commands/login.d.ts +4 -0
  249. package/dist/commands/login.d.ts.map +1 -1
  250. package/dist/commands/login.js +101 -38
  251. package/dist/commands/login.js.map +1 -1
  252. package/dist/commands/orient.d.ts +107 -1
  253. package/dist/commands/orient.d.ts.map +1 -1
  254. package/dist/commands/orient.js +24 -8
  255. package/dist/commands/orient.js.map +1 -1
  256. package/dist/commands/profile.d.ts +24 -0
  257. package/dist/commands/profile.d.ts.map +1 -0
  258. package/dist/commands/profile.js +82 -0
  259. package/dist/commands/profile.js.map +1 -0
  260. package/dist/commands/promote.d.ts +12 -0
  261. package/dist/commands/promote.d.ts.map +1 -0
  262. package/dist/commands/promote.js +90 -0
  263. package/dist/commands/promote.js.map +1 -0
  264. package/dist/commands/proposals.d.ts +9 -0
  265. package/dist/commands/proposals.d.ts.map +1 -0
  266. package/dist/commands/proposals.js +24 -0
  267. package/dist/commands/proposals.js.map +1 -0
  268. package/dist/commands/reject.d.ts +14 -0
  269. package/dist/commands/reject.d.ts.map +1 -0
  270. package/dist/commands/reject.js +43 -0
  271. package/dist/commands/reject.js.map +1 -0
  272. package/dist/commands/relate.d.ts +16 -0
  273. package/dist/commands/relate.d.ts.map +1 -0
  274. package/dist/commands/relate.js +98 -0
  275. package/dist/commands/relate.js.map +1 -0
  276. package/dist/commands/search.d.ts +1 -0
  277. package/dist/commands/search.d.ts.map +1 -1
  278. package/dist/commands/search.js +5 -3
  279. package/dist/commands/search.js.map +1 -1
  280. package/dist/commands/session.d.ts +20 -0
  281. package/dist/commands/session.d.ts.map +1 -0
  282. package/dist/commands/session.js +148 -0
  283. package/dist/commands/session.js.map +1 -0
  284. package/dist/commands/setup.d.ts +15 -0
  285. package/dist/commands/setup.d.ts.map +1 -0
  286. package/dist/commands/setup.js +168 -0
  287. package/dist/commands/setup.js.map +1 -0
  288. package/dist/commands/update.d.ts +17 -0
  289. package/dist/commands/update.d.ts.map +1 -0
  290. package/dist/commands/update.js +178 -0
  291. package/dist/commands/update.js.map +1 -0
  292. package/dist/commands/verify.d.ts +13 -0
  293. package/dist/commands/verify.d.ts.map +1 -0
  294. package/dist/commands/verify.js +49 -0
  295. package/dist/commands/verify.js.map +1 -0
  296. package/dist/commands/workspace.d.ts +41 -0
  297. package/dist/commands/workspace.d.ts.map +1 -0
  298. package/dist/commands/workspace.js +239 -0
  299. package/dist/commands/workspace.js.map +1 -0
  300. package/dist/formatters/audit.d.ts +46 -0
  301. package/dist/formatters/audit.d.ts.map +1 -0
  302. package/dist/formatters/audit.js +81 -0
  303. package/dist/formatters/audit.js.map +1 -0
  304. package/dist/formatters/brief.d.ts +112 -0
  305. package/dist/formatters/brief.d.ts.map +1 -0
  306. package/dist/formatters/brief.js +179 -0
  307. package/dist/formatters/brief.js.map +1 -0
  308. package/dist/formatters/capture.d.ts +48 -0
  309. package/dist/formatters/capture.d.ts.map +1 -0
  310. package/dist/formatters/capture.js +77 -0
  311. package/dist/formatters/capture.js.map +1 -0
  312. package/dist/formatters/chain-walk.d.ts +33 -0
  313. package/dist/formatters/chain-walk.d.ts.map +1 -0
  314. package/dist/formatters/chain-walk.js +54 -0
  315. package/dist/formatters/chain-walk.js.map +1 -0
  316. package/dist/formatters/changes.d.ts +25 -0
  317. package/dist/formatters/changes.d.ts.map +1 -0
  318. package/dist/formatters/changes.js +60 -0
  319. package/dist/formatters/changes.js.map +1 -0
  320. package/dist/formatters/collections.d.ts +40 -0
  321. package/dist/formatters/collections.d.ts.map +1 -0
  322. package/dist/formatters/collections.js +93 -0
  323. package/dist/formatters/collections.js.map +1 -0
  324. package/dist/formatters/constellation.d.ts +34 -0
  325. package/dist/formatters/constellation.d.ts.map +1 -0
  326. package/dist/formatters/constellation.js +38 -0
  327. package/dist/formatters/constellation.js.map +1 -0
  328. package/dist/formatters/cross-cut.d.ts +21 -0
  329. package/dist/formatters/cross-cut.d.ts.map +1 -0
  330. package/dist/formatters/cross-cut.js +32 -0
  331. package/dist/formatters/cross-cut.js.map +1 -0
  332. package/dist/formatters/entry.d.ts +11 -4
  333. package/dist/formatters/entry.d.ts.map +1 -1
  334. package/dist/formatters/entry.js +24 -8
  335. package/dist/formatters/entry.js.map +1 -1
  336. package/dist/formatters/fields.d.ts +32 -0
  337. package/dist/formatters/fields.d.ts.map +1 -0
  338. package/dist/formatters/fields.js +49 -0
  339. package/dist/formatters/fields.js.map +1 -0
  340. package/dist/formatters/handshake.d.ts +24 -0
  341. package/dist/formatters/handshake.d.ts.map +1 -0
  342. package/dist/formatters/handshake.js +69 -0
  343. package/dist/formatters/handshake.js.map +1 -0
  344. package/dist/formatters/orient.d.ts +104 -1
  345. package/dist/formatters/orient.d.ts.map +1 -1
  346. package/dist/formatters/orient.js +140 -17
  347. package/dist/formatters/orient.js.map +1 -1
  348. package/dist/formatters/promote.d.ts +30 -0
  349. package/dist/formatters/promote.d.ts.map +1 -0
  350. package/dist/formatters/promote.js +39 -0
  351. package/dist/formatters/promote.js.map +1 -0
  352. package/dist/formatters/proposals.d.ts +45 -0
  353. package/dist/formatters/proposals.d.ts.map +1 -0
  354. package/dist/formatters/proposals.js +62 -0
  355. package/dist/formatters/proposals.js.map +1 -0
  356. package/dist/formatters/relate.d.ts +14 -0
  357. package/dist/formatters/relate.d.ts.map +1 -0
  358. package/dist/formatters/relate.js +16 -0
  359. package/dist/formatters/relate.js.map +1 -0
  360. package/dist/formatters/search.d.ts +0 -4
  361. package/dist/formatters/search.d.ts.map +1 -1
  362. package/dist/formatters/search.js +4 -1
  363. package/dist/formatters/search.js.map +1 -1
  364. package/dist/formatters/session.d.ts +11 -0
  365. package/dist/formatters/session.d.ts.map +1 -0
  366. package/dist/formatters/session.js +53 -0
  367. package/dist/formatters/session.js.map +1 -0
  368. package/dist/formatters/update.d.ts +17 -0
  369. package/dist/formatters/update.d.ts.map +1 -0
  370. package/dist/formatters/update.js +45 -0
  371. package/dist/formatters/update.js.map +1 -0
  372. package/dist/formatters/verify.d.ts +11 -0
  373. package/dist/formatters/verify.d.ts.map +1 -0
  374. package/dist/formatters/verify.js +11 -0
  375. package/dist/formatters/verify.js.map +1 -0
  376. package/dist/generators/__tests__/surface-profiles.test.d.ts +2 -0
  377. package/dist/generators/__tests__/surface-profiles.test.d.ts.map +1 -0
  378. package/dist/generators/__tests__/surface-profiles.test.js +89 -0
  379. package/dist/generators/__tests__/surface-profiles.test.js.map +1 -0
  380. package/dist/generators/adapters.d.ts +44 -0
  381. package/dist/generators/adapters.d.ts.map +1 -0
  382. package/dist/generators/adapters.js +290 -0
  383. package/dist/generators/adapters.js.map +1 -0
  384. package/dist/generators/adapters.test.d.ts +2 -0
  385. package/dist/generators/adapters.test.d.ts.map +1 -0
  386. package/dist/generators/adapters.test.js +27 -0
  387. package/dist/generators/adapters.test.js.map +1 -0
  388. package/dist/generators/archetypes.d.ts +52 -0
  389. package/dist/generators/archetypes.d.ts.map +1 -0
  390. package/dist/generators/archetypes.js +153 -0
  391. package/dist/generators/archetypes.js.map +1 -0
  392. package/dist/generators/archetypes.test.d.ts +2 -0
  393. package/dist/generators/archetypes.test.d.ts.map +1 -0
  394. package/dist/generators/archetypes.test.js +237 -0
  395. package/dist/generators/archetypes.test.js.map +1 -0
  396. package/dist/generators/briefing-md.d.ts +8 -0
  397. package/dist/generators/briefing-md.d.ts.map +1 -0
  398. package/dist/generators/briefing-md.js +51 -0
  399. package/dist/generators/briefing-md.js.map +1 -0
  400. package/dist/generators/chain-classifier.d.ts +49 -0
  401. package/dist/generators/chain-classifier.d.ts.map +1 -0
  402. package/dist/generators/chain-classifier.js +180 -0
  403. package/dist/generators/chain-classifier.js.map +1 -0
  404. package/dist/generators/chain-classifier.test.d.ts +2 -0
  405. package/dist/generators/chain-classifier.test.d.ts.map +1 -0
  406. package/dist/generators/chain-classifier.test.js +257 -0
  407. package/dist/generators/chain-classifier.test.js.map +1 -0
  408. package/dist/generators/chain-rules.d.ts +42 -0
  409. package/dist/generators/chain-rules.d.ts.map +1 -0
  410. package/dist/generators/chain-rules.js +144 -0
  411. package/dist/generators/chain-rules.js.map +1 -0
  412. package/dist/generators/chain-rules.test.d.ts +2 -0
  413. package/dist/generators/chain-rules.test.d.ts.map +1 -0
  414. package/dist/generators/chain-rules.test.js +179 -0
  415. package/dist/generators/chain-rules.test.js.map +1 -0
  416. package/dist/generators/context-md.d.ts +8 -0
  417. package/dist/generators/context-md.d.ts.map +1 -0
  418. package/dist/generators/context-md.js +134 -0
  419. package/dist/generators/context-md.js.map +1 -0
  420. package/dist/generators/handshake-diff.d.ts +67 -0
  421. package/dist/generators/handshake-diff.d.ts.map +1 -0
  422. package/dist/generators/handshake-diff.js +183 -0
  423. package/dist/generators/handshake-diff.js.map +1 -0
  424. package/dist/generators/handshake-diff.test.d.ts +2 -0
  425. package/dist/generators/handshake-diff.test.d.ts.map +1 -0
  426. package/dist/generators/handshake-diff.test.js +264 -0
  427. package/dist/generators/handshake-diff.test.js.map +1 -0
  428. package/dist/generators/portable-knowledge.d.ts +143 -0
  429. package/dist/generators/portable-knowledge.d.ts.map +1 -0
  430. package/dist/generators/portable-knowledge.js +504 -0
  431. package/dist/generators/portable-knowledge.js.map +1 -0
  432. package/dist/generators/portable-knowledge.test.d.ts +2 -0
  433. package/dist/generators/portable-knowledge.test.d.ts.map +1 -0
  434. package/dist/generators/portable-knowledge.test.js +927 -0
  435. package/dist/generators/portable-knowledge.test.js.map +1 -0
  436. package/dist/generators/surface-profiles.d.ts +49 -0
  437. package/dist/generators/surface-profiles.d.ts.map +1 -0
  438. package/dist/generators/surface-profiles.js +98 -0
  439. package/dist/generators/surface-profiles.js.map +1 -0
  440. package/dist/index.d.ts +3 -2
  441. package/dist/index.d.ts.map +1 -1
  442. package/dist/index.js +642 -37
  443. package/dist/index.js.map +1 -1
  444. package/dist/lib/activation.d.ts +28 -0
  445. package/dist/lib/activation.d.ts.map +1 -0
  446. package/dist/lib/activation.js +57 -0
  447. package/dist/lib/activation.js.map +1 -0
  448. package/dist/lib/activation.test.d.ts +6 -0
  449. package/dist/lib/activation.test.d.ts.map +1 -0
  450. package/dist/lib/activation.test.js +121 -0
  451. package/dist/lib/activation.test.js.map +1 -0
  452. package/dist/lib/client.d.ts +61 -0
  453. package/dist/lib/client.d.ts.map +1 -1
  454. package/dist/lib/client.js +258 -12
  455. package/dist/lib/client.js.map +1 -1
  456. package/dist/lib/config.d.ts +84 -4
  457. package/dist/lib/config.d.ts.map +1 -1
  458. package/dist/lib/config.js +322 -42
  459. package/dist/lib/config.js.map +1 -1
  460. package/dist/lib/constants.d.ts +42 -0
  461. package/dist/lib/constants.d.ts.map +1 -0
  462. package/dist/lib/constants.js +76 -0
  463. package/dist/lib/constants.js.map +1 -0
  464. package/dist/lib/conversation-engine.d.ts +45 -0
  465. package/dist/lib/conversation-engine.d.ts.map +1 -0
  466. package/dist/lib/conversation-engine.js +112 -0
  467. package/dist/lib/conversation-engine.js.map +1 -0
  468. package/dist/lib/conversation-phases.d.ts +59 -0
  469. package/dist/lib/conversation-phases.d.ts.map +1 -0
  470. package/dist/lib/conversation-phases.js +11 -0
  471. package/dist/lib/conversation-phases.js.map +1 -0
  472. package/dist/lib/conversation-signals.d.ts +30 -0
  473. package/dist/lib/conversation-signals.d.ts.map +1 -0
  474. package/dist/lib/conversation-signals.js +64 -0
  475. package/dist/lib/conversation-signals.js.map +1 -0
  476. package/dist/lib/deployment.d.ts +23 -0
  477. package/dist/lib/deployment.d.ts.map +1 -0
  478. package/dist/lib/deployment.js +78 -0
  479. package/dist/lib/deployment.js.map +1 -0
  480. package/dist/lib/deployment.test.d.ts +5 -0
  481. package/dist/lib/deployment.test.d.ts.map +1 -0
  482. package/dist/lib/deployment.test.js +54 -0
  483. package/dist/lib/deployment.test.js.map +1 -0
  484. package/dist/lib/errors.d.ts +58 -0
  485. package/dist/lib/errors.d.ts.map +1 -0
  486. package/dist/lib/errors.js +67 -0
  487. package/dist/lib/errors.js.map +1 -0
  488. package/dist/lib/experiment.d.ts +18 -0
  489. package/dist/lib/experiment.d.ts.map +1 -0
  490. package/dist/lib/experiment.js +28 -0
  491. package/dist/lib/experiment.js.map +1 -0
  492. package/dist/lib/format.d.ts +10 -0
  493. package/dist/lib/format.d.ts.map +1 -0
  494. package/dist/lib/format.js +27 -0
  495. package/dist/lib/format.js.map +1 -0
  496. package/dist/lib/glossary.d.ts +19 -0
  497. package/dist/lib/glossary.d.ts.map +1 -0
  498. package/dist/lib/glossary.js +53 -0
  499. package/dist/lib/glossary.js.map +1 -0
  500. package/dist/lib/onboarding-path-b.d.ts +10 -0
  501. package/dist/lib/onboarding-path-b.d.ts.map +1 -0
  502. package/dist/lib/onboarding-path-b.js +214 -0
  503. package/dist/lib/onboarding-path-b.js.map +1 -0
  504. package/dist/lib/onboarding-phases.d.ts +9 -0
  505. package/dist/lib/onboarding-phases.d.ts.map +1 -0
  506. package/dist/lib/onboarding-phases.js +120 -0
  507. package/dist/lib/onboarding-phases.js.map +1 -0
  508. package/dist/lib/onboarding-shared.d.ts +81 -0
  509. package/dist/lib/onboarding-shared.d.ts.map +1 -0
  510. package/dist/lib/onboarding-shared.js +190 -0
  511. package/dist/lib/onboarding-shared.js.map +1 -0
  512. package/dist/lib/onboarding-topics.d.ts +27 -0
  513. package/dist/lib/onboarding-topics.d.ts.map +1 -0
  514. package/dist/lib/onboarding-topics.js +57 -0
  515. package/dist/lib/onboarding-topics.js.map +1 -0
  516. package/dist/lib/onboarding.d.ts +17 -0
  517. package/dist/lib/onboarding.d.ts.map +1 -0
  518. package/dist/lib/onboarding.js +350 -0
  519. package/dist/lib/onboarding.js.map +1 -0
  520. package/dist/lib/profiles.d.ts +39 -0
  521. package/dist/lib/profiles.d.ts.map +1 -0
  522. package/dist/lib/profiles.js +185 -0
  523. package/dist/lib/profiles.js.map +1 -0
  524. package/dist/lib/prompts.d.ts +65 -0
  525. package/dist/lib/prompts.d.ts.map +1 -0
  526. package/dist/lib/prompts.js +132 -0
  527. package/dist/lib/prompts.js.map +1 -0
  528. package/dist/lib/repo-detect.d.ts +33 -0
  529. package/dist/lib/repo-detect.d.ts.map +1 -0
  530. package/dist/lib/repo-detect.js +83 -0
  531. package/dist/lib/repo-detect.js.map +1 -0
  532. package/dist/lib/runner.d.ts +33 -0
  533. package/dist/lib/runner.d.ts.map +1 -0
  534. package/dist/lib/runner.js +79 -0
  535. package/dist/lib/runner.js.map +1 -0
  536. package/dist/lib/session.d.ts +17 -0
  537. package/dist/lib/session.d.ts.map +1 -0
  538. package/dist/lib/session.js +43 -0
  539. package/dist/lib/session.js.map +1 -0
  540. package/dist/lib/spinner.d.ts +27 -0
  541. package/dist/lib/spinner.d.ts.map +1 -0
  542. package/dist/lib/spinner.js +76 -0
  543. package/dist/lib/spinner.js.map +1 -0
  544. package/dist/lib/spinner.test.d.ts +2 -0
  545. package/dist/lib/spinner.test.d.ts.map +1 -0
  546. package/dist/lib/spinner.test.js +39 -0
  547. package/dist/lib/spinner.test.js.map +1 -0
  548. package/dist/lib/strip.d.ts +12 -0
  549. package/dist/lib/strip.d.ts.map +1 -0
  550. package/dist/lib/strip.js +41 -0
  551. package/dist/lib/strip.js.map +1 -0
  552. package/dist/lib/style.d.ts +94 -0
  553. package/dist/lib/style.d.ts.map +1 -0
  554. package/dist/lib/style.js +167 -0
  555. package/dist/lib/style.js.map +1 -0
  556. package/dist/lib/style.test.d.ts +7 -0
  557. package/dist/lib/style.test.d.ts.map +1 -0
  558. package/dist/lib/style.test.js +263 -0
  559. package/dist/lib/style.test.js.map +1 -0
  560. package/dist/lib/telemetry.d.ts +15 -0
  561. package/dist/lib/telemetry.d.ts.map +1 -0
  562. package/dist/lib/telemetry.js +47 -0
  563. package/dist/lib/telemetry.js.map +1 -0
  564. package/dist/lib/tokenConstants.d.ts +17 -0
  565. package/dist/lib/tokenConstants.d.ts.map +1 -0
  566. package/dist/lib/tokenConstants.js +17 -0
  567. package/dist/lib/tokenConstants.js.map +1 -0
  568. package/dist/lib/wizard-surfaces.d.ts +47 -0
  569. package/dist/lib/wizard-surfaces.d.ts.map +1 -0
  570. package/dist/lib/wizard-surfaces.js +176 -0
  571. package/dist/lib/wizard-surfaces.js.map +1 -0
  572. package/dist/lib/wizard-surfaces.test.d.ts +2 -0
  573. package/dist/lib/wizard-surfaces.test.d.ts.map +1 -0
  574. package/dist/lib/wizard-surfaces.test.js +127 -0
  575. package/dist/lib/wizard-surfaces.test.js.map +1 -0
  576. package/dist/lib/wizard-trust.d.ts +31 -0
  577. package/dist/lib/wizard-trust.d.ts.map +1 -0
  578. package/dist/lib/wizard-trust.js +66 -0
  579. package/dist/lib/wizard-trust.js.map +1 -0
  580. package/dist/lib/wizard-trust.test.d.ts +2 -0
  581. package/dist/lib/wizard-trust.test.d.ts.map +1 -0
  582. package/dist/lib/wizard-trust.test.js +32 -0
  583. package/dist/lib/wizard-trust.test.js.map +1 -0
  584. package/dist/lib/workspace-probe.d.ts +16 -0
  585. package/dist/lib/workspace-probe.d.ts.map +1 -0
  586. package/dist/lib/workspace-probe.js +33 -0
  587. package/dist/lib/workspace-probe.js.map +1 -0
  588. package/package.json +13 -4
  589. package/templates/archetypes/boundary.md +23 -0
  590. package/templates/archetypes/constraint.md +23 -0
  591. package/templates/archetypes/convention.md +23 -0
  592. package/templates/archetypes/policy.md +23 -0
  593. package/templates/archetypes/quality-gate.md +23 -0
  594. package/templates/archetypes/workflow.md +23 -0
  595. package/templates/general/code-integrity.md +11 -0
  596. package/templates/general/getting-started.md +12 -0
  597. package/templates/node-ts/code-integrity.md +13 -0
  598. package/templates/node-ts/testing.md +12 -0
  599. package/templates/python/code-integrity.md +13 -0
  600. package/templates/python/testing.md +12 -0
@@ -0,0 +1,927 @@
1
+ /**
2
+ * portable-knowledge — unit tests.
3
+ * BET-169: transport-aware skill dispatch, target filtering, and transport section stripping.
4
+ */
5
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { join } from 'path';
7
+ // vi.mock calls are hoisted — use vi.hoisted() for constants referenced inside factories.
8
+ const { vfs } = vi.hoisted(() => ({
9
+ vfs: {},
10
+ }));
11
+ vi.mock('fs', () => ({
12
+ mkdirSync: vi.fn(),
13
+ writeFileSync: vi.fn((path, content) => {
14
+ vfs[path] = content;
15
+ }),
16
+ existsSync: vi.fn((path) => {
17
+ // Check if the path itself or any child key exists (for directory checks)
18
+ if (path in vfs)
19
+ return true;
20
+ // For directory checks: return true if any key starts with path + '/'
21
+ return Object.keys(vfs).some((k) => k.startsWith(path + '/'));
22
+ }),
23
+ readFileSync: vi.fn((path, _enc) => {
24
+ if (path in vfs)
25
+ return vfs[path];
26
+ throw Object.assign(new Error(`ENOENT: no such file '${path}'`), { code: 'ENOENT' });
27
+ }),
28
+ readdirSync: vi.fn((dir) => {
29
+ const prefix = dir.endsWith('/') ? dir : dir + '/';
30
+ const files = new Set();
31
+ for (const key of Object.keys(vfs)) {
32
+ if (key.startsWith(prefix)) {
33
+ const rest = key.slice(prefix.length);
34
+ const parts = rest.split('/');
35
+ if (parts.length === 1)
36
+ files.add(parts[0]);
37
+ }
38
+ }
39
+ return [...files];
40
+ }),
41
+ }));
42
+ import { readCanonicalSkills, readCanonicalRules, shouldEmitToTarget, stripTransportSections, filterByLevel, evaluateConditions, generateCursorSkill, generateCursorRule, generateClaudeRule, generateCodexSkill, generateCodexSkillIndex, generateClaudeSkillRouter, } from './portable-knowledge.js';
43
+ const PB_DIR = '/tmp/pb-test/.productbrain';
44
+ describe('readCanonicalSkills', () => {
45
+ beforeEach(() => {
46
+ Object.keys(vfs).forEach((k) => delete vfs[k]);
47
+ });
48
+ it('parses targets from frontmatter', () => {
49
+ vfs[join(PB_DIR, 'skills', 'test-skill.md')] = `---
50
+ name: test-skill
51
+ description: A test skill
52
+ triggers:
53
+ - test
54
+ targets:
55
+ - claude
56
+ ---
57
+
58
+ # Test Skill Body
59
+ `;
60
+ const skills = readCanonicalSkills(PB_DIR);
61
+ expect(skills).toHaveLength(1);
62
+ expect(skills[0].targets).toEqual(['claude']);
63
+ });
64
+ it('returns undefined targets when not specified in frontmatter', () => {
65
+ vfs[join(PB_DIR, 'skills', 'universal-skill.md')] = `---
66
+ name: universal-skill
67
+ description: A universal skill
68
+ triggers:
69
+ - universal
70
+ ---
71
+
72
+ # Universal Skill Body
73
+ `;
74
+ const skills = readCanonicalSkills(PB_DIR);
75
+ expect(skills).toHaveLength(1);
76
+ expect(skills[0].targets).toBeUndefined();
77
+ });
78
+ it('parses multiple targets', () => {
79
+ vfs[join(PB_DIR, 'skills', 'multi-target.md')] = `---
80
+ name: multi-target
81
+ description: Multi-target skill
82
+ triggers:
83
+ - multi
84
+ targets:
85
+ - claude
86
+ - cursor
87
+ ---
88
+
89
+ # Multi-Target Body
90
+ `;
91
+ const skills = readCanonicalSkills(PB_DIR);
92
+ expect(skills).toHaveLength(1);
93
+ expect(skills[0].targets).toEqual(['claude', 'cursor']);
94
+ });
95
+ it('parses level from frontmatter', () => {
96
+ vfs[join(PB_DIR, 'skills', 'leveled-skill.md')] = `---
97
+ name: leveled-skill
98
+ description: A leveled skill
99
+ level: core
100
+ triggers:
101
+ - leveled
102
+ ---
103
+
104
+ # Leveled Skill Body
105
+ `;
106
+ const skills = readCanonicalSkills(PB_DIR);
107
+ expect(skills).toHaveLength(1);
108
+ expect(skills[0].level).toBe('core');
109
+ });
110
+ it('returns undefined level when not specified in frontmatter', () => {
111
+ vfs[join(PB_DIR, 'skills', 'no-level-skill.md')] = `---
112
+ name: no-level-skill
113
+ description: A skill without level
114
+ triggers:
115
+ - nolevel
116
+ ---
117
+
118
+ # No Level Skill Body
119
+ `;
120
+ const skills = readCanonicalSkills(PB_DIR);
121
+ expect(skills).toHaveLength(1);
122
+ expect(skills[0].level).toBeUndefined();
123
+ });
124
+ });
125
+ describe('shouldEmitToTarget', () => {
126
+ it('returns true when targets is undefined (emit to all)', () => {
127
+ const skill = {
128
+ name: 'test',
129
+ description: '',
130
+ triggers: [],
131
+ body: '',
132
+ sourcePath: '',
133
+ };
134
+ expect(shouldEmitToTarget(skill, 'claude')).toBe(true);
135
+ expect(shouldEmitToTarget(skill, 'cursor')).toBe(true);
136
+ expect(shouldEmitToTarget(skill, 'copilot')).toBe(true);
137
+ expect(shouldEmitToTarget(skill, 'codex')).toBe(true);
138
+ });
139
+ it('returns true when target is in the list', () => {
140
+ const skill = {
141
+ name: 'test',
142
+ description: '',
143
+ triggers: [],
144
+ body: '',
145
+ sourcePath: '',
146
+ targets: ['claude'],
147
+ };
148
+ expect(shouldEmitToTarget(skill, 'claude')).toBe(true);
149
+ });
150
+ it('returns false when target is not in the list', () => {
151
+ const skill = {
152
+ name: 'test',
153
+ description: '',
154
+ triggers: [],
155
+ body: '',
156
+ sourcePath: '',
157
+ targets: ['claude'],
158
+ };
159
+ expect(shouldEmitToTarget(skill, 'cursor')).toBe(false);
160
+ expect(shouldEmitToTarget(skill, 'copilot')).toBe(false);
161
+ });
162
+ it('works for CanonicalRule as well', () => {
163
+ const rule = {
164
+ name: 'test-rule',
165
+ description: '',
166
+ autoApply: true,
167
+ body: '',
168
+ sourcePath: '',
169
+ targets: ['cursor'],
170
+ };
171
+ expect(shouldEmitToTarget(rule, 'cursor')).toBe(true);
172
+ expect(shouldEmitToTarget(rule, 'claude')).toBe(false);
173
+ });
174
+ });
175
+ describe('stripTransportSections', () => {
176
+ it('keeps content for the matching target and removes delimiters', () => {
177
+ const body = `Universal content above.
178
+
179
+ <!-- transport:claude -->
180
+ Claude-specific instructions here.
181
+ <!-- /transport -->
182
+
183
+ Universal content below.`;
184
+ const result = stripTransportSections(body, 'claude');
185
+ expect(result).toContain('Claude-specific instructions here.');
186
+ expect(result).toContain('Universal content above.');
187
+ expect(result).toContain('Universal content below.');
188
+ expect(result).not.toContain('<!-- transport:claude -->');
189
+ expect(result).not.toContain('<!-- /transport -->');
190
+ });
191
+ it('removes blocks for other targets entirely', () => {
192
+ const body = `Universal content.
193
+
194
+ <!-- transport:cursor -->
195
+ Cursor-only instructions.
196
+ <!-- /transport -->
197
+
198
+ More universal.`;
199
+ const result = stripTransportSections(body, 'claude');
200
+ expect(result).toContain('Universal content.');
201
+ expect(result).toContain('More universal.');
202
+ expect(result).not.toContain('Cursor-only instructions.');
203
+ expect(result).not.toContain('<!-- transport:cursor -->');
204
+ });
205
+ it('handles multiple transport blocks for different targets', () => {
206
+ const body = `# Heading
207
+
208
+ <!-- transport:claude -->
209
+ Claude dispatch: use Agent tool.
210
+ <!-- /transport -->
211
+
212
+ <!-- transport:cursor -->
213
+ Cursor dispatch: use fresh conversation.
214
+ <!-- /transport -->
215
+
216
+ ## Footer`;
217
+ const claudeResult = stripTransportSections(body, 'claude');
218
+ expect(claudeResult).toContain('Claude dispatch: use Agent tool.');
219
+ expect(claudeResult).not.toContain('Cursor dispatch: use fresh conversation.');
220
+ expect(claudeResult).toContain('# Heading');
221
+ expect(claudeResult).toContain('## Footer');
222
+ const cursorResult = stripTransportSections(body, 'cursor');
223
+ expect(cursorResult).toContain('Cursor dispatch: use fresh conversation.');
224
+ expect(cursorResult).not.toContain('Claude dispatch: use Agent tool.');
225
+ expect(cursorResult).toContain('# Heading');
226
+ expect(cursorResult).toContain('## Footer');
227
+ });
228
+ it('keeps universal content untouched when no transport blocks exist', () => {
229
+ const body = `# Just a normal skill
230
+
231
+ No transport sections here.
232
+
233
+ ## Section 2
234
+
235
+ More content.`;
236
+ const result = stripTransportSections(body, 'claude');
237
+ expect(result).toBe(body);
238
+ });
239
+ it('handles markdown and code blocks inside transport sections', () => {
240
+ const body = `Universal.
241
+
242
+ <!-- transport:claude -->
243
+ **Bold text** and \`inline code\`.
244
+
245
+ \`\`\`bash
246
+ git diff main..HEAD
247
+ \`\`\`
248
+
249
+ - List item 1
250
+ - List item 2
251
+ <!-- /transport -->
252
+
253
+ End.`;
254
+ const result = stripTransportSections(body, 'claude');
255
+ expect(result).toContain('**Bold text** and `inline code`.');
256
+ expect(result).toContain('git diff main..HEAD');
257
+ expect(result).toContain('- List item 1');
258
+ expect(result).toContain('End.');
259
+ });
260
+ it('handles transport sections with no trailing newline after closing tag', () => {
261
+ const body = `Before.
262
+ <!-- transport:claude -->
263
+ Content.
264
+ <!-- /transport -->After.`;
265
+ const result = stripTransportSections(body, 'claude');
266
+ expect(result).toContain('Content.');
267
+ expect(result).toContain('After.');
268
+ });
269
+ });
270
+ describe('generateCursorSkill (transport stripping)', () => {
271
+ it('strips non-cursor transport sections from generated output', () => {
272
+ const skill = {
273
+ name: 'test-skill',
274
+ description: 'Test skill',
275
+ triggers: ['test'],
276
+ body: `# Heading
277
+
278
+ <!-- transport:claude -->
279
+ Claude-only content.
280
+ <!-- /transport -->
281
+
282
+ <!-- transport:cursor -->
283
+ Cursor-only content.
284
+ <!-- /transport -->
285
+
286
+ Universal content.`,
287
+ sourcePath: '/tmp/.productbrain/skills/test-skill.md',
288
+ };
289
+ const output = generateCursorSkill(skill);
290
+ expect(output).toContain('Cursor-only content.');
291
+ expect(output).not.toContain('Claude-only content.');
292
+ expect(output).toContain('Universal content.');
293
+ expect(output).not.toContain('<!-- transport:');
294
+ });
295
+ });
296
+ describe('generateCodexSkill / generateCodexSkillIndex', () => {
297
+ it('strips non-codex transport sections from generated Codex skill output', () => {
298
+ const skill = {
299
+ name: 'test-skill',
300
+ description: 'Test skill',
301
+ triggers: ['test'],
302
+ body: `# Heading
303
+
304
+ <!-- transport:claude -->
305
+ Claude-only content.
306
+ <!-- /transport -->
307
+
308
+ <!-- transport:codex -->
309
+ Codex-only content.
310
+ <!-- /transport -->
311
+
312
+ Universal content.`,
313
+ sourcePath: '/tmp/.productbrain/skills/test-skill.md',
314
+ };
315
+ const output = generateCodexSkill(skill);
316
+ expect(output).toContain('Codex-only content.');
317
+ expect(output).not.toContain('Claude-only content.');
318
+ expect(output).toContain('Universal content.');
319
+ });
320
+ it('generates a Codex skill index with links to projected skills', () => {
321
+ const skills = [
322
+ {
323
+ name: 'preflight',
324
+ description: 'Ground the task before changing code.',
325
+ triggers: ['preflight', 'system check'],
326
+ body: '# Preflight',
327
+ sourcePath: '/tmp/.productbrain/skills/preflight.md',
328
+ },
329
+ ];
330
+ const output = generateCodexSkillIndex(skills);
331
+ expect(output).toContain('Product Brain Skills for Codex');
332
+ expect(output).toContain('## preflight');
333
+ expect(output).toContain('`preflight`');
334
+ expect(output).toContain('Read: `.codex/skills/preflight.md`');
335
+ });
336
+ it('hides learnings and template files from the Codex skill index', () => {
337
+ const skills = [
338
+ {
339
+ name: 'preflight',
340
+ description: 'Ground the task before changing code.',
341
+ triggers: ['preflight'],
342
+ body: '# Preflight',
343
+ sourcePath: '/tmp/.productbrain/skills/preflight.md',
344
+ },
345
+ {
346
+ name: 'retro-learnings',
347
+ description: 'Internal learnings companion.',
348
+ triggers: [],
349
+ body: '# Retro Learnings',
350
+ sourcePath: '/tmp/.productbrain/skills/retro-learnings.md',
351
+ },
352
+ {
353
+ name: 'LEARNINGS-TEMPLATE',
354
+ description: 'Template',
355
+ triggers: [],
356
+ body: '# Template',
357
+ sourcePath: '/tmp/.productbrain/skills/LEARNINGS-TEMPLATE.md',
358
+ },
359
+ ];
360
+ const output = generateCodexSkillIndex(skills);
361
+ expect(output).toContain('## preflight');
362
+ expect(output).not.toContain('## retro-learnings');
363
+ expect(output).not.toContain('## LEARNINGS-TEMPLATE');
364
+ expect(output).toContain('hidden from this index');
365
+ });
366
+ });
367
+ describe('generateClaudeSkillRouter (target filtering)', () => {
368
+ it('includes only skills with matching or no targets', () => {
369
+ const claudeOnly = {
370
+ name: 'claude-skill',
371
+ description: 'Claude only',
372
+ triggers: ['test-claude'],
373
+ body: '# Claude',
374
+ sourcePath: '/tmp/skills/claude-skill.md',
375
+ targets: ['claude'],
376
+ };
377
+ const cursorOnly = {
378
+ name: 'cursor-skill',
379
+ description: 'Cursor only',
380
+ triggers: ['test-cursor'],
381
+ body: '# Cursor',
382
+ sourcePath: '/tmp/skills/cursor-skill.md',
383
+ targets: ['cursor'],
384
+ };
385
+ const universal = {
386
+ name: 'universal-skill',
387
+ description: 'Universal',
388
+ triggers: ['test-universal'],
389
+ body: '# Universal',
390
+ sourcePath: '/tmp/skills/universal-skill.md',
391
+ };
392
+ // Filter skills for Claude (as handshake.ts would do)
393
+ const claudeSkills = [claudeOnly, cursorOnly, universal].filter((s) => shouldEmitToTarget(s, 'claude'));
394
+ const router = generateClaudeSkillRouter(claudeSkills);
395
+ expect(router).toContain('claude-skill');
396
+ expect(router).toContain('universal-skill');
397
+ expect(router).not.toContain('cursor-skill');
398
+ });
399
+ it('skill with targets: [claude] does not appear in Cursor output', () => {
400
+ const claudeOnly = {
401
+ name: 'claude-exclusive',
402
+ description: 'Claude exclusive skill',
403
+ triggers: ['claude-trigger'],
404
+ body: '# Claude Exclusive',
405
+ sourcePath: '/tmp/skills/claude-exclusive.md',
406
+ targets: ['claude'],
407
+ };
408
+ // Filter for Cursor (as handshake.ts would do)
409
+ const cursorSkills = [claudeOnly].filter((s) => shouldEmitToTarget(s, 'cursor'));
410
+ expect(cursorSkills).toHaveLength(0);
411
+ });
412
+ it('skill with no targets appears in both Claude and Cursor output', () => {
413
+ const universal = {
414
+ name: 'universal',
415
+ description: 'Universal skill',
416
+ triggers: ['uni'],
417
+ body: '# Universal',
418
+ sourcePath: '/tmp/skills/universal.md',
419
+ };
420
+ const claudeSkills = [universal].filter((s) => shouldEmitToTarget(s, 'claude'));
421
+ const cursorSkills = [universal].filter((s) => shouldEmitToTarget(s, 'cursor'));
422
+ expect(claudeSkills).toHaveLength(1);
423
+ expect(cursorSkills).toHaveLength(1);
424
+ const router = generateClaudeSkillRouter(claudeSkills);
425
+ expect(router).toContain('universal');
426
+ const cursorOutput = generateCursorSkill(cursorSkills[0]);
427
+ expect(cursorOutput).toContain('universal');
428
+ });
429
+ });
430
+ describe('generateCursorRule (scope propagation)', () => {
431
+ const baseRule = {
432
+ name: 'test-rule',
433
+ description: 'A test rule',
434
+ autoApply: true,
435
+ body: '# Rule body content',
436
+ sourcePath: '/tmp/.productbrain/rules/test-rule.md',
437
+ };
438
+ it('emits empty globs and alwaysApply: true when scope is empty string', () => {
439
+ const rule = { ...baseRule, scope: '' };
440
+ const output = generateCursorRule(rule);
441
+ expect(output).toContain('globs: ');
442
+ expect(output).toContain('alwaysApply: true');
443
+ expect(output).toContain('# Rule body content');
444
+ });
445
+ it('emits empty globs and alwaysApply: true when scope is undefined', () => {
446
+ const rule = { ...baseRule };
447
+ const output = generateCursorRule(rule);
448
+ expect(output).toContain('globs: ');
449
+ expect(output).toContain('alwaysApply: true');
450
+ });
451
+ it('emits glob pattern and alwaysApply: false when scope is set', () => {
452
+ const rule = { ...baseRule, scope: 'convex/**/*.ts' };
453
+ const output = generateCursorRule(rule);
454
+ expect(output).toContain('globs: convex/**/*.ts');
455
+ expect(output).toContain('alwaysApply: false');
456
+ });
457
+ it('respects autoApply: false with empty scope', () => {
458
+ const rule = { ...baseRule, autoApply: false, scope: '' };
459
+ const output = generateCursorRule(rule);
460
+ expect(output).toContain('alwaysApply: false');
461
+ });
462
+ it('replaces .productbrain paths with .cursor paths in body', () => {
463
+ const rule = {
464
+ ...baseRule,
465
+ body: 'Read `.productbrain/rules/foo.md` and `.productbrain/skills/bar.md`.',
466
+ };
467
+ const output = generateCursorRule(rule);
468
+ expect(output).toContain('.cursor/rules/foo.mdc');
469
+ expect(output).toContain('.cursor/skills/bar/SKILL.md');
470
+ expect(output).not.toContain('.productbrain/rules/foo.md');
471
+ });
472
+ it('includes source marker comment', () => {
473
+ const output = generateCursorRule(baseRule);
474
+ expect(output).toContain('source: .productbrain/rules/test-rule.md');
475
+ });
476
+ });
477
+ describe('generateClaudeRule (scope propagation)', () => {
478
+ const baseRule = {
479
+ name: 'test-rule',
480
+ description: 'A test rule',
481
+ autoApply: true,
482
+ body: '# Rule body content',
483
+ sourcePath: '/tmp/.productbrain/rules/test-rule.md',
484
+ };
485
+ it('omits paths when scope is empty string', () => {
486
+ const rule = { ...baseRule, scope: '' };
487
+ const output = generateClaudeRule(rule);
488
+ expect(output).not.toContain('paths:');
489
+ expect(output).toContain('description: "A test rule"');
490
+ expect(output).toContain('# Rule body content');
491
+ });
492
+ it('omits paths when scope is undefined', () => {
493
+ const rule = { ...baseRule };
494
+ const output = generateClaudeRule(rule);
495
+ expect(output).not.toContain('paths:');
496
+ });
497
+ it('emits paths array when scope is set', () => {
498
+ const rule = { ...baseRule, scope: 'convex/**/*.ts' };
499
+ const output = generateClaudeRule(rule);
500
+ expect(output).toContain('paths:');
501
+ expect(output).toContain(' - "convex/**/*.ts"');
502
+ });
503
+ it('escapes quotes in description', () => {
504
+ const rule = { ...baseRule, description: 'Rule with "quotes" inside' };
505
+ const output = generateClaudeRule(rule);
506
+ expect(output).toContain('description: "Rule with \\"quotes\\" inside"');
507
+ });
508
+ it('replaces .productbrain paths with .claude paths in body', () => {
509
+ const rule = {
510
+ ...baseRule,
511
+ body: 'Read `.productbrain/rules/foo.md` for details.',
512
+ };
513
+ const output = generateClaudeRule(rule);
514
+ expect(output).toContain('.claude/rules/foo.md');
515
+ expect(output).not.toContain('.productbrain/rules/foo.md');
516
+ });
517
+ it('includes source marker comment', () => {
518
+ const output = generateClaudeRule(baseRule);
519
+ expect(output).toContain('source: .productbrain/rules/test-rule.md');
520
+ });
521
+ });
522
+ describe('generateCodexSkillIndex (empty primary skills)', () => {
523
+ it('shows empty message when all skills are learnings/templates', () => {
524
+ const skills = [
525
+ {
526
+ name: 'retro-learnings',
527
+ description: 'Internal learnings companion.',
528
+ triggers: [],
529
+ body: '# Retro Learnings',
530
+ sourcePath: '/tmp/.productbrain/skills/retro-learnings.md',
531
+ },
532
+ {
533
+ name: 'LEARNINGS-TEMPLATE',
534
+ description: 'Template',
535
+ triggers: [],
536
+ body: '# Template',
537
+ sourcePath: '/tmp/.productbrain/skills/LEARNINGS-TEMPLATE.md',
538
+ },
539
+ ];
540
+ const output = generateCodexSkillIndex(skills);
541
+ expect(output).toContain('No Product Brain skills are currently projected');
542
+ });
543
+ });
544
+ describe('filterByLevel', () => {
545
+ const items = [
546
+ { name: 'core-item', level: 'core' },
547
+ { name: 'intermediate-item', level: 'intermediate' },
548
+ { name: 'expert-item', level: 'expert' },
549
+ { name: 'no-level-item' },
550
+ ];
551
+ it('filterByLevel("beginner") returns only level:core items + items with no level', () => {
552
+ const result = filterByLevel(items, 'beginner');
553
+ const names = result.map((i) => i.name);
554
+ expect(names).toContain('core-item');
555
+ expect(names).toContain('no-level-item');
556
+ expect(names).not.toContain('intermediate-item');
557
+ expect(names).not.toContain('expert-item');
558
+ expect(result).toHaveLength(2);
559
+ });
560
+ it('filterByLevel("intermediate") returns core + intermediate + no level', () => {
561
+ const result = filterByLevel(items, 'intermediate');
562
+ const names = result.map((i) => i.name);
563
+ expect(names).toContain('core-item');
564
+ expect(names).toContain('intermediate-item');
565
+ expect(names).toContain('no-level-item');
566
+ expect(names).not.toContain('expert-item');
567
+ expect(result).toHaveLength(3);
568
+ });
569
+ it('filterByLevel("expert") returns all items', () => {
570
+ const result = filterByLevel(items, 'expert');
571
+ expect(result).toHaveLength(4);
572
+ });
573
+ it('filterByLevel(undefined) returns all items (backward compat)', () => {
574
+ const result = filterByLevel(items, undefined);
575
+ expect(result).toHaveLength(4);
576
+ });
577
+ it('filterByLevel(null-ish) returns all items (backward compat)', () => {
578
+ const result = filterByLevel(items);
579
+ expect(result).toHaveLength(4);
580
+ });
581
+ it('unknown level throws an error', () => {
582
+ expect(() => filterByLevel(items, 'unknown')).toThrow('Unknown level "unknown"');
583
+ });
584
+ });
585
+ describe('filterByLevel — stage-gating (DEC-443)', () => {
586
+ const items = [
587
+ { name: 'core-item', level: 'core' },
588
+ { name: 'intermediate-item', level: 'intermediate' },
589
+ { name: 'expert-item', level: 'expert' },
590
+ { name: 'no-level-item' },
591
+ ];
592
+ it('stage=blank caps at core even if requestedLevel=expert', () => {
593
+ const result = filterByLevel(items, 'expert', 'blank');
594
+ const names = result.map((i) => i.name);
595
+ expect(names).toContain('core-item');
596
+ expect(names).toContain('no-level-item');
597
+ expect(names).not.toContain('intermediate-item');
598
+ expect(names).not.toContain('expert-item');
599
+ expect(result).toHaveLength(2);
600
+ });
601
+ it('stage=seed caps at intermediate (core + intermediate + no-level)', () => {
602
+ const result = filterByLevel(items, 'expert', 'seed');
603
+ const names = result.map((i) => i.name);
604
+ expect(names).toContain('core-item');
605
+ expect(names).toContain('intermediate-item');
606
+ expect(names).toContain('no-level-item');
607
+ expect(names).not.toContain('expert-item');
608
+ expect(result).toHaveLength(3);
609
+ });
610
+ it('stage=grounded applies no cap — all items returned', () => {
611
+ const result = filterByLevel(items, 'expert', 'grounded');
612
+ expect(result).toHaveLength(4);
613
+ });
614
+ it('stage=connected applies no cap — all items returned', () => {
615
+ const result = filterByLevel(items, 'expert', 'connected');
616
+ expect(result).toHaveLength(4);
617
+ });
618
+ it('stage=undefined (no profile) — no cap, backward compat', () => {
619
+ const result = filterByLevel(items, 'expert', undefined);
620
+ expect(result).toHaveLength(4);
621
+ });
622
+ it('unknown stage — fail-open, no cap applied', () => {
623
+ const result = filterByLevel(items, 'expert', 'unknown-stage');
624
+ expect(result).toHaveLength(4);
625
+ });
626
+ it('no requestedLevel + stage=blank still caps at beginner (core + no-level only)', () => {
627
+ const result = filterByLevel(items, undefined, 'blank');
628
+ const names = result.map((i) => i.name);
629
+ expect(names).toContain('core-item');
630
+ expect(names).toContain('no-level-item');
631
+ expect(names).not.toContain('intermediate-item');
632
+ expect(names).not.toContain('expert-item');
633
+ expect(result).toHaveLength(2);
634
+ });
635
+ });
636
+ describe('evaluateConditions', () => {
637
+ const noProfile = null;
638
+ const profile = {
639
+ stage: 'grounded',
640
+ totalRelations: 10,
641
+ };
642
+ const emptyRepo = { detectedStack: [] };
643
+ const sveltekitRepo = { detectedStack: ['sveltekit', 'typescript'] };
644
+ // 1. No conditions → included
645
+ it('no conditions → included', () => {
646
+ const result = evaluateConditions({}, profile, sveltekitRepo);
647
+ expect(result.included).toBe(true);
648
+ expect(result.reasons).toEqual(['no conditions']);
649
+ });
650
+ // 2. Matching when_stack → included
651
+ it('matching when_stack → included', () => {
652
+ const result = evaluateConditions({ when_stack: 'sveltekit' }, profile, sveltekitRepo);
653
+ expect(result.included).toBe(true);
654
+ expect(result.reasons.some((r) => r.includes('matched'))).toBe(true);
655
+ });
656
+ // 3. Non-matching when_stack → excluded with reason
657
+ it('non-matching when_stack → excluded with reason', () => {
658
+ const result = evaluateConditions({ when_stack: 'nextjs' }, profile, sveltekitRepo);
659
+ expect(result.included).toBe(false);
660
+ expect(result.reasons.some((r) => r.includes('when_stack=nextjs'))).toBe(true);
661
+ });
662
+ // 4. when_stack case-insensitive matching
663
+ it('when_stack is case-insensitive', () => {
664
+ const result = evaluateConditions({ when_stack: 'SvelteKit' }, profile, { detectedStack: ['sveltekit'] });
665
+ expect(result.included).toBe(true);
666
+ });
667
+ // 5. when_minStage ordering: blank < seed < grounded < connected < critical
668
+ it('when_minStage ordering: stage below threshold → excluded', () => {
669
+ const seedProfile = { stage: 'seed', totalRelations: 10 };
670
+ const result = evaluateConditions({ when_minStage: 'grounded' }, seedProfile, emptyRepo);
671
+ expect(result.included).toBe(false);
672
+ expect(result.reasons.some((r) => r.includes('when_minStage=grounded'))).toBe(true);
673
+ });
674
+ // 6. when_minStage with stage below threshold → excluded
675
+ it('when_minStage blank < connected threshold → excluded', () => {
676
+ const blankProfile = { stage: 'blank', totalRelations: 0 };
677
+ const result = evaluateConditions({ when_minStage: 'connected' }, blankProfile, emptyRepo);
678
+ expect(result.included).toBe(false);
679
+ });
680
+ // 7. when_minStage with stage at threshold → included
681
+ it('when_minStage with stage exactly at threshold → included', () => {
682
+ const result = evaluateConditions({ when_minStage: 'grounded' }, profile, emptyRepo);
683
+ expect(result.included).toBe(true);
684
+ expect(result.reasons.some((r) => r.includes('satisfied'))).toBe(true);
685
+ });
686
+ // Also verify stages above threshold pass
687
+ it('when_minStage with stage above threshold → included', () => {
688
+ const criticalProfile = { stage: 'critical', totalRelations: 10 };
689
+ const result = evaluateConditions({ when_minStage: 'grounded' }, criticalProfile, emptyRepo);
690
+ expect(result.included).toBe(true);
691
+ });
692
+ // 8. when_minGovernance with enough relations → included
693
+ it('when_minGovernance with enough relations → included', () => {
694
+ const result = evaluateConditions({ when_minGovernance: '5' }, profile, emptyRepo);
695
+ expect(result.included).toBe(true);
696
+ expect(result.reasons.some((r) => r.includes('satisfied'))).toBe(true);
697
+ });
698
+ // 9. when_minGovernance with too few → excluded
699
+ it('when_minGovernance with too few relations → excluded', () => {
700
+ const lowRelProfile = { stage: 'grounded', totalRelations: 3 };
701
+ const result = evaluateConditions({ when_minGovernance: '5' }, lowRelProfile, emptyRepo);
702
+ expect(result.included).toBe(false);
703
+ expect(result.reasons.some((r) => r.includes('when_minGovernance=5'))).toBe(true);
704
+ });
705
+ // 10. Multiple conditions: all pass → included
706
+ it('multiple conditions: all pass → included', () => {
707
+ const result = evaluateConditions({ when_stack: 'sveltekit', when_minStage: 'seed', when_minGovernance: '5' }, profile, sveltekitRepo);
708
+ expect(result.included).toBe(true);
709
+ });
710
+ // 11. Multiple conditions: one fails → excluded (AND semantics)
711
+ it('multiple conditions: one fails → excluded', () => {
712
+ const result = evaluateConditions({ when_stack: 'sveltekit', when_minStage: 'critical' }, profile, // stage=grounded, below critical
713
+ sveltekitRepo);
714
+ expect(result.included).toBe(false);
715
+ expect(result.reasons.some((r) => r.includes('when_minStage=critical'))).toBe(true);
716
+ });
717
+ // 12. Null profile + when_minStage → included (fail-open)
718
+ it('null profile with when_minStage → fail-open (included)', () => {
719
+ const result = evaluateConditions({ when_minStage: 'grounded' }, noProfile, emptyRepo);
720
+ expect(result.included).toBe(true);
721
+ expect(result.reasons.some((r) => r.includes('fail-open'))).toBe(true);
722
+ });
723
+ // 13. Null profile + when_stack only → still evaluated (stack comes from repoContext)
724
+ it('null profile with when_stack only → still evaluated from repoContext', () => {
725
+ const matchResult = evaluateConditions({ when_stack: 'sveltekit' }, noProfile, sveltekitRepo);
726
+ expect(matchResult.included).toBe(true);
727
+ const noMatchResult = evaluateConditions({ when_stack: 'nextjs' }, noProfile, sveltekitRepo);
728
+ expect(noMatchResult.included).toBe(false);
729
+ });
730
+ });
731
+ describe('BET-170 acceptance criteria — integration', () => {
732
+ /**
733
+ * Mock rules cover all combinations: stack condition, stage condition,
734
+ * governance condition, and unconditional at core/intermediate/expert levels.
735
+ */
736
+ const mockRules = [
737
+ { name: 'deployment', level: 'core', conditions: { when_stack: 'sveltekit' } },
738
+ { name: 'feature-flags', level: 'intermediate', conditions: { when_minStage: 'grounded' } },
739
+ { name: 'domain-boundaries', level: 'expert', conditions: { when_minGovernance: '5' } },
740
+ { name: 'code-integrity', level: 'core' }, // no conditions — always included
741
+ { name: 'review-gate', level: 'intermediate' }, // no conditions
742
+ { name: 'orchestrator-mode', level: 'expert' }, // no conditions
743
+ ];
744
+ function applyPipeline(rules, profile, repo) {
745
+ const profileStage = profile?.stage;
746
+ // stage-gated level filter: no requestedLevel (defaults to widest), stage caps it
747
+ const levelFiltered = filterByLevel(rules, undefined, profileStage);
748
+ // condition filter
749
+ return levelFiltered.filter((rule) => {
750
+ const result = evaluateConditions(rule.conditions ?? {}, profile, repo);
751
+ return result.included;
752
+ });
753
+ }
754
+ // AC1: Fresh Python workspace (stage=blank, 0 entries) — only core items with no conditions
755
+ it('AC1: stage=blank, python — only core unconditional items', () => {
756
+ const profile = { stage: 'blank', totalEntries: 0, totalRelations: 0 };
757
+ const repo = { detectedStack: ['python'] };
758
+ const result = applyPipeline(mockRules, profile, repo);
759
+ const names = result.map((r) => r.name);
760
+ // Only code-integrity: core level, no conditions
761
+ // deployment: core but when_stack=sveltekit fails (python != sveltekit)
762
+ expect(names).toContain('code-integrity');
763
+ expect(names).not.toContain('deployment'); // core but sveltekit condition fails
764
+ expect(names).not.toContain('feature-flags'); // intermediate — capped by blank stage
765
+ expect(names).not.toContain('domain-boundaries'); // expert — capped by blank stage
766
+ expect(names).not.toContain('review-gate'); // intermediate — capped by blank stage
767
+ expect(names).not.toContain('orchestrator-mode'); // expert — capped by blank stage
768
+ expect(result).toHaveLength(1);
769
+ });
770
+ // AC2: Node/TS workspace (stage=grounded, 30 entries) — stack-matched, all levels
771
+ it('AC2: stage=grounded, sveltekit/typescript, 30 entries — all rules included', () => {
772
+ const profile = { stage: 'grounded', totalEntries: 30, totalRelations: 15 };
773
+ const repo = { detectedStack: ['typescript', 'sveltekit'] };
774
+ const result = applyPipeline(mockRules, profile, repo);
775
+ const names = result.map((r) => r.name);
776
+ // All conditions pass: sveltekit matches, grounded >= grounded, 15 >= 5
777
+ expect(names).toContain('deployment');
778
+ expect(names).toContain('feature-flags');
779
+ expect(names).toContain('domain-boundaries');
780
+ expect(names).toContain('code-integrity');
781
+ expect(names).toContain('review-gate');
782
+ expect(names).toContain('orchestrator-mode');
783
+ expect(result).toHaveLength(6);
784
+ });
785
+ // AC3: Mature workspace (stage=connected, 100+ entries) — all rules and skills
786
+ it('AC3: stage=connected, 150 entries — all rules projected without filtering', () => {
787
+ const profile = { stage: 'connected', totalEntries: 150, totalRelations: 80 };
788
+ const repo = { detectedStack: ['typescript', 'sveltekit'] };
789
+ const result = applyPipeline(mockRules, profile, repo);
790
+ expect(result).toHaveLength(6);
791
+ });
792
+ // AC4: Unknown stack — general template set, no stack-specific rules
793
+ it('AC4: stage=grounded, unknown stack — only non-stack-conditional rules', () => {
794
+ const profile = { stage: 'grounded', totalEntries: 30, totalRelations: 15 };
795
+ const repo = { detectedStack: [] };
796
+ const result = applyPipeline(mockRules, profile, repo);
797
+ const names = result.map((r) => r.name);
798
+ // deployment requires sveltekit stack — excluded
799
+ // all others: feature-flags (grounded ok), domain-boundaries (15 >= 5), unconditionals all pass
800
+ expect(names).not.toContain('deployment');
801
+ expect(names).toContain('feature-flags');
802
+ expect(names).toContain('domain-boundaries');
803
+ expect(names).toContain('code-integrity');
804
+ expect(names).toContain('review-gate');
805
+ expect(names).toContain('orchestrator-mode');
806
+ expect(result).toHaveLength(5);
807
+ });
808
+ // AC5: workspaceReadiness failure (null profile) — fail-open, all rules/skills project
809
+ it('AC5: null profile (readiness failure) — fail-open, profile conditions pass', () => {
810
+ const profile = null;
811
+ const repo = { detectedStack: ['typescript'] };
812
+ const result = applyPipeline(mockRules, profile, repo);
813
+ const names = result.map((r) => r.name);
814
+ // Null profile:
815
+ // - filterByLevel: no stage cap → all levels pass
816
+ // - evaluateConditions: when_minStage + when_minGovernance → fail-open (included)
817
+ // - when_stack: still evaluated against repoContext; sveltekit not in [typescript] → excluded
818
+ expect(names).not.toContain('deployment'); // when_stack=sveltekit, typescript doesn't match
819
+ expect(names).toContain('feature-flags'); // fail-open (profile null)
820
+ expect(names).toContain('domain-boundaries'); // fail-open (profile null)
821
+ expect(names).toContain('code-integrity');
822
+ expect(names).toContain('review-gate');
823
+ expect(names).toContain('orchestrator-mode');
824
+ expect(result).toHaveLength(5);
825
+ });
826
+ // Scale test: 0/5/50/500 entries — filtering correctness at different entry counts
827
+ it('scale test: filtering correctness at 0/5/50/500 totalEntries', () => {
828
+ const repo = { detectedStack: ['sveltekit'] };
829
+ // 0 entries: blank stage, low relations
830
+ // deployment: core level (passes stage cap), sveltekit matches → included
831
+ // code-integrity: core level, no conditions → included
832
+ // everything else: capped by blank stage (intermediate/expert excluded)
833
+ const result0 = applyPipeline(mockRules, { stage: 'blank', totalEntries: 0, totalRelations: 0 }, repo);
834
+ const names0 = result0.map((r) => r.name);
835
+ expect(names0).toContain('deployment');
836
+ expect(names0).toContain('code-integrity');
837
+ expect(names0).not.toContain('feature-flags'); // intermediate — capped
838
+ expect(names0).not.toContain('domain-boundaries'); // expert — capped
839
+ expect(names0).not.toContain('review-gate'); // intermediate — capped
840
+ expect(names0).not.toContain('orchestrator-mode'); // expert — capped
841
+ expect(result0).toHaveLength(2);
842
+ // 5 entries: seed stage, 2 relations (below governance threshold)
843
+ const result5 = applyPipeline(mockRules, { stage: 'seed', totalEntries: 5, totalRelations: 2 }, repo);
844
+ const names5 = result5.map((r) => r.name);
845
+ // seed caps at intermediate; domain-boundaries (expert) + orchestrator-mode (expert) excluded
846
+ // domain-boundaries also fails governance (2 < 5)
847
+ // feature-flags: intermediate ok, but when_minStage=grounded fails for seed → excluded
848
+ // deployment: core ok, sveltekit matches → included
849
+ expect(names5).toContain('deployment');
850
+ expect(names5).toContain('code-integrity');
851
+ expect(names5).toContain('review-gate');
852
+ expect(names5).not.toContain('domain-boundaries');
853
+ expect(names5).not.toContain('orchestrator-mode');
854
+ expect(names5).not.toContain('feature-flags');
855
+ // 50 entries: grounded stage, 20 relations
856
+ const result50 = applyPipeline(mockRules, { stage: 'grounded', totalEntries: 50, totalRelations: 20 }, repo);
857
+ expect(result50).toHaveLength(6);
858
+ // 500 entries: connected stage, 200 relations
859
+ const result500 = applyPipeline(mockRules, { stage: 'connected', totalEntries: 500, totalRelations: 200 }, repo);
860
+ expect(result500).toHaveLength(6);
861
+ });
862
+ });
863
+ describe('readCanonicalRules — backward compat with when_* frontmatter', () => {
864
+ beforeEach(() => {
865
+ Object.keys(vfs).forEach((k) => delete vfs[k]);
866
+ });
867
+ it('parses when_stack from frontmatter into conditions', () => {
868
+ vfs[join(PB_DIR, 'rules', 'deploy.md')] = `---
869
+ name: deploy
870
+ description: Deploy rule
871
+ scope: ""
872
+ autoApply: true
873
+ when_stack: sveltekit
874
+ ---
875
+
876
+ # Deploy body
877
+ `;
878
+ const rules = readCanonicalRules(PB_DIR);
879
+ expect(rules).toHaveLength(1);
880
+ expect(rules[0].conditions?.when_stack).toBe('sveltekit');
881
+ });
882
+ it('parses when_minStage from frontmatter into conditions', () => {
883
+ vfs[join(PB_DIR, 'rules', 'flags.md')] = `---
884
+ name: flags
885
+ description: Feature flags rule
886
+ scope: ""
887
+ autoApply: false
888
+ when_minStage: grounded
889
+ ---
890
+
891
+ # Flags body
892
+ `;
893
+ const rules = readCanonicalRules(PB_DIR);
894
+ expect(rules).toHaveLength(1);
895
+ expect(rules[0].conditions?.when_minStage).toBe('grounded');
896
+ });
897
+ it('parses when_minGovernance from frontmatter into conditions', () => {
898
+ vfs[join(PB_DIR, 'rules', 'domains.md')] = `---
899
+ name: domains
900
+ description: Domain boundaries rule
901
+ scope: ""
902
+ autoApply: true
903
+ when_minGovernance: 5
904
+ ---
905
+
906
+ # Domains body
907
+ `;
908
+ const rules = readCanonicalRules(PB_DIR);
909
+ expect(rules).toHaveLength(1);
910
+ expect(rules[0].conditions?.when_minGovernance).toBe('5');
911
+ });
912
+ it('returns undefined conditions when no when_* keys are present (backward compat)', () => {
913
+ vfs[join(PB_DIR, 'rules', 'plain.md')] = `---
914
+ name: plain
915
+ description: Plain rule
916
+ scope: ""
917
+ autoApply: true
918
+ ---
919
+
920
+ # Plain body
921
+ `;
922
+ const rules = readCanonicalRules(PB_DIR);
923
+ expect(rules).toHaveLength(1);
924
+ expect(rules[0].conditions).toBeUndefined();
925
+ });
926
+ });
927
+ //# sourceMappingURL=portable-knowledge.test.js.map