agentix-cli 0.18.0 → 0.19.0

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 (171) hide show
  1. package/README.md +75 -27
  2. package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
  3. package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
  4. package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
  5. package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
  6. package/dist/chunk-23AUU4S2.js +102 -0
  7. package/dist/chunk-23AUU4S2.js.map +1 -0
  8. package/dist/chunk-4MREHZP5.js +111 -0
  9. package/dist/chunk-4MREHZP5.js.map +1 -0
  10. package/dist/chunk-4YCH6IZV.js +2 -0
  11. package/dist/chunk-6B74TO2J.js +743 -0
  12. package/dist/chunk-6B74TO2J.js.map +1 -0
  13. package/dist/chunk-7P5QRX5W.js +108 -0
  14. package/dist/chunk-7P5QRX5W.js.map +1 -0
  15. package/dist/chunk-A6BKSZQA.js +3 -0
  16. package/dist/chunk-A6BKSZQA.js.map +1 -0
  17. package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
  18. package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
  19. package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
  20. package/dist/chunk-E4ZZILAK.js +15 -0
  21. package/dist/chunk-E4ZZILAK.js.map +1 -0
  22. package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
  23. package/dist/chunk-JTJIPNXY.js +2 -0
  24. package/dist/chunk-JTJIPNXY.js.map +1 -0
  25. package/dist/chunk-MBOLXKA2.js +6 -0
  26. package/dist/chunk-MBOLXKA2.js.map +1 -0
  27. package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
  28. package/dist/chunk-MTRWXRFF.js.map +1 -0
  29. package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
  30. package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
  31. package/dist/chunk-QW2OYR3N.js +7 -0
  32. package/dist/chunk-QW2OYR3N.js.map +1 -0
  33. package/dist/chunk-SV3I4CI7.js +440 -0
  34. package/dist/chunk-SV3I4CI7.js.map +1 -0
  35. package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
  36. package/dist/chunk-VWTBHLMX.js +1 -0
  37. package/dist/chunk-WFHIIVLL.js +719 -0
  38. package/dist/chunk-WFHIIVLL.js.map +1 -0
  39. package/dist/chunk-WPPUOTY6.js +6 -0
  40. package/dist/chunk-WPPUOTY6.js.map +1 -0
  41. package/dist/cli.js +1 -1
  42. package/dist/cli.js.map +1 -1
  43. package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
  44. package/dist/config-QXWKEQON.js +2 -0
  45. package/dist/config-mutator-53XGR2NT.js +2 -0
  46. package/dist/conflict-detector-D7VIYPDI.js +2 -0
  47. package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
  48. package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
  49. package/dist/debug-N3B5NVJU.js +2 -0
  50. package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
  51. package/dist/doctor-SEOUFHSL.js +2 -0
  52. package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
  53. package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
  54. package/dist/inbox-BIN2S5XN.js +2 -0
  55. package/dist/index.d.ts +830 -100
  56. package/dist/index.js +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
  59. package/dist/loader-5CA3O5GM.js +2 -0
  60. package/dist/loader-6IHUCPVO.js +2 -0
  61. package/dist/memory-extract-KRCHD3OP.js +2 -0
  62. package/dist/migrate-v2-QO7SLKIM.js +4 -0
  63. package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
  64. package/dist/processes-RJ6RDP32.js +2 -0
  65. package/dist/program-SASDC2GV.js +2 -0
  66. package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
  67. package/dist/query-HZYYRCLU.js +2 -0
  68. package/dist/recipes-EPLE2SNT.js +2 -0
  69. package/dist/registry-HM4ELSXS.js +2 -0
  70. package/dist/rotation-memo-GMTZPMNE.js +13 -0
  71. package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
  72. package/dist/scheduler-XVFIK4XH.js +2 -0
  73. package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
  74. package/dist/store-BAPDZHXE.js +2 -0
  75. package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
  76. package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
  77. package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
  78. package/dist/timers-5LPZN55J.js +2 -0
  79. package/dist/types-VECTMYTO.js +2 -0
  80. package/dist/types-VECTMYTO.js.map +1 -0
  81. package/dist/types-ZRLHHCGB.js +2 -0
  82. package/dist/types-ZRLHHCGB.js.map +1 -0
  83. package/dist/web/activity-graph.global.js +654 -0
  84. package/dist/web/activity-graph.global.js.map +1 -0
  85. package/dist/web/workflow-editor.global.js +1 -1
  86. package/dist/web/workflow-editor.global.js.map +1 -1
  87. package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
  88. package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
  89. package/dist/wiki-53W5NLZV.js +2 -0
  90. package/dist/wiki-53W5NLZV.js.map +1 -0
  91. package/dist/workspace-setup-FPGN5VNY.js +2 -0
  92. package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
  93. package/package.json +3 -2
  94. package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
  95. package/dist/chunk-34NG7UDX.js +0 -11
  96. package/dist/chunk-34NG7UDX.js.map +0 -1
  97. package/dist/chunk-ASSJ4Z6Z.js +0 -164
  98. package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
  99. package/dist/chunk-BLUXL6HR.js +0 -854
  100. package/dist/chunk-BLUXL6HR.js.map +0 -1
  101. package/dist/chunk-BTAEYL3P.js +0 -72
  102. package/dist/chunk-BTAEYL3P.js.map +0 -1
  103. package/dist/chunk-DSCMCVTV.js +0 -7
  104. package/dist/chunk-DSCMCVTV.js.map +0 -1
  105. package/dist/chunk-HPDHNIA3.js +0 -2
  106. package/dist/chunk-IM376SD6.js +0 -13
  107. package/dist/chunk-IM376SD6.js.map +0 -1
  108. package/dist/chunk-LEIYC3BF.js +0 -104
  109. package/dist/chunk-LEIYC3BF.js.map +0 -1
  110. package/dist/chunk-LY7JGDV7.js +0 -6
  111. package/dist/chunk-LY7JGDV7.js.map +0 -1
  112. package/dist/chunk-U364GNQX.js +0 -719
  113. package/dist/chunk-U364GNQX.js.map +0 -1
  114. package/dist/chunk-WZEYII2S.js +0 -6
  115. package/dist/chunk-WZEYII2S.js.map +0 -1
  116. package/dist/config-UOHEG4BW.js +0 -2
  117. package/dist/config-mutator-KSEBWKJH.js +0 -2
  118. package/dist/debug-TGAP7E7W.js +0 -2
  119. package/dist/inbox-O5UB3YQN.js.map +0 -1
  120. package/dist/loader-BGWA7IRY.js +0 -2
  121. package/dist/loader-LBHKSXBK.js +0 -2
  122. package/dist/memory-extract-KKPKAOPH.js +0 -2
  123. package/dist/program-2MDELQLX.js +0 -2
  124. package/dist/query-FZAL7AQD.js +0 -2
  125. package/dist/recipes-LZ56TA3X.js +0 -2
  126. package/dist/registry-ONJSILH4.js +0 -2
  127. package/dist/scheduler-XRM2RJIV.js +0 -2
  128. package/dist/store-4UCO56K2.js +0 -2
  129. package/dist/timers-QB4KYNAE.js +0 -2
  130. package/dist/types-ECMEC5BZ.js +0 -2
  131. package/dist/types-TTB6DJUE.js +0 -2
  132. package/dist/usage-dashboard-FQGPPABD.js +0 -803
  133. package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
  134. package/dist/wiki-OUYKM7MB.js +0 -2
  135. package/dist/workspace-setup-6GDBEORT.js +0 -2
  136. /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
  137. /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
  138. /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
  139. /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
  140. /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
  141. /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
  142. /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
  143. /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
  144. /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
  145. /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
  146. /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
  147. /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
  148. /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
  149. /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
  150. /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
  151. /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
  152. /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
  153. /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
  154. /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
  155. /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
  156. /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
  157. /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
  158. /package/dist/{registry-ONJSILH4.js.map → program-SASDC2GV.js.map} +0 -0
  159. /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
  160. /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
  161. /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
  162. /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
  163. /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
  164. /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
  165. /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
  166. /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
  167. /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
  168. /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
  169. /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
  170. /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
  171. /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
@@ -0,0 +1,108 @@
1
+ import{a as _,b as P,c as T,d as G}from"./chunk-JTJIPNXY.js";import{b as Q}from"./chunk-64P4QHSZ.js";import{l as X}from"./chunk-JGXE2YNV.js";import{c as M,d as L}from"./chunk-QOYAX2FT.js";var A={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},F=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as D,writeFileSync as J,appendFileSync as re,existsSync as I,mkdirSync as q,renameSync as ie}from"fs";import{resolve as E,dirname as ee}from"path";import{createHash as ne}from"crypto";var U=class{baseDir;log;constructor(e={}){this.baseDir=e.baseDir??E(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),q(this.baseDir,{recursive:!0})}schemaPath(){return E(this.baseDir,"schema.json")}nodesPath(){return E(this.baseDir,"nodes.json")}indexPath(){return E(this.baseDir,"index.json")}classificationsPath(){return E(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!I(e))return this.saveSchema(A),this.log("seeded starter schema at",e),A;let t=D(e,"utf-8"),s=_.safeParse(JSON.parse(t));if(!s.success)throw new Error(`Invalid ${e}: ${s.error.issues.map(r=>r.message).join("; ")}`);if((s.data.version??1)<(A.version??2)){let r=e.replace(/\.json$/,`.v${s.data.version??1}.bak.json`);try{J(r,t),this.log(`migrated graph schema v${s.data.version??1} -> v${A.version}; old saved at ${r}`)}catch(o){this.log(`could not back up old schema: ${o?.message??o}`)}this.saveSchema(A);let n=this.nodesPath();if(I(n)){let o=n.replace(/\.json$/,".v1.bak.json");try{J(o,D(n,"utf-8")),this.log(`archived v1 nodes.json -> ${o}`)}catch(i){this.log(`could not back up old nodes: ${i?.message??i}`)}try{let i=Z();this.saveNodes(i)}catch(i){this.log(`could not seed v2 nodes: ${i?.message??i}`)}}return A}return s.data}saveSchema(e){let t=_.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(s=>s.message).join("; ")}`);B(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!I(e)){let r=Z();return this.saveNodes(r),r}let t=D(e,"utf-8"),s=T.safeParse(JSON.parse(t));if(!s.success)throw new Error(`Invalid ${e}: ${s.error.issues.map(r=>r.message).join("; ")}`);return s.data}saveNodes(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(s=>s.message).join("; ")}`);B(this.nodesPath(),t.data)}validateNode(e,t,s){let r=t.levels.find(i=>i.id===e.level);if(!r)throw new Error(`Unknown level: "${e.level}"`);let n=r.axes.filter(i=>!i.optional).filter(i=>!(i.name in e.axes));if(n.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${n.map(i=>i.name).join(", ")}`);for(let i of r.axes){let a=e.axes[i.name];if(a!=null){if(i.type==="enum"&&i.values&&!i.values.includes(a))throw new Error(`Node ${e.id}: axis ${i.name}="${a}" not in enum ${JSON.stringify(i.values)}`);if(i.type==="ref"){if(!s.some(c=>c.id===a))throw new Error(`Node ${e.id}: axis ${i.name}="${a}" refers to unknown node`);if(i.refLevel){let c=s.find(h=>h.id===a);if(c.level!==i.refLevel)throw new Error(`Node ${e.id}: axis ${i.name} expected level "${i.refLevel}" but got "${c.level}"`)}}}}let o=t.levels.findIndex(i=>i.id===e.level);if(o===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let i=s.find(c=>c.id===e.parentId);if(!i)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let a=t.levels.findIndex(c=>c.id===i.level);if(a<0||a>=o)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${i.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,s,r){let n=[],o=[],i=new Date().toISOString();for(let a=0;a<e.length;a++){let c=e[a],h=this.loadNodes().nodes;if(h.find(v=>v.id===c)){o.push(c);continue}let d=0;for(let v=a-1;v>=0;v--){let w=e[v],N=h.find(k=>k.id===w)||n.find(k=>k.id===w);if(N){d=s.levels.findIndex(k=>k.id===N.level)+1;break}}let u=t[c]??{},l=null;for(let v=d;v<s.levels.length;v++){let w=s.levels[v];if(w.axes.filter(k=>!k.optional).filter(k=>!(k.name in u)).length===0){l=w.id,d=v+1;break}}if(!l&&d<s.levels.length&&(l=s.levels[d].id,d++),!l)throw new Error(`Can't place node ${c} \u2014 no schema level left (cursor=${d}, levels=${s.levels.length})`);let p=null,m=s.levels.findIndex(v=>v.id===l);for(let v=a-1;v>=0;v--){let w=e[v],N=h.find(C=>C.id===w)||n.find(C=>C.id===w);if(!N)continue;let k=s.levels.findIndex(C=>C.id===N.level);if(k>=0&&k<m){p=w;break}}let f={id:c,level:l,parentId:p,axes:u,createdAt:i,createdBy:r};this.addNode(f),n.push(f)}return{added:n,skipped:o}}addNode(e){let t=this.loadSchema(),s=this.loadNodes();if(s.nodes.some(r=>r.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...s.nodes,e]),s.nodes.push(e),this.saveNodes(s),e}appendClassification(e){let t=P.safeParse(e);if(!t.success){let n=t.error.issues.map(o=>`${o.path.join(".")}: ${o.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${n} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let s=JSON.stringify(t.data)+`
2
+ `,r=this.classificationsPath();return I(r)||q(ee(r),{recursive:!0}),re(r,s),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listByStatus(e,t=200){return this.readAllCollapsed().filter(r=>r.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!I(e))return[];let s=D(e,"utf-8").split(`
3
+ `).filter(Boolean),r=new Map;for(let n of s)try{let o=P.safeParse(JSON.parse(n));if(!o.success)continue;r.set(o.data.msgHash,o.data)}catch{}return Array.from(r.values()).sort((n,o)=>n.ts<o.ts?1:-1)}updateClassificationStatus(e,t){let r=this.readAllCollapsed().find(o=>o.msgHash===e);if(!r)return null;let n={...r,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let o=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;n.preview=((r.preview||"")+o).slice(0,500)}return this.appendClassification(n),n}readRecentClassifications(e=50){let t=this.classificationsPath();if(!I(t))return[];let r=D(t,"utf-8").split(`
4
+ `).filter(Boolean),n=[];for(let o=r.length-1;o>=0&&n.length<e;o--)try{let i=P.safeParse(JSON.parse(r[o]));i.success&&n.push(i.data)}catch{}return n}fingerprint(e){let s=[e.text.toLowerCase().replace(/\s+/g," ").trim(),e.channel??"",e.sender??""].join("");return ne("sha256").update(s).digest("hex").slice(0,32)}loadIndex(){let e=this.indexPath();if(!I(e))return{version:1,entries:{}};let t=D(e,"utf-8"),s=G.safeParse(JSON.parse(t));if(!s.success)throw new Error(`Invalid ${e}: ${s.error.issues.map(r=>r.message).join("; ")}`);return s.data}saveIndex(e){let t=G.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(s=>s.message).join("; ")}`);B(this.indexPath(),t.data)}getFingerprint(e){return this.loadIndex().entries[e]}setFingerprint(e,t){let s=this.loadIndex();s.entries[e]={...t,fingerprint:e,updatedAt:new Date().toISOString()},this.saveIndex(s)}updateNodeAxes(e,t){let s=this.loadSchema(),r=this.loadNodes(),n=r.nodes.find(i=>i.id===e);if(!n)throw new Error(`Node not found: ${e}`);let o={...n,axes:t};return this.validateNode(o,s,r.nodes.map(i=>i.id===e?o:i)),r.nodes=r.nodes.map(i=>i.id===e?o:i),this.saveNodes(r),o}deleteNode(e){let t=this.loadNodes(),s=t.nodes.find(n=>n.parentId===e);if(s)throw new Error(`Cannot delete "${e}" \u2014 child node "${s.id}" still points to it`);let r=t.nodes.find(n=>Object.values(n.axes).some(o=>o===e));if(r)throw new Error(`Cannot delete "${e}" \u2014 node "${r.id}" references it via an axis`);t.nodes=t.nodes.filter(n=>n.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),s=new Set(this.loadNodes().nodes.map(r=>r.id));for(let r=0;r<e.path.length;r++){let n=e.path[r];if(s.has(n))continue;let o=t.levels[r]?.id;if(!o)break;let i=e.proposedAxes?.[n]??{},a=r===0?null:e.path[r-1];this.addNode({id:n,level:o,parentId:a,axes:i,createdAt:new Date().toISOString(),createdBy:e.agentId}),s.add(n)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function B(g,e){q(ee(g),{recursive:!0});let t=`${g}.tmp-${process.pid}`;J(t,JSON.stringify(e,null,2)),ie(t,g)}function Z(){let g=new Date().toISOString(),e=[],t=new Set;for(let s of F)t.has(s.category)||(e.push({id:s.category,level:"category",parentId:null,axes:{kind:s.category},createdAt:g,createdBy:"starter"}),t.add(s.category));for(let s of F)e.push({id:s.verb,level:"verb",parentId:s.category,axes:{name:s.verb,...s.description?{description:s.description}:{}},createdAt:g,createdBy:"starter"});return{version:1,nodes:e}}import{createHash as oe}from"crypto";var H=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;classifierModel;classifierProviderName;providerInstance;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.classifierModel=e.classifierModel??"claude-haiku-4-5-20251001",this.classifierProviderName=e.classifierProvider??"claude-code",this.log=e.log??console.error.bind(console,"[classifier]")}getProvider(){return this.providerInstance||(this.providerInstance=X(this.classifierProviderName)),this.providerInstance}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),s=this.store.getFingerprint(t);if(s)return this.store.appendClassification({ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,path:s.path,proposedAxes:{},leaf:s.leaf,source:"cache",status:"approved",preview:e.text.slice(0,200)}),{path:s.path,pathId:z(s.path),pathLabel:V(s.path,this.store.loadNodes().nodes),axes:{},leaf:s.leaf,source:"cache",status:"approved"};let r=this.store.loadSchema(),n=this.store.loadNodes().nodes,o=await this.proposePath(e,r,n).catch(f=>(this.log("LLM proposal failed:",f?.message||f),null));if(!o)return null;let{path:i,proposedAxes:a,confidence:c,leaf:h}=o;if(i.length===0)return null;let b={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,path:i,proposedAxes:a,leaf:h,source:"llm",status:"pending",confidence:c,preview:e.text.slice(0,200)},d=new Set(n.map(f=>f.id)),u=i.map((f,v)=>d.has(f)?-1:v).filter(f=>f>=0),l=!1;if(this.autoApproveStructure==="any")l=!0;else if(this.autoApproveStructure==="extend-leaves"){let f=u.length===0,v=u.length===1&&u[0]===i.length-1;l=f||v}let p=c!==void 0&&c>=this.autoApprove,m="pending";if(l||p)try{this.commitNewNodes(i,a,r,n,e.sender),this.store.setFingerprint(t,{path:i,leaf:h}),m="approved"}catch(f){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(i)}`,`reason=${(f?.message||f).toString().slice(0,200)}`)}return b.status=m,this.store.appendClassification(b),{path:i,pathId:z(i),pathLabel:V(i,this.store.loadNodes().nodes),axes:a,leaf:h,source:"llm",status:m,confidence:c}}async proposePath(e,t,s){let r=s.map(l=>({id:l.id,level:l.level,parentId:l.parentId,axes:l.axes})),n=["You are the intent classifier for AgentX. You answer ONE question:",'"what kind of work does this message describe?" \u2014 verb-level only.',"","IMPORTANT \u2014 what NOT to encode in the path:","- Client / company / project / team names. Those are already on the event"," metadata (project, channel, agentId). Embedding them in the path causes"," duplicate nodes (one per client) for the same verb.","- The specific subject (issue number, file name, person name). Same reason.","- The agent's name or role. Pick the verb the *requester* intended.","","SCHEMA (fixed levels \u2014 pick one node per level):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (prefer reusing these; only invent a new node when no","existing one fits):","```json",JSON.stringify(r,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK:","1. Classify into 'category' (closed enum): code, ops, support, admin,"," knowledge, social, system. Pick the closest fit.","2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,",' e.g. "review.merge-request", "deploy.staging", "investigate.error",',' "chat.greeting", "fix.bug". Reuse an existing verb when the message is'," the same kind of work as something already classified, even when the"," client / project / subject differs.","","Examples (right vs wrong):",'- "Please review MR #957 on mtgl/system" \u2192 ["code", "review.merge-request"]',' NOT ["business", "noqta", "mtgl-v2", "review-mr-957-system"]','- "Deploy ksi-v2 to staging please" \u2192 ["ops", "deploy.staging"]',' NOT ["business", "noqta", "ksi-v2", "deploy-ksi-v2-to-staging"]','- "Hello Atlas" \u2192 ["support", "chat.greeting"]',"","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*","- no spaces, no uppercase, no Arabic/other non-Latin",'- verb ids should use a "category.specific" or "verb.modifier" shape',"","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
5
+ `),o=await this.callProvider(n),i=ae(o);if(!i||!Array.isArray(i.path))return null;let a=/^[a-z0-9][a-z0-9._-]*$/,c=new Map,h=i.path.filter(l=>typeof l=="string"&&l.length>0).map(l=>{let p=l.trim(),m=te(p);return p!==m&&c.set(p,m),m}).filter(l=>a.test(l));if(h.length===0)return null;let b={};if(i.proposedAxes&&typeof i.proposedAxes=="object")for(let[l,p]of Object.entries(i.proposedAxes)){if(!p||typeof p!="object")continue;let m=c.get(l)??te(l);if(!m)continue;let f={};for(let[v,w]of Object.entries(p))typeof w=="string"&&(f[v]=w);!f.name&&l!==m&&(f.name=l.trim()),b[m]=f}let d={};i.leaf&&typeof i.leaf=="object"&&(typeof i.leaf.input=="string"&&(d.input=i.leaf.input),typeof i.leaf.output=="string"&&(d.output=i.leaf.output));let u=typeof i.confidence=="number"?Math.max(0,Math.min(1,i.confidence)):void 0;return{path:h,proposedAxes:b,confidence:u,leaf:d}}commitNewNodes(e,t,s,r,n){this.store.commitNodesAlongPath(e,t,s,n)}async callProvider(e){let t=this.getProvider(),s=new AbortController,r=setTimeout(()=>s.abort(),3e4);try{return(await t.generate([{role:"system",content:"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences."},{role:"user",content:e}],{model:this.classifierModel,maxTokens:800})).content||""}finally{clearTimeout(r)}}};function te(g){let e=g.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function z(g){return oe("sha1").update(g.join("")).digest("hex").slice(0,16)}function V(g,e){return g.map(t=>{let s=e.find(n=>n.id===t);return s&&(s.axes?.name||s.axes?.what)||t}).join(" \u203A ")}function Y(g,e){if(g.length===0||e.length===0)return 0;let t=0;for(let r=0;r<Math.min(g.length,e.length)&&g[r]===e[r];r++)t++;if(t===0)return 0;let s=Math.max(g.length,e.length);return t/s}function ae(g){if(!g)return null;try{return JSON.parse(g)}catch{}let e=g.match(/```(?:json)?\s*([\s\S]*?)```/);if(e)try{return JSON.parse(e[1])}catch{}let t=g.indexOf("{");if(t<0)return null;let s=0;for(let r=t;r<g.length;r++)if(g[r]==="{")s++;else if(g[r]==="}"&&(s--,s===0))try{return JSON.parse(g.slice(t,r+1))}catch{return null}return null}import{readFileSync as W,writeFileSync as S,appendFileSync as ce,existsSync as x,mkdirSync as j,readdirSync as O,statSync as le}from"fs";import{resolve as y,join as de,relative as $,dirname as pe}from"path";var R=class{baseDir;rawDir;log;constructor(e=y(process.cwd(),".agentx/wiki"),t=console.error.bind(console,"[wiki]")){this.baseDir=e,this.rawDir=y(e,"raw/entries"),this.log=t,j(this.rawDir,{recursive:!0}),j(y(e,"raw"),{recursive:!0}),this.ensureSchema()}canRead(e,t){return e.access==="public"?!0:t?!!(e.owner===t||e.access==="shared"&&e.sharedWith?.includes(t)):!1}canWrite(e,t){return e.owner===t}addEntry(e){let t=`${e.date}_${e.id}.md`,s=y(this.rawDir,t),r=["---",`id: ${e.id}`,`date: ${e.date}`,`agent: ${e.agentId}`,`source: ${e.source}`];if(e.sourceContext&&r.push(`context: ${e.sourceContext}`),e.meta)for(let[n,o]of Object.entries(e.meta))r.push(`${n}: ${JSON.stringify(o)}`);return r.push("---","",e.content),S(s,r.join(`
6
+ `)),this.appendLog("ingest",`${e.id} from ${e.agentId} via ${e.source}`),t}listEntries(e){if(!x(this.rawDir))return[];let t=O(this.rawDir).filter(r=>r.endsWith(".md")).sort(),s=[];for(let r of t){let n=W(y(this.rawDir,r),"utf-8"),o=this.parseEntry(n,r);o&&(e?.agentId&&o.agentId!==e.agentId||e?.after&&o.date<e.after||e?.before&&o.date>e.before||s.push(o))}return s}parseEntry(e,t){let s=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!s)return null;let r=s[1],n=s[2].trim(),o=i=>r.match(new RegExp(`^${i}:\\s*(.+)$`,"m"))?.[1]?.trim()||"";return{id:o("id")||t.replace(".md",""),date:o("date"),agentId:o("agent"),source:o("source"),sourceContext:o("context")||void 0,content:n}}writeArticle(e,t,s,r){let n=this.readArticle(e);if(n&&!this.canWrite(n.meta,r))return this.log(`Permission denied: "${r}" cannot write "${e}" (owner: ${n.meta.owner})`),!1;t.type&&!Q(t.type)&&(this.log(`Dropping invalid article type "${t.type}" for "${e}" \u2014 enum is [person, project, place, concept, event, decision, pattern]`),t={...t,type:void 0});let o=y(this.baseDir,e);n&&this.saveVersion(e,o),j(pe(o),{recursive:!0});let i=["---",`title: "${t.title}"`];t.type&&i.push(`type: ${t.type}`),t.related?.length&&i.push(`related: [${t.related.map(c=>`"${c}"`).join(", ")}]`),i.push(`tags: [${(t.tags||[]).map(c=>`"${c}"`).join(", ")}]`,`owner: ${t.owner}`,`access: ${t.access}`),t.sharedWith?.length&&i.push(`shared_with: [${t.sharedWith.map(c=>`"${c}"`).join(", ")}]`),i.push(`created: ${t.created}`,`last_updated: ${t.lastUpdated}`,`sources: [${t.sources.map(c=>`"${c}"`).join(", ")}]`),t.graphPath?.length&&i.push(`graph_path: [${t.graphPath.map(c=>`"${c}"`).join(", ")}]`),i.push("---","",s),S(o,i.join(`
7
+ `));let a=n?"update":"create";return this.appendLog(a,`${t.title} [${(t.tags||[]).slice(0,5).join(", ")}] by ${r} at ${e}`),!0}saveVersion(e,t){try{if(!x(t))return;let s=W(t,"utf-8"),r=new Date().toISOString().replace(/[:.]/g,"-"),n=y(this.baseDir,"_versions",e.replace(/\.md$/,""));j(n,{recursive:!0}),S(y(n,`${r}.md`),s)}catch{}}getVersions(e){let t=y(this.baseDir,"_versions",e.replace(/\.md$/,""));if(!x(t))return[];try{return O(t).filter(s=>s.endsWith(".md")).sort().reverse().map(s=>({timestamp:s.replace(/\.md$/,"").replace(/-/g,(r,n)=>n<19?n===10?"T":n===13||n===16?":":"-":r),path:y(t,s)}))}catch{return[]}}restoreVersion(e,t){let r=this.getVersions(e).find(n=>n.timestamp.startsWith(t));if(!r)return!1;try{let n=W(r.path,"utf-8"),o=y(this.baseDir,e);return this.saveVersion(e,o),S(o,n),this.appendLog("restore",`${e} restored to ${t}`),!0}catch{return!1}}readArticle(e){let t=y(this.baseDir,e);if(!x(t))return null;let s=W(t,"utf-8");return this.parseArticle(s,e)}readArticleAs(e,t){let s=this.readArticle(e);return!s||!this.canRead(s.meta,t)?null:s}parseArticle(e,t){let s=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!s)return null;let r=s[1],n=s[2].trim(),o=l=>r.match(new RegExp(`^${l}:\\s*(.+)$`,"m"))?.[1]?.trim().replace(/^"(.*)"$/,"$1")||"",i=l=>{let p=r.match(new RegExp(`^${l}:\\s*\\[(.*)\\]$`,"m"));return p?p[1].split(",").map(m=>m.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):[]},a=o("type")||o("kind"),h=new Set(["person","project","place","concept","event","decision","pattern"]).has(a)?a:void 0,b=i("tags").filter((l,p,m)=>l&&m.indexOf(l)===p),d=i("related"),u=i("graph_path");return{meta:{title:o("title"),type:h,related:d.length?d:void 0,tags:b,owner:o("owner"),access:o("access")||"public",sharedWith:i("shared_with"),created:o("created"),lastUpdated:o("last_updated"),sources:i("sources"),graphPath:u.length?u:void 0},content:n,path:t}}listArticles(e){let t=[];return this.walkDir(this.baseDir,s=>{if(!s.endsWith(".md"))return;let r=$(this.baseDir,s);if(r.startsWith("raw/")||r.startsWith("_"))return;let n=this.readArticle(r);n&&this.canRead(n.meta,e)&&t.push(n)}),t}search(e,t,s=10){let r=e.toLowerCase(),n=[];if(this.walkDir(this.baseDir,d=>{if(!d.endsWith(".md"))return;let u=$(this.baseDir,d);if(u.startsWith("raw/")||u.startsWith("_"))return;let l=this.readArticle(u);!l||!this.canRead(l.meta,t)||n.push(l)}),n.length===0)return[];let o=n.map(d=>d.content),i=y(this.baseDir,"_bm25_cache.json"),a=L(o,i),c=M(e,a),h=new Map(c.map(d=>[d.docIndex,d.score]));return n.map((d,u)=>{let l=h.get(u)??0;return d.meta.title.toLowerCase().includes(r)&&(l+=10),d.meta.tags?.some(p=>p.toLowerCase().includes(r))&&(l+=5),{article:d,score:l}}).filter(d=>d.score>0).sort((d,u)=>u.score-d.score).slice(0,s).map(d=>d.article)}findRelevant(e,t,s=3,r,n={graph:.6,bm25:.4}){let o=[];if(this.walkDir(this.baseDir,p=>{if(!p.endsWith(".md"))return;let m=$(this.baseDir,p);if(m.startsWith("raw/")||m.startsWith("_"))return;let f=this.readArticle(m);!f||!this.canRead(f.meta,t)||o.push(f)}),o.length===0)return[];let i=o.map(p=>`${p.meta.title} ${(p.meta.tags||[]).join(" ")} ${p.content}`),a=y(this.baseDir,"_bm25_relevance_cache.json"),c=L(i,a),h=M(e,c),b=h.reduce((p,m)=>Math.max(p,m.score),0)||1,d=new Map(h.map(p=>[p.docIndex,p.score/b])),u=!!(r&&r.length>0);return o.map((p,m)=>{let f=d.get(m)??0,v=0;u&&p.meta.graphPath?.length&&(v=Y(r,p.meta.graphPath));let w=u?n.graph*v+n.bm25*f:f;return{article:p,score:w}}).filter(p=>p.score>0).sort((p,m)=>m.score-p.score).slice(0,s).map(p=>p.article)}buildContext(e,t=4e3){if(e.length===0)return"";let s=["[Wiki Knowledge]"],r=0;for(let n of e){let o=`
8
+ ## ${n.meta.title} [${(n.meta.tags||[]).slice(0,3).join(", ")}]`,i=n.content.length>600?n.content.slice(0,600)+"...":n.content,a=o+`
9
+ `+i;if(r+a.length>t)break;s.push(a),r+=a.length}return s.push(`
10
+ [End Wiki Knowledge]`),s.join(`
11
+ `)}rebuildIndex(){let e=[],t=new Map;this.walkDir(this.baseDir,o=>{if(!o.endsWith(".md"))return;let i=$(this.baseDir,o);if(i.startsWith("raw/")||i.startsWith("_"))return;let a=this.readArticle(i);if(!a)return;let c=a.content.match(/\[\[([^\]]+)\]\]/g)||[];for(let b of c){let d=b.replace(/\[\[|\]\]/g,"");t.set(d,(t.get(d)||0)+1)}let h=[a.meta.title.toLowerCase()];a.meta.tags&&h.push(...a.meta.tags.map(b=>b.toLowerCase())),e.push({path:i,title:a.meta.title,type:a.meta.type,related:a.meta.related,tags:a.meta.tags||[],owner:a.meta.owner,access:a.meta.access,sharedWith:a.meta.sharedWith,aliases:h,backlinks:t.get(a.meta.title)||0,sources:a.meta.sources,lastUpdated:a.meta.lastUpdated})});let s={articles:e,lastRebuilt:new Date().toISOString()};S(y(this.baseDir,"_index.json"),JSON.stringify(s,null,2)),this.writeCatalog(e,s.lastRebuilt);let r=["# Wiki Index","",`Last rebuilt: ${s.lastRebuilt}`,""],n=new Map;for(let o of e){let i=o.access||"public",a=n.get(i)||[];a.push(o),n.set(i,a)}for(let[o,i]of Array.from(n.entries()).sort()){r.push(`## ${o}`,"");for(let a of i.sort((c,h)=>c.title.localeCompare(h.title))){let c=a.type?` [${a.type}]`:"";r.push(`- [${a.title}](${a.path})${c} \u2014 owner: ${a.owner}`)}r.push("")}return S(y(this.baseDir,"WIKI.md"),r.join(`
12
+ `)),this.log(`Index rebuilt: ${e.length} articles`),this.appendLog("rebuild-index",`${e.length} articles indexed`),s}writeCatalog(e,t){let s=["person","project","place","concept","event","decision","pattern","untyped"],r={person:"## People",project:"## Projects",place:"## Places",concept:"## Concepts",event:"## Events",decision:"## Decisions",pattern:"## Patterns",untyped:"## Untyped (needs migration)"},n=new Map;for(let i of e){let a=i.type||"untyped",c=n.get(a)||[];c.push(i),n.set(a,c)}let o=["# Wiki Content Catalog","",`_Last rebuilt: ${t}_`,"","Articles grouped by type. Agentic query walks this file first to pick candidates.",""];for(let i of s){let a=n.get(i);if(!(!a||a.length===0)){o.push(r[i]||`## ${i}`,"");for(let c of a.sort((h,b)=>h.title.localeCompare(b.title))){let h=c.access==="private"?" (private)":c.access==="shared"?" (shared)":"",b=c.related?.length?` \u2192 [[${c.related.slice(0,3).join("]], [[")}]]${c.related.length>3?", \u2026":""}`:"";o.push(`- [${c.title}](${c.path})${h}${b}`)}o.push("")}}S(y(this.baseDir,"_index.md"),o.join(`
13
+ `))}stats(){let e=0,t={},s={},r={};this.walkDir(this.baseDir,o=>{if(!o.endsWith(".md"))return;let i=$(this.baseDir,o);if(i.startsWith("raw/")||i.startsWith("_")||i==="WIKI.md")return;let a=this.readArticle(i);if(a){e++;for(let c of a.meta.tags||[])t[c]=(t[c]||0)+1;s[a.meta.access]=(s[a.meta.access]||0)+1,r[a.meta.owner]=(r[a.meta.owner]||0)+1}});let n=x(this.rawDir)?O(this.rawDir).filter(o=>o.endsWith(".md")).length:0;return{totalArticles:e,totalEntries:n,articlesByType:t,articlesByAccess:s,articlesByOwner:r}}appendLog(e,t){let s=y(this.baseDir,"log.md"),n=`## [${new Date().toISOString().replace("T"," ").slice(0,16)}] ${e} | ${t}
14
+
15
+ `;x(s)||S(s,`# Wiki Log
16
+
17
+ Chronological record of all wiki operations.
18
+
19
+ `),ce(s,n)}getLog(e=20){let t=y(this.baseDir,"log.md");return x(t)?W(t,"utf-8").split(/^## /m).filter(n=>n.startsWith("[")).slice(-e).map(n=>n.trim()):[]}ensureSchema(){let e=y(this.baseDir,"_schema.md");if(x(e))return;S(e,`# Wiki Schema
20
+
21
+ A personal wiki maintained by LLMs. Karpathy pattern: plain files, aggressive tagging,
22
+ structure emerges from data. The wiki is a compounding artifact.
23
+
24
+ ## Structure
25
+
26
+ \`\`\`
27
+ wiki/
28
+ _schema.md # This file
29
+ worldview.md # YOUR mental model \u2014 the LLM reads this during absorb
30
+ raw/entries/ # Immutable raw sources (conversations, imports, anything)
31
+ [LLM-chosen dirs] # Structure emerges from data \u2014 the LLM picks paths
32
+ \`\`\`
33
+
34
+ The LLM decides how to organize articles. You describe YOUR world in worldview.md.
35
+
36
+ ## Article Format
37
+
38
+ \`\`\`yaml
39
+ ---
40
+ title: "Staging Deployment Process"
41
+ tags: ["deploy", "staging", "devops", "process", "2026-04-06"]
42
+ owner: devops-agent
43
+ access: public
44
+ created: 2026-04-06
45
+ last_updated: 2026-04-06
46
+ sources: ["entry-id-1", "entry-id-2"]
47
+ ---
48
+
49
+ Article content with [[wikilinks]] and section tags.
50
+
51
+ ## Deploy Steps
52
+ <!-- tags: process, runbook, staging -->
53
+ Content specific to this section...
54
+ \`\`\`
55
+
56
+ ## Tags
57
+
58
+ Tags are the primary context-narrowing mechanism. The more tags, the better.
59
+
60
+ Tag aggressively:
61
+ - **Who**: people, agents, teams involved
62
+ - **What**: project, client, topic, technology
63
+ - **When**: dates, periods, milestones
64
+ - **Where**: server, environment, channel
65
+ - **How**: process, decision, pattern
66
+ - **Your terms**: whatever vocabulary makes sense in YOUR world
67
+
68
+ Section tags (\`<!-- tags: ... -->\`) let different parts of an article
69
+ match different queries. A deploy article might have sections tagged
70
+ "runbook" and "incident" separately.
71
+
72
+ ## Operations
73
+
74
+ ### Absorb
75
+ Read raw entries \u2192 read worldview.md \u2192 tag aggressively \u2192 create/update articles.
76
+ Also identify GAPS: "We mention X but have no article for it."
77
+
78
+ ### Query
79
+ Match by tags first \u2192 then keyword within matched articles \u2192 synthesize.
80
+ If agent is working on a specific project, only send content tagged for that project.
81
+
82
+ ### Lint
83
+ Check for: orphan articles, broken [[wikilinks]], untagged articles,
84
+ articles over 100 lines that should split, mentioned-but-missing topics.
85
+
86
+ ## Worldview
87
+
88
+ Edit \`worldview.md\` to describe YOUR world. This is not a schema \u2014 it's your
89
+ mental model. The LLM reads it during absorb to understand where things go.
90
+
91
+ Example worldview.md:
92
+ \`\`\`
93
+ I'm the founder of Acme Corp.
94
+ We build software products.
95
+ Our agents: Marketing Bot, DevOps Bot, Coordinator.
96
+ Clients: ClientA (enterprise), ClientB (startup).
97
+ Infrastructure: local dev machine, cloud server.
98
+ We use GitLab for version control.
99
+ \`\`\`
100
+ `),this.log("Created _schema.md")}extractWikilinks(e){return(e.match(/\[\[([^\]]+)\]\]/g)||[]).map(s=>s.replace(/\[\[|\]\]/g,""))}buildBacklinks(){let e={};return this.walkDir(this.baseDir,t=>{if(!t.endsWith(".md"))return;let s=$(this.baseDir,t);if(s.startsWith("raw/")||s.startsWith("_")||s==="WIKI.md"||s==="log.md")return;let r=W(t,"utf-8"),n=this.extractWikilinks(r);for(let o of n)e[o]||(e[o]=[]),e[o].push(s)}),S(y(this.baseDir,"_backlinks.json"),JSON.stringify(e,null,2)),e}getBacklinks(e){let t=y(this.baseDir,"_backlinks.json");if(!x(t))return[];try{return JSON.parse(W(t,"utf-8"))[e]||[]}catch{return[]}}findByTags(e,t,s=10){let r=e.map(o=>o.toLowerCase()),n=[];return this.walkDir(this.baseDir,o=>{if(!o.endsWith(".md"))return;let i=$(this.baseDir,o);if(i.startsWith("raw/")||i.startsWith("_"))return;let a=this.readArticle(i);if(!a||t&&!this.canRead(a.meta,t))return;let c=(a.meta.tags||[]).map(u=>u.toLowerCase()),h=(a.content.match(/<!--\s*tags?:\s*([^>]+)\s*-->/gi)||[]).flatMap(u=>u.replace(/<!--\s*tags?:\s*|\s*-->/gi,"").split(",").map(l=>l.trim().toLowerCase())),b=[...c,...h],d=r.filter(u=>b.includes(u)).length;d>0&&n.push({article:a,score:d})}),n.sort((o,i)=>i.score-o.score).slice(0,s).map(o=>o.article)}getAllTags(){let e=new Map;return this.walkDir(this.baseDir,t=>{if(!t.endsWith(".md"))return;let s=$(this.baseDir,t);if(s.startsWith("raw/")||s.startsWith("_"))return;let r=this.readArticle(s);if(r?.meta.tags)for(let n of r.meta.tags)e.set(n,(e.get(n)||0)+1)}),e}getWorldview(){let e=y(this.baseDir,"worldview.md");return x(e)?W(e,"utf-8"):null}setWorldview(e){S(y(this.baseDir,"worldview.md"),e),this.appendLog("worldview","Updated worldview")}lint(){let e=[],t=new Set,s=new Map,r=new Map;this.walkDir(this.baseDir,o=>{if(!o.endsWith(".md"))return;let i=$(this.baseDir,o);if(i.startsWith("raw/")||i.startsWith("_")||i==="WIKI.md"||i==="log.md")return;let a=this.readArticle(i);if(!a)return;t.add(a.meta.title),r.set(a.meta.title,i);let c=this.extractWikilinks(a.content);s.set(i,c);let h=a.content.split(`
101
+ `).length;h>100&&e.push({type:"bloated",article:i,message:`${h} lines \u2014 consider splitting`}),a.meta.sources?.length||e.push({type:"unsourced",article:i,message:"No sources listed in frontmatter"}),h<10&&a.content.trim().length<100&&e.push({type:"stub",article:i,message:"Very short article \u2014 needs enrichment"})});for(let[o,i]of s)for(let a of i)t.has(a)||e.push({type:"broken-link",article:o,message:`[[${a}]] \u2014 target article not found`});let n=this.buildBacklinks();for(let[o,i]of r)(!n[o]||n[o].length===0)&&e.push({type:"orphan",article:i,message:`No other articles link to "${o}"`});return this.appendLog("lint",`Found ${e.length} issues`),e}getUnabsorbedEntries(){let e=new Set;return this.walkDir(this.baseDir,s=>{if(!s.endsWith(".md"))return;let r=$(this.baseDir,s);if(r.startsWith("raw/")||r.startsWith("_"))return;let n=this.readArticle(r);if(n?.meta.sources)for(let o of n.meta.sources)e.add(o)}),this.listEntries().filter(s=>!e.has(s.id))}buildAbsorbPrompt(e=20){let t=this.getUnabsorbedEntries().slice(0,e);if(t.length===0)return null;let s=t.map(r=>`[${r.date} ${r.agentId} via ${r.source}] ${r.content.slice(0,200)}`).join(`
102
+
103
+ `);return`/wiki absorb
104
+
105
+ There are ${t.length} unprocessed entries. Read each, understand what it means, and create or update wiki articles.
106
+
107
+ ${s}`}walkDir(e,t){if(x(e))for(let s of O(e)){let r=de(e,s);le(r).isDirectory()?this.walkDir(r,t):t(r)}}};import{resolve as K}from"path";import{existsSync as he,readdirSync as ge,mkdirSync as ue}from"fs";var se=class{baseDir;agentsDir;mode;sharedStore;agentStores=new Map;log;constructor(e=K(process.cwd(),".agentx/wiki"),t=console.error.bind(console,"[wiki-hub]"),s="graph"){this.baseDir=e,this.agentsDir=K(e,"agents"),this.mode=s,this.log=t,ue(this.agentsDir,{recursive:!0}),this.sharedStore=new R(e,t)}getMode(){return this.mode}getAgentWiki(e){let t=`${e}:${this.mode}`;if(this.agentStores.has(t))return this.agentStores.get(t);let s=K(this.agentsDir,e,this.mode),r=new R(s,this.log);return this.agentStores.set(t,r),r}getSharedStore(){return this.sharedStore}listAgents(){let e=new Set,t=this.sharedStore.listEntries();for(let s of t)e.add(s.agentId);if(he(this.agentsDir))for(let s of ge(this.agentsDir))!s.startsWith("_")&&!s.startsWith(".")&&e.add(s);return[...e].sort()}getAgentEntries(e){return this.sharedStore.listEntries({agentId:e})}getUnabsorbedEntries(e){let t=this.getAgentWiki(e),s=this.getAgentEntries(e),r=new Set,n=t.rebuildIndex();for(let o of n.articles)if(o.sources)for(let i of o.sources)r.add(i);return s.filter(o=>!r.has(o.id))}summary(){return this.listAgents().map(t=>{let s=this.getAgentEntries(t),n=this.getAgentWiki(t).rebuildIndex(),o=this.getUnabsorbedEntries(t);return{agentId:t,totalEntries:s.length,totalArticles:n.articles.length,unabsorbed:o.length,articles:n.articles}})}};export{A as a,U as b,H as c,V as d,R as e,se as f};
108
+ //# sourceMappingURL=chunk-7P5QRX5W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/graph/starter-schema.ts","../src/graph/store.ts","../src/graph/classifier.ts","../src/wiki/store.ts","../src/wiki/hub.ts"],"sourcesContent":["import type { GraphSchema } from \"./types\"\n\n/**\n * Ships with the install. When .agentx/graph/schema.json is missing,\n * the store seeds this so a fresh daemon has something to classify\n * against.\n *\n * v2 — Phase 1 of the classifier-retire plan: verb-level intent\n * taxonomy. The classifier now answers ONLY \"what kind of work is\n * this\" — it does not embed client/project/agent in the leaf because\n * those already live on the event metadata (project, subject,\n * agentId). One copy of \"review.merge-request\" instead of one per\n * client.\n *\n * Two levels:\n * category — broad work area (code/ops/support/admin/knowledge/social/system)\n * verb — specific intent (review.merge-request, deploy.staging, etc.)\n *\n * The category is closed-set; the verb is open-set so the operator\n * can add new ones via `agentx graph review` over time. Most paths\n * will reuse the seeded verbs below.\n *\n * Why so few levels: every level is information the classifier\n * actually has to decide. The previous v1 schema (scope > location >\n * org > unit > activity) re-encoded data already on the event —\n * forcing a 5-LLM-question prompt to recover what we already knew.\n */\nexport const STARTER_SCHEMA: GraphSchema = {\n version: 2,\n levels: [\n {\n id: \"category\",\n description: \"Top-level kind of work. Closed set — pick the closest fit.\",\n axes: [\n {\n name: \"kind\",\n type: \"enum\",\n values: [\n \"code\", // anything code-related: review, fix, implement, refactor\n \"ops\", // infra: deploy, monitor, rollback, scale, incident\n \"support\", // human-facing: answer question, triage, route\n \"admin\", // governance: config change, scheduling, token, role\n \"knowledge\", // wiki, docs, research, summarize\n \"social\", // marketing, comms, brief, content\n \"system\", // internal infrastructure (rare; mostly classifier itself)\n ],\n description: \"Which category this work belongs in.\",\n },\n ],\n },\n {\n id: \"verb\",\n description: \"Specific intent — verb-level only, NOT client/project-bound.\",\n axes: [\n {\n name: \"name\",\n type: \"free\",\n description:\n \"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, \" +\n \"investigate.error, deploy.staging, deploy.production, rollback.release, \" +\n \"spec.feature, triage.issue, document.feature, chat.greeting, \" +\n \"chat.support-request, schedule.add, config.change, ingest.contact, \" +\n \"transcribe.audio, brief.daily, summarize.thread, plan.work.\",\n },\n ],\n },\n ],\n leafInput: {\n name: \"input\",\n type: \"enum\",\n values: [\n \"telegram\",\n \"whatsapp\",\n \"slack\",\n \"discord\",\n \"gitlab-mr\",\n \"gitlab-issue\",\n \"github-issue\",\n \"github-pr\",\n \"cron\",\n \"webhook\",\n \"mesh\",\n \"http\",\n ],\n description: \"Where the message entered the system.\",\n },\n leafOutput: {\n name: \"output\",\n type: \"free\",\n description:\n \"One short sentence describing what the agent is expected to produce. \" +\n \"Generic — refers to the verb's outcome, not the specific client/project \" +\n \"(those are on the event metadata already).\",\n },\n}\n\n/** Pre-seeded verbs shipped with v2. The classifier picks from these\n * first; new verbs go through the review queue before they're\n * committed (so the taxonomy doesn't drift). Adding to this list is\n * cheap — the seed runs once when nodes.json is empty. */\nexport const STARTER_VERB_NODES: Array<{\n category: \"code\" | \"ops\" | \"support\" | \"admin\" | \"knowledge\" | \"social\" | \"system\"\n verb: string\n description?: string\n}> = [\n // code\n { category: \"code\", verb: \"review.merge-request\", description: \"Review a GitLab MR or GitHub PR\" },\n { category: \"code\", verb: \"review.code-change\", description: \"General code review (snippet, diff)\" },\n { category: \"code\", verb: \"fix.bug\", description: \"Fix a reported bug\" },\n { category: \"code\", verb: \"implement.feature\", description: \"Build a new feature\" },\n { category: \"code\", verb: \"investigate.error\", description: \"Investigate a failing test, exception, pipeline\" },\n { category: \"code\", verb: \"refactor.code\", description: \"Restructure existing code without behaviour change\" },\n { category: \"code\", verb: \"spec.feature\", description: \"Write or update a feature spec / RFC\" },\n { category: \"code\", verb: \"triage.issue\", description: \"Classify, label, or assign an issue\" },\n { category: \"code\", verb: \"answer.code-question\", description: \"Answer a code-related question\" },\n\n // ops\n { category: \"ops\", verb: \"deploy.staging\", description: \"Deploy to a staging/preview environment\" },\n { category: \"ops\", verb: \"deploy.production\", description: \"Deploy to production\" },\n { category: \"ops\", verb: \"rollback.release\", description: \"Roll back a release\" },\n { category: \"ops\", verb: \"investigate.incident\", description: \"Investigate a production incident\" },\n { category: \"ops\", verb: \"monitor.system\", description: \"Check system status / metrics\" },\n { category: \"ops\", verb: \"rotate.credential\", description: \"Rotate a token, key, certificate\" },\n { category: \"ops\", verb: \"audit.security\", description: \"Review for security issues\" },\n\n // support\n { category: \"support\", verb: \"chat.greeting\", description: \"A casual hello, no actionable request\" },\n { category: \"support\", verb: \"chat.support-request\", description: \"End-user / customer asking for help\" },\n { category: \"support\", verb: \"chat.casual\", description: \"Informal back-and-forth chat\" },\n { category: \"support\", verb: \"answer.question\", description: \"Answer an open-ended question\" },\n { category: \"support\", verb: \"route.request\", description: \"Forward a request to the right agent\" },\n\n // admin\n { category: \"admin\", verb: \"config.change\", description: \"Change agentx.json / environment / config\" },\n { category: \"admin\", verb: \"schedule.add\", description: \"Add a new cron / scheduled task\" },\n { category: \"admin\", verb: \"schedule.remove\", description: \"Remove or disable a schedule\" },\n { category: \"admin\", verb: \"token.create\", description: \"Mint a scoped API token\" },\n { category: \"admin\", verb: \"agent.add\", description: \"Configure a new agent\" },\n { category: \"admin\", verb: \"channel.add\", description: \"Wire up a new channel\" },\n\n // knowledge\n { category: \"knowledge\", verb: \"document.feature\", description: \"Write or update documentation\" },\n { category: \"knowledge\", verb: \"summarize.thread\", description: \"Summarize a long conversation/thread\" },\n { category: \"knowledge\", verb: \"research.topic\", description: \"Investigate / gather info on a topic\" },\n { category: \"knowledge\", verb: \"wiki.absorb\", description: \"Roll raw entries into typed wiki articles\" },\n\n // social\n { category: \"social\", verb: \"brief.daily\", description: \"Compose a daily brief / digest\" },\n { category: \"social\", verb: \"draft.post\", description: \"Draft a marketing post / announcement\" },\n { category: \"social\", verb: \"report.weekly\", description: \"Compose a weekly status report\" },\n\n // system (the classifier itself; rare to expose)\n { category: \"system\", verb: \"classify.intent\", description: \"Internal: graph-classifier sub-call\" },\n]\n\n","import {\n readFileSync,\n writeFileSync,\n appendFileSync,\n existsSync,\n mkdirSync,\n renameSync,\n} from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { createHash } from \"crypto\"\nimport {\n graphSchemaSchema,\n nodesFileSchema,\n indexFileSchema,\n classificationSchema,\n type GraphSchema,\n type GraphNode,\n type NodesFile,\n type IndexFile,\n type Classification,\n type FingerprintEntry,\n} from \"./types\"\nimport { STARTER_SCHEMA, STARTER_VERB_NODES } from \"./starter-schema\"\n\n// --- Intent Knowledge Graph filesystem store ---\n//\n// Mirrors the .agentx/wiki pattern: plain JSON on disk, one directory, no DB.\n// Not thread-safe across processes — writes go through safeWrite (tmp + rename).\n\nexport interface GraphStoreOptions {\n baseDir?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class GraphStore {\n readonly baseDir: string\n private log: (...args: unknown[]) => void\n\n constructor(opts: GraphStoreOptions = {}) {\n this.baseDir = opts.baseDir ?? resolve(process.cwd(), \".agentx/graph\")\n this.log = opts.log ?? console.error.bind(console, \"[graph]\")\n mkdirSync(this.baseDir, { recursive: true })\n }\n\n // --- Paths ---\n\n schemaPath(): string { return resolve(this.baseDir, \"schema.json\") }\n nodesPath(): string { return resolve(this.baseDir, \"nodes.json\") }\n indexPath(): string { return resolve(this.baseDir, \"index.json\") }\n classificationsPath(): string { return resolve(this.baseDir, \"classifications.jsonl\") }\n\n // --- Schema ---\n\n /** Load the graph schema, seeding STARTER_SCHEMA if the file is missing.\n * Phase 1 of the classifier-retire plan ships a v2 verb-level schema;\n * installs with the v1 hierarchical schema (scope > org > unit >\n * activity) get auto-migrated on next load: the v1 file is renamed\n * to schema.v1.bak.json, the v2 starter is written, and the\n * per-event nodes.json + classifications.jsonl are left alone for\n * historical reading. New events classify under v2 from then on. */\n loadSchema(): GraphSchema {\n const p = this.schemaPath()\n if (!existsSync(p)) {\n this.saveSchema(STARTER_SCHEMA)\n this.log(\"seeded starter schema at\", p)\n return STARTER_SCHEMA\n }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = graphSchemaSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n if ((parsed.data.version ?? 1) < (STARTER_SCHEMA.version ?? 2)) {\n const backup = p.replace(/\\.json$/, `.v${parsed.data.version ?? 1}.bak.json`)\n try {\n writeFileSync(backup, raw)\n this.log(`migrated graph schema v${parsed.data.version ?? 1} -> v${STARTER_SCHEMA.version}; old saved at ${backup}`)\n } catch (e: any) {\n this.log(`could not back up old schema: ${e?.message ?? e}`)\n }\n this.saveSchema(STARTER_SCHEMA)\n // Same treatment for nodes.json — the v1 nodes were parented under\n // levels that don't exist in v2 and would fail validation. Archive\n // them and re-seed.\n const np = this.nodesPath()\n if (existsSync(np)) {\n const nbackup = np.replace(/\\.json$/, `.v1.bak.json`)\n try {\n writeFileSync(nbackup, readFileSync(np, \"utf-8\"))\n this.log(`archived v1 nodes.json -> ${nbackup}`)\n } catch (e: any) {\n this.log(`could not back up old nodes: ${e?.message ?? e}`)\n }\n try {\n const seeded = seedNodesFile()\n this.saveNodes(seeded)\n } catch (e: any) {\n this.log(`could not seed v2 nodes: ${e?.message ?? e}`)\n }\n }\n return STARTER_SCHEMA\n }\n return parsed.data\n }\n\n saveSchema(schema: GraphSchema): void {\n const parsed = graphSchemaSchema.safeParse(schema)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid schema: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.schemaPath(), parsed.data)\n }\n\n // --- Nodes ---\n\n loadNodes(): NodesFile {\n const p = this.nodesPath()\n if (!existsSync(p)) {\n // Seed the verb-level taxonomy on first load. Lets the classifier\n // pick from a stable set of common verbs out of the box, instead of\n // inventing slightly-different variants for every event.\n const seeded = seedNodesFile()\n this.saveNodes(seeded)\n return seeded\n }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = nodesFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }\n\n saveNodes(file: NodesFile): void {\n const parsed = nodesFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid nodes file: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.nodesPath(), parsed.data)\n }\n\n /** Validate a node against the schema AND uniqueness within the nodes file. */\n validateNode(node: GraphNode, schema: GraphSchema, all: GraphNode[]): void {\n const level = schema.levels.find((l) => l.id === node.level)\n if (!level) throw new Error(`Unknown level: \"${node.level}\"`)\n\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in node.axes))\n if (missing.length) {\n throw new Error(\n `Node ${node.id} (${node.level}) missing required axes: ${missing.map((a) => a.name).join(\", \")}`,\n )\n }\n\n for (const a of level.axes) {\n const v = node.axes[a.name]\n if (v == null) continue\n if (a.type === \"enum\" && a.values && !a.values.includes(v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" not in enum ${JSON.stringify(a.values)}`)\n }\n if (a.type === \"ref\") {\n if (!all.some((n) => n.id === v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" refers to unknown node`)\n }\n if (a.refLevel) {\n const target = all.find((n) => n.id === v)!\n if (target.level !== a.refLevel) {\n throw new Error(\n `Node ${node.id}: axis ${a.name} expected level \"${a.refLevel}\" but got \"${target.level}\"`,\n )\n }\n }\n }\n }\n\n const levelIdx = schema.levels.findIndex((l) => l.id === node.level)\n if (levelIdx === 0) {\n if (node.parentId !== null) {\n throw new Error(`Root-level node ${node.id} must have parentId: null`)\n }\n } else {\n if (!node.parentId) {\n throw new Error(`Non-root node ${node.id} must have a parentId`)\n }\n const parent = all.find((n) => n.id === node.parentId)\n if (!parent) throw new Error(`Node ${node.id}: parent \"${node.parentId}\" not found`)\n const parentLevelIdx = schema.levels.findIndex((l) => l.id === parent.level)\n // Parent may be at any higher level — intermediate levels can be skipped\n // for orgs that don't use them (e.g. remote-first: no location node).\n if (parentLevelIdx < 0 || parentLevelIdx >= levelIdx) {\n throw new Error(\n `Node ${node.id} at level \"${node.level}\" has parent at level \"${parent.level}\" — parent must be at a higher level`,\n )\n }\n }\n }\n\n /**\n * Commit a full classification path: for each path element that doesn't\n * yet exist, infer its schema level by matching the proposed axes against\n * the level's required axes (first match ≥ cursor wins — this handles\n * paths that skip levels, like business → noqta where location is\n * absent). Refreshes the node list between adds so callers in a loop\n * don't hit \"Node id already exists\" on the second iteration.\n */\n commitNodesAlongPath(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n createdBy?: string,\n ): { added: GraphNode[]; skipped: string[] } {\n const added: GraphNode[] = []\n const skipped: string[] = []\n const now = new Date().toISOString()\n\n for (let i = 0; i < path.length; i++) {\n const id = path[i]\n // Load fresh each iteration — cheap O(N) file read, avoids stale snapshots.\n const current = this.loadNodes().nodes\n const existing = current.find((n) => n.id === id)\n if (existing) {\n skipped.push(id)\n continue\n }\n\n // Cursor: how deep the schema-levels we've already filled up to.\n let cursor = 0\n // Scan backwards through path[0..i-1] to find the deepest already-committed\n // or just-added node and resume after its level.\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const priorNode = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (priorNode) {\n cursor = schema.levels.findIndex((l) => l.id === priorNode.level) + 1\n break\n }\n }\n\n const axes = proposedAxes[id] ?? {}\n // Find the first level at or after the cursor whose required axes are\n // satisfied by proposedAxes[id]. \"Required\" here = listed in level.axes;\n // we accept a partial match (axes present in proposal, even if some\n // schema axes are missing — validateNode will catch truly broken ones).\n let chosenLevel: string | null = null\n for (let j = cursor; j < schema.levels.length; j++) {\n const level = schema.levels[j]\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in axes))\n if (missing.length === 0) {\n chosenLevel = level.id\n cursor = j + 1\n break\n }\n }\n // Fallback: if no level's axes fully match, try the NEXT level after\n // the cursor anyway. Better to commit at the likely level and let\n // validateNode reject on write than to silently drop the whole path.\n if (!chosenLevel && cursor < schema.levels.length) {\n chosenLevel = schema.levels[cursor].id\n cursor++\n }\n if (!chosenLevel) {\n throw new Error(`Can't place node ${id} — no schema level left (cursor=${cursor}, levels=${schema.levels.length})`)\n }\n\n // Parent is the DEEPEST already-placed node at a level < chosenLevel.\n let parentId: string | null = null\n const chosenIdx = schema.levels.findIndex((l) => l.id === chosenLevel)\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const prior = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (!prior) continue\n const priorIdx = schema.levels.findIndex((l) => l.id === prior.level)\n if (priorIdx >= 0 && priorIdx < chosenIdx) { parentId = priorId; break }\n }\n\n const node: GraphNode = {\n id, level: chosenLevel, parentId, axes,\n createdAt: now,\n createdBy,\n }\n this.addNode(node) // throws if schema validation fails\n added.push(node)\n }\n\n return { added, skipped }\n }\n\n /** Append a node. Caller is responsible for id uniqueness; we check and throw. */\n addNode(node: GraphNode): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n if (file.nodes.some((n) => n.id === node.id)) {\n throw new Error(`Node id already exists: ${node.id}`)\n }\n this.validateNode(node, schema, [...file.nodes, node])\n file.nodes.push(node)\n this.saveNodes(file)\n return node\n }\n\n // --- Classifications (append-only log) ---\n\n appendClassification(c: Classification): boolean {\n const parsed = classificationSchema.safeParse(c)\n if (!parsed.success) {\n // Before the fix this threw and took down the whole task. Now we drop\n // the bad classification and log enough detail (the offending path +\n // axes keys) for the operator to diagnose the prompt. Returning false\n // lets callers decide whether to retry with a cleaner proposal.\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\" · \")\n this.log(\n `skipped invalid classification — ${issues} ·`,\n `path=${JSON.stringify(c.path)}`,\n `axes-keys=${JSON.stringify(Object.keys(c.proposedAxes || {}))}`,\n )\n return false\n }\n const line = JSON.stringify(parsed.data) + \"\\n\"\n const p = this.classificationsPath()\n if (!existsSync(p)) {\n mkdirSync(dirname(p), { recursive: true })\n }\n appendFileSync(p, line)\n return true\n }\n\n /**\n * All pending classifications, newest first. Collapses the append-only\n * ledger by msgHash — the most recent entry per message wins (so if an\n * entry was updated via updateClassificationStatus, we see the current\n * state, not the original).\n */\n listPendingClassifications(limit = 200): Classification[] {\n return this.listByStatus(\"pending\", limit)\n }\n\n /** Same contract as listPendingClassifications but filtered to any status. */\n listByStatus(status: Classification[\"status\"], limit = 200): Classification[] {\n const all = this.readAllCollapsed()\n return all.filter((c) => c.status === status).slice(0, limit)\n }\n\n /**\n * Read every classification, collapse by msgHash keeping the newest entry\n * per message. Used by listByStatus + the review loop to see current state.\n * Returns newest-first order.\n */\n private readAllCollapsed(): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const byHash = new Map<string, Classification>()\n for (const line of lines) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(line))\n if (!parsed.success) continue\n // Later entries overwrite earlier ones for the same msgHash.\n byHash.set(parsed.data.msgHash, parsed.data)\n } catch {\n // skip malformed\n }\n }\n // Newest-first — sort by ts desc.\n return Array.from(byHash.values()).sort((a, b) => (a.ts < b.ts ? 1 : -1))\n }\n\n /**\n * Append a status-update entry for an existing classification. The ledger\n * stays append-only; readers collapse by msgHash. Returns the updated\n * classification, or null if no prior entry for msgHash exists.\n */\n updateClassificationStatus(\n msgHash: string,\n update: {\n status: Classification[\"status\"]\n reviewer?: string\n reviewReason?: string\n },\n ): Classification | null {\n const all = this.readAllCollapsed()\n const prior = all.find((c) => c.msgHash === msgHash)\n if (!prior) return null\n const next: Classification = {\n ...prior,\n status: update.status,\n ts: new Date().toISOString(),\n }\n // Store reviewer + reason in the preview field so readers can see who\n // flipped what without needing a separate review-log file. Non-breaking:\n // preview is already optional text meant for UI display.\n if (update.reviewer || update.reviewReason) {\n const suffix = ` · ${update.reviewer ? `reviewer=${update.reviewer}` : \"\"}${update.reviewReason ? ` · ${update.reviewReason}` : \"\"}`\n next.preview = ((prior.preview || \"\") + suffix).slice(0, 500)\n }\n this.appendClassification(next)\n return next\n }\n\n /** Read the last N classifications, newest first. Cheap enough for the admin UI. */\n readRecentClassifications(limit = 50): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const out: Classification[] = []\n for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(lines[i]))\n if (parsed.success) out.push(parsed.data)\n } catch {\n // skip malformed line — append-only log; don't fail reads on one bad line\n }\n }\n return out\n }\n\n // --- Fingerprint index ---\n\n /**\n * Hash that represents \"this kind of message\" for snap-to-path caching.\n * Normalizes casing + whitespace; includes channel + sender so two people\n * writing the same words can still land on different paths if they should.\n */\n fingerprint(msg: {\n text: string\n channel?: string\n sender?: string\n }): string {\n const norm = msg.text.toLowerCase().replace(/\\s+/g, \" \").trim()\n const payload = [norm, msg.channel ?? \"\", msg.sender ?? \"\"].join(\"\\u0001\")\n return createHash(\"sha256\").update(payload).digest(\"hex\").slice(0, 32)\n }\n\n loadIndex(): IndexFile {\n const p = this.indexPath()\n if (!existsSync(p)) return { version: 1, entries: {} }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = indexFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }\n\n saveIndex(file: IndexFile): void {\n const parsed = indexFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid index: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.indexPath(), parsed.data)\n }\n\n getFingerprint(fp: string): FingerprintEntry | undefined {\n return this.loadIndex().entries[fp]\n }\n\n setFingerprint(fp: string, entry: Omit<FingerprintEntry, \"fingerprint\" | \"updatedAt\">): void {\n const file = this.loadIndex()\n file.entries[fp] = {\n ...entry,\n fingerprint: fp,\n updatedAt: new Date().toISOString(),\n }\n this.saveIndex(file)\n }\n\n /** Update the axes on an existing node. Schema-validates the result. */\n updateNodeAxes(id: string, axes: Record<string, string>): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n const node = file.nodes.find((n) => n.id === id)\n if (!node) throw new Error(`Node not found: ${id}`)\n const updated: GraphNode = { ...node, axes }\n this.validateNode(updated, schema, file.nodes.map((n) => (n.id === id ? updated : n)))\n file.nodes = file.nodes.map((n) => (n.id === id ? updated : n))\n this.saveNodes(file)\n return updated\n }\n\n /** Remove a node. Refuses to delete if any other node still references it\n * (child via parentId, or axis ref). */\n deleteNode(id: string): void {\n const file = this.loadNodes()\n const child = file.nodes.find((n) => n.parentId === id)\n if (child) {\n throw new Error(`Cannot delete \"${id}\" — child node \"${child.id}\" still points to it`)\n }\n const refHolder = file.nodes.find((n) =>\n Object.values(n.axes).some((v) => v === id),\n )\n if (refHolder) {\n throw new Error(`Cannot delete \"${id}\" — node \"${refHolder.id}\" references it via an axis`)\n }\n file.nodes = file.nodes.filter((n) => n.id !== id)\n this.saveNodes(file)\n }\n\n /** Approve a pending classification. Commits any new nodes along its path,\n * stamps the fingerprint index so future similar messages hit the cache,\n * and appends an audit entry. Idempotent — no-op if already cached. */\n approveClassification(c: Classification): void {\n if (this.getFingerprint(c.msgHash)) return // already approved\n // Commit any path nodes the LLM proposed but hadn't been confirmed yet.\n const schema = this.loadSchema()\n const known = new Set(this.loadNodes().nodes.map((n) => n.id))\n for (let i = 0; i < c.path.length; i++) {\n const id = c.path[i]\n if (known.has(id)) continue\n const level = schema.levels[i]?.id\n if (!level) break\n const axes = c.proposedAxes?.[id] ?? {}\n const parentId = i === 0 ? null : c.path[i - 1]\n this.addNode({\n id,\n level,\n parentId,\n axes,\n createdAt: new Date().toISOString(),\n createdBy: c.agentId,\n })\n known.add(id)\n }\n this.setFingerprint(c.msgHash, { path: c.path, leaf: c.leaf })\n this.appendClassification({ ...c, status: \"approved\", ts: new Date().toISOString() })\n }\n\n /** Record rejection — no nodes committed, no fingerprint set. */\n rejectClassification(c: Classification): void {\n this.appendClassification({ ...c, status: \"rejected\", ts: new Date().toISOString() })\n }\n}\n\n/** Atomic write: tmp file + rename. Avoids half-written JSON on crashes. */\nfunction safeWriteJson(path: string, data: unknown): void {\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.tmp-${process.pid}`\n writeFileSync(tmp, JSON.stringify(data, null, 2))\n renameSync(tmp, path)\n}\n\n/** Build the seed nodes.json — one category node per enum value, plus\n * one verb node per STARTER_VERB_NODES entry parented under its\n * category. Idempotent shape: same input always produces the same\n * file, so re-seeding is safe. */\nfunction seedNodesFile(): NodesFile {\n const now = new Date().toISOString()\n const nodes: NodesFile[\"nodes\"] = []\n const seenCats = new Set<string>()\n for (const v of STARTER_VERB_NODES) {\n if (!seenCats.has(v.category)) {\n nodes.push({\n id: v.category,\n level: \"category\",\n parentId: null,\n axes: { kind: v.category },\n createdAt: now,\n createdBy: \"starter\",\n })\n seenCats.add(v.category)\n }\n }\n for (const v of STARTER_VERB_NODES) {\n nodes.push({\n id: v.verb,\n level: \"verb\",\n parentId: v.category,\n axes: { name: v.verb, ...(v.description ? { description: v.description } : {}) },\n createdAt: now,\n createdBy: \"starter\",\n })\n }\n return { version: 1, nodes }\n}\n","import { createHash } from \"crypto\"\nimport { GraphStore } from \"./store\"\nimport {\n type GraphSchema,\n type GraphNode,\n type Classification,\n} from \"./types\"\nimport { createProvider, type ProviderName, type AgentProvider } from \"@/agent/providers\"\n\n// --- Intent Knowledge Graph classifier ---\n//\n// For every incoming message, returns a path through the taxonomy:\n// 1. fingerprint → cache hit → reuse path (0 LLM calls, source=cache)\n// 2. miss → LLM draftAgent proposes { path, axes } → validated → pending\n// 3. autoApproveConfidence met → committed + cached\n//\n// Pending paths are still usable immediately (caller tags artifacts with\n// them); approval flips the flag and populates the fingerprint index so\n// the next similar message hits the cache.\n\nexport interface ClassifyInput {\n text: string\n channel?: string\n sender?: string\n /** Agent that will RECEIVE the message after classification. When this\n * equals `draftAgent`, classification is skipped to prevent a deadlock —\n * the classifier's sub-task would otherwise queue behind the in-progress\n * main task on the same agent. */\n agentId?: string\n}\n\nexport interface ClassifyResult {\n /** Node ids from root to leaf. Shorter-than-full paths are allowed. */\n path: string[]\n /** Stable hash of the path — used as the `graph:<pathId>` wiki tag. */\n pathId: string\n /** Human-readable \"A › B › C\" path for logs + UI. */\n pathLabel: string\n /** Axis values asserted by the classifier. Keyed by node id. */\n axes: Record<string, Record<string, string>>\n leaf: { input?: string; output?: string }\n source: \"cache\" | \"llm\"\n status: \"pending\" | \"approved\"\n confidence?: number\n}\n\nexport type ApprovalStructurePolicy = \"strict\" | \"extend-leaves\" | \"any\"\n\nexport interface ClassifierOptions {\n store: GraphStore\n /** Base URL of the daemon — used to POST /task for LLM proposals. */\n daemonUrl: string\n /** Optional bearer token for the daemon. */\n token?: string\n /** Which agent makes the proposal. Required if caller ever expects a\n * non-cached classification (cache hits work without an agent). */\n draftAgent?: string\n /** Structural approval policy — see config.ts for semantics. Default\n * \"extend-leaves\" (auto-approve pure reuse + single-leaf additions). */\n autoApproveStructure?: ApprovalStructurePolicy\n /** Minimum confidence (0..1) to commit without human approval. 1.0 = never.\n * OR'd with the structural policy. */\n autoApproveConfidence?: number\n /** Provider used for the in-process classifier call. Defaults to\n * \"claude-code\", which auto-detects auth via `loadAuthConfig()` —\n * Claude Max OAuth (CLI subprocess), API key, etc. NEVER opens a\n * paid path beyond what the operator has already configured. */\n classifierProvider?: ProviderName\n /** Model id passed to the provider. For claude-code OAuth this is\n * mapped through `CLI_MODEL_ALIASES` (sonnet/opus/haiku). Cheap\n * default — classification is metadata, not work. */\n classifierModel?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class Classifier {\n private store: GraphStore\n private daemonUrl: string\n private token?: string\n private draftAgent?: string\n private autoApprove: number\n private autoApproveStructure: ApprovalStructurePolicy\n private classifierModel: string\n private classifierProviderName: ProviderName\n private providerInstance?: AgentProvider\n private log: (...args: unknown[]) => void\n\n constructor(opts: ClassifierOptions) {\n this.store = opts.store\n this.daemonUrl = opts.daemonUrl.replace(/\\/+$/, \"\")\n this.token = opts.token\n this.draftAgent = opts.draftAgent\n this.autoApprove = opts.autoApproveConfidence ?? 1.0\n this.autoApproveStructure = opts.autoApproveStructure ?? \"extend-leaves\"\n this.classifierModel = opts.classifierModel ?? \"claude-haiku-4-5-20251001\"\n this.classifierProviderName = opts.classifierProvider ?? \"claude-code\"\n this.log = opts.log ?? console.error.bind(console, \"[classifier]\")\n }\n\n /** Lazily build the provider instance. Lazy because constructing\n * ClaudeCodeProvider hits auth-store; deferring it keeps the\n * classifier importable in tests / dry-run paths that never call\n * classify(). */\n private getProvider(): AgentProvider {\n if (!this.providerInstance) {\n this.providerInstance = createProvider(this.classifierProviderName)\n }\n return this.providerInstance\n }\n\n async classify(input: ClassifyInput): Promise<ClassifyResult | null> {\n const fp = this.store.fingerprint({\n text: input.text,\n channel: input.channel,\n sender: input.sender,\n })\n\n // 1. Cache hit — instant return, zero LLM. Logged to classifications.jsonl\n // so the cache-hit rate is measurable from the persisted record alone\n // (otherwise hits leave no trace and ROI of the classifier is invisible).\n const cached = this.store.getFingerprint(fp)\n if (cached) {\n this.store.appendClassification({\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n path: cached.path,\n proposedAxes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n preview: input.text.slice(0, 200),\n })\n return {\n path: cached.path,\n pathId: hashPath(cached.path),\n pathLabel: pathLabel(cached.path, this.store.loadNodes().nodes),\n axes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n }\n }\n\n // 2. Cache miss — need an LLM proposal. Phase 2 of classifier-retire\n // replaced the /task → graph-agent dispatch with a direct Anthropic\n // Messages call, so we no longer need a draftAgent and the\n // self-dispatch deadlock guard (`agentId === draftAgent`) is moot.\n // `graph.enabled` is the only gate now.\n const schema = this.store.loadSchema()\n const nodes = this.store.loadNodes().nodes\n const proposal = await this.proposePath(input, schema, nodes).catch((e) => {\n this.log(\"LLM proposal failed:\", e?.message || e)\n return null\n })\n if (!proposal) return null\n\n // 3. Validate + persist as pending. Any NEW node the LLM proposed is\n // only committed if the whole classification auto-approves below.\n const { path, proposedAxes, confidence, leaf } = proposal\n if (path.length === 0) return null\n\n const classification: Classification = {\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n path,\n proposedAxes,\n leaf,\n source: \"llm\",\n status: \"pending\",\n confidence,\n preview: input.text.slice(0, 200),\n }\n\n // --- Auto-approval policy (see config.ts for full semantics) ---\n //\n // Two conditions, OR'd. Either approves the classification:\n // 1. Structure policy — what KIND of change the proposed path makes:\n // strict: nothing auto — always pending\n // extend-leaves: auto if the path either (a) reuses only existing\n // nodes, or (b) adds exactly one new node at the\n // DEEPEST level (a new leaf). Structural changes\n // (new mid-path or root node) still queue.\n // any: auto regardless of structure.\n // 2. Confidence — LLM-self-reported confidence >= autoApproveConfidence.\n //\n // Pending classifications still persist to the log, but don't commit nodes\n // and don't populate the fingerprint cache (so similar messages re-query).\n const existingIds = new Set(nodes.map((n) => n.id))\n const newNodeIndices = path\n .map((id, idx) => (existingIds.has(id) ? -1 : idx))\n .filter((idx) => idx >= 0)\n\n let structureOk = false\n if (this.autoApproveStructure === \"any\") {\n structureOk = true\n } else if (this.autoApproveStructure === \"extend-leaves\") {\n const isReuseOnly = newNodeIndices.length === 0\n const isSingleLeafAddition =\n newNodeIndices.length === 1 && newNodeIndices[0] === path.length - 1\n structureOk = isReuseOnly || isSingleLeafAddition\n }\n const confidenceOk = confidence !== undefined && confidence >= this.autoApprove\n\n let status: \"pending\" | \"approved\" = \"pending\"\n if (structureOk || confidenceOk) {\n // Commit any new nodes; if ANY fail schema validation (missing axes,\n // bad ref, etc.) fall back to pending for the whole classification\n // rather than half-committing the graph and bubbling the error up to\n // the task handler.\n try {\n this.commitNewNodes(path, proposedAxes, schema, nodes, input.sender)\n this.store.setFingerprint(fp, { path, leaf })\n status = \"approved\"\n } catch (e: any) {\n this.log(\n `auto-approve failed — staying pending ·`,\n `path=${JSON.stringify(path)}`,\n `reason=${(e?.message || e).toString().slice(0, 200)}`,\n )\n }\n }\n classification.status = status\n this.store.appendClassification(classification)\n\n return {\n path,\n pathId: hashPath(path),\n pathLabel: pathLabel(path, this.store.loadNodes().nodes),\n axes: proposedAxes,\n leaf,\n source: \"llm\",\n status,\n confidence,\n }\n }\n\n /** Direct call to Anthropic to propose a path. Pure I/O; no side effects.\n * Bypasses the mesh / dispatch / ledger pipeline so a classification\n * doesn't surface as a sibling task in the activity graph. */\n private async proposePath(\n input: ClassifyInput,\n schema: GraphSchema,\n nodes: GraphNode[],\n ): Promise<{\n path: string[]\n proposedAxes: Record<string, Record<string, string>>\n confidence?: number\n leaf: { input?: string; output?: string }\n } | null> {\n const nodesForPrompt = nodes.map((n) => ({\n id: n.id,\n level: n.level,\n parentId: n.parentId,\n axes: n.axes,\n }))\n const userPrompt = [\n `You are the intent classifier for AgentX. You answer ONE question:`,\n `\"what kind of work does this message describe?\" — verb-level only.`,\n ``,\n `IMPORTANT — what NOT to encode in the path:`,\n `- Client / company / project / team names. Those are already on the event`,\n ` metadata (project, channel, agentId). Embedding them in the path causes`,\n ` duplicate nodes (one per client) for the same verb.`,\n `- The specific subject (issue number, file name, person name). Same reason.`,\n `- The agent's name or role. Pick the verb the *requester* intended.`,\n ``,\n `SCHEMA (fixed levels — pick one node per level):`,\n \"```json\",\n JSON.stringify(schema, null, 2),\n \"```\",\n ``,\n `EXISTING NODES (prefer reusing these; only invent a new node when no`,\n `existing one fits):`,\n \"```json\",\n JSON.stringify(nodesForPrompt, null, 2),\n \"```\",\n ``,\n `MESSAGE:`,\n \"```\",\n input.text.slice(0, 2000),\n \"```\",\n `channel: ${input.channel ?? \"?\"}`,\n `sender: ${input.sender ?? \"?\"}`,\n ``,\n `TASK:`,\n `1. Classify into 'category' (closed enum): code, ops, support, admin,`,\n ` knowledge, social, system. Pick the closest fit.`,\n `2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,`,\n ` e.g. \"review.merge-request\", \"deploy.staging\", \"investigate.error\",`,\n ` \"chat.greeting\", \"fix.bug\". Reuse an existing verb when the message is`,\n ` the same kind of work as something already classified, even when the`,\n ` client / project / subject differs.`,\n ``,\n `Examples (right vs wrong):`,\n `- \"Please review MR #957 on mtgl/system\" → [\"code\", \"review.merge-request\"]`,\n ` NOT [\"business\", \"noqta\", \"mtgl-v2\", \"review-mr-957-system\"]`,\n `- \"Deploy ksi-v2 to staging please\" → [\"ops\", \"deploy.staging\"]`,\n ` NOT [\"business\", \"noqta\", \"ksi-v2\", \"deploy-ksi-v2-to-staging\"]`,\n `- \"Hello Atlas\" → [\"support\", \"chat.greeting\"]`,\n ``,\n `NODE ID RULES (strict — invalid ids get dropped):`,\n `- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*`,\n `- no spaces, no uppercase, no Arabic/other non-Latin`,\n `- verb ids should use a \"category.specific\" or \"verb.modifier\" shape`,\n ``,\n `Return ONE JSON object on one line, no prose, no fences:`,\n ` { \"path\": string[], \"proposedAxes\": { [nodeId]: { [axisName]: string } }, \"leaf\": { \"input\"?: string, \"output\"?: string }, \"confidence\": number }`,\n `confidence in [0,1]. Prefer low confidence over guessing.`,\n ].join(\"\\n\")\n\n // Phase 2 of classifier-retire: in-process provider call.\n // Replaces the `/task → graph-agent → mesh` dispatch path that\n // generated a sibling ledger event per classification (60–70% of\n // dispatches in the activity graph). Reuses whatever auth the\n // operator already configured (Claude Max OAuth via CLI subprocess,\n // Anthropic API key, etc.) — never opens a paid path the operator\n // didn't already opt into. The provider returns a plain JSON-text\n // GenerationResult, so we parse the same way as before.\n const text = await this.callProvider(userPrompt)\n const parsed = extractJson(text)\n if (!parsed || !Array.isArray(parsed.path)) return null\n\n // LLM often returns human-readable labels (\"Business\", \"Sales Manager\").\n // The node-id schema requires a lowercase slug, so normalize here and\n // remember the original → slug mapping so proposedAxes stays attached.\n // We also drop any element that can't be slugified into a valid id (e.g.\n // a path entry of pure Arabic script) — better to classify with a shorter\n // path than to have the store reject the whole classification.\n const NODE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/\n const slugMap = new Map<string, string>()\n const path: string[] = parsed.path\n .filter((s: unknown): s is string => typeof s === \"string\" && s.length > 0)\n .map((s: string) => {\n const trimmed = s.trim()\n const slugged = slugifyNodeId(trimmed)\n if (trimmed !== slugged) slugMap.set(trimmed, slugged)\n return slugged\n })\n .filter((s: string) => NODE_ID_RE.test(s))\n if (path.length === 0) return null\n\n const proposedAxes: Record<string, Record<string, string>> = {}\n if (parsed.proposedAxes && typeof parsed.proposedAxes === \"object\") {\n for (const [rawNodeId, axes] of Object.entries(parsed.proposedAxes as Record<string, any>)) {\n if (!axes || typeof axes !== \"object\") continue\n const nodeId = slugMap.get(rawNodeId) ?? slugifyNodeId(rawNodeId)\n if (!nodeId) continue\n const clean: Record<string, string> = {}\n for (const [k, v] of Object.entries(axes)) {\n if (typeof v === \"string\") clean[k] = v\n }\n // If the LLM didn't volunteer a human-readable name but we slugified\n // one away, preserve the original as a `name` axis so the UI can\n // render \"Sales Manager\" not \"sales-manager\".\n if (!clean.name && rawNodeId !== nodeId) clean.name = rawNodeId.trim()\n proposedAxes[nodeId] = clean\n }\n }\n\n const leaf: { input?: string; output?: string } = {}\n if (parsed.leaf && typeof parsed.leaf === \"object\") {\n if (typeof parsed.leaf.input === \"string\") leaf.input = parsed.leaf.input\n if (typeof parsed.leaf.output === \"string\") leaf.output = parsed.leaf.output\n }\n\n const confidence = typeof parsed.confidence === \"number\"\n ? Math.max(0, Math.min(1, parsed.confidence))\n : undefined\n\n return { path, proposedAxes, confidence, leaf }\n }\n\n /** Create any nodes along `path` that don't exist yet. Delegates to the\n * store so level inference (for paths that skip levels) + fresh reloads\n * between adds are shared with the review path. */\n private commitNewNodes(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n _existing: GraphNode[],\n createdBy?: string,\n ): void {\n this.store.commitNodesAlongPath(path, proposedAxes, schema, createdBy)\n }\n\n /** Run the prompt through whichever AgentProvider the operator already\n * configured (Claude Max subscription via CLI, Anthropic API key, …).\n * Hard rule: NEVER open a paid path the operator didn't opt into —\n * this is just a thin wrapper that reuses existing auth. 30s timeout\n * is enough for Haiku/CLI cold-start; any longer and the classifier\n * is stalling the caller. */\n private async callProvider(userPrompt: string): Promise<string> {\n const provider = this.getProvider()\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), 30_000)\n try {\n const res = await provider.generate(\n [\n {\n role: \"system\",\n content:\n \"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences.\",\n },\n { role: \"user\", content: userPrompt },\n ],\n { model: this.classifierModel, maxTokens: 800 },\n )\n return res.content || \"\"\n } finally {\n clearTimeout(timer)\n }\n }\n}\n\n/** Normalize an LLM-proposed node label to a valid node id. Matches the\n * schema's `^[a-z0-9][a-z0-9._-]*$` — lowercase, alnum/./-/_, leading alnum.\n * Dots are preserved for verb ids like \"review.merge-request\". */\nfunction slugifyNodeId(raw: string): string {\n const s = raw\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^[-_]+|[-_]+$/g, \"\")\n .replace(/-{2,}/g, \"-\")\n if (!s) return \"\"\n // Ensure leading char is alnum (schema rejects leading _ or -).\n return /^[a-z0-9]/.test(s) ? s : `n-${s}`.replace(/[-_]+$/, \"\")\n}\n\n// --- path helpers used by both classifier + wiki retrieval ---\n\n/** Deterministic hash of a path, used as the `graph:<pathId>` wiki tag. */\nexport function hashPath(path: string[]): string {\n return createHash(\"sha1\")\n .update(path.join(\"\\u0001\"))\n .digest(\"hex\")\n .slice(0, 16)\n}\n\n/** \"Business › Noqta › DevOps › Review MR\" — for the UI + context render. */\nexport function pathLabel(path: string[], nodes: GraphNode[]): string {\n return path\n .map((id) => {\n const n = nodes.find((x) => x.id === id)\n if (!n) return id\n // Prefer a `name` axis when the schema has one; fall back to id.\n const name = n.axes?.name || n.axes?.what || id\n return name\n })\n .join(\" › \")\n}\n\n/**\n * Depth of the deepest common ancestor between two paths, normalized to [0,1].\n * Used to score wiki articles: exact match = 1, shared grandparent = 0.5, none = 0.\n */\nexport function ancestryScore(a: string[], b: string[]): number {\n if (a.length === 0 || b.length === 0) return 0\n let shared = 0\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n if (a[i] === b[i]) shared++\n else break\n }\n if (shared === 0) return 0\n const depth = Math.max(a.length, b.length)\n return shared / depth\n}\n\n// --- tiny JSON extractor (agents wrap output in fences sometimes) ---\n\nfunction extractJson(text: string): any {\n if (!text) return null\n try {\n return JSON.parse(text)\n } catch {\n /* fall through */\n }\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/)\n if (fenced) {\n try {\n return JSON.parse(fenced[1])\n } catch {\n /* fall through */\n }\n }\n // Last-ditch: grab the first balanced {...}.\n const start = text.indexOf(\"{\")\n if (start < 0) return null\n let depth = 0\n for (let i = start; i < text.length; i++) {\n if (text[i] === \"{\") depth++\n else if (text[i] === \"}\") {\n depth--\n if (depth === 0) {\n try {\n return JSON.parse(text.slice(start, i + 1))\n } catch {\n return null\n }\n }\n }\n }\n return null\n}\n","import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, readdirSync, statSync } from \"fs\"\nimport { resolve, join, relative, dirname } from \"path\"\nimport { isWikiArticleType } from \"./types\"\nimport type { WikiArticle, WikiArticleMeta, WikiEntry, WikiIndex, WikiAccess } from \"./types\"\nimport { buildIndex, buildIndexCached, scoreAll } from \"../memory/bm25\"\nimport { ancestryScore as ancestryOf } from \"@/graph\"\n\n// --- Wiki Store: filesystem-based knowledge base with permissions ---\n//\n// Structure:\n// .agentx/wiki/\n// _index.md # Master index with aliases\n// _backlinks.json # Reverse link index\n// raw/entries/ # One .md per ingested entry\n// {directories}/ # Articles organized by type\n//\n// Permission model:\n// - private: only owner agent reads/writes\n// - shared: owner writes, listed agents read\n// - public: owner writes, all agents read\n\nexport class WikiStore {\n readonly baseDir: string\n private rawDir: string\n private log: (...args: unknown[]) => void\n\n constructor(\n baseDir: string = resolve(process.cwd(), \".agentx/wiki\"),\n log: (...args: unknown[]) => void = console.error.bind(console, \"[wiki]\"),\n ) {\n this.baseDir = baseDir\n this.rawDir = resolve(baseDir, \"raw/entries\")\n this.log = log\n\n mkdirSync(this.rawDir, { recursive: true })\n mkdirSync(resolve(baseDir, \"raw\"), { recursive: true })\n\n // Initialize schema if missing\n this.ensureSchema()\n }\n\n // --- Permission checks ---\n\n /**\n * Check if an agent can read an article.\n */\n canRead(article: WikiArticleMeta, agentId?: string): boolean {\n if (article.access === \"public\") return true\n if (!agentId) return false\n if (article.owner === agentId) return true\n if (article.access === \"shared\" && article.sharedWith?.includes(agentId)) return true\n return false\n }\n\n /**\n * Check if an agent can write an article.\n */\n canWrite(article: WikiArticleMeta, agentId: string): boolean {\n return article.owner === agentId\n }\n\n // --- Raw entries (ingestion) ---\n\n /**\n * Add a raw entry (from a conversation, cron result, etc.)\n */\n addEntry(entry: WikiEntry): string {\n const filename = `${entry.date}_${entry.id}.md`\n const filepath = resolve(this.rawDir, filename)\n\n const frontmatter = [\n \"---\",\n `id: ${entry.id}`,\n `date: ${entry.date}`,\n `agent: ${entry.agentId}`,\n `source: ${entry.source}`,\n ]\n if (entry.sourceContext) frontmatter.push(`context: ${entry.sourceContext}`)\n if (entry.meta) {\n for (const [k, v] of Object.entries(entry.meta)) {\n frontmatter.push(`${k}: ${JSON.stringify(v)}`)\n }\n }\n frontmatter.push(\"---\", \"\", entry.content)\n\n writeFileSync(filepath, frontmatter.join(\"\\n\"))\n this.appendLog(\"ingest\", `${entry.id} from ${entry.agentId} via ${entry.source}`)\n return filename\n }\n\n /**\n * List raw entries, optionally filtered by agent or date range.\n */\n listEntries(filter?: { agentId?: string; after?: string; before?: string }): WikiEntry[] {\n if (!existsSync(this.rawDir)) return []\n\n const files = readdirSync(this.rawDir).filter(f => f.endsWith(\".md\")).sort()\n const entries: WikiEntry[] = []\n\n for (const file of files) {\n const content = readFileSync(resolve(this.rawDir, file), \"utf-8\")\n const entry = this.parseEntry(content, file)\n if (!entry) continue\n\n if (filter?.agentId && entry.agentId !== filter.agentId) continue\n if (filter?.after && entry.date < filter.after) continue\n if (filter?.before && entry.date > filter.before) continue\n\n entries.push(entry)\n }\n\n return entries\n }\n\n private parseEntry(content: string, filename: string): WikiEntry | null {\n const fmMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/)\n if (!fmMatch) return null\n\n const fm = fmMatch[1]\n const body = fmMatch[2].trim()\n\n const get = (key: string): string => {\n const m = fm.match(new RegExp(`^${key}:\\\\s*(.+)$`, \"m\"))\n return m?.[1]?.trim() || \"\"\n }\n\n return {\n id: get(\"id\") || filename.replace(\".md\", \"\"),\n date: get(\"date\"),\n agentId: get(\"agent\"),\n source: get(\"source\"),\n sourceContext: get(\"context\") || undefined,\n content: body,\n }\n }\n\n // --- Articles (compiled knowledge) ---\n\n /**\n * Write or update a wiki article.\n */\n writeArticle(\n path: string,\n meta: WikiArticleMeta,\n content: string,\n agentId: string,\n ): boolean {\n // LLM picks the path. We just write the file.\n const existing = this.readArticle(path)\n if (existing && !this.canWrite(existing.meta, agentId)) {\n this.log(`Permission denied: \"${agentId}\" cannot write \"${path}\" (owner: ${existing.meta.owner})`)\n return false\n }\n\n // Drop invalid `type` at write time. The parser already rejects invalid\n // values on read (returns type=undefined), so silently serialising a\n // stray value would cause the round-trip to look fine but the article\n // to render as \"untyped\" in the catalog. Log + drop beats surprise.\n if (meta.type && !isWikiArticleType(meta.type)) {\n this.log(`Dropping invalid article type \"${meta.type}\" for \"${path}\" — enum is [person, project, place, concept, event, decision, pattern]`)\n meta = { ...meta, type: undefined }\n }\n\n const fullPath = resolve(this.baseDir, path)\n\n // Version the previous content before overwriting (immutable history)\n if (existing) {\n this.saveVersion(path, fullPath)\n }\n\n mkdirSync(dirname(fullPath), { recursive: true })\n\n const frontmatter = [\n \"---\",\n `title: \"${meta.title}\"`,\n ]\n if (meta.type) frontmatter.push(`type: ${meta.type}`)\n if (meta.related?.length) {\n frontmatter.push(`related: [${meta.related.map(t => `\"${t}\"`).join(\", \")}]`)\n }\n frontmatter.push(\n `tags: [${(meta.tags || []).map(t => `\"${t}\"`).join(\", \")}]`,\n `owner: ${meta.owner}`,\n `access: ${meta.access}`,\n )\n if (meta.sharedWith?.length) {\n frontmatter.push(`shared_with: [${meta.sharedWith.map(s => `\"${s}\"`).join(\", \")}]`)\n }\n frontmatter.push(\n `created: ${meta.created}`,\n `last_updated: ${meta.lastUpdated}`,\n `sources: [${meta.sources.map(s => `\"${s}\"`).join(\", \")}]`,\n )\n if (meta.graphPath?.length) {\n frontmatter.push(`graph_path: [${meta.graphPath.map(s => `\"${s}\"`).join(\", \")}]`)\n }\n frontmatter.push(\"---\", \"\", content)\n\n writeFileSync(fullPath, frontmatter.join(\"\\n\"))\n const action = existing ? \"update\" : \"create\"\n this.appendLog(action, `${meta.title} [${(meta.tags || []).slice(0, 5).join(\", \")}] by ${agentId} at ${path}`)\n return true\n }\n\n // --- Article Versioning ---\n\n /**\n * Save current article content as an immutable version before overwriting.\n * Versions stored in _versions/{article-path}/{timestamp}.md\n */\n private saveVersion(articlePath: string, fullPath: string): void {\n try {\n if (!existsSync(fullPath)) return\n const content = readFileSync(fullPath, \"utf-8\")\n const ts = new Date().toISOString().replace(/[:.]/g, \"-\")\n const versionDir = resolve(this.baseDir, \"_versions\", articlePath.replace(/\\.md$/, \"\"))\n mkdirSync(versionDir, { recursive: true })\n writeFileSync(resolve(versionDir, `${ts}.md`), content)\n } catch {\n // Version save is best-effort\n }\n }\n\n /**\n * List all versions of an article (newest first).\n */\n getVersions(articlePath: string): Array<{ timestamp: string; path: string }> {\n const versionDir = resolve(this.baseDir, \"_versions\", articlePath.replace(/\\.md$/, \"\"))\n if (!existsSync(versionDir)) return []\n\n try {\n return readdirSync(versionDir)\n .filter(f => f.endsWith(\".md\"))\n .sort()\n .reverse()\n .map(f => ({\n timestamp: f.replace(/\\.md$/, \"\").replace(/-/g, (m, i) => i < 19 ? (i === 10 ? \"T\" : i === 13 || i === 16 ? \":\" : \"-\") : m),\n path: resolve(versionDir, f),\n }))\n } catch {\n return []\n }\n }\n\n /**\n * Restore a specific version of an article.\n */\n restoreVersion(articlePath: string, versionTimestamp: string): boolean {\n const versions = this.getVersions(articlePath)\n const version = versions.find(v => v.timestamp.startsWith(versionTimestamp))\n if (!version) return false\n\n try {\n const content = readFileSync(version.path, \"utf-8\")\n const fullPath = resolve(this.baseDir, articlePath)\n // Save current as a version too before restoring\n this.saveVersion(articlePath, fullPath)\n writeFileSync(fullPath, content)\n this.appendLog(\"restore\", `${articlePath} restored to ${versionTimestamp}`)\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Read an article. Returns null if not found.\n */\n readArticle(path: string): WikiArticle | null {\n const fullPath = resolve(this.baseDir, path)\n if (!existsSync(fullPath)) return null\n\n const raw = readFileSync(fullPath, \"utf-8\")\n return this.parseArticle(raw, path)\n }\n\n /**\n * Read an article with permission check.\n */\n readArticleAs(path: string, agentId: string): WikiArticle | null {\n const article = this.readArticle(path)\n if (!article) return null\n if (!this.canRead(article.meta, agentId)) return null\n return article\n }\n\n private parseArticle(raw: string, path: string): WikiArticle | null {\n const fmMatch = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/)\n if (!fmMatch) return null\n\n const fm = fmMatch[1]\n const content = fmMatch[2].trim()\n\n const get = (key: string): string => {\n const m = fm.match(new RegExp(`^${key}:\\\\s*(.+)$`, \"m\"))\n return m?.[1]?.trim().replace(/^\"(.*)\"$/, \"$1\") || \"\"\n }\n\n const getArray = (key: string): string[] => {\n const m = fm.match(new RegExp(`^${key}:\\\\s*\\\\[(.*)\\\\]$`, \"m\"))\n if (!m) return []\n return m[1].split(\",\").map(s => s.trim().replace(/^\"(.*)\"$/, \"$1\")).filter(Boolean)\n }\n\n // `type` and `kind` are the new structural spine; no longer stuffed into tags.\n // Legacy articles may have a `kind:` field; we promote it to `type` on read so\n // the migration (Phase 2) can patch it in place.\n const rawType = get(\"type\") || get(\"kind\")\n const validTypes = new Set([\n \"person\", \"project\", \"place\", \"concept\", \"event\", \"decision\", \"pattern\",\n ])\n const type = validTypes.has(rawType) ? (rawType as WikiArticleMeta[\"type\"]) : undefined\n\n const tags = getArray(\"tags\").filter((v, i, a) => v && a.indexOf(v) === i)\n const related = getArray(\"related\")\n const graphPath = getArray(\"graph_path\")\n return {\n meta: {\n title: get(\"title\"),\n type,\n related: related.length ? related : undefined,\n tags,\n owner: get(\"owner\"),\n access: (get(\"access\") as WikiAccess) || \"public\",\n sharedWith: getArray(\"shared_with\"),\n created: get(\"created\"),\n lastUpdated: get(\"last_updated\"),\n sources: getArray(\"sources\"),\n graphPath: graphPath.length ? graphPath : undefined,\n },\n content,\n path,\n }\n }\n\n // --- Index and discovery ---\n\n /**\n * List all articles accessible to an agent.\n */\n listArticles(agentId: string): WikiArticle[] {\n const articles: WikiArticle[] = []\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (article && this.canRead(article.meta, agentId)) {\n articles.push(article)\n }\n })\n return articles\n }\n\n /**\n * Search articles by keyword. Returns articles the agent can read.\n */\n search(query: string, agentId: string, maxResults: number = 10): WikiArticle[] {\n const queryLower = query.toLowerCase()\n const articles: WikiArticle[] = []\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (!article || !this.canRead(article.meta, agentId)) return\n articles.push(article)\n })\n\n if (articles.length === 0) return []\n\n // BM25 over article content (cached to disk)\n const docs = articles.map((a) => a.content)\n const cachePath = resolve(this.baseDir, \"_bm25_cache.json\")\n const index = buildIndexCached(docs, cachePath)\n const bm25Scores = scoreAll(query, index)\n const scoreMap = new Map(bm25Scores.map((r) => [r.docIndex, r.score]))\n\n const results = articles.map((article, i) => {\n let score = scoreMap.get(i) ?? 0\n // Additive bonuses for title and tag matches\n if (article.meta.title.toLowerCase().includes(queryLower)) score += 10\n if (article.meta.tags?.some(t => t.toLowerCase().includes(queryLower))) score += 5\n return { article, score }\n })\n\n return results\n .filter((r) => r.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults)\n .map((r) => r.article)\n }\n\n /**\n * Find articles relevant to a message (for context injection).\n *\n * Scoring:\n * - BM25 over title+tags+content (always)\n * - + ancestry bonus if the caller classified the message through the\n * intent graph and articles carry `graphPath` from a previous classification\n *\n * `weights.graph` is scaled against `weights.bm25`. When the caller omits\n * `messagePath`, retrieval collapses to pure BM25 and matches legacy behavior.\n */\n findRelevant(\n message: string,\n agentId?: string,\n maxArticles: number = 3,\n messagePath?: string[],\n weights: { graph: number; bm25: number } = { graph: 0.6, bm25: 0.4 },\n ): WikiArticle[] {\n const articles: WikiArticle[] = []\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (!article || !this.canRead(article.meta, agentId)) return\n articles.push(article)\n })\n\n if (articles.length === 0) return []\n\n const docs = articles.map(\n (a) => `${a.meta.title} ${(a.meta.tags || []).join(\" \")} ${a.content}`,\n )\n const cachePath = resolve(this.baseDir, \"_bm25_relevance_cache.json\")\n const index = buildIndexCached(docs, cachePath)\n const bm25Scores = scoreAll(message, index)\n\n // Normalize BM25 so the two signals live on [0,1] before the weighted sum.\n const maxBm25 = bm25Scores.reduce((m, s) => Math.max(m, s.score), 0) || 1\n const bm25Map = new Map(bm25Scores.map((s) => [s.docIndex, s.score / maxBm25]))\n\n const hasGraph = !!(messagePath && messagePath.length > 0)\n const scored = articles.map((article, i) => {\n const bm25 = bm25Map.get(i) ?? 0\n let ancestry = 0\n if (hasGraph && article.meta.graphPath?.length) {\n ancestry = ancestryOf(messagePath!, article.meta.graphPath)\n }\n // When the caller passed no path, BM25 gets the full weight.\n const combined = hasGraph\n ? weights.graph * ancestry + weights.bm25 * bm25\n : bm25\n return { article, score: combined }\n })\n\n return scored\n .filter((r) => r.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, maxArticles)\n .map((r) => r.article)\n }\n\n /**\n * Build context string from relevant wiki articles for prompt injection.\n * Token-efficient: includes title + first ~500 chars of each article.\n */\n buildContext(articles: WikiArticle[], maxChars: number = 4000): string {\n if (articles.length === 0) return \"\"\n\n const lines: string[] = [\"[Wiki Knowledge]\"]\n\n let totalChars = 0\n for (const article of articles) {\n const header = `\\n## ${article.meta.title} [${(article.meta.tags || []).slice(0, 3).join(\", \")}]`\n const body = article.content.length > 600\n ? article.content.slice(0, 600) + \"...\"\n : article.content\n\n const chunk = header + \"\\n\" + body\n if (totalChars + chunk.length > maxChars) break\n\n lines.push(chunk)\n totalChars += chunk.length\n }\n\n lines.push(\"\\n[End Wiki Knowledge]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Rebuild the master index.\n */\n rebuildIndex(): WikiIndex {\n const articles: WikiIndex[\"articles\"] = []\n const backlinks = new Map<string, number>()\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (!article) return\n\n // Count wikilinks for backlinks\n const links = article.content.match(/\\[\\[([^\\]]+)\\]\\]/g) || []\n for (const link of links) {\n const target = link.replace(/\\[\\[|\\]\\]/g, \"\")\n backlinks.set(target, (backlinks.get(target) || 0) + 1)\n }\n\n // Extract aliases from title and tags\n const aliases = [article.meta.title.toLowerCase()]\n if (article.meta.tags) {\n aliases.push(...article.meta.tags.map(t => t.toLowerCase()))\n }\n\n articles.push({\n path: relPath,\n title: article.meta.title,\n type: article.meta.type,\n related: article.meta.related,\n tags: article.meta.tags || [],\n owner: article.meta.owner,\n access: article.meta.access,\n sharedWith: article.meta.sharedWith,\n aliases,\n backlinks: backlinks.get(article.meta.title) || 0,\n sources: article.meta.sources,\n lastUpdated: article.meta.lastUpdated,\n })\n })\n\n const index: WikiIndex = {\n articles,\n lastRebuilt: new Date().toISOString(),\n }\n\n // Machine-readable index\n writeFileSync(\n resolve(this.baseDir, \"_index.json\"),\n JSON.stringify(index, null, 2),\n )\n\n // Farzapedia-style content catalog at _index.md — the one file the\n // agentic query reads to pick candidate articles by `type` + title.\n // Load-bearing in Phase 3; grouped by type, alphabetical within group.\n this.writeCatalog(articles, index.lastRebuilt)\n\n // Human-readable WIKI.md — legacy view, grouped by access level.\n const md = [\"# Wiki Index\", \"\", `Last rebuilt: ${index.lastRebuilt}`, \"\"]\n const byAccess = new Map<string, typeof articles>()\n for (const a of articles) {\n const bucket = a.access || \"public\"\n const list = byAccess.get(bucket) || []\n list.push(a)\n byAccess.set(bucket, list)\n }\n for (const [bucket, list] of Array.from(byAccess.entries()).sort()) {\n md.push(`## ${bucket}`, \"\")\n for (const a of list.sort((x, y) => x.title.localeCompare(y.title))) {\n const t = a.type ? ` [${a.type}]` : \"\"\n md.push(`- [${a.title}](${a.path})${t} — owner: ${a.owner}`)\n }\n md.push(\"\")\n }\n writeFileSync(resolve(this.baseDir, \"WIKI.md\"), md.join(\"\\n\"))\n\n this.log(`Index rebuilt: ${articles.length} articles`)\n this.appendLog(\"rebuild-index\", `${articles.length} articles indexed`)\n return index\n }\n\n /**\n * Write `_index.md` — the Farzapedia-style content catalog grouped by\n * article `type`. This is the one file the agentic query (Phase 3) reads\n * to pick candidate articles before walking the wikilink subgraph.\n *\n * Articles without a `type` land in \"Untyped\" at the bottom; the Phase 2\n * migration backfills those.\n */\n private writeCatalog(\n articles: WikiIndex[\"articles\"],\n lastRebuilt: string,\n ): void {\n const typeOrder: Array<NonNullable<WikiArticleMeta[\"type\"]> | \"untyped\"> = [\n \"person\", \"project\", \"place\", \"concept\", \"event\", \"decision\", \"pattern\", \"untyped\",\n ]\n const typeHeaders: Record<string, string> = {\n person: \"## People\",\n project: \"## Projects\",\n place: \"## Places\",\n concept: \"## Concepts\",\n event: \"## Events\",\n decision: \"## Decisions\",\n pattern: \"## Patterns\",\n untyped: \"## Untyped (needs migration)\",\n }\n\n const byType = new Map<string, typeof articles>()\n for (const a of articles) {\n const bucket = a.type || \"untyped\"\n const list = byType.get(bucket) || []\n list.push(a)\n byType.set(bucket, list)\n }\n\n const lines: string[] = [\n \"# Wiki Content Catalog\",\n \"\",\n `_Last rebuilt: ${lastRebuilt}_`,\n \"\",\n \"Articles grouped by type. Agentic query walks this file first to pick candidates.\",\n \"\",\n ]\n\n for (const type of typeOrder) {\n const list = byType.get(type)\n if (!list || list.length === 0) continue\n lines.push(typeHeaders[type] || `## ${type}`, \"\")\n for (const a of list.sort((x, y) => x.title.localeCompare(y.title))) {\n const access = a.access === \"private\" ? \" (private)\" : a.access === \"shared\" ? \" (shared)\" : \"\"\n const related = a.related?.length ? ` → [[${a.related.slice(0, 3).join(\"]], [[\")}]]${a.related.length > 3 ? \", …\" : \"\"}` : \"\"\n lines.push(`- [${a.title}](${a.path})${access}${related}`)\n }\n lines.push(\"\")\n }\n\n writeFileSync(resolve(this.baseDir, \"_index.md\"), lines.join(\"\\n\"))\n }\n\n /**\n * Get wiki stats.\n */\n stats(): {\n totalArticles: number\n totalEntries: number\n articlesByType: Record<string, number>\n articlesByAccess: Record<string, number>\n articlesByOwner: Record<string, number>\n } {\n let totalArticles = 0\n const byType: Record<string, number> = {}\n const byAccess: Record<string, number> = {}\n const byOwner: Record<string, number> = {}\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\") || relPath === \"WIKI.md\") return\n\n const article = this.readArticle(relPath)\n if (!article) return\n\n totalArticles++\n for (const tag of (article.meta.tags || [])) {\n byType[tag] = (byType[tag] || 0) + 1\n }\n byAccess[article.meta.access] = (byAccess[article.meta.access] || 0) + 1\n byOwner[article.meta.owner] = (byOwner[article.meta.owner] || 0) + 1\n })\n\n const entryFiles = existsSync(this.rawDir)\n ? readdirSync(this.rawDir).filter(f => f.endsWith(\".md\")).length\n : 0\n\n return {\n totalArticles,\n totalEntries: entryFiles,\n articlesByType: byType,\n articlesByAccess: byAccess,\n articlesByOwner: byOwner,\n }\n }\n\n // --- Helpers ---\n\n // --- Log (chronological, append-only) ---\n\n /**\n * Append an entry to log.md.\n * Format: ## [YYYY-MM-DD HH:MM] action | details\n */\n appendLog(action: string, details: string): void {\n const logPath = resolve(this.baseDir, \"log.md\")\n const timestamp = new Date().toISOString().replace(\"T\", \" \").slice(0, 16)\n const entry = `## [${timestamp}] ${action} | ${details}\\n\\n`\n\n if (!existsSync(logPath)) {\n writeFileSync(logPath, \"# Wiki Log\\n\\nChronological record of all wiki operations.\\n\\n\")\n }\n\n appendFileSync(logPath, entry)\n }\n\n /**\n * Get recent log entries.\n */\n getLog(limit: number = 20): string[] {\n const logPath = resolve(this.baseDir, \"log.md\")\n if (!existsSync(logPath)) return []\n\n const content = readFileSync(logPath, \"utf-8\")\n const entries = content.split(/^## /m).filter(e => e.startsWith(\"[\")).slice(-limit)\n return entries.map(e => e.trim())\n }\n\n // --- Schema ---\n\n /**\n * Create _schema.md if it doesn't exist.\n * This tells the LLM how to operate on the wiki.\n */\n private ensureSchema(): void {\n const schemaPath = resolve(this.baseDir, \"_schema.md\")\n if (existsSync(schemaPath)) return\n\n const schema = `# Wiki Schema\n\nA personal wiki maintained by LLMs. Karpathy pattern: plain files, aggressive tagging,\nstructure emerges from data. The wiki is a compounding artifact.\n\n## Structure\n\n\\`\\`\\`\nwiki/\n _schema.md # This file\n worldview.md # YOUR mental model — the LLM reads this during absorb\n raw/entries/ # Immutable raw sources (conversations, imports, anything)\n [LLM-chosen dirs] # Structure emerges from data — the LLM picks paths\n\\`\\`\\`\n\nThe LLM decides how to organize articles. You describe YOUR world in worldview.md.\n\n## Article Format\n\n\\`\\`\\`yaml\n---\ntitle: \"Staging Deployment Process\"\ntags: [\"deploy\", \"staging\", \"devops\", \"process\", \"2026-04-06\"]\nowner: devops-agent\naccess: public\ncreated: 2026-04-06\nlast_updated: 2026-04-06\nsources: [\"entry-id-1\", \"entry-id-2\"]\n---\n\nArticle content with [[wikilinks]] and section tags.\n\n## Deploy Steps\n<!-- tags: process, runbook, staging -->\nContent specific to this section...\n\\`\\`\\`\n\n## Tags\n\nTags are the primary context-narrowing mechanism. The more tags, the better.\n\nTag aggressively:\n- **Who**: people, agents, teams involved\n- **What**: project, client, topic, technology\n- **When**: dates, periods, milestones\n- **Where**: server, environment, channel\n- **How**: process, decision, pattern\n- **Your terms**: whatever vocabulary makes sense in YOUR world\n\nSection tags (\\`<!-- tags: ... -->\\`) let different parts of an article\nmatch different queries. A deploy article might have sections tagged\n\"runbook\" and \"incident\" separately.\n\n## Operations\n\n### Absorb\nRead raw entries → read worldview.md → tag aggressively → create/update articles.\nAlso identify GAPS: \"We mention X but have no article for it.\"\n\n### Query\nMatch by tags first → then keyword within matched articles → synthesize.\nIf agent is working on a specific project, only send content tagged for that project.\n\n### Lint\nCheck for: orphan articles, broken [[wikilinks]], untagged articles,\narticles over 100 lines that should split, mentioned-but-missing topics.\n\n## Worldview\n\nEdit \\`worldview.md\\` to describe YOUR world. This is not a schema — it's your\nmental model. The LLM reads it during absorb to understand where things go.\n\nExample worldview.md:\n\\`\\`\\`\nI'm the founder of Acme Corp.\nWe build software products.\nOur agents: Marketing Bot, DevOps Bot, Coordinator.\nClients: ClientA (enterprise), ClientB (startup).\nInfrastructure: local dev machine, cloud server.\nWe use GitLab for version control.\n\\`\\`\\`\n`\n\n writeFileSync(schemaPath, schema)\n this.log(\"Created _schema.md\")\n }\n\n // --- Wikilinks and Backlinks ---\n\n /**\n * Extract all [[wikilinks]] from article content.\n */\n extractWikilinks(content: string): string[] {\n const matches = content.match(/\\[\\[([^\\]]+)\\]\\]/g) || []\n return matches.map(m => m.replace(/\\[\\[|\\]\\]/g, \"\"))\n }\n\n /**\n * Build backlinks index: for each article, which other articles link to it.\n */\n buildBacklinks(): Record<string, string[]> {\n const backlinks: Record<string, string[]> = {}\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\") || relPath === \"WIKI.md\" || relPath === \"log.md\") return\n\n const content = readFileSync(filePath, \"utf-8\")\n const links = this.extractWikilinks(content)\n\n for (const link of links) {\n if (!backlinks[link]) backlinks[link] = []\n backlinks[link].push(relPath)\n }\n })\n\n // Save\n writeFileSync(\n resolve(this.baseDir, \"_backlinks.json\"),\n JSON.stringify(backlinks, null, 2),\n )\n\n return backlinks\n }\n\n /**\n * Get backlinks for a specific article title.\n */\n getBacklinks(title: string): string[] {\n const path = resolve(this.baseDir, \"_backlinks.json\")\n if (!existsSync(path)) return []\n try {\n const data = JSON.parse(readFileSync(path, \"utf-8\"))\n return data[title] || []\n } catch {\n return []\n }\n }\n\n // --- Tag-based filtering (Karpathy: tags are the context narrowing mechanism) ---\n\n /**\n * Find articles matching ANY of the given tags.\n * This is how agents get relevant context — only articles tagged with\n * the client/project/topic they're working on.\n */\n findByTags(tags: string[], agentId?: string, limit: number = 10): WikiArticle[] {\n const lowerTags = tags.map(t => t.toLowerCase())\n const results: Array<{ article: WikiArticle; score: number }> = []\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (!article) return\n if (agentId && !this.canRead(article.meta, agentId)) return\n\n // Score: how many of the requested tags match?\n const articleTags = (article.meta.tags || []).map(t => t.toLowerCase())\n // Also check section tags: <!-- tags: foo, bar -->\n const sectionTags = (article.content.match(/<!--\\s*tags?:\\s*([^>]+)\\s*-->/gi) || [])\n .flatMap(m => m.replace(/<!--\\s*tags?:\\s*|\\s*-->/gi, \"\").split(\",\").map(t => t.trim().toLowerCase()))\n\n const allTags = [...articleTags, ...sectionTags]\n const score = lowerTags.filter(t => allTags.includes(t)).length\n\n if (score > 0) {\n results.push({ article, score })\n }\n })\n\n return results\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(r => r.article)\n }\n\n /**\n * Get all unique tags across all articles.\n */\n getAllTags(): Map<string, number> {\n const tagCounts = new Map<string, number>()\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (!article?.meta.tags) return\n\n for (const tag of article.meta.tags) {\n tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1)\n }\n })\n\n return tagCounts\n }\n\n // --- Worldview (user's mental model — input to absorb) ---\n\n /**\n * Get the worldview file. This is the user's description of their world —\n * the LLM uses it during absorb to know where things go.\n * Returns null if not set.\n */\n getWorldview(): string | null {\n const path = resolve(this.baseDir, \"worldview.md\")\n if (!existsSync(path)) return null\n return readFileSync(path, \"utf-8\")\n }\n\n /**\n * Set the worldview. The user describes their world: companies, clients,\n * projects, team, processes. The LLM uses this as context during absorb.\n */\n setWorldview(content: string): void {\n writeFileSync(resolve(this.baseDir, \"worldview.md\"), content)\n this.appendLog(\"worldview\", \"Updated worldview\")\n }\n\n // --- Lint ---\n\n /**\n * Run a lint pass on the wiki. Returns issues found.\n */\n lint(): Array<{ type: string; article: string; message: string }> {\n const issues: Array<{ type: string; article: string; message: string }> = []\n const allTitles = new Set<string>()\n const allLinks = new Map<string, string[]>() // article -> outbound links\n const articlePaths = new Map<string, string>() // title -> path\n\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\") || relPath === \"WIKI.md\" || relPath === \"log.md\") return\n\n const article = this.readArticle(relPath)\n if (!article) return\n\n allTitles.add(article.meta.title)\n articlePaths.set(article.meta.title, relPath)\n\n const links = this.extractWikilinks(article.content)\n allLinks.set(relPath, links)\n\n // Check: article too long\n const lines = article.content.split(\"\\n\").length\n if (lines > 100) {\n issues.push({\n type: \"bloated\",\n article: relPath,\n message: `${lines} lines — consider splitting`,\n })\n }\n\n // Check: no sources\n if (!article.meta.sources?.length) {\n issues.push({\n type: \"unsourced\",\n article: relPath,\n message: \"No sources listed in frontmatter\",\n })\n }\n\n // Check: stub (too short)\n if (lines < 10 && article.content.trim().length < 100) {\n issues.push({\n type: \"stub\",\n article: relPath,\n message: \"Very short article — needs enrichment\",\n })\n }\n })\n\n // Check: broken wikilinks\n for (const [articlePath, links] of allLinks) {\n for (const link of links) {\n if (!allTitles.has(link)) {\n issues.push({\n type: \"broken-link\",\n article: articlePath,\n message: `[[${link}]] — target article not found`,\n })\n }\n }\n }\n\n // Check: orphan articles (no inbound links)\n const backlinks = this.buildBacklinks()\n for (const [title, path] of articlePaths) {\n if (!backlinks[title] || backlinks[title].length === 0) {\n issues.push({\n type: \"orphan\",\n article: path,\n message: `No other articles link to \"${title}\"`,\n })\n }\n }\n\n this.appendLog(\"lint\", `Found ${issues.length} issues`)\n return issues\n }\n\n // --- Absorb summary (for automated cron) ---\n\n /**\n * Get unabsorbed entries (entries not referenced by any article's sources field).\n */\n getUnabsorbedEntries(): WikiEntry[] {\n const allSources = new Set<string>()\n\n // Collect all source IDs referenced by articles\n this.walkDir(this.baseDir, (filePath) => {\n if (!filePath.endsWith(\".md\")) return\n const relPath = relative(this.baseDir, filePath)\n if (relPath.startsWith(\"raw/\") || relPath.startsWith(\"_\")) return\n\n const article = this.readArticle(relPath)\n if (article?.meta.sources) {\n for (const s of article.meta.sources) allSources.add(s)\n }\n })\n\n // Find entries not in any article's sources\n const entries = this.listEntries()\n return entries.filter(e => !allSources.has(e.id))\n }\n\n /**\n * Build an absorb prompt for unprocessed entries.\n * Returns a prompt string an agent can execute to compile entries into articles.\n */\n buildAbsorbPrompt(maxEntries: number = 20): string | null {\n const unabsorbed = this.getUnabsorbedEntries().slice(0, maxEntries)\n if (unabsorbed.length === 0) return null\n\n const entrySummaries = unabsorbed.map(e =>\n `[${e.date} ${e.agentId} via ${e.source}] ${e.content.slice(0, 200)}`\n ).join(\"\\n\\n\")\n\n return `/wiki absorb\\n\\nThere are ${unabsorbed.length} unprocessed entries. Read each, understand what it means, and create or update wiki articles.\\n\\n${entrySummaries}`\n }\n\n private walkDir(dir: string, callback: (filePath: string) => void): void {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const stat = statSync(full)\n if (stat.isDirectory()) {\n this.walkDir(full, callback)\n } else {\n callback(full)\n }\n }\n }\n}\n","import { WikiStore } from \"./store\"\nimport { resolve } from \"path\"\nimport { existsSync, readdirSync, mkdirSync } from \"fs\"\nimport type { WikiEntry } from \"./types\"\n\nexport type WikiMode = \"flat\" | \"graph\" | \"unified\"\n\n/**\n * WikiHub: manages per-agent wikis with a shared raw entry pool.\n * Supports two compilation modes:\n * - flat: Karpathy pattern — tags, LLM-chosen paths, worldview, gap detection\n * - graph: Knowledge graph — kind, parent, hierarchy, events, entities\n *\n * Both modes share the same raw entries. Articles stored separately:\n * agents/<id>/flat/ ← Karpathy compilation\n * agents/<id>/graph/ ← Knowledge graph compilation\n */\nexport class WikiHub {\n private baseDir: string\n private agentsDir: string\n private mode: WikiMode\n private sharedStore: WikiStore\n private agentStores: Map<string, WikiStore> = new Map()\n private log: (...args: unknown[]) => void\n\n constructor(\n baseDir: string = resolve(process.cwd(), \".agentx/wiki\"),\n log: (...args: unknown[]) => void = console.error.bind(console, \"[wiki-hub]\"),\n mode: WikiMode = \"graph\",\n ) {\n this.baseDir = baseDir\n this.agentsDir = resolve(baseDir, \"agents\")\n this.mode = mode\n this.log = log\n\n mkdirSync(this.agentsDir, { recursive: true })\n this.sharedStore = new WikiStore(baseDir, log)\n }\n\n getMode(): WikiMode { return this.mode }\n\n /**\n * Get or create the wiki store for a specific agent (mode-aware).\n * flat: agents/<id>/flat/\n * graph: agents/<id>/graph/\n */\n getAgentWiki(agentId: string): WikiStore {\n const key = `${agentId}:${this.mode}`\n if (this.agentStores.has(key)) return this.agentStores.get(key)!\n\n const agentDir = resolve(this.agentsDir, agentId, this.mode)\n const store = new WikiStore(agentDir, this.log)\n this.agentStores.set(key, store)\n return store\n }\n\n getSharedStore(): WikiStore {\n return this.sharedStore\n }\n\n listAgents(): string[] {\n const agents = new Set<string>()\n\n const entries = this.sharedStore.listEntries()\n for (const e of entries) agents.add(e.agentId)\n\n if (existsSync(this.agentsDir)) {\n for (const dir of readdirSync(this.agentsDir)) {\n if (!dir.startsWith(\"_\") && !dir.startsWith(\".\")) {\n agents.add(dir)\n }\n }\n }\n\n return [...agents].sort()\n }\n\n getAgentEntries(agentId: string): WikiEntry[] {\n return this.sharedStore.listEntries({ agentId })\n }\n\n getUnabsorbedEntries(agentId: string): WikiEntry[] {\n const agentWiki = this.getAgentWiki(agentId)\n const agentEntries = this.getAgentEntries(agentId)\n\n const absorbedIds = new Set<string>()\n const index = agentWiki.rebuildIndex()\n for (const article of index.articles) {\n if (article.sources) {\n for (const s of article.sources) absorbedIds.add(s)\n }\n }\n\n return agentEntries.filter(e => !absorbedIds.has(e.id))\n }\n\n summary(): AgentWikiSummary[] {\n const agents = this.listAgents()\n return agents.map(agentId => {\n const entries = this.getAgentEntries(agentId)\n const wiki = this.getAgentWiki(agentId)\n const index = wiki.rebuildIndex()\n const unabsorbed = this.getUnabsorbedEntries(agentId)\n\n return {\n agentId,\n totalEntries: entries.length,\n totalArticles: index.articles.length,\n unabsorbed: unabsorbed.length,\n articles: index.articles,\n }\n })\n }\n}\n\nexport interface AgentWikiSummary {\n agentId: string\n totalEntries: number\n totalArticles: number\n unabsorbed: number\n articles: Array<{ title: string; path: string; tags?: string[] }>\n}\n"],"mappings":"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","names":["STARTER_SCHEMA","STARTER_VERB_NODES","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","renameSync","resolve","dirname","createHash","GraphStore","opts","resolve","mkdirSync","p","existsSync","STARTER_SCHEMA","raw","readFileSync","parsed","graphSchemaSchema","i","backup","writeFileSync","e","np","nbackup","seeded","seedNodesFile","schema","safeWriteJson","nodesFileSchema","file","node","all","level","l","missing","a","v","n","target","levelIdx","parent","parentLevelIdx","path","proposedAxes","createdBy","added","skipped","now","id","current","cursor","j","priorId","priorNode","axes","chosenLevel","parentId","chosenIdx","prior","priorIdx","c","classificationSchema","issues","line","dirname","appendFileSync","limit","status","lines","byHash","b","msgHash","update","next","suffix","out","msg","payload","createHash","indexFileSchema","fp","entry","updated","child","refHolder","known","data","tmp","renameSync","nodes","seenCats","STARTER_VERB_NODES","createHash","Classifier","opts","createProvider","input","fp","cached","hashPath","pathLabel","schema","nodes","proposal","e","path","proposedAxes","confidence","leaf","classification","existingIds","n","newNodeIndices","id","idx","structureOk","isReuseOnly","isSingleLeafAddition","confidenceOk","status","nodesForPrompt","userPrompt","text","parsed","extractJson","NODE_ID_RE","slugMap","s","trimmed","slugged","slugifyNodeId","rawNodeId","axes","nodeId","clean","k","v","_existing","createdBy","provider","ac","timer","raw","createHash","x","ancestryScore","a","b","shared","i","depth","fenced","start","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","readdirSync","statSync","resolve","join","relative","dirname","WikiStore","baseDir","resolve","log","mkdirSync","article","agentId","entry","filename","filepath","frontmatter","k","v","writeFileSync","filter","existsSync","files","readdirSync","f","entries","file","content","readFileSync","fmMatch","fm","body","get","key","path","meta","existing","isWikiArticleType","fullPath","dirname","t","s","action","articlePath","ts","versionDir","m","i","versionTimestamp","version","raw","getArray","rawType","type","tags","a","related","graphPath","articles","filePath","relPath","relative","query","maxResults","queryLower","docs","cachePath","index","buildIndexCached","bm25Scores","scoreAll","scoreMap","r","score","b","message","maxArticles","messagePath","weights","maxBm25","bm25Map","hasGraph","bm25","ancestry","ancestryScore","combined","maxChars","lines","totalChars","header","chunk","backlinks","links","link","target","aliases","md","byAccess","bucket","list","x","y","lastRebuilt","typeOrder","typeHeaders","byType","access","totalArticles","byOwner","tag","entryFiles","details","logPath","appendFileSync","limit","e","schemaPath","title","lowerTags","results","articleTags","sectionTags","allTags","tagCounts","issues","allTitles","allLinks","articlePaths","allSources","maxEntries","unabsorbed","entrySummaries","dir","callback","full","join","statSync","resolve","existsSync","readdirSync","mkdirSync","WikiHub","baseDir","log","mode","WikiStore","agentId","key","agentDir","store","agents","entries","e","dir","agentWiki","agentEntries","absorbedIds","index","article","s","unabsorbed"]}
@@ -0,0 +1,3 @@
1
+ import{readFileSync as a,existsSync as g}from"fs";import{resolve as p}from"path";function d(s){let e=[];for(let i of s.split(`
2
+ `)){let t=i.trim();if(!t||t.startsWith("#"))continue;let r=t.startsWith("export ")?t.slice(7).trim():t,o=r.indexOf("=");if(o===-1)continue;let c=r.slice(0,o).trim(),n=r.slice(o+1).trim();n.length>=2&&(n.startsWith('"')&&n.endsWith('"')||n.startsWith("'")&&n.endsWith("'"))&&(n=n.slice(1,-1)),c&&e.push([c,n])}return e}function u(s){let e={};for(let i of[".env",".env.gitlab"]){let t=p(s,i);if(g(t))try{for(let[r,o]of d(a(t,"utf-8")))e[r]=o}catch{}}return e}var f=new Map;function v(s=process.cwd()){let e=f.get(s);if(e)return e;let i=p(s,".env"),t;if(!g(i))t=new Set;else try{t=new Set(d(a(i,"utf-8")).map(([r])=>r))}catch{t=new Set}return f.set(s,t),t}function m(s,e=process.env,i=process.cwd()){let t=u(s),r=v(i),o={...e};for(let c of r)c in t||delete o[c];for(let[c,n]of Object.entries(t))o[c]=n;return o}export{m as a};
3
+ //# sourceMappingURL=chunk-A6BKSZQA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/workspace-env.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// Loads per-agent secrets from the agent's workspace and tells the runtime\n// which keys the daemon's own .env injected, so we can strip those before\n// spawning the agent's claude process. Without this, every agent inherits\n// the daemon's GITLAB_TOKEN regardless of whether their workspace has its\n// own .env.gitlab — defeating per-agent identity boundaries (each agent\n// should commit/push as itself, not as the daemon's account).\n\n/**\n * Parse a dotenv-style file. Tolerates the `export KEY=VALUE` form used by\n * shell-sourced files like .env.gitlab on clawd-server.\n */\nfunction parseDotEnv(content: string): Array<[string, string]> {\n const out: Array<[string, string]> = []\n for (const raw of content.split(\"\\n\")) {\n const trimmed = raw.trim()\n if (!trimmed || trimmed.startsWith(\"#\")) continue\n const stripped = trimmed.startsWith(\"export \") ? trimmed.slice(7).trim() : trimmed\n const eq = stripped.indexOf(\"=\")\n if (eq === -1) continue\n const key = stripped.slice(0, eq).trim()\n let value = stripped.slice(eq + 1).trim()\n if (value.length >= 2 && ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\")))) {\n value = value.slice(1, -1)\n }\n if (key) out.push([key, value])\n }\n return out\n}\n\n/**\n * Read .env then .env.gitlab from a workspace and return their merged keys.\n * .env.gitlab wins on collision — it's the more specific per-channel file.\n */\nexport function loadWorkspaceEnv(workspace: string): Record<string, string> {\n const merged: Record<string, string> = {}\n for (const fname of [\".env\", \".env.gitlab\"]) {\n const path = resolve(workspace, fname)\n if (!existsSync(path)) continue\n try {\n for (const [k, v] of parseDotEnv(readFileSync(path, \"utf-8\"))) {\n merged[k] = v\n }\n } catch { /* best-effort */ }\n }\n return merged\n}\n\n/**\n * Keys defined in the daemon's own .env file, cached per daemonCwd. The\n * runtime uses this to strip those keys from the child env before adding\n * the workspace overrides — so a daemon-level GITLAB_TOKEN never leaks to\n * an agent that hasn't put one in its own workspace.\n */\nconst daemonEnvKeysCache = new Map<string, Set<string>>()\n\nexport function getDaemonEnvKeys(daemonCwd: string = process.cwd()): Set<string> {\n const cached = daemonEnvKeysCache.get(daemonCwd)\n if (cached) return cached\n const path = resolve(daemonCwd, \".env\")\n let keys: Set<string>\n if (!existsSync(path)) {\n keys = new Set()\n } else {\n try {\n keys = new Set(parseDotEnv(readFileSync(path, \"utf-8\")).map(([k]) => k))\n } catch {\n keys = new Set()\n }\n }\n daemonEnvKeysCache.set(daemonCwd, keys)\n return keys\n}\n\n/** Test-only: clear the cache so a different daemonCwd is re-read. */\nexport function _resetDaemonEnvKeysCache(): void {\n daemonEnvKeysCache.clear()\n}\n\n/**\n * Build the env object passed to a spawned agent process. Starts from\n * parentEnv (default process.env), removes any keys that came from the\n * daemon's own .env (so daemon-level secrets don't leak), then layers in\n * workspace .env / .env.gitlab.\n *\n * - System env (PATH, HOME, USER, …) survives untouched.\n * - Daemon-level secrets (e.g. GITLAB_TOKEN in /home/clawd/agentx/.env) are\n * stripped UNLESS the workspace provides its own value for that key.\n * - Workspace env (per-agent .env / .env.gitlab) wins on the final merge.\n */\nexport function buildAgentEnv(\n workspace: string,\n parentEnv: NodeJS.ProcessEnv = process.env,\n daemonCwd: string = process.cwd(),\n): NodeJS.ProcessEnv {\n const workspaceEnv = loadWorkspaceEnv(workspace)\n const daemonOwnedKeys = getDaemonEnvKeys(daemonCwd)\n const child: NodeJS.ProcessEnv = { ...parentEnv }\n for (const k of daemonOwnedKeys) {\n if (!(k in workspaceEnv)) delete child[k]\n }\n for (const [k, v] of Object.entries(workspaceEnv)) {\n child[k] = v\n }\n return child\n}\n"],"mappings":"AAAA,OAAS,gBAAAA,EAAc,cAAAC,MAAkB,KACzC,OAAS,WAAAC,MAAe,OAaxB,SAASC,EAAYC,EAA0C,CAC7D,IAAMC,EAA+B,CAAC,EACtC,QAAWC,KAAOF,EAAQ,MAAM;AAAA,CAAI,EAAG,CACrC,IAAMG,EAAUD,EAAI,KAAK,EACzB,GAAI,CAACC,GAAWA,EAAQ,WAAW,GAAG,EAAG,SACzC,IAAMC,EAAWD,EAAQ,WAAW,SAAS,EAAIA,EAAQ,MAAM,CAAC,EAAE,KAAK,EAAIA,EACrEE,EAAKD,EAAS,QAAQ,GAAG,EAC/B,GAAIC,IAAO,GAAI,SACf,IAAMC,EAAMF,EAAS,MAAM,EAAGC,CAAE,EAAE,KAAK,EACnCE,EAAQH,EAAS,MAAMC,EAAK,CAAC,EAAE,KAAK,EACpCE,EAAM,QAAU,IAAOA,EAAM,WAAW,GAAG,GAAKA,EAAM,SAAS,GAAG,GAAOA,EAAM,WAAW,GAAG,GAAKA,EAAM,SAAS,GAAG,KACtHA,EAAQA,EAAM,MAAM,EAAG,EAAE,GAEvBD,GAAKL,EAAI,KAAK,CAACK,EAAKC,CAAK,CAAC,EAEhC,OAAON,CACT,CAMO,SAASO,EAAiBC,EAA2C,CAC1E,IAAMC,EAAiC,CAAC,EACxC,QAAWC,IAAS,CAAC,OAAQ,aAAa,EAAG,CAC3C,IAAMC,EAAOd,EAAQW,EAAWE,CAAK,EACrC,GAAKd,EAAWe,CAAI,EACpB,GAAI,CACF,OAAW,CAACC,EAAGC,CAAC,IAAKf,EAAYH,EAAagB,EAAM,OAAO,CAAC,EAC1DF,EAAOG,CAAC,EAAIC,CAEhB,MAAE,CAA0B,EAE9B,OAAOJ,CACT,CAQA,IAAMK,EAAqB,IAAI,IAExB,SAASC,EAAiBC,EAAoB,QAAQ,IAAI,EAAgB,CAC/E,IAAMC,EAASH,EAAmB,IAAIE,CAAS,EAC/C,GAAIC,EAAQ,OAAOA,EACnB,IAAMN,EAAOd,EAAQmB,EAAW,MAAM,EAClCE,EACJ,GAAI,CAACtB,EAAWe,CAAI,EAClBO,EAAO,IAAI,QAEX,IAAI,CACFA,EAAO,IAAI,IAAIpB,EAAYH,EAAagB,EAAM,OAAO,CAAC,EAAE,IAAI,CAAC,CAACC,CAAC,IAAMA,CAAC,CAAC,CACzE,MAAE,CACAM,EAAO,IAAI,GACb,CAEF,OAAAJ,EAAmB,IAAIE,EAAWE,CAAI,EAC/BA,CACT,CAkBO,SAASC,EACdC,EACAC,EAA+B,QAAQ,IACvCC,EAAoB,QAAQ,IAAI,EACb,CACnB,IAAMC,EAAeC,EAAiBJ,CAAS,EACzCK,EAAkBC,EAAiBJ,CAAS,EAC5CK,EAA2B,CAAE,GAAGN,CAAU,EAChD,QAAWO,KAAKH,EACRG,KAAKL,GAAe,OAAOI,EAAMC,CAAC,EAE1C,OAAW,CAACA,EAAGC,CAAC,IAAK,OAAO,QAAQN,CAAY,EAC9CI,EAAMC,CAAC,EAAIC,EAEb,OAAOF,CACT","names":["readFileSync","existsSync","resolve","parseDotEnv","content","out","raw","trimmed","stripped","eq","key","value","loadWorkspaceEnv","workspace","merged","fname","path","k","v","daemonEnvKeysCache","getDaemonEnvKeys","daemonCwd","cached","keys","buildAgentEnv","workspace","parentEnv","daemonCwd","workspaceEnv","loadWorkspaceEnv","daemonOwnedKeys","getDaemonEnvKeys","child","k","v"]}