chainlesschain 0.162.100 → 0.162.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 (209) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-C4AUvxsD.js → AIOps-C1k00Gsz.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CQx5g40U.js → ActionButton-BinI6ZDq.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-Cj8kniQB.js → Analytics-Cg0jo_cd.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-uksktrtP.js → AppLayout-CClMs_AA.js} +4 -4
  6. package/src/assets/web-panel/assets/{Audit-Ddw1D-Sa.js → Audit-BtQgvFdM.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-FY6UxY3D.js → Backup-43FRi7ZI.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-Dyy3sgKP.js → BaseInput-Cfza-FZU.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-tiAnsEKt.js → Chat-7AjHSiSC.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DdrOqOOC.js → ChatBubbleRenderer-Bh1YNdtx.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-BNcfTk-N.js → Checkbox-DgjfqlWW.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-pRCacnwB.js → Codegen-CkwOBThj.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-B6YlZPJZ.js → Col-Cohk5WYM.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-CVxyOMmk.js → Community-D3LMGh3F.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DUcI72Cz.js → Compact-DqoMJmh0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-Jwoo1gAe.js → Compliance-BVjWZlEj.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-Dy4KE7GI.js → Cowork-C0lqQPqu.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-CuK2QRSM.js → Cron-DHd_PJZS.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-CLVhl9qA.js → Crosschain-BphK2Q9u.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-CbbeulSb.js → DID-CWIw0aG4.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-B5mZKvMY.js → Dashboard-CKgDU9U6.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-R_QKvEks.js → Dropdown-DkIlDUXF.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-MLN19CuX.js → EmailListRenderer-5sXA9hwZ.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Da6P7Opx.js → FamilyGuardDashboard-CgcdJLH7.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DSIDBZjI.js → Federation-DPOcJISA.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-Tg9XUdYt.js → FormItemContext-DxOutBMJ.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-D6BoiR4f.js → GenericCardRenderer-BYVNtkcd.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BwKxt-az.js → Git-B37LKxOu.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-CZ6acDLS.js → Governance-CnKcx5un.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-FpwqxLVZ.js → Inference-CbRAWN3_.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-BptTOYDC.js → KnowledgeGraph-D0w6RfE_.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-BqqAJttN.js → Logs-DxBDKpzq.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-BBWAUKbG.js → Marketplace-DNLi6O_3.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-B3SbHbb2.js → McpTools-s0sBCPXw.js} +4 -4
  35. package/src/assets/web-panel/assets/{Memory-B2TeOSG3.js → Memory-BdSEgpmP.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-Cc23XCoy.js → MobileBridge-Dz7nz4K9.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-BNVM6ZVt.js → MobileProjects-DakdA2PC.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-BpCkBS7F.js → Mtc-Be9OBypk.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-CVGrJf70.js → MtcAudit-KI2ih4Tl.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-CDjGMJeY.js → Multisig-Cg3jHFjL.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CzQV0HBf.js → NLProgramming-BepHx8fU.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-opsvQMjX.js → Notes-2f05HyqN.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-YcsTplJb.js → NotificationSettings-Blwio_G5.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-D5nRE_x6.js → OrderTableRenderer-D7HJfvo_.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-WjorRlbN.js → Organization-Uihlz1cI.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Bp5-oTQ9.js → Overflow-gZx8kADL.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-OkFf348U.js → P2P-DqEEGqba.js} +2 -2
  48. package/src/assets/web-panel/assets/PdhVaultBrowser-BHUi1nb5.js +7 -0
  49. package/src/assets/web-panel/assets/{Permissions-cKThyS90.js → Permissions-ERF2ZQ3G.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-_GrM7ozt.js → PersonalDataHub-CzDy9CFB.js} +2 -2
  51. package/src/assets/web-panel/assets/{Pipeline-CkY3lGDl.js → Pipeline-Dy4x9Ejw.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CnS_dd_g.js → Privacy-C6UJys52.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-CUcPk9vr.js → ProjectInit-Bmi027fa.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-i2i7Ykz7.js → ProjectSettings-NinSZmGA.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-DB8eQuaI.js → Projects-DeW-p-hT.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-C_c6uhkR.js → Providers-4hxrBTLo.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-CzGlGrW2.js → QuickAsk-B5inZlW8.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-BgubzHgA.js → Recommend-7lpBm00-.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CJm5qyVb.js → Reputation-QfdGZxYO.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-DTo8pzXo.js → Row-DqHC7BCT.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-D9dBFFbB.js → RssFeed-B3F1aGP6.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-CdtKcva0.js → Search-BXtjuoYO.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-I_w7dCJB.js → Security-Iy0MOV-A.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-BNkH7gB_.js → Services-C_puMp_6.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-CmjuiQ-X.js → Skeleton-Bg6mTVZU.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-DwWRjcC9.js → Skills-D4wXhanR.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-reA2nX1r.js → Sla-Cgr2B9R6.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-29sZleYf.js → SpeechSettings-ctYqZEY5.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DpREiryw.js → SyncSettings-C3qWh9y4.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-DSFhXmqt.js → Tasks-D3AAgUx1.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-ocxFfRbU.js → Templates-Bm_Dyz8T.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-CB8ceTRF.js → Tenant-DOSrOw9X.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-DrG0bEOS.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-5gi8YyK8.js → TimelineRenderer-BB8Qq6Cx.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-5ei6T7Fr.js → Tokens-DqyF_Flh.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-CCCK4AdU.js → Trigger-ClwQC2hP.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-u_Ai2JnF.js → Trust-DGQSLPQ0.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DOYAKABP.js → UkeySign-BAkeYsUO.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BCijqxrL.js → VideoEditing-WyUxS65Y.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-B3DYN-Zc.js → Wallet-Br7GdjEg.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DtUm68s2.js → WebAuthn-bKs1dahj.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-DLeKMTsN.js → WorkflowEditor-CVqAALYe.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CQaZBF2-.js → chat-BDGDRbab.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-FgoJ2BwA.js → colors-DIUxZwT8.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-eO7813ss.js → compact-item-C2iM4GmN.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-Dmmz4MHV.js → createContext-D27NeHwO.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-Daz0PHhS.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-Dsqq2vZS.js → hasIn-D1lXdsFj.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-iQRQbgks.js → index-B5ZB5lsV.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DdQeoRb5.js → index-B6kc4-nD.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BPTR7Mec.js → index-B8POWlbO.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-amwzyKGM.js → index-BE9bNvdn.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-PQCrkiem.js → index-BIVYPlpl.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Bc9ttIaX.js → index-BTUFhfHs.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DXoO_gOa.js → index-BTedVe_v.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CzmnOjxM.js → index-Bi8u5GsP.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D5R74AU3.js → index-Bil9t91t.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-ZGiG2FFd.js → index-BqbjsJcS.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-uVTBfxr4.js → index-CAnMUhHO.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-D9EiKGCp.js → index-CDzSUTpa.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-tvGjyRJX.js → index-CEO9vhv7.js} +3 -3
  102. package/src/assets/web-panel/assets/{index-FxdLXcjM.js → index-CFEwtE3q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-zfGbhmH7.js → index-CFY1KjB1.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BNoEuwKO.js → index-CKMDgjfh.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DHiXX4Vs.js → index-CNk98bb1.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CGivPnGs.js → index-Ca8n97h9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-FmwguCzK.js → index-Cb9u9MMo.js} +1 -1
  108. package/src/assets/web-panel/assets/index-Cbe_eJAR.js +1 -0
  109. package/src/assets/web-panel/assets/{index-CjH-1YTY.js → index-Cbqr8kW6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DqmgVJGi.js → index-CfR3Pvsu.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Ds1EXfXm.js → index-Cgf3zMwq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Dt5foqaR.js → index-CiA-L5ld.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DhJPhaFo.js → index-CoygRreE.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BNRSfhkU.js → index-CsIw-YQF.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DYX4azu5.js → index-DFhdJm7M.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BOmxCZDa.js → index-DK0jgmJg.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CWKCFa8f.js → index-Dc4k4V1u.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-laG9PnzA.js → index-DfSav-yQ.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DgcPjW1c.js +1 -0
  120. package/src/assets/web-panel/assets/{index-CoCyHNT3.js → index-DkOu8er7.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BbY4e6os.js → index-DnCVI295.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D0wmbnlR.js → index-Do13fQ5W.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-RbLIx9Bf.js → index-DrS-Mrm-.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DAsxek9K.js → index-ET5LYk9B.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-0QR3T83p.js → index-HZGu1woY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-3j2RiDPG.js → index-Y1rkRas5.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-G_MQzt5R.js → index-yIM-KxVm.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-C3uZWt9M.js → initDefaultProps-C0Yrc6yY.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-Br29X6q6.js → motion-DnOI85ek.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-B2uHAfsg.js → move-DT9zQPZp.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-DY4JQnIP.js → omit-DFtDi5Wd.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-DUANwwrT.js → pickAttrs-Cobkb_rd.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-ClrBK_Je.js → placementArrow-DWQDu4i-.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-FzBdoJrD.js → responsiveObserve-daoQcWcX.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Bn6of40H.js → slide-BLkzd2OV.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-By0_n-TE.js → statusUtils-Uyxi81M0.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-Rj-gDlgS.js → styleChecker-Dtw5lUcs.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-mAGJOuoi.js → useFlexGapSupport-_JpMgpdt.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-CUheuma1.js → useFs-huwAlPD-.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-DRIdZ3Xz.js → usePersonalDataHub-7cafj8_f.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-0B469T9A.js → vnode-DYUSfRt7.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-yAup1syc.js → zoom-B-tjvmer.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/a2a.js +2 -1
  145. package/src/commands/bi.js +4 -3
  146. package/src/commands/browse.js +3 -2
  147. package/src/commands/codegen.js +7 -6
  148. package/src/commands/collab.js +2 -1
  149. package/src/commands/crosschain.js +5 -4
  150. package/src/commands/dao.js +10 -10
  151. package/src/commands/dbevo.js +3 -2
  152. package/src/commands/economy.js +8 -6
  153. package/src/commands/evolution.js +10 -8
  154. package/src/commands/export.js +2 -1
  155. package/src/commands/federation.js +8 -7
  156. package/src/commands/fusion.js +4 -3
  157. package/src/commands/git.js +2 -1
  158. package/src/commands/governance.js +6 -5
  159. package/src/commands/hardening.js +3 -2
  160. package/src/commands/hmemory.js +2 -1
  161. package/src/commands/hub.js +107 -0
  162. package/src/commands/inference.js +9 -8
  163. package/src/commands/infra.js +9 -8
  164. package/src/commands/instinct.js +66 -0
  165. package/src/commands/learning.js +66 -0
  166. package/src/commands/marketplace.js +5 -4
  167. package/src/commands/matrix.js +2 -1
  168. package/src/commands/memory.js +66 -0
  169. package/src/commands/nlprog.js +4 -3
  170. package/src/commands/nostr.js +4 -3
  171. package/src/commands/note.js +2 -1
  172. package/src/commands/ops.js +3 -2
  173. package/src/commands/perception.js +6 -5
  174. package/src/commands/privacy.js +13 -12
  175. package/src/commands/quantization.js +3 -2
  176. package/src/commands/recommend.js +8 -7
  177. package/src/commands/reputation.js +4 -3
  178. package/src/commands/runtime.js +6 -5
  179. package/src/commands/sandbox.js +5 -4
  180. package/src/commands/session.js +2 -1
  181. package/src/commands/sla.js +6 -5
  182. package/src/commands/social.js +6 -5
  183. package/src/commands/stress.js +7 -6
  184. package/src/commands/sync.js +2 -1
  185. package/src/commands/tenant.js +3 -2
  186. package/src/commands/trust.js +7 -6
  187. package/src/commands/wallet.js +2 -1
  188. package/src/lib/__tests__/agents.test.js +225 -0
  189. package/src/lib/__tests__/cowork-observe-html.test.js +141 -0
  190. package/src/lib/__tests__/logger.test.js +110 -0
  191. package/src/lib/__tests__/project-inventory.test.js +280 -0
  192. package/src/lib/__tests__/project-root.test.js +96 -0
  193. package/src/lib/__tests__/provider-stream.test.js +190 -0
  194. package/src/lib/cli-arg.js +41 -0
  195. package/src/lib/cli-numeric.js +12 -3
  196. package/src/lib/instinct-manager.js +64 -0
  197. package/src/lib/learning/learning-tables.js +83 -0
  198. package/src/lib/mcp-oauth.js +10 -2
  199. package/src/lib/memory-manager.js +67 -0
  200. package/src/lib/plan-mode.js +3 -2
  201. package/src/repl/__tests__/mcp-prompt.test.js +155 -0
  202. package/src/repl/__tests__/slash-macro.test.js +81 -0
  203. package/src/runtime/__tests__/coding-agent-contract-shared.test.js +136 -0
  204. package/src/runtime/__tests__/coding-agent-events.test.js +263 -0
  205. package/src/assets/web-panel/assets/PdhVaultBrowser-ue3TxFAw.js +0 -7
  206. package/src/assets/web-panel/assets/Terminal-Zx-Q7Yq2.js +0 -3
  207. package/src/assets/web-panel/assets/devWarning-UPy4WDBL.js +0 -1
  208. package/src/assets/web-panel/assets/index-B3QyuL3m.js +0 -1
  209. package/src/assets/web-panel/assets/index-CCeQm4U9.js +0 -1
@@ -20,6 +20,70 @@ export function ensureInstinctsTable(db) {
20
20
  `);
21
21
  }
22
22
 
23
+ /** datetime('now')-style timestamp for rows missing one on import. */
24
+ function _instinctNowSqlite() {
25
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
26
+ }
27
+
28
+ /**
29
+ * Export ALL instincts (full dump) for §8.3 cross-device backup. Deterministic
30
+ * order so the same DB exports identically.
31
+ */
32
+ export function exportInstincts(db) {
33
+ ensureInstinctsTable(db);
34
+ return db
35
+ .prepare(
36
+ `SELECT id, category, pattern, confidence, occurrences, last_seen, created_at
37
+ FROM instincts ORDER BY created_at ASC, id ASC`,
38
+ )
39
+ .all();
40
+ }
41
+
42
+ /**
43
+ * Import instincts (§8.3 restore): idempotent upsert by id — preserves created_at,
44
+ * updates category/pattern/confidence/occurrences/last_seen. Never aborts mid-batch.
45
+ */
46
+ export function importInstincts(db, rows) {
47
+ ensureInstinctsTable(db);
48
+ if (!Array.isArray(rows)) {
49
+ throw new Error("importInstincts: rows must be a JSON array");
50
+ }
51
+ const stmt = db.prepare(
52
+ `INSERT INTO instincts (id, category, pattern, confidence, occurrences, last_seen, created_at)
53
+ VALUES (@id, @category, @pattern, @confidence, @occurrences, @last_seen, @created_at)
54
+ ON CONFLICT(id) DO UPDATE SET
55
+ category = excluded.category,
56
+ pattern = excluded.pattern,
57
+ confidence = excluded.confidence,
58
+ occurrences = excluded.occurrences,
59
+ last_seen = excluded.last_seen`,
60
+ );
61
+ let imported = 0;
62
+ let failed = 0;
63
+ const errors = [];
64
+ for (const r of rows) {
65
+ try {
66
+ if (!r || !r.id || !r.category || !r.pattern) {
67
+ throw new Error("invalid instinct (need id + category + pattern)");
68
+ }
69
+ stmt.run({
70
+ id: String(r.id),
71
+ category: r.category,
72
+ pattern: r.pattern,
73
+ confidence: typeof r.confidence === "number" ? r.confidence : 0.5,
74
+ occurrences: Number.isInteger(r.occurrences) ? r.occurrences : 1,
75
+ last_seen: r.last_seen || _instinctNowSqlite(),
76
+ created_at: r.created_at || _instinctNowSqlite(),
77
+ });
78
+ imported += 1;
79
+ } catch (e) {
80
+ failed += 1;
81
+ if (errors.length < 10) errors.push({ id: r && r.id, error: e.message });
82
+ }
83
+ }
84
+ return { ok: failed === 0, imported, failed, errors };
85
+ }
86
+
23
87
  function generateId() {
24
88
  const hex = () =>
25
89
  Math.floor(Math.random() * 0x10000)
@@ -64,3 +64,86 @@ export function ensureLearningTables(db) {
64
64
  )
65
65
  `);
66
66
  }
67
+
68
+ /** datetime('now')-style timestamp for rows missing one on import. */
69
+ function _learningNowSqlite() {
70
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
71
+ }
72
+
73
+ /**
74
+ * Export ALL learning trajectories (full dump) for §8.3 cross-device backup.
75
+ * Includes the synthesized_skill linkage (synthesized skills travel with the
76
+ * trajectories that produced them). The learning_trajectory_tags side-table is
77
+ * derived and intentionally not exported in v1.
78
+ */
79
+ export function exportTrajectories(db) {
80
+ ensureLearningTables(db);
81
+ return db
82
+ .prepare(
83
+ `SELECT id, session_id, user_intent, tool_chain, tool_count, final_response,
84
+ outcome_score, outcome_source, complexity_level, synthesized_skill,
85
+ created_at, completed_at
86
+ FROM learning_trajectories ORDER BY created_at ASC, id ASC`,
87
+ )
88
+ .all();
89
+ }
90
+
91
+ /**
92
+ * Import learning trajectories (§8.3 restore): idempotent upsert by id —
93
+ * preserves created_at, updates the rest. Never aborts mid-batch.
94
+ */
95
+ export function importTrajectories(db, rows) {
96
+ ensureLearningTables(db);
97
+ if (!Array.isArray(rows)) {
98
+ throw new Error("importTrajectories: rows must be a JSON array");
99
+ }
100
+ const stmt = db.prepare(
101
+ `INSERT INTO learning_trajectories
102
+ (id, session_id, user_intent, tool_chain, tool_count, final_response,
103
+ outcome_score, outcome_source, complexity_level, synthesized_skill,
104
+ created_at, completed_at)
105
+ VALUES (@id, @session_id, @user_intent, @tool_chain, @tool_count, @final_response,
106
+ @outcome_score, @outcome_source, @complexity_level, @synthesized_skill,
107
+ @created_at, @completed_at)
108
+ ON CONFLICT(id) DO UPDATE SET
109
+ session_id = excluded.session_id,
110
+ user_intent = excluded.user_intent,
111
+ tool_chain = excluded.tool_chain,
112
+ tool_count = excluded.tool_count,
113
+ final_response = excluded.final_response,
114
+ outcome_score = excluded.outcome_score,
115
+ outcome_source = excluded.outcome_source,
116
+ complexity_level = excluded.complexity_level,
117
+ synthesized_skill = excluded.synthesized_skill,
118
+ completed_at = excluded.completed_at`,
119
+ );
120
+ let imported = 0;
121
+ let failed = 0;
122
+ const errors = [];
123
+ for (const r of rows) {
124
+ try {
125
+ if (!r || !r.id || !r.session_id) {
126
+ throw new Error("invalid trajectory (need id + session_id)");
127
+ }
128
+ stmt.run({
129
+ id: String(r.id),
130
+ session_id: String(r.session_id),
131
+ user_intent: r.user_intent ?? null,
132
+ tool_chain: r.tool_chain ?? "[]",
133
+ tool_count: Number.isInteger(r.tool_count) ? r.tool_count : 0,
134
+ final_response: r.final_response ?? null,
135
+ outcome_score: typeof r.outcome_score === "number" ? r.outcome_score : null,
136
+ outcome_source: r.outcome_source ?? null,
137
+ complexity_level: r.complexity_level ?? "simple",
138
+ synthesized_skill: r.synthesized_skill ?? null,
139
+ created_at: r.created_at || _learningNowSqlite(),
140
+ completed_at: r.completed_at ?? null,
141
+ });
142
+ imported += 1;
143
+ } catch (e) {
144
+ failed += 1;
145
+ if (errors.length < 10) errors.push({ id: r && r.id, error: e.message });
146
+ }
147
+ }
148
+ return { ok: failed === 0, imported, failed, errors };
149
+ }
@@ -410,7 +410,7 @@ function defaultOpenBrowser(url) {
410
410
  }
411
411
 
412
412
  /** Wait for the OAuth redirect on a localhost callback server; resolve {code,state}. */
413
- function waitForCallback({
413
+ export function waitForCallback({
414
414
  port,
415
415
  host = "127.0.0.1",
416
416
  path = "/callback",
@@ -446,7 +446,15 @@ function waitForCallback({
446
446
  server.close();
447
447
  reject(new Error("timed out waiting for the OAuth callback"));
448
448
  }, timeout);
449
- server.on("error", reject);
449
+ // Backstop timer should never keep the process alive on its own (the
450
+ // listening server does that); the loop exits promptly once it closes.
451
+ timer.unref?.();
452
+ server.on("error", (err) => {
453
+ // Without clearing the timer here, a bind failure (e.g. port in use)
454
+ // left it pending for the full `timeout`, delaying process exit.
455
+ clearTimeout(timer);
456
+ reject(err);
457
+ });
450
458
  server.listen(port, host);
451
459
  });
452
460
  }
@@ -133,6 +133,73 @@ export function listMemory(db, options = {}) {
133
133
  .all(limit);
134
134
  }
135
135
 
136
+ /**
137
+ * Export ALL memory entries (full dump, no limit) for §8.3 cross-device backup.
138
+ * Returns rows with id/content/category/importance/source/created_at/updated_at,
139
+ * ordered deterministically so the same DB exports identically.
140
+ */
141
+ export function exportMemory(db) {
142
+ ensureMemoryTable(db);
143
+ return db
144
+ .prepare(
145
+ `SELECT id, content, category, importance, source, created_at, updated_at
146
+ FROM memory_entries ORDER BY created_at ASC, id ASC`,
147
+ )
148
+ .all();
149
+ }
150
+
151
+ /** datetime('now')-style timestamp for entries missing one on import. */
152
+ function _nowSqlite() {
153
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
154
+ }
155
+
156
+ /**
157
+ * Import memory entries (§8.3 backup restore): idempotent upsert by id —
158
+ * preserves the original created_at, updates content/category/importance/source/
159
+ * updated_at. Never aborts mid-batch; returns {ok, imported, failed, errors}.
160
+ */
161
+ export function importMemory(db, entries) {
162
+ ensureMemoryTable(db);
163
+ if (!Array.isArray(entries)) {
164
+ throw new Error("importMemory: entries must be a JSON array");
165
+ }
166
+ const stmt = db.prepare(
167
+ `INSERT INTO memory_entries
168
+ (id, content, category, importance, source, created_at, updated_at)
169
+ VALUES (@id, @content, @category, @importance, @source, @created_at, @updated_at)
170
+ ON CONFLICT(id) DO UPDATE SET
171
+ content = excluded.content,
172
+ category = excluded.category,
173
+ importance = excluded.importance,
174
+ source = excluded.source,
175
+ updated_at = excluded.updated_at`,
176
+ );
177
+ let imported = 0;
178
+ let failed = 0;
179
+ const errors = [];
180
+ for (const e of entries) {
181
+ try {
182
+ if (!e || !e.id || typeof e.content !== "string") {
183
+ throw new Error("invalid memory entry (need id + content)");
184
+ }
185
+ stmt.run({
186
+ id: String(e.id),
187
+ content: e.content,
188
+ category: e.category || "general",
189
+ importance: Number.isInteger(e.importance) ? e.importance : 3,
190
+ source: e.source || "user",
191
+ created_at: e.created_at || _nowSqlite(),
192
+ updated_at: e.updated_at || _nowSqlite(),
193
+ });
194
+ imported += 1;
195
+ } catch (err) {
196
+ failed += 1;
197
+ if (errors.length < 10) errors.push({ id: e && e.id, error: err.message });
198
+ }
199
+ }
200
+ return { ok: failed === 0, imported, failed, errors };
201
+ }
202
+
136
203
  /**
137
204
  * Delete a memory entry
138
205
  */
@@ -31,6 +31,7 @@ export const PlanState = {
31
31
  APPROVED: "approved",
32
32
  EXECUTING: "executing",
33
33
  COMPLETED: "completed",
34
+ FAILED: "failed",
34
35
  REJECTED: "rejected",
35
36
  };
36
37
 
@@ -499,10 +500,10 @@ export class PlanModeManager extends EventEmitter {
499
500
  });
500
501
 
501
502
  const allDone = results.every((r) => r.success);
502
- this.state = allDone ? PlanState.COMPLETED : PlanState.COMPLETED;
503
+ this.state = allDone ? PlanState.COMPLETED : PlanState.FAILED;
503
504
  this.currentPlan.status = allDone
504
505
  ? PlanState.COMPLETED
505
- : PlanState.COMPLETED;
506
+ : PlanState.FAILED;
506
507
 
507
508
  return { results, success: allDone };
508
509
  }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * mcp-prompt tests (previously untested) — the pure core that surfaces MCP
5
+ * server prompts as `/mcp__<server>__<prompt>` slash commands. Covers
6
+ * parseMcpPromptCommand (server/name split incl. `__` in names, JSON-object args
7
+ * vs non-JSON tail, reject non-mcp / missing parts), renderPromptMessages
8
+ * (text + embedded resource text, ignore binary, single vs array content),
9
+ * expandMcpPrompt (parse -> getPrompt -> render, null for non-mcp, throw when no
10
+ * client) and renderMcpSurface (overview / empty / no-client). No I/O.
11
+ */
12
+
13
+ import { describe, it, expect, vi } from "vitest";
14
+ import {
15
+ parseMcpPromptCommand,
16
+ renderPromptMessages,
17
+ expandMcpPrompt,
18
+ renderMcpSurface,
19
+ } from "../mcp-prompt.js";
20
+
21
+ describe("parseMcpPromptCommand", () => {
22
+ it("returns null for non-mcp lines", () => {
23
+ expect(parseMcpPromptCommand("hello")).toBe(null);
24
+ expect(parseMcpPromptCommand("/other__a__b")).toBe(null);
25
+ expect(parseMcpPromptCommand("")).toBe(null);
26
+ });
27
+
28
+ it("parses server and prompt name with empty args", () => {
29
+ expect(parseMcpPromptCommand("/mcp__github__review")).toEqual({
30
+ server: "github",
31
+ name: "review",
32
+ args: {},
33
+ });
34
+ });
35
+
36
+ it("keeps '__' inside the prompt name", () => {
37
+ expect(parseMcpPromptCommand("/mcp__srv__deep__name").name).toBe(
38
+ "deep__name",
39
+ );
40
+ });
41
+
42
+ it("parses a trailing JSON object as args", () => {
43
+ expect(parseMcpPromptCommand('/mcp__s__p {"a":1,"b":"x"}').args).toEqual({
44
+ a: 1,
45
+ b: "x",
46
+ });
47
+ });
48
+
49
+ it("treats a non-JSON tail as { input: tail }", () => {
50
+ expect(parseMcpPromptCommand("/mcp__s__p just some text").args).toEqual({
51
+ input: "just some text",
52
+ });
53
+ });
54
+
55
+ it("treats a JSON array tail as { input } (not an object)", () => {
56
+ expect(parseMcpPromptCommand("/mcp__s__p [1,2]").args).toEqual({
57
+ input: "[1,2]",
58
+ });
59
+ });
60
+
61
+ it("returns null when server or prompt is missing", () => {
62
+ expect(parseMcpPromptCommand("/mcp__srv")).toBe(null); // < 3 parts
63
+ expect(parseMcpPromptCommand("/mcp____p")).toBe(null); // empty server
64
+ });
65
+ });
66
+
67
+ describe("renderPromptMessages", () => {
68
+ it("returns '' when there are no messages", () => {
69
+ expect(renderPromptMessages(null)).toBe("");
70
+ expect(renderPromptMessages({ messages: [] })).toBe("");
71
+ });
72
+
73
+ it("joins text blocks with blank lines", () => {
74
+ const r = renderPromptMessages({
75
+ messages: [
76
+ { content: [{ type: "text", text: "one" }] },
77
+ { content: [{ type: "text", text: "two" }] },
78
+ ],
79
+ });
80
+ expect(r).toBe("one\n\ntwo");
81
+ });
82
+
83
+ it("includes embedded resource text and ignores binary/unknown blocks", () => {
84
+ const r = renderPromptMessages({
85
+ messages: [
86
+ {
87
+ content: [
88
+ { type: "text", text: "T" },
89
+ { type: "resource", resource: { text: "R" } },
90
+ { type: "image", data: "..." },
91
+ null,
92
+ ],
93
+ },
94
+ ],
95
+ });
96
+ expect(r).toBe("T\n\nR");
97
+ });
98
+
99
+ it("handles content given as a single block (not an array)", () => {
100
+ const r = renderPromptMessages({
101
+ messages: [{ content: { type: "text", text: "solo" } }],
102
+ });
103
+ expect(r).toBe("solo");
104
+ });
105
+ });
106
+
107
+ describe("expandMcpPrompt", () => {
108
+ it("returns null for a non-mcp line", async () => {
109
+ expect(await expandMcpPrompt("hi", {})).toBe(null);
110
+ });
111
+
112
+ it("throws when no MCP client is connected", async () => {
113
+ await expect(expandMcpPrompt("/mcp__s__p", null)).rejects.toThrow(
114
+ /No MCP servers/,
115
+ );
116
+ });
117
+
118
+ it("calls getPrompt with parsed args and renders the result", async () => {
119
+ const getPrompt = vi.fn(async () => ({
120
+ messages: [{ content: [{ type: "text", text: "rendered" }] }],
121
+ }));
122
+ const out = await expandMcpPrompt('/mcp__s__p {"k":1}', { getPrompt });
123
+ expect(getPrompt).toHaveBeenCalledWith("s", "p", { k: 1 });
124
+ expect(out).toBe("rendered");
125
+ });
126
+ });
127
+
128
+ describe("renderMcpSurface", () => {
129
+ it("reports when no client is connected", () => {
130
+ expect(renderMcpSurface(null)).toMatch(/No MCP servers/);
131
+ });
132
+
133
+ it("lists resources and prompts with counts", () => {
134
+ const surface = renderMcpSurface({
135
+ listResources: () => [{ uri: "file://x", server: "fs", name: "X" }],
136
+ listPrompts: () => [
137
+ { server: "gh", name: "review", description: "code review" },
138
+ ],
139
+ });
140
+ expect(surface).toContain("MCP resources (1):");
141
+ expect(surface).toContain("file://x [fs] — X");
142
+ expect(surface).toContain("MCP prompts (1):");
143
+ expect(surface).toContain("/mcp__gh__review — code review");
144
+ });
145
+
146
+ it("notes when servers expose nothing", () => {
147
+ const surface = renderMcpSurface({
148
+ listResources: () => [],
149
+ listPrompts: () => [],
150
+ });
151
+ expect(surface).toContain(
152
+ "(connected servers expose no resources or prompts)",
153
+ );
154
+ });
155
+ });
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * resolveSlashMacro tests (previously untested) — the REPL's user-command-macro
5
+ * wire (.claude/commands/*.md). The routing safety matters: a leading `/name`
6
+ * that resolves to a macro is expanded, but plain text or an unknown `/...`
7
+ * (e.g. asking about "/etc/hosts") must pass through verbatim to the LLM and
8
+ * never be swallowed. Deps (getCommand/expandCommand) are injected; no I/O.
9
+ */
10
+
11
+ import { describe, it, expect, vi } from "vitest";
12
+ import { resolveSlashMacro } from "../slash-macro.js";
13
+
14
+ const deps = (macro, expand = { prompt: "EXPANDED", warnings: [] }) => ({
15
+ getCommand: vi.fn(() => macro),
16
+ expandCommand: vi.fn(() => expand),
17
+ });
18
+
19
+ describe("resolveSlashMacro", () => {
20
+ it("returns plain text unchanged (no slash)", async () => {
21
+ const r = await resolveSlashMacro(" just a question ", {
22
+ deps: deps(null),
23
+ });
24
+ expect(r.matched).toBe(false);
25
+ expect(r.promptText).toBe("just a question"); // trimmed
26
+ });
27
+
28
+ it("passes an unknown /... through verbatim (does not swallow /etc/hosts)", async () => {
29
+ const d = deps(null);
30
+ const r = await resolveSlashMacro("/etc/hosts is interesting", { deps: d });
31
+ expect(r.matched).toBe(false);
32
+ expect(r.promptText).toBe("/etc/hosts is interesting");
33
+ // first token after the slash was probed, but no macro matched
34
+ expect(d.getCommand).toHaveBeenCalledWith("etc/hosts", expect.any(String));
35
+ });
36
+
37
+ it("expands a resolved macro and reports its name/scope", async () => {
38
+ const macro = { name: "deploy", scope: "project" };
39
+ const d = deps(macro);
40
+ const r = await resolveSlashMacro("/deploy prod fast", {
41
+ deps: d,
42
+ cwd: "/proj",
43
+ });
44
+ expect(r.matched).toBe(true);
45
+ expect(r.promptText).toBe("EXPANDED");
46
+ expect(r.name).toBe("deploy");
47
+ expect(r.scope).toBe("project");
48
+ expect(d.getCommand).toHaveBeenCalledWith("deploy", "/proj");
49
+ expect(d.expandCommand).toHaveBeenCalledWith(macro, ["prod", "fast"], {
50
+ cwd: "/proj",
51
+ });
52
+ });
53
+
54
+ it("propagates warnings from expandCommand", async () => {
55
+ const d = deps(
56
+ { name: "x", scope: "personal" },
57
+ {
58
+ prompt: "P",
59
+ warnings: ["missing $2"],
60
+ },
61
+ );
62
+ const r = await resolveSlashMacro("/x a", { deps: d });
63
+ expect(r.warnings).toEqual(["missing $2"]);
64
+ });
65
+
66
+ it("does not treat a bare '/' as a command", async () => {
67
+ const d = deps({ name: "should-not-fire", scope: "project" });
68
+ const r = await resolveSlashMacro("/", { deps: d });
69
+ expect(r.matched).toBe(false);
70
+ expect(d.getCommand).not.toHaveBeenCalled(); // empty head → never probed
71
+ });
72
+
73
+ it("handles empty/nullish input", async () => {
74
+ expect((await resolveSlashMacro("", { deps: deps(null) })).matched).toBe(
75
+ false,
76
+ );
77
+ expect(
78
+ (await resolveSlashMacro(null, { deps: deps(null) })).promptText,
79
+ ).toBe("");
80
+ });
81
+ });
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * coding-agent-contract-shared tests (previously untested) — the single source
5
+ * of truth for the coding agent's tool surface (the function-calling contracts
6
+ * the LLM sees). Rather than hardcode the whole table, this pins the accessor
7
+ * contracts + cross-table invariants: every listed name resolves to a contract
8
+ * AND a function definition, lookups return defensive clones, unknown -> null,
9
+ * the OpenAI function-tool shape, names/tier filtering, MVP/extension membership
10
+ * and the policy projection. Anchored on the stable `read_file` tool. No I/O.
11
+ */
12
+
13
+ import { describe, it, expect } from "vitest";
14
+ import { createRequire } from "module";
15
+
16
+ const require = createRequire(import.meta.url);
17
+ const {
18
+ CODING_AGENT_MVP_TOOL_NAMES,
19
+ CODING_AGENT_EXTENSION_TOOL_NAMES,
20
+ getCodingAgentFunctionToolDefinition,
21
+ getCodingAgentFunctionToolDefinitions,
22
+ getCodingAgentToolContract,
23
+ getCodingAgentToolContracts,
24
+ getCodingAgentToolPolicy,
25
+ isCodingAgentMvpTool,
26
+ listCodingAgentToolNames,
27
+ } = require("../coding-agent-contract-shared.cjs");
28
+
29
+ describe("listCodingAgentToolNames + consistency", () => {
30
+ it("returns a non-empty list of unique names including read_file", () => {
31
+ const names = listCodingAgentToolNames();
32
+ expect(names.length).toBeGreaterThan(0);
33
+ expect(new Set(names).size).toBe(names.length);
34
+ expect(names).toContain("read_file");
35
+ });
36
+
37
+ it("every listed name resolves to both a contract and a function definition", () => {
38
+ for (const n of listCodingAgentToolNames()) {
39
+ expect(getCodingAgentToolContract(n)).toBeTruthy();
40
+ expect(getCodingAgentFunctionToolDefinition(n)).toBeTruthy();
41
+ }
42
+ });
43
+ });
44
+
45
+ describe("getCodingAgentToolContract", () => {
46
+ it("returns the contract for a known tool", () => {
47
+ const c = getCodingAgentToolContract("read_file");
48
+ expect(c.name).toBe("read_file");
49
+ expect(c.tier).toBe("mvp");
50
+ });
51
+
52
+ it("returns null for an unknown tool", () => {
53
+ expect(getCodingAgentToolContract("__nope__")).toBe(null);
54
+ });
55
+
56
+ it("returns a defensive clone (mutation does not leak back)", () => {
57
+ const a = getCodingAgentToolContract("read_file");
58
+ a.name = "MUTATED";
59
+ expect(getCodingAgentToolContract("read_file").name).toBe("read_file");
60
+ });
61
+ });
62
+
63
+ describe("getCodingAgentFunctionToolDefinition", () => {
64
+ it("emits an OpenAI function-tool definition for a known tool", () => {
65
+ const def = getCodingAgentFunctionToolDefinition("read_file");
66
+ expect(def.type).toBe("function");
67
+ expect(def.function.name).toBe("read_file");
68
+ expect(typeof def.function.description).toBe("string");
69
+ expect(def.function.parameters.type).toBe("object");
70
+ expect(def.function.parameters.required).toContain("path");
71
+ });
72
+
73
+ it("returns null for an unknown tool", () => {
74
+ expect(getCodingAgentFunctionToolDefinition("__nope__")).toBe(null);
75
+ });
76
+ });
77
+
78
+ describe("getCodingAgentFunctionToolDefinitions filtering", () => {
79
+ it("filters by an explicit names allow-list", () => {
80
+ const defs = getCodingAgentFunctionToolDefinitions({
81
+ names: ["read_file"],
82
+ });
83
+ expect(defs).toHaveLength(1);
84
+ expect(defs[0].function.name).toBe("read_file");
85
+ });
86
+
87
+ it("treats an empty names array as no filter (returns all)", () => {
88
+ const all = getCodingAgentFunctionToolDefinitions();
89
+ expect(getCodingAgentFunctionToolDefinitions({ names: [] })).toHaveLength(
90
+ all.length,
91
+ );
92
+ });
93
+
94
+ it("returns [] for unknown names", () => {
95
+ expect(
96
+ getCodingAgentFunctionToolDefinitions({ names: ["__nope__"] }),
97
+ ).toHaveLength(0);
98
+ });
99
+ });
100
+
101
+ describe("tier filtering", () => {
102
+ it("getCodingAgentToolContracts({tier}) returns only that tier", () => {
103
+ const mvp = getCodingAgentToolContracts({ tier: "mvp" });
104
+ expect(mvp.length).toBeGreaterThan(0);
105
+ expect(mvp.every((t) => t.tier === "mvp")).toBe(true);
106
+ });
107
+ });
108
+
109
+ describe("isCodingAgentMvpTool + membership", () => {
110
+ it("is true for every MVP name and false for unknown", () => {
111
+ for (const n of CODING_AGENT_MVP_TOOL_NAMES)
112
+ expect(isCodingAgentMvpTool(n)).toBe(true);
113
+ expect(isCodingAgentMvpTool("__nope__")).toBe(false);
114
+ });
115
+
116
+ it("MVP and extension names are all valid tool names", () => {
117
+ const all = new Set(listCodingAgentToolNames());
118
+ for (const n of CODING_AGENT_MVP_TOOL_NAMES) expect(all.has(n)).toBe(true);
119
+ for (const n of CODING_AGENT_EXTENSION_TOOL_NAMES)
120
+ expect(all.has(n)).toBe(true);
121
+ });
122
+ });
123
+
124
+ describe("getCodingAgentToolPolicy", () => {
125
+ it("projects the policy fields for a known tool", () => {
126
+ const p = getCodingAgentToolPolicy("read_file");
127
+ expect(p).toHaveProperty("tier");
128
+ expect(p).toHaveProperty("riskLevel");
129
+ expect(p).toHaveProperty("permissions");
130
+ expect(typeof p.planModeBehavior).toBe("string"); // defaults to "standard"
131
+ });
132
+
133
+ it("returns null for an unknown tool", () => {
134
+ expect(getCodingAgentToolPolicy("__nope__")).toBe(null);
135
+ });
136
+ });