@productbrain/cli 0.1.0-beta.1 → 0.1.0-beta.102

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 (747) hide show
  1. package/README.md +127 -0
  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__/authority-domains.test.d.ts +2 -0
  11. package/dist/__tests__/authority-domains.test.d.ts.map +1 -0
  12. package/dist/__tests__/authority-domains.test.js +48 -0
  13. package/dist/__tests__/authority-domains.test.js.map +1 -0
  14. package/dist/__tests__/batch-transformations.test.d.ts +2 -0
  15. package/dist/__tests__/batch-transformations.test.d.ts.map +1 -0
  16. package/dist/__tests__/batch-transformations.test.js +263 -0
  17. package/dist/__tests__/batch-transformations.test.js.map +1 -0
  18. package/dist/__tests__/capture.test.d.ts +2 -0
  19. package/dist/__tests__/capture.test.d.ts.map +1 -0
  20. package/dist/__tests__/capture.test.js +377 -0
  21. package/dist/__tests__/capture.test.js.map +1 -0
  22. package/dist/__tests__/config.test.d.ts +8 -0
  23. package/dist/__tests__/config.test.d.ts.map +1 -0
  24. package/dist/__tests__/config.test.js +296 -0
  25. package/dist/__tests__/config.test.js.map +1 -0
  26. package/dist/__tests__/constants.test.d.ts +2 -0
  27. package/dist/__tests__/constants.test.d.ts.map +1 -0
  28. package/dist/__tests__/constants.test.js +141 -0
  29. package/dist/__tests__/constants.test.js.map +1 -0
  30. package/dist/__tests__/constellation.test.d.ts +2 -0
  31. package/dist/__tests__/constellation.test.d.ts.map +1 -0
  32. package/dist/__tests__/constellation.test.js +254 -0
  33. package/dist/__tests__/constellation.test.js.map +1 -0
  34. package/dist/__tests__/context-strategy.test.d.ts +2 -0
  35. package/dist/__tests__/context-strategy.test.d.ts.map +1 -0
  36. package/dist/__tests__/context-strategy.test.js +79 -0
  37. package/dist/__tests__/context-strategy.test.js.map +1 -0
  38. package/dist/__tests__/envelope-contract.test.d.ts +15 -0
  39. package/dist/__tests__/envelope-contract.test.d.ts.map +1 -0
  40. package/dist/__tests__/envelope-contract.test.js +126 -0
  41. package/dist/__tests__/envelope-contract.test.js.map +1 -0
  42. package/dist/__tests__/errors.test.d.ts +2 -0
  43. package/dist/__tests__/errors.test.d.ts.map +1 -0
  44. package/dist/__tests__/errors.test.js +117 -0
  45. package/dist/__tests__/errors.test.js.map +1 -0
  46. package/dist/__tests__/experiment.test.d.ts +6 -0
  47. package/dist/__tests__/experiment.test.d.ts.map +1 -0
  48. package/dist/__tests__/experiment.test.js +69 -0
  49. package/dist/__tests__/experiment.test.js.map +1 -0
  50. package/dist/__tests__/fields.test.d.ts +2 -0
  51. package/dist/__tests__/fields.test.d.ts.map +1 -0
  52. package/dist/__tests__/fields.test.js +238 -0
  53. package/dist/__tests__/fields.test.js.map +1 -0
  54. package/dist/__tests__/glossary.test.d.ts +2 -0
  55. package/dist/__tests__/glossary.test.d.ts.map +1 -0
  56. package/dist/__tests__/glossary.test.js +32 -0
  57. package/dist/__tests__/glossary.test.js.map +1 -0
  58. package/dist/__tests__/handshake-preview.test.d.ts +2 -0
  59. package/dist/__tests__/handshake-preview.test.d.ts.map +1 -0
  60. package/dist/__tests__/handshake-preview.test.js +279 -0
  61. package/dist/__tests__/handshake-preview.test.js.map +1 -0
  62. package/dist/__tests__/handshake.test.d.ts +2 -0
  63. package/dist/__tests__/handshake.test.d.ts.map +1 -0
  64. package/dist/__tests__/handshake.test.js +555 -0
  65. package/dist/__tests__/handshake.test.js.map +1 -0
  66. package/dist/__tests__/hook-intents.test.d.ts +2 -0
  67. package/dist/__tests__/hook-intents.test.d.ts.map +1 -0
  68. package/dist/__tests__/hook-intents.test.js +184 -0
  69. package/dist/__tests__/hook-intents.test.js.map +1 -0
  70. package/dist/__tests__/ingest.test.d.ts +2 -0
  71. package/dist/__tests__/ingest.test.d.ts.map +1 -0
  72. package/dist/__tests__/ingest.test.js +185 -0
  73. package/dist/__tests__/ingest.test.js.map +1 -0
  74. package/dist/__tests__/init.test.d.ts +7 -0
  75. package/dist/__tests__/init.test.d.ts.map +1 -0
  76. package/dist/__tests__/init.test.js +146 -0
  77. package/dist/__tests__/init.test.js.map +1 -0
  78. package/dist/__tests__/login.test.d.ts +2 -0
  79. package/dist/__tests__/login.test.d.ts.map +1 -0
  80. package/dist/__tests__/login.test.js +167 -0
  81. package/dist/__tests__/login.test.js.map +1 -0
  82. package/dist/__tests__/manifest.test.d.ts +6 -0
  83. package/dist/__tests__/manifest.test.d.ts.map +1 -0
  84. package/dist/__tests__/manifest.test.js +138 -0
  85. package/dist/__tests__/manifest.test.js.map +1 -0
  86. package/dist/__tests__/method-registry.integration.test.d.ts +6 -0
  87. package/dist/__tests__/method-registry.integration.test.d.ts.map +1 -0
  88. package/dist/__tests__/method-registry.integration.test.js +18 -0
  89. package/dist/__tests__/method-registry.integration.test.js.map +1 -0
  90. package/dist/__tests__/method-registry.test.d.ts +14 -0
  91. package/dist/__tests__/method-registry.test.d.ts.map +1 -0
  92. package/dist/__tests__/method-registry.test.js +134 -0
  93. package/dist/__tests__/method-registry.test.js.map +1 -0
  94. package/dist/__tests__/onboarding-path-b.test.d.ts +2 -0
  95. package/dist/__tests__/onboarding-path-b.test.d.ts.map +1 -0
  96. package/dist/__tests__/onboarding-path-b.test.js +46 -0
  97. package/dist/__tests__/onboarding-path-b.test.js.map +1 -0
  98. package/dist/__tests__/onboarding.test.d.ts +6 -0
  99. package/dist/__tests__/onboarding.test.d.ts.map +1 -0
  100. package/dist/__tests__/onboarding.test.js +347 -0
  101. package/dist/__tests__/onboarding.test.js.map +1 -0
  102. package/dist/__tests__/orient.test.d.ts +2 -0
  103. package/dist/__tests__/orient.test.d.ts.map +1 -0
  104. package/dist/__tests__/orient.test.js +196 -0
  105. package/dist/__tests__/orient.test.js.map +1 -0
  106. package/dist/__tests__/personal-layer.test.d.ts +12 -0
  107. package/dist/__tests__/personal-layer.test.d.ts.map +1 -0
  108. package/dist/__tests__/personal-layer.test.js +304 -0
  109. package/dist/__tests__/personal-layer.test.js.map +1 -0
  110. package/dist/__tests__/profiles.test.d.ts +2 -0
  111. package/dist/__tests__/profiles.test.d.ts.map +1 -0
  112. package/dist/__tests__/profiles.test.js +212 -0
  113. package/dist/__tests__/profiles.test.js.map +1 -0
  114. package/dist/__tests__/promote.test.d.ts +2 -0
  115. package/dist/__tests__/promote.test.d.ts.map +1 -0
  116. package/dist/__tests__/promote.test.js +230 -0
  117. package/dist/__tests__/promote.test.js.map +1 -0
  118. package/dist/__tests__/prompts.test.d.ts +6 -0
  119. package/dist/__tests__/prompts.test.d.ts.map +1 -0
  120. package/dist/__tests__/prompts.test.js +146 -0
  121. package/dist/__tests__/prompts.test.js.map +1 -0
  122. package/dist/__tests__/proposals.test.d.ts +2 -0
  123. package/dist/__tests__/proposals.test.d.ts.map +1 -0
  124. package/dist/__tests__/proposals.test.js +167 -0
  125. package/dist/__tests__/proposals.test.js.map +1 -0
  126. package/dist/__tests__/relate.test.d.ts +2 -0
  127. package/dist/__tests__/relate.test.d.ts.map +1 -0
  128. package/dist/__tests__/relate.test.js +103 -0
  129. package/dist/__tests__/relate.test.js.map +1 -0
  130. package/dist/__tests__/repo-detect.test.d.ts +2 -0
  131. package/dist/__tests__/repo-detect.test.d.ts.map +1 -0
  132. package/dist/__tests__/repo-detect.test.js +215 -0
  133. package/dist/__tests__/repo-detect.test.js.map +1 -0
  134. package/dist/__tests__/runner.test.d.ts +2 -0
  135. package/dist/__tests__/runner.test.d.ts.map +1 -0
  136. package/dist/__tests__/runner.test.js +219 -0
  137. package/dist/__tests__/runner.test.js.map +1 -0
  138. package/dist/__tests__/session-state-machine.test.d.ts +2 -0
  139. package/dist/__tests__/session-state-machine.test.d.ts.map +1 -0
  140. package/dist/__tests__/session-state-machine.test.js +154 -0
  141. package/dist/__tests__/session-state-machine.test.js.map +1 -0
  142. package/dist/__tests__/session-touch.test.d.ts +2 -0
  143. package/dist/__tests__/session-touch.test.d.ts.map +1 -0
  144. package/dist/__tests__/session-touch.test.js +134 -0
  145. package/dist/__tests__/session-touch.test.js.map +1 -0
  146. package/dist/__tests__/session.test.d.ts +2 -0
  147. package/dist/__tests__/session.test.d.ts.map +1 -0
  148. package/dist/__tests__/session.test.js +46 -0
  149. package/dist/__tests__/session.test.js.map +1 -0
  150. package/dist/__tests__/setup-ingest.test.d.ts +2 -0
  151. package/dist/__tests__/setup-ingest.test.d.ts.map +1 -0
  152. package/dist/__tests__/setup-ingest.test.js +55 -0
  153. package/dist/__tests__/setup-ingest.test.js.map +1 -0
  154. package/dist/__tests__/setup-resolver.test.d.ts +14 -0
  155. package/dist/__tests__/setup-resolver.test.d.ts.map +1 -0
  156. package/dist/__tests__/setup-resolver.test.js +228 -0
  157. package/dist/__tests__/setup-resolver.test.js.map +1 -0
  158. package/dist/__tests__/setup.test.d.ts +2 -0
  159. package/dist/__tests__/setup.test.d.ts.map +1 -0
  160. package/dist/__tests__/setup.test.js +141 -0
  161. package/dist/__tests__/setup.test.js.map +1 -0
  162. package/dist/__tests__/spinner-labels.test.d.ts +2 -0
  163. package/dist/__tests__/spinner-labels.test.d.ts.map +1 -0
  164. package/dist/__tests__/spinner-labels.test.js +23 -0
  165. package/dist/__tests__/spinner-labels.test.js.map +1 -0
  166. package/dist/__tests__/state.test.d.ts +6 -0
  167. package/dist/__tests__/state.test.d.ts.map +1 -0
  168. package/dist/__tests__/state.test.js +97 -0
  169. package/dist/__tests__/state.test.js.map +1 -0
  170. package/dist/__tests__/strip.test.d.ts +2 -0
  171. package/dist/__tests__/strip.test.d.ts.map +1 -0
  172. package/dist/__tests__/strip.test.js +136 -0
  173. package/dist/__tests__/strip.test.js.map +1 -0
  174. package/dist/__tests__/surface-profiles.test.d.ts +2 -0
  175. package/dist/__tests__/surface-profiles.test.d.ts.map +1 -0
  176. package/dist/__tests__/surface-profiles.test.js +233 -0
  177. package/dist/__tests__/surface-profiles.test.js.map +1 -0
  178. package/dist/__tests__/surfaces.test.d.ts +2 -0
  179. package/dist/__tests__/surfaces.test.d.ts.map +1 -0
  180. package/dist/__tests__/surfaces.test.js +46 -0
  181. package/dist/__tests__/surfaces.test.js.map +1 -0
  182. package/dist/__tests__/update.test.d.ts +2 -0
  183. package/dist/__tests__/update.test.d.ts.map +1 -0
  184. package/dist/__tests__/update.test.js +228 -0
  185. package/dist/__tests__/update.test.js.map +1 -0
  186. package/dist/__tests__/workspace.test.d.ts +2 -0
  187. package/dist/__tests__/workspace.test.d.ts.map +1 -0
  188. package/dist/__tests__/workspace.test.js +328 -0
  189. package/dist/__tests__/workspace.test.js.map +1 -0
  190. package/dist/commands/accept.d.ts +18 -0
  191. package/dist/commands/accept.d.ts.map +1 -0
  192. package/dist/commands/accept.js +76 -0
  193. package/dist/commands/accept.js.map +1 -0
  194. package/dist/commands/admin/cockpit.d.ts +90 -0
  195. package/dist/commands/admin/cockpit.d.ts.map +1 -0
  196. package/dist/commands/admin/cockpit.js +618 -0
  197. package/dist/commands/admin/cockpit.js.map +1 -0
  198. package/dist/commands/admin/index.d.ts +21 -0
  199. package/dist/commands/admin/index.d.ts.map +1 -0
  200. package/dist/commands/admin/index.js +256 -0
  201. package/dist/commands/admin/index.js.map +1 -0
  202. package/dist/commands/admin/inspect.d.ts +30 -0
  203. package/dist/commands/admin/inspect.d.ts.map +1 -0
  204. package/dist/commands/admin/inspect.js +555 -0
  205. package/dist/commands/admin/inspect.js.map +1 -0
  206. package/dist/commands/admin/inspect.test.d.ts +7 -0
  207. package/dist/commands/admin/inspect.test.d.ts.map +1 -0
  208. package/dist/commands/admin/inspect.test.js +90 -0
  209. package/dist/commands/admin/inspect.test.js.map +1 -0
  210. package/dist/commands/admin/manage.d.ts +8 -0
  211. package/dist/commands/admin/manage.d.ts.map +1 -0
  212. package/dist/commands/admin/manage.js +76 -0
  213. package/dist/commands/admin/manage.js.map +1 -0
  214. package/dist/commands/admin/seed.d.ts +46 -0
  215. package/dist/commands/admin/seed.d.ts.map +1 -0
  216. package/dist/commands/admin/seed.js +729 -0
  217. package/dist/commands/admin/seed.js.map +1 -0
  218. package/dist/commands/admin/seed.test.d.ts +11 -0
  219. package/dist/commands/admin/seed.test.d.ts.map +1 -0
  220. package/dist/commands/admin/seed.test.js +123 -0
  221. package/dist/commands/admin/seed.test.js.map +1 -0
  222. package/dist/commands/audit.d.ts +25 -0
  223. package/dist/commands/audit.d.ts.map +1 -0
  224. package/dist/commands/audit.js +188 -0
  225. package/dist/commands/audit.js.map +1 -0
  226. package/dist/commands/authority-domains.d.ts +140 -0
  227. package/dist/commands/authority-domains.d.ts.map +1 -0
  228. package/dist/commands/authority-domains.js +268 -0
  229. package/dist/commands/authority-domains.js.map +1 -0
  230. package/dist/commands/brand-pack.d.ts +2 -0
  231. package/dist/commands/brand-pack.d.ts.map +1 -0
  232. package/dist/commands/brand-pack.js +25 -0
  233. package/dist/commands/brand-pack.js.map +1 -0
  234. package/dist/commands/brief.d.ts +28 -0
  235. package/dist/commands/brief.d.ts.map +1 -0
  236. package/dist/commands/brief.js +75 -0
  237. package/dist/commands/brief.js.map +1 -0
  238. package/dist/commands/capture.d.ts +30 -0
  239. package/dist/commands/capture.d.ts.map +1 -0
  240. package/dist/commands/capture.js +339 -0
  241. package/dist/commands/capture.js.map +1 -0
  242. package/dist/commands/chain-walk.d.ts +14 -0
  243. package/dist/commands/chain-walk.d.ts.map +1 -0
  244. package/dist/commands/chain-walk.js +38 -0
  245. package/dist/commands/chain-walk.js.map +1 -0
  246. package/dist/commands/changes.d.ts +11 -0
  247. package/dist/commands/changes.d.ts.map +1 -0
  248. package/dist/commands/changes.js +46 -0
  249. package/dist/commands/changes.js.map +1 -0
  250. package/dist/commands/codex-prep.d.ts +12 -0
  251. package/dist/commands/codex-prep.d.ts.map +1 -0
  252. package/dist/commands/codex-prep.js +122 -0
  253. package/dist/commands/codex-prep.js.map +1 -0
  254. package/dist/commands/collections.d.ts +22 -0
  255. package/dist/commands/collections.d.ts.map +1 -0
  256. package/dist/commands/collections.js +77 -0
  257. package/dist/commands/collections.js.map +1 -0
  258. package/dist/commands/connect-integration.test.d.ts +7 -0
  259. package/dist/commands/connect-integration.test.d.ts.map +1 -0
  260. package/dist/commands/connect-integration.test.js +211 -0
  261. package/dist/commands/connect-integration.test.js.map +1 -0
  262. package/dist/commands/connect-screens.d.ts +24 -0
  263. package/dist/commands/connect-screens.d.ts.map +1 -0
  264. package/dist/commands/connect-screens.js +97 -0
  265. package/dist/commands/connect-screens.js.map +1 -0
  266. package/dist/commands/connect.d.ts +23 -0
  267. package/dist/commands/connect.d.ts.map +1 -0
  268. package/dist/commands/connect.js +289 -0
  269. package/dist/commands/connect.js.map +1 -0
  270. package/dist/commands/connect.test.d.ts +6 -0
  271. package/dist/commands/connect.test.d.ts.map +1 -0
  272. package/dist/commands/connect.test.js +297 -0
  273. package/dist/commands/connect.test.js.map +1 -0
  274. package/dist/commands/constellation.d.ts +11 -0
  275. package/dist/commands/constellation.d.ts.map +1 -0
  276. package/dist/commands/constellation.js +33 -0
  277. package/dist/commands/constellation.js.map +1 -0
  278. package/dist/commands/context.d.ts +2 -1
  279. package/dist/commands/context.d.ts.map +1 -1
  280. package/dist/commands/context.js +25 -10
  281. package/dist/commands/context.js.map +1 -1
  282. package/dist/commands/cross-cut.d.ts +11 -0
  283. package/dist/commands/cross-cut.d.ts.map +1 -0
  284. package/dist/commands/cross-cut.js +23 -0
  285. package/dist/commands/cross-cut.js.map +1 -0
  286. package/dist/commands/doctor.d.ts +18 -0
  287. package/dist/commands/doctor.d.ts.map +1 -0
  288. package/dist/commands/doctor.js +232 -0
  289. package/dist/commands/doctor.js.map +1 -0
  290. package/dist/commands/doctor.test.d.ts +8 -0
  291. package/dist/commands/doctor.test.d.ts.map +1 -0
  292. package/dist/commands/doctor.test.js +311 -0
  293. package/dist/commands/doctor.test.js.map +1 -0
  294. package/dist/commands/fields.d.ts +9 -0
  295. package/dist/commands/fields.d.ts.map +1 -0
  296. package/dist/commands/fields.js +30 -0
  297. package/dist/commands/fields.js.map +1 -0
  298. package/dist/commands/get.d.ts +8 -1
  299. package/dist/commands/get.d.ts.map +1 -1
  300. package/dist/commands/get.js +65 -8
  301. package/dist/commands/get.js.map +1 -1
  302. package/dist/commands/handshake.d.ts +142 -0
  303. package/dist/commands/handshake.d.ts.map +1 -0
  304. package/dist/commands/handshake.js +1349 -0
  305. package/dist/commands/handshake.js.map +1 -0
  306. package/dist/commands/ingest.d.ts +14 -0
  307. package/dist/commands/ingest.d.ts.map +1 -0
  308. package/dist/commands/ingest.js +189 -0
  309. package/dist/commands/ingest.js.map +1 -0
  310. package/dist/commands/init.d.ts +14 -0
  311. package/dist/commands/init.d.ts.map +1 -0
  312. package/dist/commands/init.js +109 -0
  313. package/dist/commands/init.js.map +1 -0
  314. package/dist/commands/login.d.ts +9 -0
  315. package/dist/commands/login.d.ts.map +1 -0
  316. package/dist/commands/login.js +116 -0
  317. package/dist/commands/login.js.map +1 -0
  318. package/dist/commands/method.d.ts +99 -0
  319. package/dist/commands/method.d.ts.map +1 -0
  320. package/dist/commands/method.js +781 -0
  321. package/dist/commands/method.js.map +1 -0
  322. package/dist/commands/migrate-setup.d.ts +18 -0
  323. package/dist/commands/migrate-setup.d.ts.map +1 -0
  324. package/dist/commands/migrate-setup.js +198 -0
  325. package/dist/commands/migrate-setup.js.map +1 -0
  326. package/dist/commands/orient.d.ts +109 -1
  327. package/dist/commands/orient.d.ts.map +1 -1
  328. package/dist/commands/orient.js +94 -7
  329. package/dist/commands/orient.js.map +1 -1
  330. package/dist/commands/profile.d.ts +47 -0
  331. package/dist/commands/profile.d.ts.map +1 -0
  332. package/dist/commands/profile.js +148 -0
  333. package/dist/commands/profile.js.map +1 -0
  334. package/dist/commands/promote.d.ts +12 -0
  335. package/dist/commands/promote.d.ts.map +1 -0
  336. package/dist/commands/promote.js +113 -0
  337. package/dist/commands/promote.js.map +1 -0
  338. package/dist/commands/proposals.d.ts +9 -0
  339. package/dist/commands/proposals.d.ts.map +1 -0
  340. package/dist/commands/proposals.js +24 -0
  341. package/dist/commands/proposals.js.map +1 -0
  342. package/dist/commands/reject.d.ts +14 -0
  343. package/dist/commands/reject.d.ts.map +1 -0
  344. package/dist/commands/reject.js +43 -0
  345. package/dist/commands/reject.js.map +1 -0
  346. package/dist/commands/relate.d.ts +16 -0
  347. package/dist/commands/relate.d.ts.map +1 -0
  348. package/dist/commands/relate.js +111 -0
  349. package/dist/commands/relate.js.map +1 -0
  350. package/dist/commands/search.d.ts +1 -0
  351. package/dist/commands/search.d.ts.map +1 -1
  352. package/dist/commands/search.js +10 -4
  353. package/dist/commands/search.js.map +1 -1
  354. package/dist/commands/session.d.ts +20 -0
  355. package/dist/commands/session.d.ts.map +1 -0
  356. package/dist/commands/session.js +203 -0
  357. package/dist/commands/session.js.map +1 -0
  358. package/dist/commands/setup-ingest.d.ts +17 -0
  359. package/dist/commands/setup-ingest.d.ts.map +1 -0
  360. package/dist/commands/setup-ingest.js +224 -0
  361. package/dist/commands/setup-ingest.js.map +1 -0
  362. package/dist/commands/setup-resolver.d.ts +58 -0
  363. package/dist/commands/setup-resolver.d.ts.map +1 -0
  364. package/dist/commands/setup-resolver.js +150 -0
  365. package/dist/commands/setup-resolver.js.map +1 -0
  366. package/dist/commands/setup.d.ts +15 -0
  367. package/dist/commands/setup.d.ts.map +1 -0
  368. package/dist/commands/setup.js +148 -0
  369. package/dist/commands/setup.js.map +1 -0
  370. package/dist/commands/update.d.ts +17 -0
  371. package/dist/commands/update.d.ts.map +1 -0
  372. package/dist/commands/update.js +178 -0
  373. package/dist/commands/update.js.map +1 -0
  374. package/dist/commands/usage.d.ts +40 -0
  375. package/dist/commands/usage.d.ts.map +1 -0
  376. package/dist/commands/usage.js +232 -0
  377. package/dist/commands/usage.js.map +1 -0
  378. package/dist/commands/verify.d.ts +13 -0
  379. package/dist/commands/verify.d.ts.map +1 -0
  380. package/dist/commands/verify.js +49 -0
  381. package/dist/commands/verify.js.map +1 -0
  382. package/dist/commands/welcome.d.ts +21 -0
  383. package/dist/commands/welcome.d.ts.map +1 -0
  384. package/dist/commands/welcome.js +50 -0
  385. package/dist/commands/welcome.js.map +1 -0
  386. package/dist/commands/workspace.d.ts +113 -0
  387. package/dist/commands/workspace.d.ts.map +1 -0
  388. package/dist/commands/workspace.js +263 -0
  389. package/dist/commands/workspace.js.map +1 -0
  390. package/dist/formatters/audit.d.ts +46 -0
  391. package/dist/formatters/audit.d.ts.map +1 -0
  392. package/dist/formatters/audit.js +81 -0
  393. package/dist/formatters/audit.js.map +1 -0
  394. package/dist/formatters/brief.d.ts +112 -0
  395. package/dist/formatters/brief.d.ts.map +1 -0
  396. package/dist/formatters/brief.js +179 -0
  397. package/dist/formatters/brief.js.map +1 -0
  398. package/dist/formatters/capture.d.ts +48 -0
  399. package/dist/formatters/capture.d.ts.map +1 -0
  400. package/dist/formatters/capture.js +77 -0
  401. package/dist/formatters/capture.js.map +1 -0
  402. package/dist/formatters/chain-walk.d.ts +33 -0
  403. package/dist/formatters/chain-walk.d.ts.map +1 -0
  404. package/dist/formatters/chain-walk.js +54 -0
  405. package/dist/formatters/chain-walk.js.map +1 -0
  406. package/dist/formatters/changes.d.ts +25 -0
  407. package/dist/formatters/changes.d.ts.map +1 -0
  408. package/dist/formatters/changes.js +60 -0
  409. package/dist/formatters/changes.js.map +1 -0
  410. package/dist/formatters/collections.d.ts +40 -0
  411. package/dist/formatters/collections.d.ts.map +1 -0
  412. package/dist/formatters/collections.js +93 -0
  413. package/dist/formatters/collections.js.map +1 -0
  414. package/dist/formatters/constellation.d.ts +34 -0
  415. package/dist/formatters/constellation.d.ts.map +1 -0
  416. package/dist/formatters/constellation.js +38 -0
  417. package/dist/formatters/constellation.js.map +1 -0
  418. package/dist/formatters/cross-cut.d.ts +21 -0
  419. package/dist/formatters/cross-cut.d.ts.map +1 -0
  420. package/dist/formatters/cross-cut.js +32 -0
  421. package/dist/formatters/cross-cut.js.map +1 -0
  422. package/dist/formatters/entry.d.ts +11 -4
  423. package/dist/formatters/entry.d.ts.map +1 -1
  424. package/dist/formatters/entry.js +24 -8
  425. package/dist/formatters/entry.js.map +1 -1
  426. package/dist/formatters/fields.d.ts +32 -0
  427. package/dist/formatters/fields.d.ts.map +1 -0
  428. package/dist/formatters/fields.js +49 -0
  429. package/dist/formatters/fields.js.map +1 -0
  430. package/dist/formatters/handshake.d.ts +46 -0
  431. package/dist/formatters/handshake.d.ts.map +1 -0
  432. package/dist/formatters/handshake.js +163 -0
  433. package/dist/formatters/handshake.js.map +1 -0
  434. package/dist/formatters/orient.d.ts +129 -1
  435. package/dist/formatters/orient.d.ts.map +1 -1
  436. package/dist/formatters/orient.js +156 -17
  437. package/dist/formatters/orient.js.map +1 -1
  438. package/dist/formatters/promote.d.ts +30 -0
  439. package/dist/formatters/promote.d.ts.map +1 -0
  440. package/dist/formatters/promote.js +39 -0
  441. package/dist/formatters/promote.js.map +1 -0
  442. package/dist/formatters/proposals.d.ts +45 -0
  443. package/dist/formatters/proposals.d.ts.map +1 -0
  444. package/dist/formatters/proposals.js +62 -0
  445. package/dist/formatters/proposals.js.map +1 -0
  446. package/dist/formatters/relate.d.ts +14 -0
  447. package/dist/formatters/relate.d.ts.map +1 -0
  448. package/dist/formatters/relate.js +16 -0
  449. package/dist/formatters/relate.js.map +1 -0
  450. package/dist/formatters/search.d.ts +0 -4
  451. package/dist/formatters/search.d.ts.map +1 -1
  452. package/dist/formatters/search.js +4 -1
  453. package/dist/formatters/search.js.map +1 -1
  454. package/dist/formatters/session.d.ts +11 -0
  455. package/dist/formatters/session.d.ts.map +1 -0
  456. package/dist/formatters/session.js +53 -0
  457. package/dist/formatters/session.js.map +1 -0
  458. package/dist/formatters/update.d.ts +17 -0
  459. package/dist/formatters/update.d.ts.map +1 -0
  460. package/dist/formatters/update.js +45 -0
  461. package/dist/formatters/update.js.map +1 -0
  462. package/dist/formatters/verify.d.ts +11 -0
  463. package/dist/formatters/verify.d.ts.map +1 -0
  464. package/dist/formatters/verify.js +11 -0
  465. package/dist/formatters/verify.js.map +1 -0
  466. package/dist/generators/__tests__/surface-profiles.test.d.ts +2 -0
  467. package/dist/generators/__tests__/surface-profiles.test.d.ts.map +1 -0
  468. package/dist/generators/__tests__/surface-profiles.test.js +89 -0
  469. package/dist/generators/__tests__/surface-profiles.test.js.map +1 -0
  470. package/dist/generators/adapters.d.ts +44 -0
  471. package/dist/generators/adapters.d.ts.map +1 -0
  472. package/dist/generators/adapters.js +290 -0
  473. package/dist/generators/adapters.js.map +1 -0
  474. package/dist/generators/adapters.test.d.ts +2 -0
  475. package/dist/generators/adapters.test.d.ts.map +1 -0
  476. package/dist/generators/adapters.test.js +27 -0
  477. package/dist/generators/adapters.test.js.map +1 -0
  478. package/dist/generators/archetypes.d.ts +52 -0
  479. package/dist/generators/archetypes.d.ts.map +1 -0
  480. package/dist/generators/archetypes.js +153 -0
  481. package/dist/generators/archetypes.js.map +1 -0
  482. package/dist/generators/archetypes.test.d.ts +2 -0
  483. package/dist/generators/archetypes.test.d.ts.map +1 -0
  484. package/dist/generators/archetypes.test.js +237 -0
  485. package/dist/generators/archetypes.test.js.map +1 -0
  486. package/dist/generators/boundary-manifest.d.ts +29 -0
  487. package/dist/generators/boundary-manifest.d.ts.map +1 -0
  488. package/dist/generators/boundary-manifest.js +183 -0
  489. package/dist/generators/boundary-manifest.js.map +1 -0
  490. package/dist/generators/boundary-manifest.test.d.ts +2 -0
  491. package/dist/generators/boundary-manifest.test.d.ts.map +1 -0
  492. package/dist/generators/boundary-manifest.test.js +91 -0
  493. package/dist/generators/boundary-manifest.test.js.map +1 -0
  494. package/dist/generators/briefing-md.d.ts +8 -0
  495. package/dist/generators/briefing-md.d.ts.map +1 -0
  496. package/dist/generators/briefing-md.js +51 -0
  497. package/dist/generators/briefing-md.js.map +1 -0
  498. package/dist/generators/chain-classifier.d.ts +49 -0
  499. package/dist/generators/chain-classifier.d.ts.map +1 -0
  500. package/dist/generators/chain-classifier.js +180 -0
  501. package/dist/generators/chain-classifier.js.map +1 -0
  502. package/dist/generators/chain-classifier.test.d.ts +2 -0
  503. package/dist/generators/chain-classifier.test.d.ts.map +1 -0
  504. package/dist/generators/chain-classifier.test.js +257 -0
  505. package/dist/generators/chain-classifier.test.js.map +1 -0
  506. package/dist/generators/chain-rules.d.ts +42 -0
  507. package/dist/generators/chain-rules.d.ts.map +1 -0
  508. package/dist/generators/chain-rules.js +144 -0
  509. package/dist/generators/chain-rules.js.map +1 -0
  510. package/dist/generators/chain-rules.test.d.ts +2 -0
  511. package/dist/generators/chain-rules.test.d.ts.map +1 -0
  512. package/dist/generators/chain-rules.test.js +179 -0
  513. package/dist/generators/chain-rules.test.js.map +1 -0
  514. package/dist/generators/context-md.d.ts +8 -0
  515. package/dist/generators/context-md.d.ts.map +1 -0
  516. package/dist/generators/context-md.js +134 -0
  517. package/dist/generators/context-md.js.map +1 -0
  518. package/dist/generators/handshake-diff.d.ts +67 -0
  519. package/dist/generators/handshake-diff.d.ts.map +1 -0
  520. package/dist/generators/handshake-diff.js +183 -0
  521. package/dist/generators/handshake-diff.js.map +1 -0
  522. package/dist/generators/handshake-diff.test.d.ts +2 -0
  523. package/dist/generators/handshake-diff.test.d.ts.map +1 -0
  524. package/dist/generators/handshake-diff.test.js +264 -0
  525. package/dist/generators/handshake-diff.test.js.map +1 -0
  526. package/dist/generators/manifest.d.ts +39 -0
  527. package/dist/generators/manifest.d.ts.map +1 -0
  528. package/dist/generators/manifest.js +166 -0
  529. package/dist/generators/manifest.js.map +1 -0
  530. package/dist/generators/portable-knowledge.d.ts +165 -0
  531. package/dist/generators/portable-knowledge.d.ts.map +1 -0
  532. package/dist/generators/portable-knowledge.js +613 -0
  533. package/dist/generators/portable-knowledge.js.map +1 -0
  534. package/dist/generators/portable-knowledge.test.d.ts +2 -0
  535. package/dist/generators/portable-knowledge.test.d.ts.map +1 -0
  536. package/dist/generators/portable-knowledge.test.js +927 -0
  537. package/dist/generators/portable-knowledge.test.js.map +1 -0
  538. package/dist/generators/surface-profiles.d.ts +49 -0
  539. package/dist/generators/surface-profiles.d.ts.map +1 -0
  540. package/dist/generators/surface-profiles.js +98 -0
  541. package/dist/generators/surface-profiles.js.map +1 -0
  542. package/dist/index.d.ts +3 -2
  543. package/dist/index.d.ts.map +1 -1
  544. package/dist/index.js +858 -32
  545. package/dist/index.js.map +1 -1
  546. package/dist/lib/activation.d.ts +28 -0
  547. package/dist/lib/activation.d.ts.map +1 -0
  548. package/dist/lib/activation.js +57 -0
  549. package/dist/lib/activation.js.map +1 -0
  550. package/dist/lib/activation.test.d.ts +6 -0
  551. package/dist/lib/activation.test.d.ts.map +1 -0
  552. package/dist/lib/activation.test.js +121 -0
  553. package/dist/lib/activation.test.js.map +1 -0
  554. package/dist/lib/canonicalRefs.d.ts +69 -0
  555. package/dist/lib/canonicalRefs.d.ts.map +1 -0
  556. package/dist/lib/canonicalRefs.js +83 -0
  557. package/dist/lib/canonicalRefs.js.map +1 -0
  558. package/dist/lib/client.d.ts +62 -1
  559. package/dist/lib/client.d.ts.map +1 -1
  560. package/dist/lib/client.js +259 -13
  561. package/dist/lib/client.js.map +1 -1
  562. package/dist/lib/collectionRegistry.d.ts +38 -0
  563. package/dist/lib/collectionRegistry.d.ts.map +1 -0
  564. package/dist/lib/collectionRegistry.js +112 -0
  565. package/dist/lib/collectionRegistry.js.map +1 -0
  566. package/dist/lib/config.d.ts +122 -2
  567. package/dist/lib/config.d.ts.map +1 -1
  568. package/dist/lib/config.js +426 -18
  569. package/dist/lib/config.js.map +1 -1
  570. package/dist/lib/connectKeyLabel.d.ts +9 -0
  571. package/dist/lib/connectKeyLabel.d.ts.map +1 -0
  572. package/dist/lib/connectKeyLabel.js +12 -0
  573. package/dist/lib/connectKeyLabel.js.map +1 -0
  574. package/dist/lib/constants.d.ts +42 -0
  575. package/dist/lib/constants.d.ts.map +1 -0
  576. package/dist/lib/constants.js +76 -0
  577. package/dist/lib/constants.js.map +1 -0
  578. package/dist/lib/conversation-engine.d.ts +45 -0
  579. package/dist/lib/conversation-engine.d.ts.map +1 -0
  580. package/dist/lib/conversation-engine.js +112 -0
  581. package/dist/lib/conversation-engine.js.map +1 -0
  582. package/dist/lib/conversation-phases.d.ts +59 -0
  583. package/dist/lib/conversation-phases.d.ts.map +1 -0
  584. package/dist/lib/conversation-phases.js +11 -0
  585. package/dist/lib/conversation-phases.js.map +1 -0
  586. package/dist/lib/conversation-signals.d.ts +30 -0
  587. package/dist/lib/conversation-signals.d.ts.map +1 -0
  588. package/dist/lib/conversation-signals.js +64 -0
  589. package/dist/lib/conversation-signals.js.map +1 -0
  590. package/dist/lib/deployment.d.ts +23 -0
  591. package/dist/lib/deployment.d.ts.map +1 -0
  592. package/dist/lib/deployment.js +78 -0
  593. package/dist/lib/deployment.js.map +1 -0
  594. package/dist/lib/deployment.test.d.ts +5 -0
  595. package/dist/lib/deployment.test.d.ts.map +1 -0
  596. package/dist/lib/deployment.test.js +54 -0
  597. package/dist/lib/deployment.test.js.map +1 -0
  598. package/dist/lib/errors.d.ts +60 -0
  599. package/dist/lib/errors.d.ts.map +1 -0
  600. package/dist/lib/errors.js +69 -0
  601. package/dist/lib/errors.js.map +1 -0
  602. package/dist/lib/experiment.d.ts +18 -0
  603. package/dist/lib/experiment.d.ts.map +1 -0
  604. package/dist/lib/experiment.js +28 -0
  605. package/dist/lib/experiment.js.map +1 -0
  606. package/dist/lib/format.d.ts +10 -0
  607. package/dist/lib/format.d.ts.map +1 -0
  608. package/dist/lib/format.js +27 -0
  609. package/dist/lib/format.js.map +1 -0
  610. package/dist/lib/glossary.d.ts +19 -0
  611. package/dist/lib/glossary.d.ts.map +1 -0
  612. package/dist/lib/glossary.js +53 -0
  613. package/dist/lib/glossary.js.map +1 -0
  614. package/dist/lib/hook-intents.d.ts +51 -0
  615. package/dist/lib/hook-intents.d.ts.map +1 -0
  616. package/dist/lib/hook-intents.js +85 -0
  617. package/dist/lib/hook-intents.js.map +1 -0
  618. package/dist/lib/inferSourceDate.d.ts +12 -0
  619. package/dist/lib/inferSourceDate.d.ts.map +1 -0
  620. package/dist/lib/inferSourceDate.js +44 -0
  621. package/dist/lib/inferSourceDate.js.map +1 -0
  622. package/dist/lib/method-registry.d.ts +32 -0
  623. package/dist/lib/method-registry.d.ts.map +1 -0
  624. package/dist/lib/method-registry.js +53 -0
  625. package/dist/lib/method-registry.js.map +1 -0
  626. package/dist/lib/normalizeMaterializedFilename.d.ts +28 -0
  627. package/dist/lib/normalizeMaterializedFilename.d.ts.map +1 -0
  628. package/dist/lib/normalizeMaterializedFilename.js +56 -0
  629. package/dist/lib/normalizeMaterializedFilename.js.map +1 -0
  630. package/dist/lib/normalizeMaterializedFilename.test.d.ts +16 -0
  631. package/dist/lib/normalizeMaterializedFilename.test.d.ts.map +1 -0
  632. package/dist/lib/normalizeMaterializedFilename.test.js +90 -0
  633. package/dist/lib/normalizeMaterializedFilename.test.js.map +1 -0
  634. package/dist/lib/onboarding-path-b.d.ts +10 -0
  635. package/dist/lib/onboarding-path-b.d.ts.map +1 -0
  636. package/dist/lib/onboarding-path-b.js +214 -0
  637. package/dist/lib/onboarding-path-b.js.map +1 -0
  638. package/dist/lib/onboarding-phases.d.ts +9 -0
  639. package/dist/lib/onboarding-phases.d.ts.map +1 -0
  640. package/dist/lib/onboarding-phases.js +120 -0
  641. package/dist/lib/onboarding-phases.js.map +1 -0
  642. package/dist/lib/onboarding-shared.d.ts +81 -0
  643. package/dist/lib/onboarding-shared.d.ts.map +1 -0
  644. package/dist/lib/onboarding-shared.js +190 -0
  645. package/dist/lib/onboarding-shared.js.map +1 -0
  646. package/dist/lib/onboarding-topics.d.ts +27 -0
  647. package/dist/lib/onboarding-topics.d.ts.map +1 -0
  648. package/dist/lib/onboarding-topics.js +57 -0
  649. package/dist/lib/onboarding-topics.js.map +1 -0
  650. package/dist/lib/onboarding.d.ts +17 -0
  651. package/dist/lib/onboarding.d.ts.map +1 -0
  652. package/dist/lib/onboarding.js +350 -0
  653. package/dist/lib/onboarding.js.map +1 -0
  654. package/dist/lib/profiles.d.ts +39 -0
  655. package/dist/lib/profiles.d.ts.map +1 -0
  656. package/dist/lib/profiles.js +185 -0
  657. package/dist/lib/profiles.js.map +1 -0
  658. package/dist/lib/prompts.d.ts +65 -0
  659. package/dist/lib/prompts.d.ts.map +1 -0
  660. package/dist/lib/prompts.js +132 -0
  661. package/dist/lib/prompts.js.map +1 -0
  662. package/dist/lib/repo-detect.d.ts +33 -0
  663. package/dist/lib/repo-detect.d.ts.map +1 -0
  664. package/dist/lib/repo-detect.js +83 -0
  665. package/dist/lib/repo-detect.js.map +1 -0
  666. package/dist/lib/runner.d.ts +33 -0
  667. package/dist/lib/runner.d.ts.map +1 -0
  668. package/dist/lib/runner.js +79 -0
  669. package/dist/lib/runner.js.map +1 -0
  670. package/dist/lib/session.d.ts +42 -0
  671. package/dist/lib/session.d.ts.map +1 -0
  672. package/dist/lib/session.js +109 -0
  673. package/dist/lib/session.js.map +1 -0
  674. package/dist/lib/spinner.d.ts +27 -0
  675. package/dist/lib/spinner.d.ts.map +1 -0
  676. package/dist/lib/spinner.js +76 -0
  677. package/dist/lib/spinner.js.map +1 -0
  678. package/dist/lib/spinner.test.d.ts +2 -0
  679. package/dist/lib/spinner.test.d.ts.map +1 -0
  680. package/dist/lib/spinner.test.js +39 -0
  681. package/dist/lib/spinner.test.js.map +1 -0
  682. package/dist/lib/state.d.ts +51 -0
  683. package/dist/lib/state.d.ts.map +1 -0
  684. package/dist/lib/state.js +90 -0
  685. package/dist/lib/state.js.map +1 -0
  686. package/dist/lib/strip.d.ts +12 -0
  687. package/dist/lib/strip.d.ts.map +1 -0
  688. package/dist/lib/strip.js +41 -0
  689. package/dist/lib/strip.js.map +1 -0
  690. package/dist/lib/style.d.ts +96 -0
  691. package/dist/lib/style.d.ts.map +1 -0
  692. package/dist/lib/style.js +169 -0
  693. package/dist/lib/style.js.map +1 -0
  694. package/dist/lib/style.test.d.ts +7 -0
  695. package/dist/lib/style.test.d.ts.map +1 -0
  696. package/dist/lib/style.test.js +263 -0
  697. package/dist/lib/style.test.js.map +1 -0
  698. package/dist/lib/telemetry.d.ts +15 -0
  699. package/dist/lib/telemetry.d.ts.map +1 -0
  700. package/dist/lib/telemetry.js +47 -0
  701. package/dist/lib/telemetry.js.map +1 -0
  702. package/dist/lib/tokenConstants.d.ts +17 -0
  703. package/dist/lib/tokenConstants.d.ts.map +1 -0
  704. package/dist/lib/tokenConstants.js +17 -0
  705. package/dist/lib/tokenConstants.js.map +1 -0
  706. package/dist/lib/update-check.d.ts +21 -0
  707. package/dist/lib/update-check.d.ts.map +1 -0
  708. package/dist/lib/update-check.js +145 -0
  709. package/dist/lib/update-check.js.map +1 -0
  710. package/dist/lib/wizard-surfaces.d.ts +47 -0
  711. package/dist/lib/wizard-surfaces.d.ts.map +1 -0
  712. package/dist/lib/wizard-surfaces.js +176 -0
  713. package/dist/lib/wizard-surfaces.js.map +1 -0
  714. package/dist/lib/wizard-surfaces.test.d.ts +2 -0
  715. package/dist/lib/wizard-surfaces.test.d.ts.map +1 -0
  716. package/dist/lib/wizard-surfaces.test.js +127 -0
  717. package/dist/lib/wizard-surfaces.test.js.map +1 -0
  718. package/dist/lib/wizard-trust.d.ts +31 -0
  719. package/dist/lib/wizard-trust.d.ts.map +1 -0
  720. package/dist/lib/wizard-trust.js +66 -0
  721. package/dist/lib/wizard-trust.js.map +1 -0
  722. package/dist/lib/wizard-trust.test.d.ts +2 -0
  723. package/dist/lib/wizard-trust.test.d.ts.map +1 -0
  724. package/dist/lib/wizard-trust.test.js +32 -0
  725. package/dist/lib/wizard-trust.test.js.map +1 -0
  726. package/dist/lib/workspace-probe.d.ts +19 -0
  727. package/dist/lib/workspace-probe.d.ts.map +1 -0
  728. package/dist/lib/workspace-probe.js +27 -0
  729. package/dist/lib/workspace-probe.js.map +1 -0
  730. package/dist/surfaces/registry.d.ts +20 -0
  731. package/dist/surfaces/registry.d.ts.map +1 -0
  732. package/dist/surfaces/registry.js +42 -0
  733. package/dist/surfaces/registry.js.map +1 -0
  734. package/package.json +15 -5
  735. package/templates/archetypes/boundary.md +23 -0
  736. package/templates/archetypes/constraint.md +23 -0
  737. package/templates/archetypes/convention.md +23 -0
  738. package/templates/archetypes/policy.md +23 -0
  739. package/templates/archetypes/quality-gate.md +23 -0
  740. package/templates/archetypes/workflow.md +23 -0
  741. package/templates/general/code-integrity.md +11 -0
  742. package/templates/general/getting-started.md +12 -0
  743. package/templates/method-registry.json +16 -0
  744. package/templates/node-ts/code-integrity.md +13 -0
  745. package/templates/node-ts/testing.md +12 -0
  746. package/templates/python/code-integrity.md +13 -0
  747. package/templates/python/testing.md +12 -0
@@ -0,0 +1,1349 @@
1
+ /**
2
+ * pb handshake — generate context files for AI developer tools.
3
+ * Context export wiring (read-only filesystem bridge; GLO-63, DEC-161) — not a product surface.
4
+ */
5
+ import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, copyFileSync, appendFileSync, unlinkSync, statSync } from 'fs';
6
+ import { join, dirname, resolve, basename } from 'path';
7
+ import { homedir } from 'os';
8
+ import { fileURLToPath } from 'url';
9
+ import { createHash } from 'crypto';
10
+ import { getConfigOrGuide } from '../lib/config.js';
11
+ import { select as promptSelect } from '../lib/prompts.js';
12
+ import { composeHooksFromIntents, getHookStatusForSurface } from '../lib/hook-intents.js';
13
+ import { kernelCall, kernelCallWithSession } from '../lib/client.js';
14
+ import { readSession } from '../lib/session.js';
15
+ import { detectRepo, extractWorkspaceProfile } from '../lib/repo-detect.js';
16
+ import { generateContextMd } from '../generators/context-md.js';
17
+ import { generateBriefingMd } from '../generators/briefing-md.js';
18
+ import { MARKER, generateAgentsMd, generateClaudeMd, generateCursorMdc, generateCopilotMd } from '../generators/adapters.js';
19
+ import { readCanonicalSkills, readCanonicalRules, readPersonalLayer, readPersonalSkillsLayer, generateCursorSkill, generateCursorRule, generateCodexSkill, generateCodexSkillIndex, generateClaudeRule, generateClaudeSkillRouter, shouldEmitToTarget, filterByLevel, validateCodexSkills, evaluateConditions, STAGE_TO_MAX_LEVEL, LEVEL_ORDER, } from '../generators/portable-knowledge.js';
20
+ import { generateChainRules } from '../generators/chain-rules.js';
21
+ import { saveHandshakeState, loadPreviousState, diffHandshakeState, formatDiff, buildCurrentState, } from '../generators/handshake-diff.js';
22
+ import { resolveSurfaceProfile } from '../generators/surface-profiles.js';
23
+ import { formatHandshakeReport } from '../formatters/handshake.js';
24
+ import { readManifest, filterByAdoptionState } from '../generators/manifest.js';
25
+ import { generateBoundaryManifest, getBoundaryEnforcementMode } from '../generators/boundary-manifest.js';
26
+ import { loadMethodRegistry } from '../lib/method-registry.js';
27
+ import { CLIError, ErrorCode } from '../lib/errors.js';
28
+ import { trackEvent } from '../lib/telemetry.js';
29
+ import { normalizeMaterializedFilename } from '../lib/normalizeMaterializedFilename.js';
30
+ const MAX_HANDSHAKE_WAIT_MS = 10_000; // 10 seconds
31
+ const POLL_INTERVAL_MS = 500; // 500 ms per poll
32
+ const MAX_POLLS = MAX_HANDSHAKE_WAIT_MS / POLL_INTERVAL_MS; // 20
33
+ // ── WP-379 S4: Dormant marker ─────────────────────────────────────────────────
34
+ /**
35
+ * DORMANT_MARKER — appended to previously-projected asset files when the asset's
36
+ * gate deactivates (e.g. workspace readiness exceeds the max threshold).
37
+ *
38
+ * Contract:
39
+ * - The file is NOT deleted. It persists on disk so that history is preserved
40
+ * and the agent surface remains inspectable.
41
+ * - The marker is appended at the end of the file, idempotent — if it already
42
+ * exists, no second append occurs.
43
+ * - The marker does NOT trigger a drift TEN. Dormant files are intentionally
44
+ * deactivated, not accidentally forked.
45
+ * - The marker is never included in active file writes — only dormant writes.
46
+ *
47
+ * Used by: writeDormantMarker() (write) and hasDormantMarker() (idempotency check).
48
+ * Exported for use in tests.
49
+ *
50
+ * Chain: WP-379 S4.
51
+ */
52
+ export const DORMANT_MARKER = '<!-- pb-status: dormant -->';
53
+ /**
54
+ * hasDormantMarker — check whether a file on disk already has the dormant marker.
55
+ * Used for idempotency: if the marker is already present, skip the append.
56
+ */
57
+ function hasDormantMarker(content) {
58
+ return content.includes(DORMANT_MARKER);
59
+ }
60
+ /**
61
+ * writeDormantMarker — append the dormant marker to a previously-projected file.
62
+ *
63
+ * Idempotent: if DORMANT_MARKER is already present, no-op.
64
+ * Only operates on files that have the auto-gen MARKER — we never touch
65
+ * manually-authored files.
66
+ *
67
+ * @param filePath Absolute path to the file.
68
+ * @returns 'written' | 'already-dormant' | 'skipped' (no auto-gen marker)
69
+ */
70
+ export function writeDormantMarkerToFile(filePath) {
71
+ if (!existsSync(filePath))
72
+ return 'skipped';
73
+ const content = readFileSync(filePath, 'utf8');
74
+ // Only mark files that were originally projected by pb handshake.
75
+ // Files without the auto-gen MARKER are manually authored — leave them alone.
76
+ if (!content.includes(MARKER))
77
+ return 'skipped';
78
+ if (hasDormantMarker(content))
79
+ return 'already-dormant';
80
+ // Append the marker on its own line. No trailing newline assumption —
81
+ // appendFileSync adds to whatever is already there.
82
+ appendFileSync(filePath, `\n${DORMANT_MARKER}\n`);
83
+ return 'written';
84
+ }
85
+ /**
86
+ * deriveDormantFilePaths — compute the set of on-disk file paths that would have
87
+ * been projected for a given dormant asset (by name and assetKind).
88
+ *
89
+ * Assets are projected to one or more surfaces (cursor/claude/codex) depending
90
+ * on shouldEmitToTarget. Since we don't re-run shouldEmitToTarget here, we
91
+ * speculatively probe all known surface paths and let writeDormantMarkerToFile
92
+ * decide whether each exists and has the auto-gen MARKER.
93
+ *
94
+ * @param asset The dormant asset from the server.
95
+ * @param cwd Current working directory (project root).
96
+ * @returns Array of absolute file paths to probe.
97
+ */
98
+ function deriveDormantFilePaths(asset, cwd) {
99
+ // Defense-in-depth: even though `name` originates from platform-seeded DB
100
+ // entries (not user input), validate it against a strict charset before
101
+ // interpolating into a filesystem path. Reject anything that could traverse
102
+ // out of the expected directories. WP-379 S4 review finding.
103
+ if (!/^[A-Za-z0-9._-]+$/.test(asset.name)) {
104
+ return [];
105
+ }
106
+ const paths = [];
107
+ const { name, assetKind } = asset;
108
+ if (assetKind === 'skill') {
109
+ // Cursor skill
110
+ paths.push(join(cwd, '.cursor', 'skills', name, 'SKILL.md'));
111
+ // Codex skill
112
+ paths.push(join(cwd, '.codex', 'skills', `${name}.md`));
113
+ }
114
+ else if (assetKind === 'rule' || assetKind === 'hook') {
115
+ // Cursor rule
116
+ paths.push(join(cwd, '.cursor', 'rules', `${name}.mdc`));
117
+ // Claude rule
118
+ paths.push(join(cwd, '.claude', 'rules', `${name}.md`));
119
+ }
120
+ return paths;
121
+ }
122
+ /**
123
+ * Single-shot health probe — calls `workspace.health` and inspects
124
+ * `starterSetupSeeded`. Does NOT poll internally; polling is the caller's
125
+ * responsibility (connect-screens.tsx).
126
+ *
127
+ * Returns:
128
+ * - `seeds-ready` — health query succeeded AND starterSetupSeeded is true
129
+ * - `seeds-pending` — health query succeeded but starterSetupSeeded is false
130
+ * - `probe-failed` — health query threw (network, auth, etc.)
131
+ */
132
+ export async function probeStarterSetupSeeded() {
133
+ try {
134
+ const health = await kernelCall('workspace.health', {});
135
+ if (health.starterSetupSeeded) {
136
+ return { status: 'seeds-ready' };
137
+ }
138
+ const starterGaps = (health.gaps ?? []).filter((g) => g.kind === 'starter-setup-missing' || g.kind === 'platform-domains-missing');
139
+ return {
140
+ status: 'seeds-pending',
141
+ gaps: starterGaps.length > 0 ? starterGaps : [
142
+ {
143
+ kind: 'starter-setup-missing',
144
+ severity: 'warn',
145
+ message: 'Starter setup seeds are still running.',
146
+ },
147
+ ],
148
+ };
149
+ }
150
+ catch (err) {
151
+ return {
152
+ status: 'probe-failed',
153
+ error: err instanceof Error ? err.message : String(err),
154
+ };
155
+ }
156
+ }
157
+ /**
158
+ * Poll `probeStarterSetupSeeded` up to MAX_POLLS times (10s at 500ms intervals).
159
+ * Returns the final probe result — caller decides how to render the outcome.
160
+ *
161
+ * Exported so connect-screens.tsx can use it without re-implementing the loop.
162
+ */
163
+ export async function pollUntilSeedsReady() {
164
+ for (let poll = 0; poll < MAX_POLLS; poll++) {
165
+ const result = await probeStarterSetupSeeded();
166
+ if (result.status === 'seeds-ready')
167
+ return result;
168
+ if (result.status === 'probe-failed')
169
+ return result; // don't retry on auth/network errors
170
+ // seeds-pending — wait before next poll
171
+ if (poll < MAX_POLLS - 1) {
172
+ await new Promise((res) => setTimeout(res, POLL_INTERVAL_MS));
173
+ }
174
+ }
175
+ // Final probe after exhausting waits — return whatever state we have
176
+ return probeStarterSetupSeeded();
177
+ }
178
+ const LEVELS = {
179
+ guide: {
180
+ label: 'Guide me',
181
+ description: "Explain what you're doing, ask before anything unfamiliar",
182
+ defaultMode: 'auto',
183
+ allow: ['Bash(pb:*)', 'Bash(git:*)'],
184
+ },
185
+ work: {
186
+ label: 'Just work',
187
+ description: 'Ask only when something could be risky or irreversible',
188
+ defaultMode: 'auto',
189
+ allow: ['Bash(pb:*)', 'Bash(git:*)', 'Bash(npm run:*)'],
190
+ },
191
+ silent: {
192
+ label: 'Silent',
193
+ description: "I'll review the diff; don't ask unless it's destructive",
194
+ defaultMode: 'acceptEdits',
195
+ allow: ['Bash(pb:*)', 'Bash(git:*)', 'Bash(npm run:*)', 'Bash(npx:*)'],
196
+ },
197
+ 'full-trust': {
198
+ label: 'Full trust',
199
+ description: 'Never ask (your machine, your call)',
200
+ defaultMode: 'bypassPermissions',
201
+ allow: [],
202
+ },
203
+ };
204
+ // Explicit ordering — determines menu numbering in promptLevel(). Do not reorder.
205
+ const LEVEL_KEYS = ['guide', 'work', 'silent', 'full-trust'];
206
+ // Hook failure contract (TEN-712): all hook commands MUST end with '2>/dev/null || true'
207
+ // so Claude Code always starts even if pb is unavailable. Never remove this suffix.
208
+ const INIT_PERMISSION = 'Bash(pb:*)';
209
+ function readSettings(filePath) {
210
+ if (!existsSync(filePath))
211
+ return {};
212
+ try {
213
+ return JSON.parse(readFileSync(filePath, 'utf8'));
214
+ }
215
+ catch {
216
+ console.error(`Warning: ${filePath} has invalid JSON — starting fresh.`);
217
+ return {};
218
+ }
219
+ }
220
+ // Team write: hooks + Bash(pb:*) → .claude/settings.json (safe to commit)
221
+ function writeTeamSettings(cwd, dryRun) {
222
+ const claudeDir = join(cwd, '.claude');
223
+ const settingsPath = join(claudeDir, 'settings.json');
224
+ const settings = readSettings(settingsPath);
225
+ const added = [];
226
+ const permissions = (settings.permissions ?? {});
227
+ const allow = permissions.allow ?? [];
228
+ if (!allow.includes(INIT_PERMISSION)) {
229
+ allow.push(INIT_PERMISSION);
230
+ permissions.allow = allow;
231
+ added.push('Bash(pb:*) permission');
232
+ }
233
+ settings.permissions = permissions;
234
+ const hooks = (settings.hooks ?? {});
235
+ const hookAdditions = composeHooksFromIntents(['session-start', 'session-close', 'pre-compact'], hooks);
236
+ for (const addition of hookAdditions) {
237
+ hooks[addition.event] = [...(hooks[addition.event] ?? []), addition.entry];
238
+ added.push(addition.label);
239
+ }
240
+ settings.hooks = hooks;
241
+ if (!dryRun) {
242
+ mkdirSync(claudeDir, { recursive: true });
243
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
244
+ }
245
+ return added;
246
+ }
247
+ // Personal write: trust level → ~/.claude/settings.json (never committed)
248
+ function writePersonalSettings(levelKey, dryRun) {
249
+ const level = LEVELS[levelKey];
250
+ const globalDir = join(homedir(), '.claude');
251
+ const globalPath = join(globalDir, 'settings.json');
252
+ const settings = readSettings(globalPath);
253
+ const added = [];
254
+ const permissions = (settings.permissions ?? {});
255
+ if (permissions.defaultMode !== level.defaultMode) {
256
+ permissions.defaultMode = level.defaultMode;
257
+ added.push(`defaultMode → ${level.defaultMode}`);
258
+ }
259
+ const allow = permissions.allow ?? [];
260
+ for (const entry of level.allow) {
261
+ if (!allow.includes(entry)) {
262
+ allow.push(entry);
263
+ added.push(`allow: ${entry}`);
264
+ }
265
+ }
266
+ permissions.allow = allow;
267
+ settings.permissions = permissions;
268
+ if (!dryRun) {
269
+ mkdirSync(globalDir, { recursive: true });
270
+ writeFileSync(globalPath, JSON.stringify(settings, null, 2) + '\n');
271
+ }
272
+ return added;
273
+ }
274
+ async function promptLevel() {
275
+ const result = await promptSelect({
276
+ message: 'How much should Claude explain before acting?',
277
+ options: LEVEL_KEYS.map((key) => ({
278
+ value: key,
279
+ label: LEVELS[key].label,
280
+ hint: LEVELS[key].description,
281
+ })),
282
+ });
283
+ return result;
284
+ }
285
+ export async function runHandshakeInit(options = {}) {
286
+ const cwd = process.cwd();
287
+ const dryRun = options.dryRun ?? false;
288
+ const suffix = dryRun ? ' (dry run)' : '';
289
+ if (options.level && !LEVEL_KEYS.includes(options.level)) {
290
+ throw new CLIError(`Unknown level "${options.level}".`, {
291
+ code: ErrorCode.VALIDATION_FAILED,
292
+ category: 'validation',
293
+ guidance: `Valid levels: ${LEVEL_KEYS.join(', ')}`,
294
+ });
295
+ }
296
+ console.log('Setting up Claude Code integration...\n');
297
+ // Step 1: Team config — always, no prompt
298
+ const teamAdded = writeTeamSettings(cwd, dryRun);
299
+ const teamPath = join(cwd, '.claude', 'settings.json');
300
+ if (teamAdded.length === 0) {
301
+ console.log(`✓ ${teamPath} — already up to date${suffix}`);
302
+ }
303
+ else {
304
+ console.log(`✓ ${teamPath}${suffix}`);
305
+ for (const item of teamAdded)
306
+ console.log(` + ${item}`);
307
+ }
308
+ // Step 2: Personal config — wizard or --level flag
309
+ // Cast is safe: LEVEL_KEYS.includes() validated above; invalid level already threw CLIError.
310
+ const levelKey = options.level ? options.level : await promptLevel();
311
+ const level = LEVELS[levelKey];
312
+ const personalAdded = writePersonalSettings(levelKey, dryRun);
313
+ const personalPath = join(homedir(), '.claude', 'settings.json');
314
+ if (personalAdded.length === 0) {
315
+ console.log(`✓ ${personalPath} — already at "${level.label}"${suffix}`);
316
+ }
317
+ else {
318
+ console.log(`✓ ${personalPath} — set to "${level.label}"${suffix}`);
319
+ for (const item of personalAdded)
320
+ console.log(` + ${item}`);
321
+ }
322
+ console.log('');
323
+ console.log('Team config → commit .claude/settings.json to share hooks with your team');
324
+ console.log('Personal config → ~/.claude/settings.json is private to this machine, never commit');
325
+ console.log('');
326
+ console.log('Hook failure: if pb is unavailable, hooks fail silently — Claude Code always starts.');
327
+ console.log('');
328
+ if (!dryRun)
329
+ console.log('Reload /hooks in Claude Code (or restart) to activate.');
330
+ console.log('Run `pb handshake --init --level <guide|work|silent|full-trust>` to change level.');
331
+ // Step 2b: Report multi-surface hook opt-in status (WP-310 E3b)
332
+ // Reads manifest.hooks.{cursor,copilot} and prints an informational note for
333
+ // each opted-in surface. Silence = no manifest or no hooks flags set.
334
+ // DEC-536: Claude-native default is already wired above; this block only fires
335
+ // when the user has explicitly opted in via manifest.
336
+ const pbDirForManifest = join(cwd, '.productbrain');
337
+ const initManifest = readManifest(pbDirForManifest);
338
+ const multiSurfaceOptIns = [];
339
+ if (initManifest?.hooks?.cursor)
340
+ multiSurfaceOptIns.push('cursor');
341
+ if (initManifest?.hooks?.copilot)
342
+ multiSurfaceOptIns.push('copilot');
343
+ if (multiSurfaceOptIns.length > 0) {
344
+ console.log('');
345
+ for (const surface of multiSurfaceOptIns) {
346
+ const status = getHookStatusForSurface(surface);
347
+ if (status.writable) {
348
+ // Future-proofing path: surface has hook events, would write files.
349
+ console.log(`ℹ ${surface} hooks: opted in — hooks will be written`);
350
+ }
351
+ else {
352
+ console.log(`ℹ ${surface} hooks: opted in — no auto-hooks (${surface} has no session events; run \`pb session start\` manually)`);
353
+ }
354
+ }
355
+ }
356
+ // Step 3: Scaffold starter templates if .productbrain/rules/ is empty
357
+ const rulesDir = join(cwd, '.productbrain', 'rules');
358
+ const hasExistingRules = existsSync(rulesDir) && readdirSync(rulesDir).filter((f) => f.endsWith('.md')).length > 0;
359
+ if (!hasExistingRules) {
360
+ // Detect stack from repo to pick template set
361
+ const repo = detectRepo(cwd);
362
+ const stack = repo.detectedStack.map((s) => s.toLowerCase());
363
+ let templateSet;
364
+ if (stack.some((s) => ['typescript', 'sveltekit', 'nextjs', 'react'].includes(s))) {
365
+ templateSet = 'node-ts';
366
+ }
367
+ else if (stack.includes('python')) {
368
+ templateSet = 'python';
369
+ }
370
+ else {
371
+ templateSet = 'general';
372
+ }
373
+ // Resolve templates directory relative to this file
374
+ const __filename = fileURLToPath(import.meta.url);
375
+ const __dirname = dirname(__filename);
376
+ // From dist/commands/handshake.js → ../../templates/
377
+ const templatesRoot = resolve(__dirname, '..', '..', 'templates');
378
+ const templateDir = join(templatesRoot, templateSet);
379
+ if (existsSync(templateDir)) {
380
+ const templateFiles = readdirSync(templateDir).filter((f) => f.endsWith('.md'));
381
+ if (templateFiles.length > 0) {
382
+ console.log('');
383
+ console.log(`Scaffolding starter rules from ${templateSet} template...`);
384
+ if (!dryRun) {
385
+ mkdirSync(rulesDir, { recursive: true });
386
+ for (const file of templateFiles) {
387
+ const src = join(templateDir, file);
388
+ const dest = join(rulesDir, file);
389
+ copyFileSync(src, dest);
390
+ console.log(` + .productbrain/rules/${file}`);
391
+ }
392
+ console.log('');
393
+ console.log('Run `pb handshake` to sync the scaffolded rules to your AI tools.');
394
+ }
395
+ else {
396
+ for (const file of templateFiles) {
397
+ console.log(` + .productbrain/rules/${file} (dry run)`);
398
+ }
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
404
+ /**
405
+ * Normalize volatile handshake-only timestamps before comparing generated files.
406
+ * This keeps the visible timestamps in generated artifacts while avoiding
407
+ * meaningless rewrites when semantic content is unchanged.
408
+ */
409
+ export function normalizeHandshakeContentForComparison(content) {
410
+ return content
411
+ .replace(/<!-- auto-generated by pb handshake — [0-9]{4}-[0-9]{2}-[0-9]{2}T[^>]+ -->/g, '<!-- auto-generated by pb handshake — <TIMESTAMP> -->')
412
+ .replace(/^_Generated: [0-9]{4}-[0-9]{2}-[0-9]{2}T.*_$/gm, '_Generated: <TIMESTAMP>_');
413
+ }
414
+ function shouldWriteAdapter(filePath, force) {
415
+ if (force)
416
+ return true;
417
+ if (!existsSync(filePath))
418
+ return true;
419
+ const content = readFileSync(filePath, 'utf8');
420
+ return content.includes(MARKER);
421
+ }
422
+ function deduplicateEntries(entries) {
423
+ const seen = new Set();
424
+ const result = [];
425
+ for (const e of entries) {
426
+ const key = e.entryId ?? e.name;
427
+ if (!seen.has(key)) {
428
+ seen.add(key);
429
+ result.push(e);
430
+ }
431
+ }
432
+ return result;
433
+ }
434
+ /**
435
+ * resolveProjectionCollision — WP-379 S5b
436
+ *
437
+ * Marker-scoped orphan unlink: enumerates target dirs (.cursor/rules/,
438
+ * .claude/rules/, .claude/skills/, .codex/skills/); for each file that has
439
+ * the auto-gen MARKER whose lowercase-normalized filename does NOT match any
440
+ * current active-asset materializedFilename, the file is unlinked.
441
+ *
442
+ * User-forked files (no MARKER) are never touched, regardless of name.
443
+ *
444
+ * Linux case-collision disambiguation:
445
+ * 1. Exact match (lowercase name == any canonical name): survives.
446
+ * 2. Case-variant with MARKER (marker file, no exact canonical match): unlinked.
447
+ * 3. Ambiguous (zero exact, multiple case-variants with MARKER):
448
+ * newest mtime wins; all others are unlinked; a collision TEN is
449
+ * appended to the session capture queue (not fired inline).
450
+ *
451
+ * Returns a list of unlink results so the caller can log/report them.
452
+ *
453
+ * @param cwd Project root (absolute path).
454
+ * @param assetNames The current set of canonical asset names from the server
455
+ * (e.g. ["Setup-ProductBrain", "chain-rules"]).
456
+ * @param log Progress log function.
457
+ * @param logErr Error log function.
458
+ */
459
+ export function resolveProjectionCollision(cwd, assetNames, log, logErr) {
460
+ // Target directories by extension suffix.
461
+ const TARGET_DIRS_BY_EXT = [
462
+ { dir: join(cwd, '.cursor', 'rules'), ext: '.mdc' },
463
+ { dir: join(cwd, '.claude', 'rules'), ext: '.md' },
464
+ { dir: join(cwd, '.claude', 'skills'), ext: '.md' },
465
+ { dir: join(cwd, '.codex', 'skills'), ext: '.md' },
466
+ ];
467
+ // Build a set of normalized canonical names (without extension) for fast lookup.
468
+ // We normalize all asset names to detect case-variant collisions.
469
+ // For each asset name we derive the normalized basename (the part before the ext).
470
+ // canonicalNormalizedNames: Set<normalized-stem> (lowercase + slug).
471
+ const canonicalNormalizedStems = new Set(assetNames.map((n) => normalizeMaterializedFilename(n)));
472
+ const results = [];
473
+ const collisionTens = [];
474
+ for (const { dir, ext } of TARGET_DIRS_BY_EXT) {
475
+ if (!existsSync(dir))
476
+ continue;
477
+ let files;
478
+ try {
479
+ files = readdirSync(dir);
480
+ }
481
+ catch {
482
+ continue; // unreadable dir — skip
483
+ }
484
+ // Group files by their normalized stem.
485
+ // normalizedStem → [ { filename, fullPath } ]
486
+ const groups = new Map();
487
+ for (const filename of files) {
488
+ if (!filename.endsWith(ext))
489
+ continue;
490
+ const fullPath = join(dir, filename);
491
+ // Only operate on files that have the auto-gen MARKER.
492
+ let content;
493
+ try {
494
+ content = readFileSync(fullPath, 'utf8');
495
+ }
496
+ catch {
497
+ continue; // unreadable file — skip
498
+ }
499
+ if (!content.includes(MARKER))
500
+ continue; // user-forked — never touch
501
+ const stem = basename(filename, ext);
502
+ const normalizedStem = normalizeMaterializedFilename(stem);
503
+ const group = groups.get(normalizedStem) ?? [];
504
+ group.push({ filename, fullPath });
505
+ groups.set(normalizedStem, group);
506
+ }
507
+ // Evaluate each normalized stem group.
508
+ for (const [normalizedStem, members] of groups) {
509
+ const isKnownCanonical = canonicalNormalizedStems.has(normalizedStem);
510
+ if (!isKnownCanonical) {
511
+ // All members of this group are orphans (no canonical asset with this stem).
512
+ // Unlink them all — they're stale projections of an asset no longer in the server.
513
+ for (const { filename, fullPath } of members) {
514
+ try {
515
+ unlinkSync(fullPath);
516
+ log(`Orphan unlinked: ${fullPath}`);
517
+ results.push({ action: 'unlinked', filePath: fullPath, reason: 'orphan-no-canonical-match' });
518
+ }
519
+ catch (err) {
520
+ logErr(`Warning: could not unlink orphan ${fullPath} — ${err instanceof Error ? err.message : String(err)}`);
521
+ results.push({ action: 'kept', filePath: fullPath, reason: 'unlink-failed' });
522
+ }
523
+ }
524
+ continue;
525
+ }
526
+ // The stem IS known canonical. Check for case-collision.
527
+ if (members.length === 1) {
528
+ // Single file — no collision.
529
+ results.push({ action: 'kept', filePath: members[0].fullPath, reason: 'canonical-exact' });
530
+ continue;
531
+ }
532
+ // Multiple files with the same normalized stem → case-collision.
533
+ // Rule: exact match (filename stem === normalized stem, i.e. already lowercase) wins.
534
+ const exactMatches = members.filter(({ filename }) => {
535
+ const stem = basename(filename, ext);
536
+ return stem === normalizedStem; // lowercase-equal means already normalized
537
+ });
538
+ if (exactMatches.length === 1) {
539
+ // Rule 1: exactly one exact match → keep it, unlink all case-variants.
540
+ const keeper = exactMatches[0];
541
+ results.push({ action: 'kept', filePath: keeper.fullPath, reason: 'case-exact-match-wins' });
542
+ for (const member of members) {
543
+ if (member.fullPath === keeper.fullPath)
544
+ continue;
545
+ try {
546
+ unlinkSync(member.fullPath);
547
+ log(`Case-variant unlinked: ${member.fullPath} (kept: ${keeper.filename})`);
548
+ results.push({ action: 'unlinked', filePath: member.fullPath, reason: 'case-variant-unlinked' });
549
+ }
550
+ catch (err) {
551
+ logErr(`Warning: could not unlink case-variant ${member.fullPath} — ${err instanceof Error ? err.message : String(err)}`);
552
+ results.push({ action: 'kept', filePath: member.fullPath, reason: 'unlink-failed' });
553
+ }
554
+ }
555
+ continue;
556
+ }
557
+ // Rule 3: ambiguous — zero exact matches (or multiple exact matches, which
558
+ // can't happen on a case-sensitive FS). Newest mtime wins.
559
+ // Sort by mtime descending: highest mtime = newest = winner.
560
+ const withStats = members.map(({ filename, fullPath }) => {
561
+ try {
562
+ const { mtimeMs } = statSync(fullPath);
563
+ return { filename, fullPath, mtimeMs };
564
+ }
565
+ catch {
566
+ return { filename, fullPath, mtimeMs: 0 };
567
+ }
568
+ });
569
+ withStats.sort((a, b) => b.mtimeMs - a.mtimeMs);
570
+ const winner = withStats[0];
571
+ log(`Case-collision ambiguous for ${normalizedStem}${ext}: newest mtime wins (${winner.filename})`);
572
+ results.push({ action: 'collision-ten', filePath: winner.fullPath, reason: 'ambiguous-newest-mtime-wins' });
573
+ const tenMsg = `Handshake case-collision: ambiguous filename for stem "${normalizedStem}${ext}" ` +
574
+ `(${members.map((m) => m.filename).join(', ')}). ` +
575
+ `Kept newest: ${winner.filename}. Consider renaming to ${normalizedStem}${ext}.`;
576
+ collisionTens.push(tenMsg);
577
+ for (const member of withStats.slice(1)) {
578
+ try {
579
+ unlinkSync(member.fullPath);
580
+ log(`Case-variant (ambiguous) unlinked: ${member.fullPath}`);
581
+ results.push({ action: 'unlinked', filePath: member.fullPath, reason: 'ambiguous-case-variant-unlinked' });
582
+ }
583
+ catch (err) {
584
+ logErr(`Warning: could not unlink ambiguous case-variant ${member.fullPath} — ${err instanceof Error ? err.message : String(err)}`);
585
+ results.push({ action: 'kept', filePath: member.fullPath, reason: 'unlink-failed' });
586
+ }
587
+ }
588
+ }
589
+ }
590
+ return { results, collisionTens };
591
+ }
592
+ export async function runHandshake(options = {}) {
593
+ const config = await getConfigOrGuide(() => runHandshake(options));
594
+ if (!config)
595
+ return;
596
+ const cwd = process.cwd();
597
+ const force = options.force ?? false;
598
+ const dryRun = options.dryRun ?? false;
599
+ // Preview mode: default when neither --apply nor --dry-run is passed.
600
+ // --dry-run is kept as a backward-compat alias for preview (same behavior).
601
+ const preview = !options.apply && !dryRun;
602
+ const applyMode = options.apply === true && !dryRun;
603
+ const level = options.level;
604
+ const quiet = options.quiet ?? false;
605
+ const generate = options.generate ?? false;
606
+ const timestamp = new Date().toISOString();
607
+ // Helper: emit progress line only when not quiet (used when handshake is a sub-step)
608
+ const log = (msg) => { if (!quiet)
609
+ process.stdout.write(msg + '\n'); };
610
+ const logErr = (msg) => { if (!quiet)
611
+ process.stderr.write(msg + '\n'); };
612
+ // Validate --level if provided (for handshake content filtering, not --init trust level)
613
+ const VALID_HANDSHAKE_LEVELS = ['beginner', 'intermediate', 'expert'];
614
+ if (level && !VALID_HANDSHAKE_LEVELS.includes(level)) {
615
+ throw new CLIError(`Unknown level "${level}".`, {
616
+ code: ErrorCode.VALIDATION_FAILED,
617
+ category: 'validation',
618
+ guidance: `Valid levels: ${VALID_HANDSHAKE_LEVELS.join(', ')}`,
619
+ });
620
+ }
621
+ // 1. Detect repo
622
+ const repo = detectRepo(cwd);
623
+ log(`Detecting repo: ${repo.name ?? 'unknown'}${repo.repoSlug ? ` (${repo.repoSlug})` : ''}`);
624
+ if (repo.detectedStack.length > 0) {
625
+ log(`Stack: ${repo.detectedStack.join(', ')}`);
626
+ }
627
+ // 2. Fetch orient view + workspace readiness in parallel (budget max +200ms added latency)
628
+ log('Fetching workspace context...');
629
+ let orientView = null;
630
+ let workspaceProfile = null;
631
+ try {
632
+ const workspaceReadinessPromise = kernelCall('chain.workspaceReadiness', {}).catch(() => null);
633
+ const [orientResult, readinessRaw] = await Promise.all([
634
+ kernelCall('chain.getOrientView', {}).catch((err) => {
635
+ logErr(`Warning: could not fetch workspace context — ${err instanceof Error ? err.message : err}`);
636
+ logErr('Continuing with limited context (Chain search + portable knowledge only).');
637
+ return null;
638
+ }),
639
+ workspaceReadinessPromise,
640
+ ]);
641
+ orientView = orientResult;
642
+ workspaceProfile = extractWorkspaceProfile(readinessRaw);
643
+ }
644
+ catch (err) {
645
+ logErr(`Warning: could not fetch workspace context — ${err instanceof Error ? err.message : err}`);
646
+ logErr('Continuing with limited context (Chain search + portable knowledge only).');
647
+ }
648
+ if (workspaceProfile) {
649
+ log(`Workspace profile: stage=${workspaceProfile.stage}, entries=${workspaceProfile.totalEntries}, governance=${workspaceProfile.governanceMode}`);
650
+ }
651
+ // 3. Build search queries from repo context
652
+ const searchQueries = [];
653
+ if (repo.name && repo.name.length >= 2)
654
+ searchQueries.push(repo.name);
655
+ // Add repo slug parts (e.g. "Product-OS" -> "Product-OS")
656
+ if (repo.repoSlug) {
657
+ const repoPart = repo.repoSlug.split('/').pop();
658
+ if (repoPart && repoPart.length >= 2 && repoPart !== repo.name) {
659
+ searchQueries.push(repoPart);
660
+ }
661
+ }
662
+ // Limit to 5 unique queries
663
+ const uniqueQueries = [...new Set(searchQueries.filter((q) => q.length >= 2))].slice(0, 5);
664
+ // 4. Search the Chain
665
+ let matchedEntries = [];
666
+ if (uniqueQueries.length > 0) {
667
+ log(`Searching Chain for: ${uniqueQueries.join(', ')}...`);
668
+ const searchResults = await Promise.all(uniqueQueries.map((q) => kernelCall('chain.searchEntries', { query: q }).catch(() => [])));
669
+ matchedEntries = deduplicateEntries(searchResults.flat());
670
+ }
671
+ // 5. Read canonical skills & rules — DB-first (WP-345 S0c, TEN-1459), FS fallback.
672
+ // Primary: query setup.listAssetsForUser from DB (workspace SSOT).
673
+ // Fallback: read from .productbrain/ filesystem (legacy — used when DB is empty or unavailable).
674
+ const pbDir = join(cwd, '.productbrain');
675
+ let dbSkills = [];
676
+ let dbRules = [];
677
+ let usedDbSource = false;
678
+ let dbAssetRows = [];
679
+ // WP-379 S4: dormant assets (gate-failed) — their on-disk files get the dormant marker.
680
+ let dormantDbAssetRows = [];
681
+ // WP-379 S5b: whether any setup_receipt exists for this workspace (first-run UX gate).
682
+ // undefined when server is pre-S5b (treat as unknown → suppress drift TENs conservatively).
683
+ let hasAnyReceipt = undefined;
684
+ const dbProjectionHashes = new Map();
685
+ try {
686
+ // WP-379 S4: listAssetsForUser now returns { activeAssets, dormantAssets }.
687
+ // Wire format changed from DbAsset[] to { activeAssets: DbAsset[], dormantAssets: DbAsset[] }.
688
+ // Fall back to empty arrays if the server returns the old flat-array shape (graceful degradation).
689
+ const rawResponse = await kernelCall('setup.listAssetsForUser', {}).catch(() => null);
690
+ let dbAssets = [];
691
+ if (rawResponse !== null) {
692
+ if (Array.isArray(rawResponse)) {
693
+ // Pre-S4 server — treat entire response as active assets with no dormant list.
694
+ dbAssets = rawResponse;
695
+ dormantDbAssetRows = [];
696
+ hasAnyReceipt = undefined; // unknown on legacy servers
697
+ }
698
+ else {
699
+ dbAssets = rawResponse.activeAssets ?? [];
700
+ dormantDbAssetRows = rawResponse.dormantAssets ?? [];
701
+ // WP-379 S5b: extract hasAnyReceipt when provided by the server.
702
+ // undefined means pre-S5b server — we treat as unknown (no receipts assumed).
703
+ hasAnyReceipt = rawResponse.hasAnyReceipt;
704
+ }
705
+ }
706
+ if (dbAssets.length > 0) {
707
+ dbAssetRows = dbAssets;
708
+ // Map DB assets to CanonicalSkill/CanonicalRule shapes
709
+ for (const asset of dbAssets) {
710
+ if (asset.disabledByOwner)
711
+ continue;
712
+ if (asset.assetKind === 'skill') {
713
+ dbSkills.push({
714
+ name: asset.name,
715
+ description: asset.description,
716
+ triggers: asset.triggers ?? [],
717
+ body: asset.body,
718
+ sourcePath: `db:${asset.entryId}`,
719
+ });
720
+ }
721
+ else if (asset.assetKind === 'rule' || asset.assetKind === 'hook') {
722
+ dbRules.push({
723
+ name: asset.name,
724
+ description: asset.description,
725
+ autoApply: false,
726
+ body: asset.body,
727
+ sourcePath: `db:${asset.entryId}`,
728
+ });
729
+ }
730
+ }
731
+ usedDbSource = true;
732
+ log(`Setup assets: ${dbSkills.length} skills, ${dbRules.length} rules/hooks from DB (WP-345 DB-first path)`);
733
+ if (dormantDbAssetRows.length > 0) {
734
+ log(`Setup assets: ${dormantDbAssetRows.length} dormant (gate-filtered) asset(s) will be marked on disk`);
735
+ }
736
+ }
737
+ }
738
+ catch {
739
+ // DB source unavailable — silently fall through to FS path
740
+ }
741
+ const allSkills = usedDbSource ? dbSkills : readCanonicalSkills(pbDir);
742
+ const manualRules = usedDbSource ? dbRules : readCanonicalRules(pbDir);
743
+ if (!usedDbSource) {
744
+ log('Setup assets: reading from filesystem (DB source unavailable or empty)');
745
+ }
746
+ // 5a-pre. E4: Resolve semantic refs in DB assets (WP-345, DEC-A, DEC-C, DEC-K)
747
+ // For each asset with semanticRefs[], resolve them via the Convex resolver and
748
+ // replace {{ref:key}} placeholders in the body. Runs in apply mode only (not preview).
749
+ // NG11: PostHog events fire from CLI side only (never inside Convex mutations).
750
+ if (usedDbSource && applyMode) {
751
+ const projectableDbAssets = dbAssetRows.filter((a) => !a.disabledByOwner && (a.assetKind === 'skill' || a.assetKind === 'rule' || a.assetKind === 'hook'));
752
+ const assetsWithRefs = projectableDbAssets.filter((a) => a.semanticRefs && a.semanticRefs.length > 0);
753
+ if (assetsWithRefs.length > 0) {
754
+ log(`Resolving semantic refs for ${assetsWithRefs.length} asset(s)...`);
755
+ // Collect unique ref keys across all assets
756
+ const allRefKeys = [...new Set(assetsWithRefs.flatMap((a) => a.semanticRefs))];
757
+ // Resolve all refs in a single batch call. Shape: SetupRefResolution[]
758
+ // (DEC-767 / WP-354 Build-Order #6 — kind + status discriminator).
759
+ let resolvedRefs = [];
760
+ try {
761
+ resolvedRefs = await kernelCall('setup.resolveSemanticRefs', { semanticRefs: allRefKeys });
762
+ }
763
+ catch (err) {
764
+ trackEvent('setup.refs.resolve_failed', { error: err instanceof Error ? err.message : String(err) });
765
+ logErr(`Warning: could not resolve semantic refs — ${err instanceof Error ? err.message : String(err)}`);
766
+ }
767
+ // Build resolved map: canonicalKey → display name. Only required refs
768
+ // count as unresolved warnings; seed/unknown refs are not gates.
769
+ const resolvedMap = new Map();
770
+ let unresolvedCount = 0;
771
+ for (const result of resolvedRefs) {
772
+ if (result.status === 'resolved' && result.localEntryId) {
773
+ resolvedMap.set(result.ref, result.localEntryId);
774
+ trackEvent('skill.ref.resolved', { ref: result.ref, kind: result.kind });
775
+ }
776
+ else if (result.status === 'unsupported-future') {
777
+ // seed: refs are explicitly future scope per WP-354 Build-Order #6 — not a warning.
778
+ trackEvent('skill.ref.future', { ref: result.ref, kind: result.kind });
779
+ }
780
+ else if (result.required) {
781
+ unresolvedCount++;
782
+ trackEvent('skill.ref.unresolved', { ref: result.ref, kind: result.kind, status: result.status });
783
+ }
784
+ }
785
+ if (unresolvedCount > 0) {
786
+ logErr(`Warning: ${unresolvedCount} required semantic ref(s) could not be resolved.`);
787
+ }
788
+ // Projection must preserve ref tokens as portable machine-readable refs.
789
+ // resolvedMap is only used for validation/telemetry here; generated setup
790
+ // artifacts keep {{ref:domain:...}} / {{ref:entry:...}} intact.
791
+ }
792
+ // Compute projection hash for each projected DB asset (DEC-K).
793
+ // The stored hash is sha256 of the normalized resolved asset body; DB persistence
794
+ // is deferred until the write loop confirms a matching file was emitted.
795
+ // Strip existing hash trailer and timestamp lines, normalize to LF, then hash.
796
+ const HASH_TRAILER_REGEX = /^<!--\s*pb-hash:.*-->\s*$/gm;
797
+ const TIMESTAMP_REGEX = /^<!--\s*pb-generated-at:.*-->\s*$/gm;
798
+ for (const rawAsset of projectableDbAssets) {
799
+ const resolvedAsset = [...dbSkills, ...dbRules].find((a) => a.sourcePath === `db:${rawAsset.entryId}`);
800
+ if (!resolvedAsset)
801
+ continue;
802
+ try {
803
+ // Build the projected body (what will be written to disk)
804
+ const projectedBody = resolvedAsset.body;
805
+ // Normalize: strip existing hash/timestamp trailers, convert to LF
806
+ const normalized = projectedBody
807
+ .replace(HASH_TRAILER_REGEX, '')
808
+ .replace(TIMESTAMP_REGEX, '')
809
+ .replace(/\r\n/g, '\n')
810
+ .replace(/\r/g, '\n')
811
+ .trimEnd();
812
+ // Compute sha256 hash
813
+ const hash = createHash('sha256').update(normalized, 'utf8').digest('hex');
814
+ const hashTrailer = `<!-- pb-hash: sha256:${hash} -->`;
815
+ // Append hash trailer to the projected body
816
+ resolvedAsset.body = `${normalized}\n${hashTrailer}`;
817
+ dbProjectionHashes.set(rawAsset.entryId, {
818
+ hash: `sha256:${hash}`,
819
+ assetKind: rawAsset.assetKind,
820
+ });
821
+ // Drift detection: compare against last known hash
822
+ if (rawAsset.lastProjectedHash && rawAsset.lastProjectedHash !== `sha256:${hash}`) {
823
+ trackEvent('skill.drift.detected', {
824
+ entryId: rawAsset.entryId,
825
+ assetKind: rawAsset.assetKind,
826
+ });
827
+ log(`Drift detected for asset ${rawAsset.entryId} — projecting updated version.`);
828
+ }
829
+ trackEvent('skill.projection.succeeded', {
830
+ entryId: rawAsset.entryId,
831
+ assetKind: rawAsset.assetKind,
832
+ });
833
+ }
834
+ catch (err) {
835
+ trackEvent('skill.projection.failed', {
836
+ entryId: rawAsset.entryId,
837
+ assetKind: rawAsset.assetKind,
838
+ });
839
+ logErr(`Warning: projection failed for ${rawAsset.entryId} — ${err instanceof Error ? err.message : String(err)}`);
840
+ }
841
+ }
842
+ }
843
+ // 5a. Optionally fetch and merge Chain-derived rules (--generate flag)
844
+ let chainRulesStats = null;
845
+ let chainGaps = [];
846
+ let allRules = manualRules;
847
+ if (generate) {
848
+ log('Generating Chain-derived rules...');
849
+ const chainResult = await generateChainRules(kernelCall, manualRules);
850
+ if (chainResult.sentinel) {
851
+ // MCP unavailable — inject sentinel rule and warn
852
+ allRules = [...manualRules, chainResult.sentinel];
853
+ logErr('Warning: Chain MCP unavailable — generated rules are disabled. Sentinel rule injected.');
854
+ }
855
+ else {
856
+ // Merge generated rules after manual rules (manual takes precedence on dedup)
857
+ allRules = [...manualRules, ...chainResult.rules];
858
+ chainRulesStats = chainResult.stats;
859
+ chainGaps = chainResult.gaps;
860
+ const { generatedRules, suppressedByManual, suppressedByZeroEntries } = chainResult.stats;
861
+ log(`Chain-derived rules: ${generatedRules} generated, ${suppressedByManual} suppressed by manual, ${suppressedByZeroEntries} gaps`);
862
+ if (chainGaps.length > 0) {
863
+ log(`Gaps (no matching governance entries): ${chainGaps.join(', ')}`);
864
+ }
865
+ // Diff: compare current state against previous run
866
+ const previousState = loadPreviousState(pbDir);
867
+ const currentState = buildCurrentState(chainResult.rules, chainResult.classified);
868
+ saveHandshakeState(pbDir, chainResult.rules, chainResult.classified);
869
+ const diff = diffHandshakeState(currentState, previousState);
870
+ const diffText = formatDiff(diff);
871
+ log(diffText);
872
+ }
873
+ }
874
+ // 5b. Read personal layer (WP-310 E2) — machine-local rules/skills from .productbrain/.local/
875
+ // Returns [] when the directory is absent. All returned entries have persist: 'local'.
876
+ const personalRules = readPersonalLayer(pbDir);
877
+ if (personalRules.length > 0) {
878
+ // Name collision detection — warn when a personal rule overrides a team rule
879
+ const teamRuleNames = new Set(allRules.map((r) => r.name));
880
+ for (const pr of personalRules) {
881
+ if (teamRuleNames.has(pr.name)) {
882
+ logErr(`Personal rule "${pr.name}" overrides team rule (local takes precedence).`);
883
+ }
884
+ }
885
+ // Merge: team rules first, personal rules appended (personal takes precedence via name collision above)
886
+ allRules = [...allRules, ...personalRules];
887
+ }
888
+ const personalSkills = readPersonalSkillsLayer(pbDir);
889
+ if (personalSkills.length > 0) {
890
+ const teamSkillNames = new Set(allSkills.map((s) => s.name));
891
+ for (const ps of personalSkills) {
892
+ if (teamSkillNames.has(ps.name)) {
893
+ logErr(`Personal skill "${ps.name}" overrides team skill (local takes precedence).`);
894
+ }
895
+ }
896
+ allSkills.push(...personalSkills);
897
+ }
898
+ // 5c. Apply manifest-based adoption filter (WP-310 E1)
899
+ // readManifest returns null when manifest.yaml is absent → filterByAdoptionState is a no-op.
900
+ const manifest = readManifest(pbDir);
901
+ // 5d. Load method registry (WP-310 E4) — only when manifest is present.
902
+ let registrySource;
903
+ let registryStale;
904
+ if (manifest) {
905
+ const registryResult = await loadMethodRegistry(manifest.method_source, kernelCall).catch(() => null);
906
+ if (registryResult) {
907
+ registrySource = registryResult.source;
908
+ registryStale = registryResult.stale;
909
+ }
910
+ }
911
+ const adoptionFilteredSkills = filterByAdoptionState(allSkills, manifest);
912
+ const adoptionFilteredRules = filterByAdoptionState(allRules, manifest);
913
+ // Compute adoption counts for report (only meaningful when manifest is present)
914
+ const adoptedRulesCount = manifest ? adoptionFilteredRules.length : undefined;
915
+ const rejectedRulesCount = manifest ? allRules.length - adoptionFilteredRules.length : undefined;
916
+ // Apply level filtering with stage-gating (after adoption filter, before target filtering in write loop)
917
+ // Stage caps the effective level: blank→beginner, seed→intermediate, grounded+→expert.
918
+ // If stage caps below the requested level, log it so the user knows why items were dropped.
919
+ const profileStage = workspaceProfile?.stage;
920
+ const levelFilteredSkills = filterByLevel(adoptionFilteredSkills, level, profileStage);
921
+ const levelFilteredRules = filterByLevel(adoptionFilteredRules, level, profileStage);
922
+ // Log when stage gating changes the effective level
923
+ if (profileStage) {
924
+ const stageCap = STAGE_TO_MAX_LEVEL[profileStage];
925
+ if (stageCap) {
926
+ const requestedIdx = level ? LEVEL_ORDER.indexOf(level) : LEVEL_ORDER.length - 1;
927
+ const capIdx = LEVEL_ORDER.indexOf(stageCap);
928
+ if (capIdx < requestedIdx) {
929
+ log(`Stage "${profileStage}" caps level from ${level || 'expert'} to ${stageCap}`);
930
+ }
931
+ }
932
+ }
933
+ // Apply when-condition filtering (stage-aware, workspace profile + repo context)
934
+ const canonicalSkills = levelFilteredSkills.filter((skill) => {
935
+ const result = evaluateConditions(skill.conditions ?? {}, workspaceProfile, repo);
936
+ if (dryRun && !result.included) {
937
+ log(` EXCLUDED skill ${skill.name}: ${result.reasons.join(', ')}`);
938
+ }
939
+ return result.included;
940
+ });
941
+ const canonicalRules = levelFilteredRules.filter((rule) => {
942
+ const result = evaluateConditions(rule.conditions ?? {}, workspaceProfile, repo);
943
+ if (dryRun && !result.included) {
944
+ log(` EXCLUDED rule ${rule.name}: ${result.reasons.join(', ')}`);
945
+ }
946
+ return result.included;
947
+ });
948
+ if (dryRun && canonicalSkills.length > 0) {
949
+ log(` INCLUDED skills: ${canonicalSkills.map((s) => s.name).join(', ')}`);
950
+ }
951
+ if (dryRun && canonicalRules.length > 0) {
952
+ log(` INCLUDED rules: ${canonicalRules.map((r) => r.name).join(', ')}`);
953
+ }
954
+ if (canonicalSkills.length > 0 || canonicalRules.length > 0) {
955
+ const levelSuffix = level ? ` (level: ${level})` : '';
956
+ const stageSuffix = profileStage ? `, stage: ${profileStage}` : '';
957
+ const stackSuffix = repo.detectedStack.length > 0 ? `, stack: [${repo.detectedStack.join(', ')}]` : '';
958
+ const totalSkills = allSkills.length;
959
+ const totalRules = allRules.length;
960
+ log(`Portable knowledge: ${canonicalSkills.length}/${totalSkills} skills, ${canonicalRules.length}/${totalRules} rules${levelSuffix}${stageSuffix}${stackSuffix}`);
961
+ }
962
+ // 6. Generate file contents
963
+ // Build workspace context for AGENTS.md enrichment (stage, focus, governance, entry count)
964
+ const agentsWorkspaceContext = workspaceProfile
965
+ ? {
966
+ stage: workspaceProfile.stage,
967
+ focus: orientView?.strategicContext?.currentBet ?? undefined,
968
+ governanceMode: workspaceProfile.governanceMode,
969
+ totalEntries: workspaceProfile.totalEntries,
970
+ }
971
+ : undefined;
972
+ // Collect codex-targeted skills for AGENTS.md skill directory
973
+ // Exclude persist: 'local' rules — committed adapter files must never include local-only rules.
974
+ const agentsCodexSkills = canonicalSkills
975
+ .filter((s) => shouldEmitToTarget(s, 'codex'))
976
+ .map((s) => ({
977
+ name: s.name,
978
+ description: s.description,
979
+ triggers: s.triggers,
980
+ }));
981
+ // Collect copilot-targeted skills for copilot-instructions.md skill summaries
982
+ const copilotSkills = canonicalSkills
983
+ .filter((s) => shouldEmitToTarget(s, 'copilot'))
984
+ .map((s) => ({
985
+ name: s.name,
986
+ description: s.description,
987
+ triggers: s.triggers,
988
+ }));
989
+ // Collect copilot-targeted rules for copilot-instructions.md rule summaries
990
+ const copilotRules = canonicalRules
991
+ .filter((r) => shouldEmitToTarget(r, 'copilot'))
992
+ .map((r) => ({
993
+ name: r.name,
994
+ description: r.description,
995
+ }));
996
+ const copilotProfile = resolveSurfaceProfile('copilot');
997
+ const copilotOptions = {
998
+ profile: copilotProfile,
999
+ workspaceContext: agentsWorkspaceContext,
1000
+ skills: copilotSkills.length > 0 ? copilotSkills : undefined,
1001
+ rules: copilotRules.length > 0 ? copilotRules : undefined,
1002
+ };
1003
+ const contextContent = orientView ? generateContextMd(orientView, repo, timestamp, workspaceProfile?.stage) : null;
1004
+ const briefingContent = generateBriefingMd(matchedEntries, repo, uniqueQueries, timestamp);
1005
+ const agentsContent = generateAgentsMd(timestamp, {
1006
+ workspaceContext: agentsWorkspaceContext,
1007
+ skills: agentsCodexSkills.length > 0 ? agentsCodexSkills : undefined,
1008
+ });
1009
+ const claudeContent = generateClaudeMd(timestamp);
1010
+ const cursorContent = generateCursorMdc(timestamp);
1011
+ const copilotContent = generateCopilotMd(timestamp, copilotOptions);
1012
+ const boundaryEnforcementMode = getBoundaryEnforcementMode(manifest);
1013
+ const boundaryManifestContent = boundaryEnforcementMode === 'advisory'
1014
+ ? null
1015
+ : generateBoundaryManifest(pbDir);
1016
+ // 7. Write files
1017
+ const filesWritten = [];
1018
+ const filesSkipped = [];
1019
+ const previewPlan = [];
1020
+ // Surface filtering: skip adapter writes for targets not in the allowed set
1021
+ const allowedTargets = options.surfaces && options.surfaces.length > 0
1022
+ ? new Set(options.surfaces)
1023
+ : null; // null = write all
1024
+ const writes = [
1025
+ ...(contextContent ? [{ path: join(cwd, '.productbrain', 'context.md'), relative: '.productbrain/context.md', content: contextContent, dirs: join(cwd, '.productbrain'), isAdapter: false }] : []),
1026
+ { path: join(cwd, '.productbrain', 'briefing.md'), relative: '.productbrain/briefing.md', content: briefingContent, isAdapter: false },
1027
+ { path: join(cwd, 'AGENTS.md'), relative: 'AGENTS.md', content: agentsContent, isAdapter: true, target: 'codex' },
1028
+ { path: join(cwd, 'CLAUDE.md'), relative: 'CLAUDE.md', content: claudeContent, isAdapter: true, target: 'claude' },
1029
+ { path: join(cwd, '.cursor', 'rules', 'chain.mdc'), relative: '.cursor/rules/chain.mdc', content: cursorContent, dirs: join(cwd, '.cursor', 'rules'), isAdapter: true, target: 'cursor' },
1030
+ { path: join(cwd, '.github', 'copilot-instructions.md'), relative: '.github/copilot-instructions.md', content: copilotContent, dirs: join(cwd, '.github'), isAdapter: true, target: 'copilot' },
1031
+ ...(boundaryManifestContent ? [{
1032
+ path: join(cwd, '.productbrain', 'generated', 'boundaries.json'),
1033
+ relative: '.productbrain/generated/boundaries.json',
1034
+ content: boundaryManifestContent,
1035
+ dirs: join(cwd, '.productbrain', 'generated'),
1036
+ isAdapter: false,
1037
+ }] : []),
1038
+ ];
1039
+ // Add Cursor skill copies (filtered by target)
1040
+ const cursorProfile = resolveSurfaceProfile('cursor');
1041
+ for (const skill of canonicalSkills) {
1042
+ if (!shouldEmitToTarget(skill, 'cursor'))
1043
+ continue;
1044
+ const dbAssetEntryId = skill.sourcePath.startsWith('db:') ? skill.sourcePath.slice(3) : undefined;
1045
+ const skillDir = join(cwd, '.cursor', 'skills', skill.name);
1046
+ writes.push({
1047
+ path: join(skillDir, 'SKILL.md'),
1048
+ relative: `.cursor/skills/${skill.name}/SKILL.md`,
1049
+ content: generateCursorSkill(skill, cursorProfile),
1050
+ dirs: skillDir,
1051
+ isAdapter: true,
1052
+ target: 'cursor',
1053
+ dbAssetEntryId,
1054
+ });
1055
+ }
1056
+ // Add Codex skill copies (projected markdown + index)
1057
+ const codexProfile = resolveSurfaceProfile('codex');
1058
+ const codexSkills = canonicalSkills.filter((s) => shouldEmitToTarget(s, 'codex'));
1059
+ for (const skill of codexSkills) {
1060
+ const dbAssetEntryId = skill.sourcePath.startsWith('db:') ? skill.sourcePath.slice(3) : undefined;
1061
+ writes.push({
1062
+ path: join(cwd, '.codex', 'skills', `${skill.name}.md`),
1063
+ relative: `.codex/skills/${skill.name}.md`,
1064
+ content: generateCodexSkill(skill, codexProfile),
1065
+ dirs: join(cwd, '.codex', 'skills'),
1066
+ isAdapter: true,
1067
+ target: 'codex',
1068
+ dbAssetEntryId,
1069
+ });
1070
+ }
1071
+ writes.push({
1072
+ path: join(cwd, '.codex', 'skills', 'README.md'),
1073
+ relative: '.codex/skills/README.md',
1074
+ content: generateCodexSkillIndex(codexSkills),
1075
+ dirs: join(cwd, '.codex', 'skills'),
1076
+ isAdapter: true,
1077
+ target: 'codex',
1078
+ });
1079
+ // Validate Codex-projected skills for dead references
1080
+ const codexWarnings = validateCodexSkills(codexSkills);
1081
+ // Add Cursor rule copies (filtered by target)
1082
+ for (const rule of canonicalRules) {
1083
+ if (!shouldEmitToTarget(rule, 'cursor'))
1084
+ continue;
1085
+ const dbAssetEntryId = rule.sourcePath.startsWith('db:') ? rule.sourcePath.slice(3) : undefined;
1086
+ writes.push({
1087
+ path: join(cwd, '.cursor', 'rules', `${rule.name}.mdc`),
1088
+ relative: `.cursor/rules/${rule.name}.mdc`,
1089
+ content: generateCursorRule(rule, cursorProfile),
1090
+ dirs: join(cwd, '.cursor', 'rules'),
1091
+ isAdapter: true,
1092
+ target: 'cursor',
1093
+ dbAssetEntryId,
1094
+ });
1095
+ }
1096
+ // Add Claude Code rule copies (filtered by target)
1097
+ const claudeProfile = resolveSurfaceProfile('claude');
1098
+ for (const rule of canonicalRules) {
1099
+ if (!shouldEmitToTarget(rule, 'claude'))
1100
+ continue;
1101
+ const dbAssetEntryId = rule.sourcePath.startsWith('db:') ? rule.sourcePath.slice(3) : undefined;
1102
+ writes.push({
1103
+ path: join(cwd, '.claude', 'rules', `${rule.name}.md`),
1104
+ relative: `.claude/rules/${rule.name}.md`,
1105
+ content: generateClaudeRule(rule, claudeProfile),
1106
+ dirs: join(cwd, '.claude', 'rules'),
1107
+ isAdapter: true,
1108
+ target: 'claude',
1109
+ dbAssetEntryId,
1110
+ });
1111
+ }
1112
+ // Add Claude Code skill router (filtered by target)
1113
+ const claudeSkills = canonicalSkills.filter((s) => shouldEmitToTarget(s, 'claude'));
1114
+ const skillRouterContent = generateClaudeSkillRouter(claudeSkills, claudeProfile);
1115
+ if (skillRouterContent) {
1116
+ writes.push({
1117
+ path: join(cwd, '.claude', 'rules', 'skill-router.md'),
1118
+ relative: '.claude/rules/skill-router.md',
1119
+ content: skillRouterContent,
1120
+ dirs: join(cwd, '.claude', 'rules'),
1121
+ isAdapter: true,
1122
+ target: 'claude',
1123
+ });
1124
+ }
1125
+ // 7a. WP-379 S5b: Resolve projection collisions before writing.
1126
+ // In apply mode, enumerate target dirs and unlink any auto-generated files
1127
+ // whose normalized name no longer matches any active asset from the server.
1128
+ // This prevents case-variant orphans from accumulating across handshakes.
1129
+ // Runs only when we have a DB asset list (usedDbSource) — without a DB source,
1130
+ // we can't determine which files are canonical vs. orphan.
1131
+ const collisionTensToFire = [];
1132
+ if (applyMode && usedDbSource) {
1133
+ const activeAssetNames = dbAssetRows
1134
+ .filter((a) => !a.disabledByOwner)
1135
+ .map((a) => a.name);
1136
+ const { collisionTens } = resolveProjectionCollision(cwd, activeAssetNames, log, logErr);
1137
+ collisionTensToFire.push(...collisionTens);
1138
+ }
1139
+ const forkedPaths = [];
1140
+ const projectedHashUpdates = new Map();
1141
+ const recordProjectedHash = (entryId) => {
1142
+ if (!applyMode || !entryId)
1143
+ return;
1144
+ const projection = dbProjectionHashes.get(entryId);
1145
+ if (projection)
1146
+ projectedHashUpdates.set(entryId, projection.hash);
1147
+ };
1148
+ for (const w of writes) {
1149
+ // Surface filtering: skip adapter writes for targets not in the allowed set
1150
+ if (allowedTargets && w.target && !allowedTargets.has(w.target)) {
1151
+ filesSkipped.push({ path: w.relative, reason: `filtered (surface: ${w.target})` });
1152
+ if (preview)
1153
+ previewPlan.push({ path: w.relative, status: 'filtered' });
1154
+ continue;
1155
+ }
1156
+ if (w.isAdapter && !shouldWriteAdapter(w.path, force)) {
1157
+ filesSkipped.push({ path: w.relative, reason: 'exists without auto-generated marker (use --force to overwrite)' });
1158
+ if (preview) {
1159
+ previewPlan.push({ path: w.relative, status: 'forked' });
1160
+ }
1161
+ else {
1162
+ forkedPaths.push(w.relative);
1163
+ }
1164
+ continue;
1165
+ }
1166
+ if (preview || dryRun) {
1167
+ // In preview/dry-run mode: check content to distinguish new/update/unchanged
1168
+ if (existsSync(w.path)) {
1169
+ const current = readFileSync(w.path, 'utf8');
1170
+ const nextNormalized = normalizeHandshakeContentForComparison(w.content);
1171
+ const currentNormalized = normalizeHandshakeContentForComparison(current);
1172
+ if (nextNormalized === currentNormalized) {
1173
+ filesSkipped.push({ path: w.relative, reason: 'unchanged' });
1174
+ if (preview)
1175
+ previewPlan.push({ path: w.relative, status: 'unchanged' });
1176
+ }
1177
+ else {
1178
+ filesWritten.push(w.relative + (dryRun ? ' (dry run)' : ''));
1179
+ if (preview)
1180
+ previewPlan.push({ path: w.relative, status: 'would-update' });
1181
+ }
1182
+ }
1183
+ else {
1184
+ filesWritten.push(w.relative + (dryRun ? ' (dry run)' : ''));
1185
+ if (preview)
1186
+ previewPlan.push({ path: w.relative, status: 'would-write' });
1187
+ }
1188
+ continue;
1189
+ }
1190
+ if (w.dirs)
1191
+ mkdirSync(w.dirs, { recursive: true });
1192
+ if (existsSync(w.path)) {
1193
+ const current = readFileSync(w.path, 'utf8');
1194
+ const nextNormalized = normalizeHandshakeContentForComparison(w.content);
1195
+ const currentNormalized = normalizeHandshakeContentForComparison(current);
1196
+ if (nextNormalized === currentNormalized) {
1197
+ filesSkipped.push({ path: w.relative, reason: 'unchanged' });
1198
+ recordProjectedHash(w.dbAssetEntryId);
1199
+ continue;
1200
+ }
1201
+ }
1202
+ writeFileSync(w.path, w.content);
1203
+ filesWritten.push(w.relative);
1204
+ recordProjectedHash(w.dbAssetEntryId);
1205
+ }
1206
+ if (projectedHashUpdates.size > 0) {
1207
+ const updates = [...projectedHashUpdates];
1208
+ const results = await Promise.allSettled(updates.map(([entryId, hash]) => kernelCall('setup.updateLastProjectedHash', { entryId, hash })));
1209
+ results.forEach((result, index) => {
1210
+ if (result.status === 'rejected') {
1211
+ const [entryId] = updates[index];
1212
+ trackEvent('skill.projection.failed', {
1213
+ entryId,
1214
+ reason: result.reason instanceof Error ? result.reason.message : String(result.reason),
1215
+ });
1216
+ }
1217
+ });
1218
+ }
1219
+ // 8a. Dormant marker writes (WP-379 S4) — apply mode only.
1220
+ // For each dormant asset (gate-filtered by the server), locate any previously-projected
1221
+ // on-disk files and append the DORMANT_MARKER trailer. Files are NOT deleted.
1222
+ //
1223
+ // Drift TEN exclusion: dormant-marked files have the auto-gen MARKER, so
1224
+ // shouldWriteAdapter() returns true for them. However, because the asset is dormant,
1225
+ // it is NOT in the `writes` array — it was never queued for a fresh write. Therefore,
1226
+ // dormant files will never appear in forkedPaths (forkedPaths only catches files that
1227
+ // ARE in the writes array but fail shouldWriteAdapter). The dormant marker write is a
1228
+ // separate, independent pass that runs BEFORE the drift TEN check — intentionally
1229
+ // after the main write loop to avoid interfering with active asset writes.
1230
+ //
1231
+ // Fail-open: if a dormant marker write fails, log and continue. Never crash the handshake.
1232
+ const dormantMarkedPaths = [];
1233
+ if (applyMode && dormantDbAssetRows.length > 0) {
1234
+ for (const dormantAsset of dormantDbAssetRows) {
1235
+ const candidatePaths = deriveDormantFilePaths(dormantAsset, cwd);
1236
+ for (const filePath of candidatePaths) {
1237
+ try {
1238
+ const markerResult = writeDormantMarkerToFile(filePath);
1239
+ if (markerResult === 'written') {
1240
+ dormantMarkedPaths.push(filePath);
1241
+ log(`Dormant marker written: ${filePath}`);
1242
+ }
1243
+ // 'already-dormant' and 'skipped' are silent no-ops — idempotent.
1244
+ }
1245
+ catch (err) {
1246
+ // Fail-open: dormant marker write is advisory. Log, never throw.
1247
+ logErr(`Warning: could not write dormant marker to ${filePath} — ${err instanceof Error ? err.message : String(err)}`);
1248
+ }
1249
+ }
1250
+ }
1251
+ }
1252
+ // 8. Drift logging — if apply mode encountered forked adapters and a session is active, log a draft TEN.
1253
+ // Dormant-marked files are NOT forked — they were intentionally deactivated and have the auto-gen MARKER.
1254
+ // They will never appear in forkedPaths because they are excluded from the `writes` array entirely.
1255
+ //
1256
+ // WP-379 S5b — First-run UX rule:
1257
+ // When `hasAnyReceipt` is false OR undefined (unknown / pre-S5b server), drift TENs are suppressed.
1258
+ // Rationale: on the first handshake, users may have pre-existing non-marked files from manual
1259
+ // setup; we must not flood them with TENs before they've even had one successful materialization.
1260
+ // A TEN fires only after setup_receipt.count >= 1 for this workspace.
1261
+ //
1262
+ // Conservative unknown-treatment: if the server does not provide hasAnyReceipt (old server),
1263
+ // we treat it as "no receipt exists" and suppress the TEN. The day-1 experience is more important
1264
+ // than catching every early drift case; the TEN will fire on the next run once the server is updated.
1265
+ const isFirstRun = hasAnyReceipt !== true; // true when no receipts or unknown
1266
+ if (forkedPaths.length > 0) {
1267
+ if (isFirstRun) {
1268
+ log(`Info: ${forkedPaths.length} adapter(s) skipped (user files without auto-gen marker). ` +
1269
+ 'Drift TEN suppressed — first run (no setup receipt yet). Files: ' +
1270
+ forkedPaths.join(', '));
1271
+ }
1272
+ else {
1273
+ const session = readSession();
1274
+ if (session) {
1275
+ const names = forkedPaths.join(', ');
1276
+ kernelCallWithSession('chain.createEntry', {
1277
+ collectionSlug: 'tensions',
1278
+ name: `TEN: handshake drift — ${forkedPaths.length} adapter(s) forked, sync blocked`,
1279
+ status: 'draft',
1280
+ data: {
1281
+ description: `pb handshake --apply encountered forked adapters that blocked sync. Files: ${names}. Use --force to overwrite or resolve drift manually.`,
1282
+ },
1283
+ sessionId: session.sessionId,
1284
+ createdBy: `agent:${session.sessionId}`,
1285
+ }).catch(() => { });
1286
+ }
1287
+ }
1288
+ }
1289
+ // 8. Case-collision TENs (WP-379 S5b) — fire after the first-run gate.
1290
+ // These are distinct from drift TENs: they record ambiguous filename collisions
1291
+ // where the "newest mtime wins" heuristic was applied. They fire regardless of
1292
+ // first-run status (collision is a data quality issue, not a drift issue).
1293
+ if (collisionTensToFire.length > 0) {
1294
+ const session = readSession();
1295
+ if (session) {
1296
+ for (const tenDescription of collisionTensToFire) {
1297
+ kernelCallWithSession('chain.createEntry', {
1298
+ collectionSlug: 'tensions',
1299
+ name: `TEN: handshake case-collision — ambiguous filename resolved by mtime`,
1300
+ status: 'draft',
1301
+ data: { description: tenDescription },
1302
+ sessionId: session.sessionId,
1303
+ createdBy: `agent:${session.sessionId}`,
1304
+ }).catch(() => { });
1305
+ }
1306
+ }
1307
+ }
1308
+ // 8b. Setup receipt — record which assets were materialized (apply mode only)
1309
+ // Fail-open: receipt write is advisory, never blocks the handshake.
1310
+ if (applyMode) {
1311
+ const session = readSession();
1312
+ const caller = session ? kernelCallWithSession : kernelCall;
1313
+ try {
1314
+ const receiptResult = await caller('setup.materializeSetup', {});
1315
+ if (receiptResult?.assetCount > 0) {
1316
+ log(`Setup receipt: ${receiptResult.assetCount} asset(s) recorded.`);
1317
+ }
1318
+ }
1319
+ catch (err) {
1320
+ trackEvent('setup.receipt.write_failed', { error: err instanceof Error ? err.message : String(err) });
1321
+ logErr(`Warning: could not write setup receipt — ${err instanceof Error ? err.message : String(err)}`);
1322
+ }
1323
+ }
1324
+ // 9. Report
1325
+ const report = {
1326
+ filesWritten,
1327
+ filesSkipped,
1328
+ matchedEntries,
1329
+ searchQueries: uniqueQueries,
1330
+ repo,
1331
+ codexWarnings: codexWarnings.length > 0 ? codexWarnings : undefined,
1332
+ chainRulesStats: chainRulesStats ?? undefined,
1333
+ chainGaps: chainGaps.length > 0 ? chainGaps : undefined,
1334
+ adoptedCount: adoptedRulesCount,
1335
+ rejectedCount: rejectedRulesCount,
1336
+ personalRuleCount: personalRules.length > 0 ? personalRules.length : undefined,
1337
+ personalSkillCount: personalSkills.length > 0 ? personalSkills.length : undefined,
1338
+ registrySource,
1339
+ registryStale,
1340
+ preview: preview ? true : undefined,
1341
+ previewPlan: preview && previewPlan.length > 0 ? previewPlan : undefined,
1342
+ driftConflicts: forkedPaths.length > 0 ? forkedPaths : undefined,
1343
+ };
1344
+ if (!quiet) {
1345
+ process.stdout.write('\n');
1346
+ process.stdout.write(formatHandshakeReport(report) + '\n');
1347
+ }
1348
+ }
1349
+ //# sourceMappingURL=handshake.js.map