agentix-cli 0.23.0 → 0.26.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.
- package/LICENSE +21 -0
- package/README.md +103 -102
- package/agentx.example.json +9 -3
- package/dist/App-BPNHIGUX.js +8 -0
- package/dist/App-BPNHIGUX.js.map +1 -0
- package/dist/ChatApp-6XSSZB4U.js +7 -0
- package/dist/ChatApp-6XSSZB4U.js.map +1 -0
- package/dist/{agent-FSE4KRLX.js → agent-5QMITZT6.js} +2 -2
- package/dist/architect-SEPWMWS4.js +2 -0
- package/dist/{board-dashboard-PUACKWAN.js → board-dashboard-M3372FLO.js} +4697 -985
- package/dist/board-dashboard-M3372FLO.js.map +1 -0
- package/dist/builtin-FRKAQMFS.js +2 -0
- package/dist/chunk-2WVX3LEC.js +2 -0
- package/dist/chunk-2WVX3LEC.js.map +1 -0
- package/dist/chunk-3WQFCIB6.js +24 -0
- package/dist/chunk-3WQFCIB6.js.map +1 -0
- package/dist/{chunk-IKBHHTIT.js → chunk-5U35WAZF.js} +2 -2
- package/dist/chunk-67Q4DCPS.js +54 -0
- package/dist/chunk-67Q4DCPS.js.map +1 -0
- package/dist/chunk-76Z4VHDX.js +8 -0
- package/dist/chunk-76Z4VHDX.js.map +1 -0
- package/dist/{chunk-7P5QRX5W.js → chunk-BFXONUUN.js} +2 -2
- package/dist/chunk-BTHHSY7D.js +13 -0
- package/dist/chunk-BTHHSY7D.js.map +1 -0
- package/dist/chunk-BVGJSPW4.js +7 -0
- package/dist/chunk-BVGJSPW4.js.map +1 -0
- package/dist/chunk-D46VZ7K7.js +116 -0
- package/dist/chunk-D46VZ7K7.js.map +1 -0
- package/dist/chunk-E5CXCI33.js +155 -0
- package/dist/chunk-E5CXCI33.js.map +1 -0
- package/dist/chunk-FDWOECJY.js +54 -0
- package/dist/chunk-FDWOECJY.js.map +1 -0
- package/dist/{chunk-NOF4HSGK.js → chunk-H5D7ATXS.js} +2 -2
- package/dist/chunk-HR4JSRZS.js +3 -0
- package/dist/{chunk-A6BKSZQA.js.map → chunk-HR4JSRZS.js.map} +1 -1
- package/dist/chunk-I3SIXTJO.js +44 -0
- package/dist/chunk-I3SIXTJO.js.map +1 -0
- package/dist/chunk-ISEBLHWX.js +118 -0
- package/dist/chunk-ISEBLHWX.js.map +1 -0
- package/dist/{chunk-4ITQP3XL.js → chunk-JP5IEXR7.js} +2 -2
- package/dist/{chunk-DXTIG5YV.js → chunk-K2CAPYES.js} +2 -2
- package/dist/chunk-LDHPMJ7M.js +257 -0
- package/dist/chunk-LDHPMJ7M.js.map +1 -0
- package/dist/chunk-M4AMWP3H.js +178 -0
- package/dist/chunk-M4AMWP3H.js.map +1 -0
- package/dist/chunk-METYYZKQ.js +1115 -0
- package/dist/chunk-METYYZKQ.js.map +1 -0
- package/dist/chunk-MTAPOCGA.js +47 -0
- package/dist/chunk-MTAPOCGA.js.map +1 -0
- package/dist/chunk-O6YPEOQF.js +5 -0
- package/dist/chunk-O6YPEOQF.js.map +1 -0
- package/dist/{chunk-CNNLREHD.js → chunk-P4VWIVKN.js} +268 -410
- package/dist/chunk-P4VWIVKN.js.map +1 -0
- package/dist/chunk-U2CKL755.js +4 -0
- package/dist/chunk-U2CKL755.js.map +1 -0
- package/dist/chunk-UCQ2N3KO.js +9 -0
- package/dist/chunk-UCQ2N3KO.js.map +1 -0
- package/dist/chunk-UHE5FCPB.js +431 -0
- package/dist/chunk-UHE5FCPB.js.map +1 -0
- package/dist/{chunk-KWUWZQGV.js → chunk-V3U4OF7L.js} +29 -30
- package/dist/chunk-V3U4OF7L.js.map +1 -0
- package/dist/chunk-VT5XYFDD.js +126 -0
- package/dist/chunk-VT5XYFDD.js.map +1 -0
- package/dist/chunk-W4YR2RO2.js +2 -0
- package/dist/chunk-W4YR2RO2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-HGKAC6VP.js +2 -0
- package/dist/{compaction-BTUJJ3LJ.js → compaction-NWEMOSHA.js} +3 -3
- package/dist/config-A4RWEJHV.js +2 -0
- package/dist/config-mutator-MNSFUN7D.js +2 -0
- package/dist/{context-planner-I7ID7XF3.js → context-planner-45GEWKGL.js} +2 -2
- package/dist/doctor-3Q36ZFE6.js +2 -0
- package/dist/heal-HZ3EQD52.js +2 -0
- package/dist/index.d.ts +1250 -225
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/memory-extract-AENFVVO5.js +2 -0
- package/dist/processes-UIC4XXA6.js +2 -0
- package/dist/program-SAUNRCCD.js +2 -0
- package/dist/projects-api-KRME2YKZ.js +2 -0
- package/dist/projects-api-KRME2YKZ.js.map +1 -0
- package/dist/projects-mutate-3NHVG437.js +7 -0
- package/dist/projects-mutate-3NHVG437.js.map +1 -0
- package/dist/providers-L27BJ5A6.js +2 -0
- package/dist/registry-HK3WQ7BS.js +2 -0
- package/dist/rotation-memo-B3EXXSEH.js +13 -0
- package/dist/rotation-memo-B3EXXSEH.js.map +1 -0
- package/dist/scheduler-6T2SU5ST.js +2 -0
- package/dist/surface-usage-PRL6Z4U3.js +2 -0
- package/dist/wiki-M6YNRFC4.js +2 -0
- package/dist/workflows/templates/branching.yaml +75 -0
- package/dist/workflows/templates/extract.yaml +59 -0
- package/dist/workflows/templates/linear.yaml +37 -0
- package/dist/workflows/templates/retry.yaml +61 -0
- package/dist/workspace-setup-TBQAHYHJ.js +2 -0
- package/package.json +10 -3
- package/dist/board-dashboard-PUACKWAN.js.map +0 -1
- package/dist/builtin-TQKMR42Z.js +0 -2
- package/dist/chunk-3SHMHYPD.js +0 -450
- package/dist/chunk-3SHMHYPD.js.map +0 -1
- package/dist/chunk-4INOOHND.js +0 -6
- package/dist/chunk-4INOOHND.js.map +0 -1
- package/dist/chunk-5F3J55LG.js +0 -2
- package/dist/chunk-5F3J55LG.js.map +0 -1
- package/dist/chunk-72PAN7UI.js +0 -103
- package/dist/chunk-72PAN7UI.js.map +0 -1
- package/dist/chunk-A6BKSZQA.js +0 -3
- package/dist/chunk-BHSPAP4U.js +0 -44
- package/dist/chunk-BHSPAP4U.js.map +0 -1
- package/dist/chunk-BOUKM2PS.js +0 -163
- package/dist/chunk-BOUKM2PS.js.map +0 -1
- package/dist/chunk-CDNWCYF7.js +0 -2
- package/dist/chunk-CDNWCYF7.js.map +0 -1
- package/dist/chunk-CNNLREHD.js.map +0 -1
- package/dist/chunk-EK37JHJI.js +0 -13
- package/dist/chunk-EK37JHJI.js.map +0 -1
- package/dist/chunk-EQPUB7CZ.js +0 -685
- package/dist/chunk-EQPUB7CZ.js.map +0 -1
- package/dist/chunk-EW27SCCC.js +0 -2
- package/dist/chunk-EW27SCCC.js.map +0 -1
- package/dist/chunk-GOLZ6NEL.js +0 -111
- package/dist/chunk-GOLZ6NEL.js.map +0 -1
- package/dist/chunk-JGXE2YNV.js +0 -46
- package/dist/chunk-JGXE2YNV.js.map +0 -1
- package/dist/chunk-KWUWZQGV.js.map +0 -1
- package/dist/chunk-MTRWXRFF.js +0 -380
- package/dist/chunk-MTRWXRFF.js.map +0 -1
- package/dist/chunk-N67JZAXU.js +0 -2
- package/dist/chunk-N67JZAXU.js.map +0 -1
- package/dist/chunk-PD5Q7D6Z.js +0 -7
- package/dist/chunk-PD5Q7D6Z.js.map +0 -1
- package/dist/chunk-Q6CAFGXT.js +0 -118
- package/dist/chunk-Q6CAFGXT.js.map +0 -1
- package/dist/chunk-WJXU5F4Z.js +0 -53
- package/dist/chunk-WJXU5F4Z.js.map +0 -1
- package/dist/chunk-ZZFXI3CS.js +0 -8
- package/dist/chunk-ZZFXI3CS.js.map +0 -1
- package/dist/config-ADS54KLC.js +0 -2
- package/dist/config-mutator-WTRMBZTO.js +0 -2
- package/dist/doctor-T6RB4Q5X.js +0 -2
- package/dist/editor-chat-RVLCKKB7.js +0 -137
- package/dist/editor-chat-RVLCKKB7.js.map +0 -1
- package/dist/heal-YEQYWX2L.js +0 -2
- package/dist/inbox-BIN2S5XN.js +0 -2
- package/dist/memory-extract-KRCHD3OP.js +0 -2
- package/dist/processes-RJ6RDP32.js +0 -2
- package/dist/program-V7NGFB2R.js +0 -2
- package/dist/providers-WVCMIW67.js +0 -2
- package/dist/registry-42HEWCLA.js +0 -2
- package/dist/rotation-memo-GMTZPMNE.js +0 -13
- package/dist/rotation-memo-GMTZPMNE.js.map +0 -1
- package/dist/scheduler-C56ABAVP.js +0 -2
- package/dist/slack-6P7ZJ32Y.js +0 -3
- package/dist/slack-6P7ZJ32Y.js.map +0 -1
- package/dist/store-BAPDZHXE.js +0 -2
- package/dist/task-store-RHISEU6V.js +0 -2
- package/dist/telegram-PIYDRM46.js +0 -3
- package/dist/telegram-PIYDRM46.js.map +0 -1
- package/dist/types-ZRLHHCGB.js +0 -2
- package/dist/web/workflow-editor.global.js +0 -1452
- package/dist/web/workflow-editor.global.js.map +0 -1
- package/dist/whatsapp-PFRDMUMT.js +0 -3
- package/dist/whatsapp-PFRDMUMT.js.map +0 -1
- package/dist/wiki-53W5NLZV.js +0 -2
- package/dist/workspace-setup-DQCZX2NE.js +0 -2
- package/dist/workspace-setup-DQCZX2NE.js.map +0 -1
- /package/dist/{agent-FSE4KRLX.js.map → agent-5QMITZT6.js.map} +0 -0
- /package/dist/{builtin-TQKMR42Z.js.map → architect-SEPWMWS4.js.map} +0 -0
- /package/dist/{config-ADS54KLC.js.map → builtin-FRKAQMFS.js.map} +0 -0
- /package/dist/{chunk-IKBHHTIT.js.map → chunk-5U35WAZF.js.map} +0 -0
- /package/dist/{chunk-7P5QRX5W.js.map → chunk-BFXONUUN.js.map} +0 -0
- /package/dist/{chunk-NOF4HSGK.js.map → chunk-H5D7ATXS.js.map} +0 -0
- /package/dist/{chunk-4ITQP3XL.js.map → chunk-JP5IEXR7.js.map} +0 -0
- /package/dist/{chunk-DXTIG5YV.js.map → chunk-K2CAPYES.js.map} +0 -0
- /package/dist/{config-mutator-WTRMBZTO.js.map → client-HGKAC6VP.js.map} +0 -0
- /package/dist/{compaction-BTUJJ3LJ.js.map → compaction-NWEMOSHA.js.map} +0 -0
- /package/dist/{doctor-T6RB4Q5X.js.map → config-A4RWEJHV.js.map} +0 -0
- /package/dist/{heal-YEQYWX2L.js.map → config-mutator-MNSFUN7D.js.map} +0 -0
- /package/dist/{context-planner-I7ID7XF3.js.map → context-planner-45GEWKGL.js.map} +0 -0
- /package/dist/{inbox-BIN2S5XN.js.map → doctor-3Q36ZFE6.js.map} +0 -0
- /package/dist/{memory-extract-KRCHD3OP.js.map → heal-HZ3EQD52.js.map} +0 -0
- /package/dist/{processes-RJ6RDP32.js.map → memory-extract-AENFVVO5.js.map} +0 -0
- /package/dist/{program-V7NGFB2R.js.map → processes-UIC4XXA6.js.map} +0 -0
- /package/dist/{providers-WVCMIW67.js.map → program-SAUNRCCD.js.map} +0 -0
- /package/dist/{registry-42HEWCLA.js.map → providers-L27BJ5A6.js.map} +0 -0
- /package/dist/{scheduler-C56ABAVP.js.map → registry-HK3WQ7BS.js.map} +0 -0
- /package/dist/{store-BAPDZHXE.js.map → scheduler-6T2SU5ST.js.map} +0 -0
- /package/dist/{task-store-RHISEU6V.js.map → surface-usage-PRL6Z4U3.js.map} +0 -0
- /package/dist/{types-ZRLHHCGB.js.map → wiki-M6YNRFC4.js.map} +0 -0
- /package/dist/{wiki-53W5NLZV.js.map → workspace-setup-TBQAHYHJ.js.map} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{existsSync as u,readdirSync as g,readFileSync as h,statSync as f,watch as d}from"fs";import{extname as b,join as p,resolve as m}from"path";import y from"js-yaml";var c=class{byProject=new Map;dir;log;watcher;debounce;loadErrors=[];constructor(e,t=console.error.bind(console,"[projects]")){this.dir=e,this.log=t}load(){if(this.byProject.clear(),this.loadErrors=[],!u(this.dir))return{count:0,errors:0};for(let e of this.walk(this.dir))try{let t=h(e,"utf-8"),r=y.load(t),s=this.validate(r,e);if(!s)continue;if(s._path=e,this.byProject.has(s.project)){this.log(`duplicate project "${s.project}" \u2014 keeping ${this.byProject.get(s.project)._path}, skipping ${e}`);continue}this.byProject.set(s.project,s)}catch(t){this.loadErrors.push({file:e,error:t?.message??String(t)}),this.log(`failed to load ${e}: ${t?.message??t}`)}return{count:this.byProject.size,errors:this.loadErrors.length}}startWatching(e){if(u(this.dir))try{this.watcher=d(this.dir,{recursive:!0},()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout(()=>{let t=this.load();this.log(`rules reloaded: ${t.count} project(s), ${t.errors} error(s)`),e?.()},200)})}catch(t){this.log(`watcher unavailable (${t.message}) \u2014 rules will not hot-reload`)}}stop(){this.watcher?.close(),this.debounce&&clearTimeout(this.debounce)}find(e){return this.byProject.get(e)}list(){return Array.from(this.byProject.values())}static inferKind(e){return e.kind?e.kind:e.gitlab?"gitlab":e.github?"github":"other"}health(){return{count:this.byProject.size,errors:[...this.loadErrors]}}shouldFireGitlabIssue(e,t){let r=this.find(e)?.gitlab?.issue;return r?a(r,t,"gitlab"):{allow:!0}}shouldFireGitlabNote(e,t,r){let s=this.find(e)?.gitlab?.note;return s?w(s,t,r?.knownAgentMentions):{allow:!0}}shouldFireGitlabMR(e,t){let r=this.find(e)?.gitlab?.merge_request;return r?a(r,t,"gitlab"):{allow:!0}}shouldFireGitlabPipeline(e,t){let r=this.find(e)?.gitlab?.pipeline;return r?r.actions&&r.actions.length>0&&t.status&&!r.actions.includes(t.status)?{allow:!1,reason:`pipeline status="${t.status}" not in actions=${JSON.stringify(r.actions)}`}:{allow:!0}:{allow:!0}}shouldFireGithubIssue(e,t){let r=this.find(e)?.github?.issues;return r?a(r,t,"github"):{allow:!0}}shouldFireGithubPR(e,t){let r=this.find(e)?.github?.pull_request;return r?a(r,t,"github"):{allow:!0}}*walk(e){let t;try{t=g(e)}catch{return}for(let r of t){let s=p(e,r),o;try{o=f(s)}catch{continue}if(o.isDirectory()){yield*this.walk(s);continue}let n=b(r).toLowerCase();(n===".yaml"||n===".yml")&&(yield s)}}validate(e,t){if(!e||typeof e!="object")throw new Error(`${t}: top-level must be an object`);let r=e;if(typeof r.project!="string"||!r.project.includes("/"))throw new Error(`${t}: \`project\` must be a string of the form "org/repo"`);if(r.runbook!==void 0&&typeof r.runbook!="string")throw new Error(`${t}: \`runbook\` must be a string path`);return r.runbook&&m(r.runbook),e}};function a(i,e,t){let r=e.labels??[],s=e.authorUsername??"";if(i.actions&&i.actions.length>0&&(!e.action||!i.actions.includes(e.action)))return{allow:!1,reason:`${t} issue action="${e.action}" not in actions=${JSON.stringify(i.actions)}`};if(i.excludeStates&&e.state&&i.excludeStates.includes(e.state))return{allow:!1,reason:`${t} issue state="${e.state}" in excludeStates`};if(i.requireLabels&&i.requireLabels.length>0&&!i.requireLabels.some(n=>r.includes(n)))return{allow:!1,reason:`${t} issue missing requireLabels=${JSON.stringify(i.requireLabels)} (have ${JSON.stringify(r)})`};if(i.excludeLabels&&i.excludeLabels.length>0){let o=i.excludeLabels.find(n=>r.includes(n));if(o)return{allow:!1,reason:`${t} issue carries excluded label "${o}"`}}if(i.excludeAuthors&&i.excludeAuthors.length>0){let o=i.excludeAuthors.find(n=>s.toLowerCase().includes(n.toLowerCase()));if(o)return{allow:!1,reason:`${t} issue authored by "${s}" matched excludeAuthors entry "${o}"`}}return{allow:!0}}function w(i,e,t){let r=(e.text??"").toLowerCase(),s=e.authorUsername??"";if(i.onlyOn&&i.onlyOn.length>0&&(!e.noteableType||!i.onlyOn.includes(e.noteableType)))return{allow:!1,reason:`note noteableType="${e.noteableType}" not in onlyOn=${JSON.stringify(i.onlyOn)}`};if(i.excludeAuthors&&i.excludeAuthors.length>0){let o=i.excludeAuthors.find(n=>s.toLowerCase().includes(n.toLowerCase()));if(o)return{allow:!1,reason:`note authored by "${s}" matched excludeAuthors entry "${o}"`}}return i.triggers&&i.triggers.length>0&&!i.triggers.some(n=>{if(n==="auto")return!t||t.length===0?!1:t.some(l=>r.includes(l.toLowerCase()));if(n.mention){let l=n.mention.toLowerCase();if(r.includes(l))return!0}return!!(n.keyword&&r.includes(n.keyword.toLowerCase()))})?{allow:!1,reason:"note text matched no trigger"}:{allow:!0}}export{c as a};
|
|
2
|
+
//# sourceMappingURL=chunk-2WVX3LEC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/projects/rules.ts"],"sourcesContent":["// --- Per-project webhook rules + runbook reference ---\n//\n// Project rules live in `.agentx/projects/<org>/<project>.yaml` (or any\n// nested path; the loader walks the tree). Each file declares which\n// channel events for that project may reach an agent and where the\n// project's runbook (CLAUDE.md / AGENTS.md / DEPLOY.md / RUNBOOK.md)\n// lives so the registry can inject it into the agent's system prefix.\n//\n// The store is loaded at daemon boot and watched for changes — write a\n// file, save, and the next webhook event picks up the new rules without\n// a daemon restart. Validation errors are logged and the offending file\n// is skipped (other rules keep working).\n//\n// Why per-file: agencies grow projects faster than they touch agentx\n// internals; a single project.yaml would force coordination on every\n// edit. One file per project lets each project owner edit their own\n// rules without merge conflicts.\n//\n// Schema is intentionally narrow:\n// - issue.actions / pull_request.actions / issues.actions: action allowlist\n// - requireLabels: at least one must be present (OR-of). Empty/absent = no\n// label requirement.\n// - excludeLabels: any present blocks the event.\n// - excludeStates: states that always block (e.g. \"closed\").\n// - excludeAuthors: handle prefixes that always block (bots).\n// - note.triggers: at least one must match for a comment to fire.\n// - runbook: filesystem path; the registry reads CLAUDE.md / AGENTS.md /\n// DEPLOY.md / RUNBOOK.md from this path (extensible via `runbookFiles`).\n//\n// Anything not listed by a rule passes through with the legacy default\n// (existing adapter behaviour). Rules opt INTO filtering, never opt out\n// of an event the adapter already drops.\n\nimport { existsSync, readdirSync, readFileSync, statSync, watch } from \"fs\"\nimport type { FSWatcher } from \"fs\"\nimport { extname, join, resolve } from \"path\"\nimport yaml from \"js-yaml\"\n\nexport type Channel = \"gitlab\" | \"github\"\n\n/** Declarative source of a project. Drives the Projects-page UI badge\n * and indicates which channel adapters are expected to carry events\n * for it. `gitlab` and `github` have rule clauses + live adapters;\n * `jira` and `linear` are accepted as values for tracking purposes\n * even though no adapter exists yet (events from those sources can't\n * fire workflows today). `other` is the catch-all for projects that\n * exist organisationally but aren't wired to any source. */\nexport type ProjectKind = \"gitlab\" | \"github\" | \"jira\" | \"linear\" | \"other\"\n\n/** Default runbook filenames read into the agent system prefix. Order\n * preserved so CLAUDE.md (claude convention) lands before AGENTS.md\n * (codex convention) — agents see Claude rules first. Operator can\n * override per-project via `runbookFiles`. */\nexport const DEFAULT_RUNBOOK_FILES = [\"CLAUDE.md\", \"AGENTS.md\", \"DEPLOY.md\", \"RUNBOOK.md\"] as const\n\nexport interface IssueRule {\n /** Allowed actions. Empty/absent = no action restriction. */\n actions?: string[]\n /** Issue must carry at least one of these labels. Empty/absent = no req. */\n requireLabels?: string[]\n /** Any of these labels present blocks the event. */\n excludeLabels?: string[]\n /** States that always block (gitlab: opened|closed|reopened, github: open|closed). */\n excludeStates?: string[]\n /** Username substrings/equals that always block — used for bot exclusion. */\n excludeAuthors?: string[]\n}\n\nexport interface NoteTrigger {\n /** Comment must contain this @mention to fire. Case-insensitive. */\n mention?: string\n /** Comment must contain this keyword (case-insensitive substring). */\n keyword?: string\n}\n\n/** Sentinel string that tells the matcher to accept any @-mention that\n * resolves to a configured agent (per `channels.gitlab.agentMappings`).\n * Lets a project rule say \"fire on any agent mention\" without manually\n * enumerating every handle — the canonical list lives in agentMappings.\n * Use it as a string entry in `triggers`:\n * triggers: [auto, { keyword: \"merge and deploy\" }]\n * When the channel adapter doesn't supply `knownAgentMentions`, an\n * `auto` entry is treated as a no-op (it does not block the rule). */\nexport type NoteTriggerEntry = NoteTrigger | \"auto\"\n\nexport interface NoteRule {\n /** Restrict to certain noteable types — gitlab: issue|merge_request|commit|snippet. */\n onlyOn?: string[]\n /** At least one trigger must match the comment text for the event to fire.\n * Mix explicit objects with the `\"auto\"` sentinel to delegate mention\n * enumeration to the channel's agentMappings. */\n triggers?: NoteTriggerEntry[]\n /** Username substrings/equals that always block. */\n excludeAuthors?: string[]\n}\n\nexport interface PipelineRule {\n /** Allowed pipeline statuses (success|failed|canceled|...). */\n actions?: string[]\n}\n\nexport interface ProjectRule {\n /** Project path. GitLab: `org/repo`. GitHub: `owner/repo`. Required. */\n project: string\n channel?: Channel\n /** Declarative source of this project. Optional. When unset, the\n * loader infers from clauses (presence of `gitlab:` → \"gitlab\",\n * `github:` → \"github\", neither → \"other\"). Set explicitly to track\n * jira / linear / other-source projects that don't have rule\n * clauses yet. */\n kind?: ProjectKind\n /** Human-friendly project name shown on the Projects page header. */\n displayName?: string\n /** External URL — opens the project's home (GitLab repo, GitHub repo,\n * Jira board, etc.). Rendered as a link on the project header. */\n homeUrl?: string\n /** Contact ids from `.agentx/contacts.json` associated with this\n * project. The Projects page uses these to populate the Contacts\n * section in addition to contacts that carry their own `project`\n * field. Either side may declare the link — both render. */\n contacts?: string[]\n /** Default agent for this project. Overrides channel-level resolveAgent\n * when set. Optional — leaving unset preserves existing behaviour. */\n agent?: string\n /** Filesystem path to the project root (where CLAUDE.md/AGENTS.md live).\n * When set, the registry injects those files into the agent system\n * prefix for tasks scoped to this project. */\n runbook?: string\n /** Override the default runbook file set (DEFAULT_RUNBOOK_FILES). */\n runbookFiles?: string[]\n gitlab?: {\n issue?: IssueRule\n /** Same matcher shape as issue — actions/requireLabels/excludeLabels/\n * excludeStates/excludeAuthors. GitLab MR `action` values: open,\n * reopen, update, close, approved, unapproved, approval, unapproval,\n * merge. Most W3-style review automations want `[open, reopen, update]`\n * only, with `excludeAuthors: [bot, noqta-]` to ignore agent-driven\n * MR updates. */\n merge_request?: IssueRule\n note?: NoteRule\n pipeline?: PipelineRule\n }\n github?: {\n /** GitHub uses `issues` for issue events and `pull_request` for PRs. */\n issues?: IssueRule\n pull_request?: IssueRule\n push?: { branches?: string[] }\n }\n /** Free-form notes for the operator. Ignored by the loader. */\n description?: string\n /** Internal: file path the rule was loaded from, for diagnostics. */\n _path?: string\n}\n\nexport interface FilterDecision {\n allow: boolean\n /** Reason for the decision — surfaced in logs so operators can see why\n * an event was dropped. Always present when allow=false. */\n reason?: string\n}\n\nexport class ProjectRulesStore {\n private byProject = new Map<string, ProjectRule>()\n private dir: string\n private log: (...args: unknown[]) => void\n private watcher?: FSWatcher\n private debounce?: ReturnType<typeof setTimeout>\n private loadErrors: Array<{ file: string; error: string }> = []\n\n constructor(dir: string, log: (...args: unknown[]) => void = console.error.bind(console, \"[projects]\")) {\n this.dir = dir\n this.log = log\n }\n\n load(): { count: number; errors: number } {\n this.byProject.clear()\n this.loadErrors = []\n if (!existsSync(this.dir)) {\n return { count: 0, errors: 0 }\n }\n for (const file of this.walk(this.dir)) {\n try {\n const raw = readFileSync(file, \"utf-8\")\n const parsed = yaml.load(raw) as unknown\n const rule = this.validate(parsed, file)\n if (!rule) continue\n rule._path = file\n if (this.byProject.has(rule.project)) {\n this.log(`duplicate project \"${rule.project}\" — keeping ${this.byProject.get(rule.project)!._path}, skipping ${file}`)\n continue\n }\n this.byProject.set(rule.project, rule)\n } catch (e: any) {\n this.loadErrors.push({ file, error: e?.message ?? String(e) })\n this.log(`failed to load ${file}: ${e?.message ?? e}`)\n }\n }\n return { count: this.byProject.size, errors: this.loadErrors.length }\n }\n\n startWatching(onReload?: () => void): void {\n if (!existsSync(this.dir)) return\n try {\n this.watcher = watch(this.dir, { recursive: true }, () => {\n if (this.debounce) clearTimeout(this.debounce)\n this.debounce = setTimeout(() => {\n const result = this.load()\n this.log(`rules reloaded: ${result.count} project(s), ${result.errors} error(s)`)\n onReload?.()\n }, 200)\n })\n } catch (e: any) {\n this.log(`watcher unavailable (${e.message}) — rules will not hot-reload`)\n }\n }\n\n stop(): void {\n this.watcher?.close()\n if (this.debounce) clearTimeout(this.debounce)\n }\n\n /** Look up a rule by project path. Returns undefined if no rule\n * exists — callers must treat that as \"no filter, legacy behaviour\". */\n find(project: string): ProjectRule | undefined {\n return this.byProject.get(project)\n }\n\n list(): ProjectRule[] {\n return Array.from(this.byProject.values())\n }\n\n /** Resolve a project's `kind` even when not set on the rule file. The\n * loader-side default is intentionally absent — we infer at read time\n * so an operator who flips a `gitlab:` clause to `github:` doesn't\n * also have to remember to update `kind:`. */\n static inferKind(rule: ProjectRule): ProjectKind {\n if (rule.kind) return rule.kind\n if (rule.gitlab) return \"gitlab\"\n if (rule.github) return \"github\"\n return \"other\"\n }\n\n health(): { count: number; errors: Array<{ file: string; error: string }> } {\n return { count: this.byProject.size, errors: [...this.loadErrors] }\n }\n\n // --- Filter decisions ---\n\n /** Should a GitLab issue event reach an agent? */\n shouldFireGitlabIssue(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.gitlab?.issue\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"gitlab\")\n }\n\n /** Should a GitLab MR-or-issue note event reach an agent?\n * `opts.knownAgentMentions` carries the @-handle list resolved by the\n * channel adapter from `agentMappings.gitlabUsernames`. When the rule\n * has an `\"auto\"` trigger entry, the matcher accepts any of these\n * mentions found in the comment text — single source of truth, no\n * manual sync required between rule files and agentMappings. */\n shouldFireGitlabNote(project: string, payload: {\n noteableType?: string\n text?: string\n authorUsername?: string\n }, opts?: { knownAgentMentions?: string[] }): FilterDecision {\n const rule = this.find(project)?.gitlab?.note\n if (!rule) return { allow: true }\n return matchNoteRule(rule, payload, opts?.knownAgentMentions)\n }\n\n /** Should a GitLab MR event reach an agent? Same matcher as issue —\n * filter on action / labels / state / author. */\n shouldFireGitlabMR(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.gitlab?.merge_request\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"gitlab\")\n }\n\n /** Should a GitLab pipeline event reach an agent? */\n shouldFireGitlabPipeline(project: string, payload: { status?: string }): FilterDecision {\n const rule = this.find(project)?.gitlab?.pipeline\n if (!rule) return { allow: true }\n if (rule.actions && rule.actions.length > 0 && payload.status && !rule.actions.includes(payload.status)) {\n return { allow: false, reason: `pipeline status=\"${payload.status}\" not in actions=${JSON.stringify(rule.actions)}` }\n }\n return { allow: true }\n }\n\n /** Should a GitHub issues event reach an agent? */\n shouldFireGithubIssue(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.github?.issues\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"github\")\n }\n\n /** Should a GitHub pull_request event reach an agent? */\n shouldFireGithubPR(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.github?.pull_request\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"github\")\n }\n\n // --- Internals ---\n\n private *walk(dir: string): Generator<string> {\n let entries: string[]\n try { entries = readdirSync(dir) } catch { return }\n for (const name of entries) {\n const full = join(dir, name)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (stat.isDirectory()) {\n yield* this.walk(full)\n continue\n }\n const ext = extname(name).toLowerCase()\n if (ext === \".yaml\" || ext === \".yml\") yield full\n }\n }\n\n private validate(raw: unknown, file: string): ProjectRule | null {\n if (!raw || typeof raw !== \"object\") {\n throw new Error(`${file}: top-level must be an object`)\n }\n const r = raw as Record<string, unknown>\n if (typeof r.project !== \"string\" || !r.project.includes(\"/\")) {\n throw new Error(`${file}: \\`project\\` must be a string of the form \"org/repo\"`)\n }\n if (r.runbook !== undefined && typeof r.runbook !== \"string\") {\n throw new Error(`${file}: \\`runbook\\` must be a string path`)\n }\n if (r.runbook && !resolve(r.runbook as string)) {\n // resolve never returns falsy — kept for symmetry / future expansion.\n }\n return raw as ProjectRule\n }\n}\n\nfunction matchIssueRule(rule: IssueRule, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n}, kind: Channel): FilterDecision {\n const labels = payload.labels ?? []\n const author = payload.authorUsername ?? \"\"\n if (rule.actions && rule.actions.length > 0) {\n if (!payload.action || !rule.actions.includes(payload.action)) {\n return { allow: false, reason: `${kind} issue action=\"${payload.action}\" not in actions=${JSON.stringify(rule.actions)}` }\n }\n }\n if (rule.excludeStates && payload.state && rule.excludeStates.includes(payload.state)) {\n return { allow: false, reason: `${kind} issue state=\"${payload.state}\" in excludeStates` }\n }\n if (rule.requireLabels && rule.requireLabels.length > 0) {\n const has = rule.requireLabels.some((req) => labels.includes(req))\n if (!has) {\n return { allow: false, reason: `${kind} issue missing requireLabels=${JSON.stringify(rule.requireLabels)} (have ${JSON.stringify(labels)})` }\n }\n }\n if (rule.excludeLabels && rule.excludeLabels.length > 0) {\n const hit = rule.excludeLabels.find((bad) => labels.includes(bad))\n if (hit) {\n return { allow: false, reason: `${kind} issue carries excluded label \"${hit}\"` }\n }\n }\n if (rule.excludeAuthors && rule.excludeAuthors.length > 0) {\n const hit = rule.excludeAuthors.find((bad) => author.toLowerCase().includes(bad.toLowerCase()))\n if (hit) {\n return { allow: false, reason: `${kind} issue authored by \"${author}\" matched excludeAuthors entry \"${hit}\"` }\n }\n }\n return { allow: true }\n}\n\nfunction matchNoteRule(rule: NoteRule, payload: {\n noteableType?: string\n text?: string\n authorUsername?: string\n}, knownAgentMentions?: string[]): FilterDecision {\n const text = (payload.text ?? \"\").toLowerCase()\n const author = payload.authorUsername ?? \"\"\n if (rule.onlyOn && rule.onlyOn.length > 0) {\n if (!payload.noteableType || !rule.onlyOn.includes(payload.noteableType)) {\n return { allow: false, reason: `note noteableType=\"${payload.noteableType}\" not in onlyOn=${JSON.stringify(rule.onlyOn)}` }\n }\n }\n if (rule.excludeAuthors && rule.excludeAuthors.length > 0) {\n const hit = rule.excludeAuthors.find((bad) => author.toLowerCase().includes(bad.toLowerCase()))\n if (hit) {\n return { allow: false, reason: `note authored by \"${author}\" matched excludeAuthors entry \"${hit}\"` }\n }\n }\n if (rule.triggers && rule.triggers.length > 0) {\n const matched = rule.triggers.some((t) => {\n // \"auto\" sentinel — matches any of the agent mentions resolved by the\n // adapter from agentMappings.gitlabUsernames. When the adapter doesn't\n // supply the list (or it's empty), `auto` neither matches nor blocks\n // — fall through to the next trigger entry.\n if (t === \"auto\") {\n if (!knownAgentMentions || knownAgentMentions.length === 0) return false\n return knownAgentMentions.some((m) => text.includes(m.toLowerCase()))\n }\n if (t.mention) {\n const m = t.mention.toLowerCase()\n if (text.includes(m)) return true\n }\n if (t.keyword) {\n if (text.includes(t.keyword.toLowerCase())) return true\n }\n return false\n })\n if (!matched) {\n return { allow: false, reason: `note text matched no trigger` }\n }\n }\n return { allow: true }\n}\n"],"mappings":"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","names":["existsSync","readdirSync","readFileSync","statSync","watch","extname","join","resolve","yaml","ProjectRulesStore","dir","log","existsSync","file","raw","readFileSync","parsed","yaml","rule","e","onReload","watch","result","project","payload","matchIssueRule","opts","matchNoteRule","entries","readdirSync","name","full","join","stat","statSync","ext","extname","resolve","kind","labels","author","req","hit","bad","knownAgentMentions","text","t","m"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import{readFileSync as T,writeFileSync as J,existsSync as L,readdirSync as oe,statSync as ae}from"fs";import{resolve as C,join as le}from"path";import{existsSync as N,mkdirSync as Z,readFileSync as Q,writeFileSync as H}from"fs";import{dirname as ee,resolve as K}from"path";import{z as f}from"zod";var te=f.object({type:f.enum(["gitlab","github"]),host:f.string().optional(),project:f.string(),iid:f.number().int().positive(),url:f.string()}),se=f.object({id:f.string(),title:f.string(),description:f.string().optional(),assignee:f.string().optional(),labels:f.array(f.string()).default([]),milestone:f.string().optional(),status:f.enum(["todo","doing","blocked","done"]).default("todo"),priority:f.number().int().optional(),estimatedSeconds:f.number().int().optional(),source:te.optional(),importedAt:f.string().optional(),createdAt:f.string(),updatedAt:f.string(),blocker:f.string().optional(),doneNote:f.string().optional()}),ne=f.object({version:f.literal(1),items:f.array(se).default([])}),j=class{constructor(e){this.path=e}jsonPath(){return K(process.cwd(),this.path).replace(/\.md$/i,".json")}mdPath(){return K(process.cwd(),this.path).replace(/\.json$/i,".md")}exists(){return N(this.jsonPath())}load(){let e=this.jsonPath();if(!N(e))return{version:1,items:[]};let t=Q(e,"utf-8");try{return ne.parse(JSON.parse(t))}catch(s){throw new Error(`Backlog file invalid (${e}): ${s.message}`)}}save(e){let t=this.jsonPath();Z(ee(t),{recursive:!0}),H(t,JSON.stringify(e,null,2)+`
|
|
2
|
+
`),H(this.mdPath(),this.toMarkdown(e))}list(){return this.load().items}findById(e){return this.list().find(t=>t.id===e)}add(e){let t=this.load();if(t.items.some(r=>r.id===e.id))throw new Error(`backlog item ${e.id} already exists`);let s=new Date().toISOString(),n={...e,labels:e.labels??[],status:e.status??"todo",createdAt:e.createdAt??s,updatedAt:s};return t.items.push(n),this.save(t),n}addMany(e){let t=this.load(),s=new Set(t.items.map(o=>o.id)),n=new Date().toISOString(),r=[];for(let o of e){if(s.has(o.id))continue;let i={...o,labels:o.labels??[],status:o.status??"todo",createdAt:o.createdAt??n,updatedAt:n};t.items.push(i),r.push(i),s.add(i.id)}return r.length&&this.save(t),r}update(e,t){let s=this.load(),n=s.items.findIndex(o=>o.id===e);if(n<0)throw new Error(`backlog item ${e} not found`);let r={...s.items[n],...t,id:s.items[n].id,createdAt:s.items[n].createdAt,updatedAt:new Date().toISOString()};return s.items[n]=r,this.save(s),r}remove(e){let t=this.load(),s=t.items.length;return t.items=t.items.filter(n=>n.id!==e),t.items.length===s?!1:(this.save(t),!0)}toMarkdown(e){let t=["# Backlog","","_Generated from `backlog.json`. Do not edit by hand \u2014 use `agentx backlog` commands._",""],s={doing:[],blocked:[],todo:[],done:[]};for(let r of e.items)s[r.status].push(r);let n=[["doing","Doing"],["blocked","Blocked"],["todo","To Do"],["done","Done"]];for(let[r,o]of n)if(s[r].length){t.push(`## ${o}`,"");for(let i of s[r]){let c=r==="done"?"x":" ",a=i.assignee?`@${i.assignee} `:"",d=i.estimatedSeconds?` [time: ${Math.round(i.estimatedSeconds/60)}m]`:"",g=i.source?` (${i.source.type}: ${i.source.project}#${i.source.iid})`:"";t.push(`- [${c}] ${a}${i.title}${d}${g}`),i.source?.url&&t.push(` - <${i.source.url}>`),i.labels.length&&t.push(` - labels: ${i.labels.join(", ")}`),i.milestone&&t.push(` - milestone: ${i.milestone}`),i.blocker&&t.push(` - BLOCKED: ${i.blocker}`)}t.push("")}return t.join(`
|
|
3
|
+
`)}};function ke(l){return`${l.type}:${l.project}:${l.iid}`}function q(l,e,t){let s=[];if(e.type==="gitlab"){let n=t.channels.gitlab?.agentMappings??[];for(let r of l){let o=n.find(i=>i.agentId===r);o?.gitlabUsernames?.length?s.push(o.gitlabUsernames[0]):s.push(r)}}else if(e.type==="github"){let n=t.channels.github?.agentMappings??[];for(let r of l){let o=n.find(i=>i.agentId===r);o?.githubUsernames?.length?s.push(o.githubUsernames[0]):s.push(r)}}return s}async function _(l,e,t,s=()=>{}){return l.source?l.source.type==="gitlab"?ie(l.source,e,t,s):l.source.type==="github"?re(l.source,e,t,s):{ok:!1,error:`unknown source.type ${l.source.type}`,attempts:[]}:{ok:!0,attempts:[]}}async function ie(l,e,t,s){let n=t.channels.gitlab,r=n?.token,o=n?.host||(l.host?`https://${l.host}`:void 0);if(!r||!o)return{ok:!1,error:"gitlab token/host not configured",attempts:[]};let i=[],c=async(u,p={})=>fetch(`${o}/api/v4${u}`,{...p,headers:{"PRIVATE-TOKEN":r,"Content-Type":"application/json",...p.headers||{}}}),a=encodeURIComponent(l.project),d;e.assigneeUsernames!==void 0&&(d=(await Promise.all(e.assigneeUsernames.map(async p=>{try{let y=await fetch(`${o}/api/v4/users?username=${encodeURIComponent(p)}`,{headers:{"PRIVATE-TOKEN":r}});if(!y.ok)return null;let w=await y.json();return Array.isArray(w)&&w[0]?.id?w[0].id:null}catch{return null}}))).filter(p=>typeof p=="number"));let g;if(e.milestoneTitle!==void 0)if(e.milestoneTitle===null||e.milestoneTitle==="")g=0;else try{let u=await c(`/projects/${a}/milestones?title=${encodeURIComponent(e.milestoneTitle)}`);if(u.ok){let p=await u.json();Array.isArray(p)&&p[0]?.id&&(g=p[0].id)}}catch(u){s(`[backlog-sync] milestone lookup failed: ${u.message}`)}let m={};if(e.title!==void 0&&(m.title=e.title),e.description!==void 0&&(m.description=e.description),e.labels!==void 0&&(m.labels=e.labels.join(",")),e.addLabels?.length&&(m.add_labels=e.addLabels.join(",")),e.removeLabels?.length&&(m.remove_labels=e.removeLabels.join(",")),d!==void 0&&(m.assignee_ids=d),g!==void 0&&(m.milestone_id=g),e.state&&(m.state_event=e.state==="closed"?"close":"reopen"),Object.keys(m).length)try{let u=await c(`/projects/${a}/issues/${l.iid}`,{method:"PUT",body:JSON.stringify(m)});if(i.push({kind:"PUT issue",ok:u.ok,status:u.status}),!u.ok){let p=await u.text().catch(()=>"");return{ok:!1,attempts:i,error:`PUT issue ${u.status}: ${p.slice(0,200)}`,url:l.url}}}catch(u){return i.push({kind:"PUT issue",ok:!1,error:u.message}),{ok:!1,attempts:i,error:u.message,url:l.url}}if(e.note)try{let u=await c(`/projects/${a}/issues/${l.iid}/notes`,{method:"POST",body:JSON.stringify({body:e.note})});i.push({kind:"POST note",ok:u.ok,status:u.status})}catch(u){i.push({kind:"POST note",ok:!1,error:u.message})}return{ok:i.every(u=>u.ok),attempts:i,url:l.url}}async function re(l,e,t,s){let r=t.channels.github?.token;if(!r)return{ok:!1,error:"github token not configured",attempts:[]};let o=[],i=async(m,u={})=>fetch(`https://api.github.com${m}`,{...u,headers:{Authorization:`Bearer ${r}`,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28",...u.headers||{}}}),c=l.project,a;if(e.milestoneTitle!==void 0)if(e.milestoneTitle===null||e.milestoneTitle==="")a=null;else try{let m=await i(`/repos/${c}/milestones?state=all&per_page=100`);if(m.ok){let u=await m.json(),p=Array.isArray(u)?u.find(y=>y.title===e.milestoneTitle):null;p?.number&&(a=p.number)}}catch(m){s(`[backlog-sync] github milestone lookup failed: ${m.message}`)}let d;if(e.labels!==void 0)d=e.labels;else if(e.addLabels?.length||e.removeLabels?.length)try{let m=await i(`/repos/${c}/issues/${l.iid}`);if(m.ok){let u=await m.json(),p=Array.isArray(u.labels)?u.labels.map(w=>typeof w=="string"?w:w.name):[],y=new Set([...p,...e.addLabels??[]]);for(let w of e.removeLabels??[])y.delete(w);d=[...y]}}catch(m){s(`[backlog-sync] github label fetch failed: ${m.message}`)}let g={};if(e.title!==void 0&&(g.title=e.title),e.description!==void 0&&(g.body=e.description),d!==void 0&&(g.labels=d),e.assigneeUsernames!==void 0&&(g.assignees=e.assigneeUsernames),a!==void 0&&(g.milestone=a),e.state&&(g.state=e.state),Object.keys(g).length)try{let m=await i(`/repos/${c}/issues/${l.iid}`,{method:"PATCH",body:JSON.stringify(g)});if(o.push({kind:"PATCH issue",ok:m.ok,status:m.status}),!m.ok){let u=await m.text().catch(()=>"");return{ok:!1,attempts:o,error:`PATCH issue ${m.status}: ${u.slice(0,200)}`,url:l.url}}}catch(m){return o.push({kind:"PATCH issue",ok:!1,error:m.message}),{ok:!1,attempts:o,error:m.message,url:l.url}}if(e.note)try{let m=await i(`/repos/${c}/issues/${l.iid}/comments`,{method:"POST",body:JSON.stringify({body:e.note})});o.push({kind:"POST comment",ok:m.ok,status:m.status})}catch(m){o.push({kind:"POST comment",ok:!1,error:m.message})}return{ok:o.every(m=>m.ok),attempts:o,url:l.url}}var E=/^(\s*)-\s*\[( |x|X)\]\s*(.*)$/,A=/@([a-z0-9_-]+)/i,G=/\[time:\s*([0-9hm\s]+)\]/i;function ce(l){let e=l.match(/(\d+)\s*h/i),t=l.match(/(\d+)\s*m/i);return(e?parseInt(e[1],10)*3600:0)+(t?parseInt(t[1],10)*60:0)}var U=class{constructor(e,t,s=()=>{}){this.path=e;this.daemon=t;this.log=s;this.store=new j(this.path)}type="backlog";capabilities={listAll:!0,create:!1,transition:!1,labels:!0,search:!1};store;getStore(){return this.store}readMd(){let e=C(process.cwd(),this.path);return L(e)?T(e,"utf-8").split(`
|
|
4
|
+
`):[]}writeMd(e){let t=C(process.cwd(),this.path);J(t,e.join(`
|
|
5
|
+
`))}async listOpen(e){if(this.store.exists())return this.store.list().filter(r=>r.status!=="done"&&(!r.assignee||r.assignee.toLowerCase()===e.toLowerCase())).map(r=>this.toWorkItem(r));let t=this.readMd(),s=[];for(let n=0;n<t.length;n++){let r=t[n].match(E);if(!r||r[2].toLowerCase()==="x")continue;let i=r[3],c=i.match(A);if(c&&c[1].toLowerCase()!==e.toLowerCase())continue;let a=i.match(G),d=i.replace(A,"").replace(G,"").trim();s.push({id:`backlog:${n}`,title:d||`Task at line ${n+1}`,assignee:c?c[1]:void 0,estimatedSeconds:a?ce(a[1]):void 0,priority:n})}return s}async listAll(e={}){return this.store.exists()?this.store.list().map(t=>this.toWorkItem(t)):[]}async claim(e,t){if(!this.store.exists())return;let s=this.store.findById(t);if(!s)return;let n=this.store.update(t,{assignee:s.assignee||e,status:"doing"});if(n.source&&this.daemon){let r=q([n.assignee||e],n.source,this.daemon);_(n,{assigneeUsernames:r,addLabels:["Doing"],removeLabels:["To Do"]},this.daemon,this.log).catch(o=>this.log(`[backlog-sync] claim failed for ${t}: ${o?.message??o}`))}}async report(e,t){if(this.store.exists()&&this.store.findById(e)){let i={};t.status==="done"?i.status="done":t.status==="blocked"?(i.status="blocked",i.blocker=t.blocker||t.note||void 0):t.status==="in-progress"&&(i.status="doing"),t.timeSeconds&&(i.estimatedSeconds=t.timeSeconds);let c=this.store.update(e,i);if(c.source&&this.daemon){let a={};t.status==="done"?(a.addLabels=["Done"],a.removeLabels=["Doing"]):t.status==="blocked"&&(a.addLabels=["Blocked"]),t.note&&(a.note=t.note),Object.keys(a).length&&_(c,a,this.daemon,this.log).catch(d=>this.log(`[backlog-sync] report failed for ${e}: ${d?.message??d}`))}return}if(!e.startsWith("backlog:"))throw new Error(`not a backlog id: ${e}`);let s=parseInt(e.slice(8),10),n=this.readMd(),r=n[s];if(!r)throw new Error(`backlog line ${s} missing`);if(!r.match(E))throw new Error(`backlog line ${s} not a checkbox`);if(t.status==="done"){n[s]=r.replace(/- \[ \]/,"- [x]");let i=t.timeSeconds?` <!-- done by agent, ${Math.round(t.timeSeconds/60)}m -->`:" <!-- done by agent -->";n.splice(s+1,0,i)}else t.status==="blocked"?n.splice(s+1,0,` <!-- BLOCKED: ${t.blocker||t.note||"?"} -->`):t.note&&n.splice(s+1,0,` <!-- ${t.note.replace(/-->/g,"\u2014 >")} -->`);this.writeMd(n)}toWorkItem(e){let t={todo:"todo",doing:"doing",blocked:"onhold",done:"done"}[e.status];return{id:e.id,title:e.title,description:e.description,assignee:e.assignee,estimatedSeconds:e.estimatedSeconds,priority:e.priority,labels:e.labels,stage:t,milestone:e.milestone,url:e.source?.url,updatedAt:e.updatedAt,state:e.status==="done"?"closed":"opened"}}},B=class{constructor(e,t){this.root=e;this.glob=t}type="wiki";capabilities={listAll:!1,create:!1,transition:!1,labels:!1,search:!1};walk(e,t){if(L(e))for(let s of oe(e)){let n=le(e,s);ae(n).isDirectory()?this.walk(n,t):s.endsWith(".md")&&t.push(n)}}async listOpen(e){let t=[];this.walk(C(process.cwd(),this.root),t);let s=[];for(let n of t){let r=T(n,"utf-8").split(`
|
|
6
|
+
`);for(let o=0;o<r.length;o++){let i=r[o].match(E);if(!i||i[2].toLowerCase()==="x")continue;let c=i[3].match(A);!c||c[1].toLowerCase()!==e.toLowerCase()||s.push({id:`wiki:${n}:${o}`,title:i[3].replace(A,"").trim(),assignee:c[1],priority:o})}}return s}async claim(){}async report(e,t){let s=e.split(":"),n=parseInt(s[s.length-1],10),r=s.slice(1,-1).join(":");if(!L(r))return;let o=T(r,"utf-8").split(`
|
|
7
|
+
`);t.status==="done"&&o[n]&&(o[n]=o[n].replace(/- \[ \]/,"- [x]"),J(r,o.join(`
|
|
8
|
+
`)))}},P=class{constructor(e,t,s,n,r){this.host=e;this.token=t;this.projects=s;this.agentUsernames=n;this.log=r}type="gitlab";capabilities={listAll:!0,create:!0,transition:!0,labels:!0,search:!0};usernameToAgent(e){for(let[t,s]of Object.entries(this.agentUsernames))if(s.some(n=>n.toLowerCase()===e.toLowerCase()))return t}mapIssue(e,t){let s=e.references?.full?.split("#")[0]||t,n=[],r;Array.isArray(e.labels)&&(e.labels.length&&typeof e.labels[0]=="object"?(r=e.labels.map(a=>({name:a.name,color:a.color,text_color:a.text_color})),n=r.map(a=>a.name)):n=e.labels);let i=(Array.isArray(e.assignees)&&e.assignees.length?e.assignees:e.assignee?[e.assignee]:[]).map(a=>({username:a.username,name:a.name,avatarUrl:a.avatar_url})),c=i[0]?.username;return{id:`gitlab:${s}:issue:${e.iid}`,title:e.title,description:e.description||void 0,assignee:c?this.usernameToAgent(c)||c:void 0,assignees:i,url:e.web_url,priority:e.weight??99,estimatedSeconds:e.time_stats?.time_estimate||void 0,labels:n,labelDetails:r,updatedAt:e.updated_at,state:e.state==="closed"?"closed":"opened",milestone:e.milestone?.title||void 0}}async api(e,t={}){let s=await fetch(`${this.host}/api/v4${e}`,{...t,headers:{"PRIVATE-TOKEN":this.token,"Content-Type":"application/json",...t.headers||{}}});if(!s.ok)throw new Error(`GitLab ${s.status}: ${await s.text().catch(()=>"")}`);return s.json()}async listOpen(e){let t=this.agentUsernames[e]||[];if(!t.length)return[];let s=[],n=this.projects.length?this.projects:["__ALL__"];for(let r of t)for(let o of n)try{let i=`assignee_username=${encodeURIComponent(r)}&state=opened&scope=all&per_page=20`,c=o==="__ALL__"?`/issues?${i}`:`/projects/${encodeURIComponent(o)}/issues?${i}`,a=await this.api(c);for(let d of a){let g=this.mapIssue(d,o);g.assignee=e,s.push(g)}}catch(i){this.log(`[business] GitLab listOpen failed for ${r} on ${o}: ${i.message}`)}return s}async claim(e,t){let{project:s,iid:n,type:r}=this.parseId(t),o=`/projects/${encodeURIComponent(s)}/${r==="issue"?"issues":"merge_requests"}/${n}?add_labels=Doing&remove_labels=To Do`;try{await this.api(o,{method:"PUT"})}catch(i){this.log(`[business] claim label update failed: ${i.message}`)}}async report(e,t){let{project:s,iid:n,type:r}=this.parseId(e),o=r==="issue"?"issues":"merge_requests";if(t.timeSeconds&&t.timeSeconds>0){let i=Math.max(1,Math.round(t.timeSeconds/60)),c=i>=60?`${Math.floor(i/60)}h${i%60?` ${i%60}m`:""}`:`${i}m`;try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}/add_spent_time?duration=${encodeURIComponent(c)}`,{method:"POST"})}catch(a){this.log(`[business] add_spent_time failed: ${a.message}`)}}if(t.status==="done")try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}?add_labels=Done&remove_labels=Doing`,{method:"PUT"})}catch(i){this.log(`[business] done label update failed: ${i.message}`)}else if(t.status==="blocked")try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}?add_labels=Blocked`,{method:"PUT"})}catch(i){this.log(`[business] blocked label update failed: ${i.message}`)}if(t.note)try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}/notes`,{method:"POST",body:JSON.stringify({body:t.note})})}catch(i){this.log(`[business] report note failed: ${i.message}`)}}async listAll(e={}){let t=e.sinceDays??30,s=new Date(Date.now()-t*24*36e5).toISOString(),n=new URLSearchParams,r=e.state??"opened";n.set("state",r),n.set("updated_after",s),n.set("scope","all"),n.set("per_page","100"),n.set("with_labels_details","true"),n.set("order_by","updated_at"),n.set("sort","desc"),e.labels?.length&&n.set("labels",e.labels.join(",")),e.milestone&&n.set("milestone",e.milestone),e.search&&n.set("search",e.search);let o=this.projects.length?this.projects:["__ALL__"],i=[];for(let a of o)try{let d=a==="__ALL__"?`/issues?${n.toString()}`:`/projects/${encodeURIComponent(a)}/issues?${n.toString()}`,g=await this.api(d);for(let m of g)i.push(this.mapIssue(m,a))}catch(d){this.log(`[business] GitLab listAll failed on ${a}: ${d.message}`)}let c=i;if(e.notLabels?.length){let a=new Set(e.notLabels);c=c.filter(d=>!(d.labels||[]).some(g=>a.has(g)))}if(e.withoutScopedPrefix){let a=e.withoutScopedPrefix+"::";c=c.filter(d=>!(d.labels||[]).some(g=>g.startsWith(a)))}return c}async setState(e,t){let{project:s,iid:n,type:r}=this.parseId(e),o=r==="issue"?"issues":"merge_requests";await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}?state_event=${t}`,{method:"PUT"})}async getItem(e){let{project:t,iid:s}=this.parseId(e);try{let n=await this.api(`/projects/${encodeURIComponent(t)}/issues/${s}?with_labels_details=true`);return{...this.mapIssue(n,t),descriptionHtml:n.description_html||void 0,createdAt:n.created_at,author:n.author?{username:n.author.username,name:n.author.name,avatarUrl:n.author.avatar_url}:void 0,allLabels:Array.isArray(n.labels)?typeof n.labels[0]=="object"?n.labels.map(o=>o.name):n.labels:[]}}catch(n){return this.log(`[board] GitLab getItem failed for ${e}: ${n.message}`),null}}async updateItem(e,t){let{project:s,iid:n}=this.parseId(e),r;t.assigneeUsernames&&(r=(await Promise.all(t.assigneeUsernames.map(async d=>{try{let g=await this.api(`/users?username=${encodeURIComponent(d)}`);return Array.isArray(g)&&g[0]?.id?g[0].id:null}catch{return null}}))).filter(d=>typeof d=="number"));let o;if(t.milestoneTitle!==void 0)if(t.milestoneTitle===null||t.milestoneTitle==="")o=0;else try{let a=await this.api(`/projects/${encodeURIComponent(s)}/milestones?title=${encodeURIComponent(t.milestoneTitle)}`);Array.isArray(a)&&a[0]?.id&&(o=a[0].id)}catch(a){this.log(`[board] milestone lookup failed for "${t.milestoneTitle}": ${a.message}`)}let i={};t.title!==void 0&&(i.title=t.title),t.description!==void 0&&(i.description=t.description),t.labels!==void 0&&(i.labels=t.labels.join(",")),t.addLabels?.length&&(i.add_labels=t.addLabels.join(",")),t.removeLabels?.length&&(i.remove_labels=t.removeLabels.join(",")),r!==void 0&&(i.assignee_ids=r),o!==void 0&&(i.milestone_id=o);let c=await this.api(`/projects/${encodeURIComponent(s)}/issues/${n}`,{method:"PUT",body:JSON.stringify(i)});return this.mapIssue(c,s)}async listMembers(){let e=new Map;for(let t of this.projects)try{let s=await this.api(`/projects/${encodeURIComponent(t)}/members/all?per_page=100`);for(let n of s)n.username&&!e.has(n.username)&&e.set(n.username,{username:n.username,name:n.name,avatarUrl:n.avatar_url})}catch(s){this.log(`[board] listMembers failed for ${t}: ${s.message}`)}return[...e.values()].sort((t,s)=>t.username.localeCompare(s.username))}async listMilestones(){let e=new Map;for(let s of this.projects)try{let n=await this.api(`/projects/${encodeURIComponent(s)}/milestones?per_page=100`);for(let r of n)r.title&&!e.has(r.title)&&e.set(r.title,{id:r.id,title:r.title,state:r.state,dueDate:r.due_date})}catch(n){this.log(`[board] listMilestones failed for ${s}: ${n.message}`)}let t=[...e.values()];return t.sort((s,n)=>s.state!==n.state?s.state==="active"?-1:1:s.title.localeCompare(n.title)),t}async create(e){let t=e.projectHint||this.projects[0];if(!t)throw new Error("GitLabWorkSource.create requires projectHint or configured projects");let s,n=e.assigneeUsernames?.length?e.assigneeUsernames:e.assignee?(this.agentUsernames[e.assignee]||[]).slice(0,1):[];n.length&&(s=(await Promise.all(n.map(async a=>{try{let d=await this.api(`/users?username=${encodeURIComponent(a)}`);return Array.isArray(d)&&d[0]?.id?d[0].id:null}catch(d){return this.log(`[board] user lookup failed for ${a}: ${d.message}`),null}}))).filter(a=>typeof a=="number"));let r;if(e.milestoneTitle)try{let c=await this.api(`/projects/${encodeURIComponent(t)}/milestones?title=${encodeURIComponent(e.milestoneTitle)}`);Array.isArray(c)&&c[0]?.id&&(r=c[0].id)}catch(c){this.log(`[board] milestone lookup failed for "${e.milestoneTitle}": ${c.message}`)}let o={title:e.title,description:e.description,labels:(e.labels||[]).join(",")};s?.length&&(o.assignee_ids=s),r!==void 0&&(o.milestone_id=r);let i=await this.api(`/projects/${encodeURIComponent(t)}/issues`,{method:"POST",body:JSON.stringify(o)});return this.mapIssue(i,t)}async transition(e,t,s){await this.transitionMany(e,t?[t]:[],s?[s]:[])}async transitionMany(e,t,s){let{project:n,iid:r,type:o}=this.parseId(e),i=o==="issue"?"issues":"merge_requests",c=new URLSearchParams;t.length&&c.set("add_labels",t.join(",")),s.length&&c.set("remove_labels",s.join(",")),c.toString()&&await this.api(`/projects/${encodeURIComponent(n)}/${i}/${r}?${c.toString()}`,{method:"PUT"})}parseId(e){let s=e.replace(/^gitlab:/,"").split(":"),n=s.pop(),r=s.pop()==="mr"?"mr":"issue";return{project:s.join(":"),type:r,iid:n}}};function je(l,e,t){let s=l.workSource;switch(s.type){case"backlog":return new U(s.path,e,t);case"wiki":return new B(s.path,s.glob);case"gitlab":{let n=e.channels.gitlab;if(!n?.token)throw new Error("business.workSource.type=gitlab requires channels.gitlab.token");let r={};for(let i of n.agentMappings)i.gitlabUsernames.length&&(r[i.agentId]=i.gitlabUsernames);let o=s.projects.length?s.projects:n.routes.map(i=>i.project);return new P(n.host,n.token,o,r,t)}}}import{existsSync as b,readFileSync as D,writeFileSync as z,mkdirSync as x,readdirSync as O,rmSync as de,statSync as k}from"fs";import{resolve as X,relative as me,dirname as M,join as S,extname as v,basename as F}from"path";var h=200*1024,V=new Set([".md",".markdown",".txt"]),I=new Set([".md",".markdown",".txt",".json",".jsonc",".yaml",".yml",".toml",".sh",".bash",".zsh",".py",".js",".mjs",".ts",".tsx",".jsx",".css",".html",".lock",".cfg",".ini",".env",".gitignore"]),ue=6,ge=new Set(["CLAUDE.md","SOUL.md","IDENTITY.md","PERSONA.md","AGENT.md","BOOTSTRAP.md"]);function $(l,e){if(!l)throw new Error("Agent workspace is not configured.");let t=X(l);if(b(t)||x(t,{recursive:!0}),!e||e.startsWith("/")||e.match(/^[a-zA-Z]:/))throw new Error(`Path must be relative to the workspace: ${e}`);let s=X(t,e);if(me(t,s).startsWith(".."))throw new Error(`Path escapes the workspace: ${e}`);return s}function Y(l){let e=v(l).toLowerCase();if(!V.has(e))throw new Error(`Only text files (.md, .markdown, .txt) can be edited from the admin panel (got ${e}).`)}function R(l,e){let t=l.split(/\r?\n/).find(s=>s.trim().startsWith("#"));return t&&t.replace(/^#+\s*/,"").trim().slice(0,120)||e}function Te(l){let e=$(l,"."),t=[],s=new Set,n=[];try{n=O(e)}catch{}for(let i of ge){let c=S(e,i);if(b(c)&&k(c).isFile()){let a=k(c).size,d=a>0&&a<h?W(c):"";t.push({path:i,title:R(d,i),size:a,exists:!0}),s.add(i)}else t.push({path:i,title:i,size:0,exists:!1}),s.add(i)}for(let i of n){if(s.has(i))continue;let c=S(e,i);try{let a=k(c);if(!a.isFile()||!V.has(v(i).toLowerCase()))continue;let d=a.size>0&&a.size<h?W(c):"";t.push({path:i,title:R(d,i),size:a.size,exists:!0})}catch{}}let r=[],o=S(e,".claude","skills");if(b(o))try{for(let i of O(o)){let c=S(o,i),a;try{a=k(c)}catch{continue}if(!a.isDirectory())continue;let d=S(c,"SKILL.md");if(!b(d))continue;let g=k(d),m=g.size<h?W(d):"";r.push({slug:i,title:R(m,i),size:g.size,path:`.claude/skills/${i}/SKILL.md`})}}catch{}return r.sort((i,c)=>i.slug.localeCompare(c.slug)),{identity:t,skills:r,workspace:e}}function W(l){try{return D(l,"utf-8")}catch{return""}}function Le(l,e){let t=$(l,e);if(Y(t),!b(t))return{path:e,content:""};if(k(t).size>h)throw new Error(`File is too large to edit in the admin panel (> ${h/1024} KB).`);return{path:e,content:D(t,"utf-8")}}function Ce(l,e,t){let s=$(l,e);Y(s);let n=Buffer.from(t,"utf-8");if(n.length>h)throw new Error(`File too large (${n.length} > ${h} bytes).`);return x(M(s),{recursive:!0}),z(s,n),{path:e,bytes:n.length}}function Ee(l,e,t={}){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error("Skill slug must be lowercase (letters, digits, -, _) and start with a letter or digit.");let s=`.claude/skills/${e}/SKILL.md`,n=$(l,s);if(b(n))throw new Error(`Skill "${e}" already exists.`);x(M(n),{recursive:!0});let r=t.content?.trim()?t.content:fe(e,t.title);return z(n,r),{slug:e,path:s}}function Ue(l,e){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);let t=$(l,`.claude/skills/${e}`);if(!b(t))throw new Error(`Skill "${e}" not found.`);if(!k(t).isDirectory())throw new Error(`Skill path is not a directory: ${e}`);return de(t,{recursive:!0,force:!0}),{slug:e}}function Be(l,e){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);let t=$(l,`.claude/skills/${e}`);if(!b(t))throw new Error(`Skill "${e}" not found.`);let s=[];return n(t,"",0,s),s.sort((r,o)=>r.kind!==o.kind?r.kind==="dir"?-1:1:r.path.localeCompare(o.path)),s;function n(r,o,i,c){if(i>ue)return;let a=[];try{a=O(r)}catch{return}for(let d of a){if(d==="node_modules"||d===".git")continue;let g=S(r,d),m=o?`${o}/${d}`:d,u;try{u=k(g)}catch{continue}if(u.isDirectory())c.push({path:`${m}/`,kind:"dir",size:0,editable:!1}),n(g,m,i+1,c);else if(u.isFile()){let p=v(d).toLowerCase(),y=I.has(p)||I.has(d.toLowerCase());c.push({path:m,kind:"file",size:u.size,editable:y&&u.size<h})}}}}function Pe(l,e,t){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);if(!t||t.includes(".."))throw new Error(`Invalid file path: ${t}`);let s=$(l,`.claude/skills/${e}/${t}`);if(!b(s)||!k(s).isFile())throw new Error(`File not found: ${t}`);let n=v(s).toLowerCase(),r=F(s).toLowerCase();if(!I.has(n)&&!I.has(r))throw new Error(`File type not editable: ${t}`);if(k(s).size>h)throw new Error(`File too large to edit (> ${h/1024} KB): ${t}`);return{path:t,content:D(s,"utf-8")}}function Re(l,e,t,s){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);if(!t||t.includes(".."))throw new Error(`Invalid file path: ${t}`);let n=$(l,`.claude/skills/${e}/${t}`),r=v(n).toLowerCase(),o=F(n).toLowerCase();if(!I.has(r)&&!I.has(o))throw new Error(`File type not editable: ${t}`);let i=Buffer.from(s,"utf-8");if(i.length>h)throw new Error(`File too large (${i.length} > ${h} bytes).`);return x(M(n),{recursive:!0}),z(n,i),{path:t,bytes:i.length}}function We(l,e){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);let t=$(l,`.claude/skills/${e}`);if(!b(t))throw new Error(`Skill "${e}" not found.`);let s=n=>b(S(t,n));return s("pnpm-lock.yaml")?{manager:"pnpm",file:"pnpm-lock.yaml",command:"pnpm install"}:s("yarn.lock")?{manager:"yarn",file:"yarn.lock",command:"yarn install"}:s("package.json")?{manager:"npm",file:"package.json",command:"npm install"}:s("requirements.txt")?{manager:"pip",file:"requirements.txt",command:"pip install -r requirements.txt"}:s("Gemfile")?{manager:"bundle",file:"Gemfile",command:"bundle install"}:{manager:null,file:null,command:null}}function fe(l,e){return`# ${e?.trim()||l}
|
|
9
|
+
|
|
10
|
+
Describe what this skill does and when the agent should use it. Keep it under ~5 lines \u2014 the agent reads this to decide whether to invoke the skill.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
- Trigger condition 1
|
|
14
|
+
- Trigger condition 2
|
|
15
|
+
|
|
16
|
+
## How to use
|
|
17
|
+
Document the arguments, commands, or files the skill expects. Example:
|
|
18
|
+
|
|
19
|
+
\`\`\`bash
|
|
20
|
+
# Example invocation
|
|
21
|
+
claude-code ${F(l)} --arg value
|
|
22
|
+
\`\`\`
|
|
23
|
+
`}export{j as a,ke as b,q as c,_ as d,P as e,je as f,Te as g,Le as h,Ce as i,Ee as j,Ue as k,Be as l,Pe as m,Re as n,We as o};
|
|
24
|
+
//# sourceMappingURL=chunk-3WQFCIB6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/business/work-pool.ts","../src/business/backlog-store.ts","../src/business/backlog-sync.ts","../src/daemon/file-ops.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from \"fs\"\nimport { resolve, join } from \"path\"\nimport type { BusinessConfig, BusinessWorkSource } from \"./config\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport { BacklogStore } from \"./backlog-store\"\nimport { syncBacklogItemToSource, resolveAssigneesForSource, type BacklogPatch } from \"./backlog-sync\"\n\n// --- Work pool: pluggable source of tasks for the business layer ---\n\nexport interface WorkItem {\n id: string // stable id for claim/report (e.g. \"backlog:line-12\", \"gitlab:proj:issue:42\")\n title: string\n description?: string\n assignee?: string // agentId\n estimatedSeconds?: number\n url?: string\n priority?: number // lower = higher priority\n /** Board stage / kanban column id. Was a closed enum back when boards\n * had a fixed schema; widened to `string` because per-project boards\n * can declare arbitrary column ids. The well-known names below remain\n * the standard set most boards use. */\n stage?: string // typically: \"triage\" | \"todo\" | \"doing\" | \"onhold\" | \"review\" | \"done\"\n /** Raw labels from the source (GitLab). Empty array if source doesn't support labels. */\n labels?: string[]\n /** Label name+color when the source returns colored labels (GitLab with_labels_details). */\n labelDetails?: Array<{ name: string; color: string; text_color?: string }>\n /** ISO timestamp of last update on the source. */\n updatedAt?: string\n /** Open/closed state from the source. */\n state?: \"opened\" | \"closed\"\n /** Optional milestone title (GitLab: issue.milestone.title). */\n milestone?: string\n /** GitLab assignees (0..n). `assignee` above is the agentId alias for assignees[0]. */\n assignees?: Array<{ username: string; name?: string; avatarUrl?: string }>\n}\n\n/** Member of a source (GitLab project members) — used for assignee pickers. */\nexport interface WorkMember {\n username: string\n name?: string\n avatarUrl?: string\n}\n\n/** Milestone on a source — used for milestone pickers. */\nexport interface WorkMilestone {\n id: number | string\n title: string\n state?: \"active\" | \"closed\"\n dueDate?: string\n}\n\n/** Full issue detail — a superset of WorkItem for the detail view. */\nexport interface WorkDetail extends WorkItem {\n /** Raw body, may be markdown. */\n descriptionHtml?: string\n /** ISO timestamp of creation. */\n createdAt?: string\n /** Author (issue reporter). */\n author?: { username: string; name?: string; avatarUrl?: string }\n /** Full labels list (all of them). */\n allLabels?: string[]\n}\n\nexport interface WorkReport {\n status: \"in-progress\" | \"done\" | \"blocked\"\n note?: string\n timeSeconds?: number\n blocker?: string\n}\n\n/** What a WorkSource can do beyond the minimal listOpen/claim/report triple. */\nexport interface WorkSourceCapabilities {\n /** Enumerate items beyond assignee filter — required for a board view. */\n listAll: boolean\n /** Create new items (e.g. POST GitLab issue). */\n create: boolean\n /** Arbitrary column transitions (label add/remove pair). */\n transition: boolean\n /** Surfaces labels[] on returned items. */\n labels: boolean\n /** Server-side text search. */\n search: boolean\n}\n\nexport interface WorkCreateInput {\n title: string\n description?: string\n /** agentId */\n assignee?: string\n /** Raw GitLab usernames — bypass agentId resolution for UI-driven creates. */\n assigneeUsernames?: string[]\n labels?: string[]\n /** Milestone title (resolved to id). */\n milestoneTitle?: string\n /** For sources that span multiple projects (gitlab): \"group/project\". */\n projectHint?: string\n}\n\nexport interface ListAllOpts {\n sinceDays?: number\n labels?: string[]\n /** Exclude items carrying any of these labels (client-side filter). */\n notLabels?: string[]\n /** Exclude items whose labels carry this scoped prefix (e.g. \"Status\"). */\n withoutScopedPrefix?: string\n search?: string\n assignee?: string\n /** GitLab issue state. Defaults to \"opened\" (boards hide closed issues). */\n state?: \"opened\" | \"closed\" | \"all\"\n /** Milestone filter (GitLab \"milestone\" query param). */\n milestone?: string\n}\n\nexport interface WorkSource {\n type: string\n capabilities: WorkSourceCapabilities\n listOpen(agentId: string): Promise<WorkItem[]>\n claim(agentId: string, itemId: string): Promise<void>\n report(itemId: string, update: WorkReport): Promise<void>\n /** Optional — gated by capabilities.listAll. */\n listAll?(opts: ListAllOpts): Promise<WorkItem[]>\n /** Optional — gated by capabilities.create. */\n create?(input: WorkCreateInput): Promise<WorkItem>\n /**\n * Optional — gated by capabilities.transition. Add `toLabel` and remove\n * `fromLabel` (if provided) from the item. Used by the kanban board on drag.\n */\n transition?(itemId: string, toLabel: string, fromLabel?: string): Promise<void>\n}\n\n// ---------- BacklogWorkSource: GFM checklist file ----------\n// Format in .agentx/backlog.md (or configured path):\n//\n// - [ ] @alice Implement login form [time: 2h]\n// - [x] @alice Write unit tests (done)\n// - [ ] @bob Design pricing page [time: 1h30m]\n//\n// Line number is the stable id.\n\nconst CHECK_RE = /^(\\s*)-\\s*\\[( |x|X)\\]\\s*(.*)$/\nconst MENTION_RE = /@([a-z0-9_-]+)/i\nconst TIME_RE = /\\[time:\\s*([0-9hm\\s]+)\\]/i\n\nfunction parseDuration(s: string): number {\n // \"2h30m\" -> 9000 s. \"1h\" -> 3600. \"45m\" -> 2700.\n const hMatch = s.match(/(\\d+)\\s*h/i)\n const mMatch = s.match(/(\\d+)\\s*m/i)\n return (hMatch ? parseInt(hMatch[1], 10) * 3600 : 0) + (mMatch ? parseInt(mMatch[1], 10) * 60 : 0)\n}\n\nexport class BacklogWorkSource implements WorkSource {\n type = \"backlog\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: false, transition: false, labels: true, search: false,\n }\n private store: BacklogStore\n\n constructor(\n private path: string,\n private daemon?: DaemonConfig,\n private log: (...args: unknown[]) => void = () => {},\n ) {\n this.store = new BacklogStore(this.path)\n }\n\n /** Expose the store so the CLI and sync-back can share state. */\n getStore(): BacklogStore {\n return this.store\n }\n\n private readMd(): string[] {\n const full = resolve(process.cwd(), this.path)\n if (!existsSync(full)) return []\n return readFileSync(full, \"utf-8\").split(\"\\n\")\n }\n\n private writeMd(lines: string[]): void {\n const full = resolve(process.cwd(), this.path)\n writeFileSync(full, lines.join(\"\\n\"))\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n if (this.store.exists()) {\n const items = this.store.list().filter((it) =>\n it.status !== \"done\" &&\n (!it.assignee || it.assignee.toLowerCase() === agentId.toLowerCase()),\n )\n return items.map((it) => this.toWorkItem(it))\n }\n // Legacy markdown-checklist fallback (unchanged behavior).\n const lines = this.readMd()\n const items: WorkItem[] = []\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m) continue\n const checked = m[2].toLowerCase() === \"x\"\n if (checked) continue\n const body = m[3]\n const mention = body.match(MENTION_RE)\n if (mention && mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n const timeMatch = body.match(TIME_RE)\n const title = body.replace(MENTION_RE, \"\").replace(TIME_RE, \"\").trim()\n items.push({\n id: `backlog:${i}`,\n title: title || `Task at line ${i + 1}`,\n assignee: mention ? mention[1] : undefined,\n estimatedSeconds: timeMatch ? parseDuration(timeMatch[1]) : undefined,\n priority: i,\n })\n }\n return items\n }\n\n async listAll(_opts: ListAllOpts = {}): Promise<WorkItem[]> {\n if (!this.store.exists()) return []\n return this.store.list().map((it) => this.toWorkItem(it))\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n if (!this.store.exists()) return\n const item = this.store.findById(itemId)\n if (!item) return\n const updated = this.store.update(itemId, {\n assignee: item.assignee || agentId,\n status: \"doing\",\n })\n if (updated.source && this.daemon) {\n const usernames = resolveAssigneesForSource([updated.assignee || agentId], updated.source, this.daemon)\n void syncBacklogItemToSource(updated, {\n assigneeUsernames: usernames,\n addLabels: [\"Doing\"],\n removeLabels: [\"To Do\"],\n }, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] claim failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // Structured store path.\n if (this.store.exists() && this.store.findById(itemId)) {\n const patch: Partial<import(\"./backlog-store\").BacklogItem> = {}\n if (update.status === \"done\") patch.status = \"done\"\n else if (update.status === \"blocked\") {\n patch.status = \"blocked\"\n patch.blocker = update.blocker || update.note || undefined\n } else if (update.status === \"in-progress\") patch.status = \"doing\"\n if (update.timeSeconds) patch.estimatedSeconds = update.timeSeconds\n const item = this.store.update(itemId, patch)\n\n if (item.source && this.daemon) {\n const sourcePatch: BacklogPatch = {}\n if (update.status === \"done\") {\n sourcePatch.addLabels = [\"Done\"]\n sourcePatch.removeLabels = [\"Doing\"]\n } else if (update.status === \"blocked\") {\n sourcePatch.addLabels = [\"Blocked\"]\n }\n if (update.note) sourcePatch.note = update.note\n if (Object.keys(sourcePatch).length) {\n void syncBacklogItemToSource(item, sourcePatch, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] report failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n return\n }\n\n // Legacy markdown path (line-number ids).\n if (!itemId.startsWith(\"backlog:\")) throw new Error(`not a backlog id: ${itemId}`)\n const lineNo = parseInt(itemId.slice(\"backlog:\".length), 10)\n const lines = this.readMd()\n const line = lines[lineNo]\n if (!line) throw new Error(`backlog line ${lineNo} missing`)\n const m = line.match(CHECK_RE)\n if (!m) throw new Error(`backlog line ${lineNo} not a checkbox`)\n\n if (update.status === \"done\") {\n lines[lineNo] = line.replace(/- \\[ \\]/, \"- [x]\")\n const note = update.timeSeconds\n ? ` <!-- done by agent, ${Math.round(update.timeSeconds / 60)}m -->`\n : \" <!-- done by agent -->\"\n lines.splice(lineNo + 1, 0, note)\n } else if (update.status === \"blocked\") {\n lines.splice(lineNo + 1, 0, ` <!-- BLOCKED: ${update.blocker || update.note || \"?\"} -->`)\n } else if (update.note) {\n lines.splice(lineNo + 1, 0, ` <!-- ${update.note.replace(/-->/g, \"— >\")} -->`)\n }\n this.writeMd(lines)\n }\n\n private toWorkItem(it: import(\"./backlog-store\").BacklogItem): WorkItem {\n const stage = ({ todo: \"todo\", doing: \"doing\", blocked: \"onhold\", done: \"done\" } as const)[it.status]\n return {\n id: it.id,\n title: it.title,\n description: it.description,\n assignee: it.assignee,\n estimatedSeconds: it.estimatedSeconds,\n priority: it.priority,\n labels: it.labels,\n stage,\n milestone: it.milestone,\n url: it.source?.url,\n updatedAt: it.updatedAt,\n state: it.status === \"done\" ? \"closed\" : \"opened\",\n }\n }\n}\n\n// ---------- WikiWorkSource: scan markdown files for @mention checkboxes ----------\n\nexport class WikiWorkSource implements WorkSource {\n type = \"wiki\"\n capabilities: WorkSourceCapabilities = {\n listAll: false, create: false, transition: false, labels: false, search: false,\n }\n constructor(private root: string, private glob: string) {}\n\n private walk(dir: string, out: string[]): void {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const st = statSync(full)\n if (st.isDirectory()) {\n this.walk(full, out)\n } else if (entry.endsWith(\".md\")) {\n out.push(full)\n }\n }\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const files: string[] = []\n this.walk(resolve(process.cwd(), this.root), files)\n const items: WorkItem[] = []\n for (const file of files) {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m || m[2].toLowerCase() === \"x\") continue\n const mention = m[3].match(MENTION_RE)\n if (!mention || mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n items.push({\n id: `wiki:${file}:${i}`,\n title: m[3].replace(MENTION_RE, \"\").trim(),\n assignee: mention[1],\n priority: i,\n })\n }\n }\n return items\n }\n\n async claim(): Promise<void> { /* no-op */ }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // wiki:<file>:<line>\n const parts = itemId.split(\":\")\n const lineNo = parseInt(parts[parts.length - 1], 10)\n const file = parts.slice(1, -1).join(\":\")\n if (!existsSync(file)) return\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n if (update.status === \"done\" && lines[lineNo]) {\n lines[lineNo] = lines[lineNo].replace(/- \\[ \\]/, \"- [x]\")\n writeFileSync(file, lines.join(\"\\n\"))\n }\n }\n}\n\n// ---------- GitLabWorkSource: assignee-filtered issues via GitLab API ----------\n\nexport class GitLabWorkSource implements WorkSource {\n type = \"gitlab\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: true, transition: true, labels: true, search: true,\n }\n\n constructor(\n private host: string,\n private token: string,\n private projects: string[],\n /** Map from agentId -> GitLab username(s) */\n private agentUsernames: Record<string, string[]>,\n private log: (...args: unknown[]) => void,\n ) {}\n\n /** Reverse lookup: GitLab username -> agentId. Used when resolving board items\n * back to an agent (for the reconciler and card display). */\n private usernameToAgent(username: string): string | undefined {\n for (const [agentId, names] of Object.entries(this.agentUsernames)) {\n if (names.some((n) => n.toLowerCase() === username.toLowerCase())) return agentId\n }\n return undefined\n }\n\n private mapIssue(issue: any, project: string): WorkItem {\n const ns = issue.references?.full?.split(\"#\")[0] || project\n // `labels` may be a string[] or, with with_labels_details=true, an object[].\n let labels: string[] = []\n let labelDetails: Array<{ name: string; color: string; text_color?: string }> | undefined\n if (Array.isArray(issue.labels)) {\n if (issue.labels.length && typeof issue.labels[0] === \"object\") {\n labelDetails = issue.labels.map((l: any) => ({\n name: l.name, color: l.color, text_color: l.text_color,\n }))\n labels = labelDetails!.map((l) => l.name)\n } else {\n labels = issue.labels\n }\n }\n const rawAssignees: any[] = Array.isArray(issue.assignees) && issue.assignees.length\n ? issue.assignees\n : (issue.assignee ? [issue.assignee] : [])\n const assignees = rawAssignees.map((a) => ({\n username: a.username, name: a.name, avatarUrl: a.avatar_url,\n }))\n const firstUsername = assignees[0]?.username\n return {\n id: `gitlab:${ns}:issue:${issue.iid}`,\n title: issue.title,\n description: issue.description || undefined,\n // Prefer configured agentId mapping; fall back to the raw GitLab username\n // so cards still render an assignee even without agentMappings configured.\n assignee: firstUsername ? (this.usernameToAgent(firstUsername) || firstUsername) : undefined,\n assignees,\n url: issue.web_url,\n priority: issue.weight ?? 99,\n estimatedSeconds: issue.time_stats?.time_estimate || undefined,\n labels,\n labelDetails,\n updatedAt: issue.updated_at,\n state: issue.state === \"closed\" ? \"closed\" : \"opened\",\n milestone: issue.milestone?.title || undefined,\n }\n }\n\n private async api(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetch(`${this.host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": this.token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n if (!res.ok) throw new Error(`GitLab ${res.status}: ${await res.text().catch(() => \"\")}`)\n return res.json()\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const usernames = this.agentUsernames[agentId] || []\n if (!usernames.length) return []\n\n const items: WorkItem[] = []\n const projectList = this.projects.length\n ? this.projects\n : [\"__ALL__\"] // falls back to a global issues query\n\n for (const username of usernames) {\n for (const project of projectList) {\n try {\n const q = `assignee_username=${encodeURIComponent(username)}&state=opened&scope=all&per_page=20`\n const path = project === \"__ALL__\"\n ? `/issues?${q}`\n : `/projects/${encodeURIComponent(project)}/issues?${q}`\n const issues = await this.api(path)\n for (const issue of issues) {\n const item = this.mapIssue(issue, project)\n // Force assignee to the agent we queried for (listOpen is per-agent).\n item.assignee = agentId\n items.push(item)\n }\n } catch (e: any) {\n this.log(`[business] GitLab listOpen failed for ${username} on ${project}: ${e.message}`)\n }\n }\n }\n return items\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n // itemId: \"gitlab:<project>:issue:<iid>\"\n const { project, iid, type } = this.parseId(itemId)\n // Transition labels: add \"Doing\", remove \"To Do\"\n const path = `/projects/${encodeURIComponent(project)}/${type === \"issue\" ? \"issues\" : \"merge_requests\"}/${iid}?add_labels=Doing&remove_labels=To Do`\n try {\n await this.api(path, { method: \"PUT\" })\n } catch (e: any) {\n this.log(`[business] claim label update failed: ${e.message}`)\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n\n // Time tracking\n if (update.timeSeconds && update.timeSeconds > 0) {\n const mins = Math.max(1, Math.round(update.timeSeconds / 60))\n const dur = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? ` ${mins % 60}m` : \"\"}` : `${mins}m`\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/add_spent_time?duration=${encodeURIComponent(dur)}`,\n { method: \"POST\" },\n )\n } catch (e: any) {\n this.log(`[business] add_spent_time failed: ${e.message}`)\n }\n }\n\n // Status → labels\n if (update.status === \"done\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Done&remove_labels=Doing`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] done label update failed: ${e.message}`)\n }\n } else if (update.status === \"blocked\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Blocked`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] blocked label update failed: ${e.message}`)\n }\n }\n\n // Note/comment\n if (update.note) {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/notes`,\n { method: \"POST\", body: JSON.stringify({ body: update.note }) },\n )\n } catch (e: any) {\n this.log(`[business] report note failed: ${e.message}`)\n }\n }\n }\n\n async listAll(opts: ListAllOpts = {}): Promise<WorkItem[]> {\n const sinceDays = opts.sinceDays ?? 30\n const updatedAfter = new Date(Date.now() - sinceDays * 24 * 3600_000).toISOString()\n\n const params = new URLSearchParams()\n const state = opts.state ?? \"opened\"\n params.set(\"state\", state)\n // `updated_after` makes sense for the Closed column (bounded window) and for\n // keeping the open list recent; for \"opened\" we pass it too but GitLab treats\n // the window loosely (any issue updated since counts, regardless of open-age).\n params.set(\"updated_after\", updatedAfter)\n params.set(\"scope\", \"all\")\n params.set(\"per_page\", \"100\")\n params.set(\"with_labels_details\", \"true\")\n params.set(\"order_by\", \"updated_at\")\n params.set(\"sort\", \"desc\")\n if (opts.labels?.length) params.set(\"labels\", opts.labels.join(\",\"))\n if (opts.milestone) params.set(\"milestone\", opts.milestone)\n if (opts.search) params.set(\"search\", opts.search)\n\n const projectList = this.projects.length ? this.projects : [\"__ALL__\"]\n const items: WorkItem[] = []\n for (const project of projectList) {\n try {\n const path = project === \"__ALL__\"\n ? `/issues?${params.toString()}`\n : `/projects/${encodeURIComponent(project)}/issues?${params.toString()}`\n const issues = await this.api(path)\n for (const issue of issues) items.push(this.mapIssue(issue, project))\n } catch (e: any) {\n this.log(`[business] GitLab listAll failed on ${project}: ${e.message}`)\n }\n }\n\n // Client-side negative-label filter (GitLab API supports `not[labels]` only\n // via the `not[]` nested query form, which isn't convenient via URLSearchParams).\n let result = items\n if (opts.notLabels?.length) {\n const deny = new Set(opts.notLabels)\n result = result.filter((i) => !(i.labels || []).some((l) => deny.has(l)))\n }\n if (opts.withoutScopedPrefix) {\n const prefix = opts.withoutScopedPrefix + \"::\"\n result = result.filter((i) => !(i.labels || []).some((l) => l.startsWith(prefix)))\n }\n return result\n }\n\n /** Close or reopen an issue via state_event. */\n async setState(itemId: string, to: \"close\" | \"reopen\"): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?state_event=${to}`,\n { method: \"PUT\" },\n )\n }\n\n /** Fetch a single issue's full detail. */\n async getItem(itemId: string): Promise<WorkDetail | null> {\n const { project, iid } = this.parseId(itemId)\n try {\n const issue = await this.api(\n `/projects/${encodeURIComponent(project)}/issues/${iid}?with_labels_details=true`,\n )\n const base = this.mapIssue(issue, project)\n return {\n ...base,\n descriptionHtml: issue.description_html || undefined,\n createdAt: issue.created_at,\n author: issue.author ? {\n username: issue.author.username, name: issue.author.name, avatarUrl: issue.author.avatar_url,\n } : undefined,\n allLabels: Array.isArray(issue.labels)\n ? (typeof issue.labels[0] === \"object\" ? issue.labels.map((l: any) => l.name) : issue.labels)\n : [],\n }\n } catch (e: any) {\n this.log(`[board] GitLab getItem failed for ${itemId}: ${e.message}`)\n return null\n }\n }\n\n /** Update fields on an issue. Accepts string usernames for assignees. */\n async updateItem(\n itemId: string,\n patch: {\n title?: string\n description?: string\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n assigneeUsernames?: string[]\n milestoneTitle?: string | null\n },\n ): Promise<WorkItem> {\n const { project, iid } = this.parseId(itemId)\n\n // Resolve assignee usernames → user ids (parallel).\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames) {\n const results = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch { return null }\n }))\n assigneeIds = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id (null clears the milestone).\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestoneId = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${patch.milestoneTitle}\": ${e.message}`)\n }\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues/${iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n /** List project members (direct + inherited). Deduped by username across projects. */\n async listMembers(): Promise<WorkMember[]> {\n const byUsername = new Map<string, WorkMember>()\n for (const project of this.projects) {\n try {\n const members = await this.api(\n `/projects/${encodeURIComponent(project)}/members/all?per_page=100`,\n )\n for (const m of members) {\n if (m.username && !byUsername.has(m.username)) {\n byUsername.set(m.username, {\n username: m.username, name: m.name, avatarUrl: m.avatar_url,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMembers failed for ${project}: ${e.message}`)\n }\n }\n return [...byUsername.values()].sort((a, b) => a.username.localeCompare(b.username))\n }\n\n /** List milestones (active first, then closed). Deduped by title across projects. */\n async listMilestones(): Promise<WorkMilestone[]> {\n const byTitle = new Map<string, WorkMilestone>()\n for (const project of this.projects) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?per_page=100`,\n )\n for (const m of ms) {\n if (m.title && !byTitle.has(m.title)) {\n byTitle.set(m.title, {\n id: m.id, title: m.title, state: m.state, dueDate: m.due_date,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMilestones failed for ${project}: ${e.message}`)\n }\n }\n const all = [...byTitle.values()]\n all.sort((a, b) => {\n if (a.state !== b.state) return a.state === \"active\" ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n return all\n }\n\n async create(input: WorkCreateInput): Promise<WorkItem> {\n const project = input.projectHint || this.projects[0]\n if (!project) throw new Error(\"GitLabWorkSource.create requires projectHint or configured projects\")\n\n // Resolve assignee usernames → user ids. Prefer explicit `assigneeUsernames`,\n // fall back to agentId → configured mapping for legacy callers.\n let assignee_ids: number[] | undefined\n const usernames = input.assigneeUsernames?.length\n ? input.assigneeUsernames\n : (input.assignee ? (this.agentUsernames[input.assignee] || []).slice(0, 1) : [])\n if (usernames.length) {\n const results = await Promise.all(usernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch (e: any) {\n this.log(`[board] user lookup failed for ${u}: ${e.message}`)\n return null\n }\n }))\n assignee_ids = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestone_id: number | undefined\n if (input.milestoneTitle) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(input.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestone_id = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${input.milestoneTitle}\": ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {\n title: input.title,\n description: input.description,\n labels: (input.labels || []).join(\",\"),\n }\n if (assignee_ids?.length) body.assignee_ids = assignee_ids\n if (milestone_id !== undefined) body.milestone_id = milestone_id\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues`, {\n method: \"POST\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n async transition(itemId: string, toLabel: string, fromLabel?: string): Promise<void> {\n // Legacy single add/remove — boards use transitionMany.\n await this.transitionMany(itemId, toLabel ? [toLabel] : [], fromLabel ? [fromLabel] : [])\n }\n\n /** Multi-label transition; used by board drag-drop (scoped labels produce pairs). */\n async transitionMany(itemId: string, add: string[], remove: string[]): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n const params = new URLSearchParams()\n if (add.length) params.set(\"add_labels\", add.join(\",\"))\n if (remove.length) params.set(\"remove_labels\", remove.join(\",\"))\n if (!params.toString()) return\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?${params.toString()}`,\n { method: \"PUT\" },\n )\n }\n\n private parseId(itemId: string): { project: string; type: \"issue\" | \"mr\"; iid: string } {\n // \"gitlab:<project>:issue:<iid>\" (project may contain a slash → it's joined)\n const body = itemId.replace(/^gitlab:/, \"\")\n const parts = body.split(\":\")\n const iid = parts.pop() as string\n const type = parts.pop() === \"mr\" ? \"mr\" : \"issue\"\n const project = parts.join(\":\")\n return { project, type, iid }\n }\n}\n\n// ---------- Factory ----------\n\nexport function createWorkSource(\n business: BusinessConfig,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): WorkSource {\n const ws: BusinessWorkSource = business.workSource\n switch (ws.type) {\n case \"backlog\":\n return new BacklogWorkSource(ws.path, daemon, log)\n\n case \"wiki\":\n return new WikiWorkSource(ws.path, ws.glob)\n\n case \"gitlab\": {\n const gl = daemon.channels.gitlab\n if (!gl?.token) {\n throw new Error(\"business.workSource.type=gitlab requires channels.gitlab.token\")\n }\n const agentUsernames: Record<string, string[]> = {}\n for (const mapping of gl.agentMappings) {\n if (mapping.gitlabUsernames.length) {\n agentUsernames[mapping.agentId] = mapping.gitlabUsernames\n }\n }\n const projects = ws.projects.length\n ? ws.projects\n : gl.routes.map((r) => r.project)\n return new GitLabWorkSource(gl.host, gl.token, projects, agentUsernames, log)\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\n\n// --- Structured backlog store (Plan B + import) ---\n//\n// Replaces the legacy `.agentx/backlog.md` GFM checklist as the canonical\n// store when `.agentx/backlog.json` is present. The markdown view is\n// regenerated on every save so humans still get a readable diff.\n//\n// Why structured: importing issues from gitlab/github needs a stable\n// linkage back to the source (project + iid + url) so mutations\n// (assignee, labels, status, milestone) can be pushed upstream by the\n// sync-back layer. Encoding that in markdown comments is brittle.\n\nconst sourceSchema = z.object({\n type: z.enum([\"gitlab\", \"github\"]),\n host: z.string().optional(),\n project: z.string(),\n iid: z.number().int().positive(),\n url: z.string(),\n})\n\nconst itemSchema = z.object({\n id: z.string(),\n title: z.string(),\n description: z.string().optional(),\n assignee: z.string().optional(),\n labels: z.array(z.string()).default([]),\n milestone: z.string().optional(),\n status: z.enum([\"todo\", \"doing\", \"blocked\", \"done\"]).default(\"todo\"),\n priority: z.number().int().optional(),\n estimatedSeconds: z.number().int().optional(),\n source: sourceSchema.optional(),\n importedAt: z.string().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n blocker: z.string().optional(),\n doneNote: z.string().optional(),\n})\n\nconst fileSchema = z.object({\n version: z.literal(1),\n items: z.array(itemSchema).default([]),\n})\n\nexport type BacklogSource = z.infer<typeof sourceSchema>\nexport type BacklogItem = z.infer<typeof itemSchema>\nexport type BacklogFile = z.infer<typeof fileSchema>\n\nexport class BacklogStore {\n constructor(private path: string) {}\n\n /** Absolute path of the JSON store. */\n jsonPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.md$/i, \".json\")\n }\n\n /** Absolute path of the markdown view (alongside the JSON store). */\n mdPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.json$/i, \".md\")\n }\n\n exists(): boolean {\n return existsSync(this.jsonPath())\n }\n\n load(): BacklogFile {\n const p = this.jsonPath()\n if (!existsSync(p)) return { version: 1, items: [] }\n const raw = readFileSync(p, \"utf-8\")\n try {\n return fileSchema.parse(JSON.parse(raw))\n } catch (e: any) {\n throw new Error(`Backlog file invalid (${p}): ${e.message}`)\n }\n }\n\n save(file: BacklogFile): void {\n const jsonP = this.jsonPath()\n mkdirSync(dirname(jsonP), { recursive: true })\n writeFileSync(jsonP, JSON.stringify(file, null, 2) + \"\\n\")\n writeFileSync(this.mdPath(), this.toMarkdown(file))\n }\n\n list(): BacklogItem[] {\n return this.load().items\n }\n\n findById(id: string): BacklogItem | undefined {\n return this.list().find((i) => i.id === id)\n }\n\n /** Add an item; rejects duplicates by id. Returns the saved item. */\n add(input: Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }): BacklogItem {\n const file = this.load()\n if (file.items.some((i) => i.id === input.id)) {\n throw new Error(`backlog item ${input.id} already exists`)\n }\n const now = new Date().toISOString()\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n this.save(file)\n return item\n }\n\n /** Add many; skips items whose id already exists, returns the inserted set. */\n addMany(inputs: Array<Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }>): BacklogItem[] {\n const file = this.load()\n const existing = new Set(file.items.map((i) => i.id))\n const now = new Date().toISOString()\n const added: BacklogItem[] = []\n for (const input of inputs) {\n if (existing.has(input.id)) continue\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n added.push(item)\n existing.add(item.id)\n }\n if (added.length) this.save(file)\n return added\n }\n\n update(id: string, patch: Partial<BacklogItem>): BacklogItem {\n const file = this.load()\n const idx = file.items.findIndex((i) => i.id === id)\n if (idx < 0) throw new Error(`backlog item ${id} not found`)\n const updated: BacklogItem = {\n ...file.items[idx],\n ...patch,\n id: file.items[idx].id,\n createdAt: file.items[idx].createdAt,\n updatedAt: new Date().toISOString(),\n }\n file.items[idx] = updated\n this.save(file)\n return updated\n }\n\n remove(id: string): boolean {\n const file = this.load()\n const before = file.items.length\n file.items = file.items.filter((i) => i.id !== id)\n if (file.items.length === before) return false\n this.save(file)\n return true\n }\n\n /** Render a human-readable markdown view (generated, not authoritative). */\n toMarkdown(file: BacklogFile): string {\n const lines: string[] = [\n \"# Backlog\",\n \"\",\n \"_Generated from `backlog.json`. Do not edit by hand — use `agentx backlog` commands._\",\n \"\",\n ]\n const buckets: Record<BacklogItem[\"status\"], BacklogItem[]> = {\n doing: [], blocked: [], todo: [], done: [],\n }\n for (const item of file.items) buckets[item.status].push(item)\n const sections: Array<[BacklogItem[\"status\"], string]> = [\n [\"doing\", \"Doing\"], [\"blocked\", \"Blocked\"], [\"todo\", \"To Do\"], [\"done\", \"Done\"],\n ]\n for (const [key, label] of sections) {\n if (!buckets[key].length) continue\n lines.push(`## ${label}`, \"\")\n for (const it of buckets[key]) {\n const check = key === \"done\" ? \"x\" : \" \"\n const mention = it.assignee ? `@${it.assignee} ` : \"\"\n const time = it.estimatedSeconds\n ? ` [time: ${Math.round(it.estimatedSeconds / 60)}m]`\n : \"\"\n const src = it.source ? ` (${it.source.type}: ${it.source.project}#${it.source.iid})` : \"\"\n lines.push(`- [${check}] ${mention}${it.title}${time}${src}`)\n if (it.source?.url) lines.push(` - <${it.source.url}>`)\n if (it.labels.length) lines.push(` - labels: ${it.labels.join(\", \")}`)\n if (it.milestone) lines.push(` - milestone: ${it.milestone}`)\n if (it.blocker) lines.push(` - BLOCKED: ${it.blocker}`)\n }\n lines.push(\"\")\n }\n return lines.join(\"\\n\")\n }\n}\n\n/** Generate a stable BacklogItem.id for a fresh manual item. */\nexport function manualBacklogId(): string {\n return `manual:${randomUUID()}`\n}\n\n/** Build the canonical id for an imported source issue. */\nexport function sourceBacklogId(source: BacklogSource): string {\n return `${source.type}:${source.project}:${source.iid}`\n}\n","import type { BacklogItem, BacklogSource } from \"./backlog-store\"\nimport type { DaemonConfig } from \"@/daemon/config\"\n\n// --- Sync-back layer: backlog item mutations → upstream issue ---\n//\n// When a backlog item carries a `source` ref (gitlab or github), changes\n// applied via the backlog work-source need to flow back to the original\n// issue: assignees, labels, title, description, milestone, open/close.\n// Otherwise the imported view drifts from upstream and humans browsing\n// the source see stale state.\n//\n// This module is intentionally stateless — callers pass the patch they\n// just applied to the local item; we forward the same shape to the\n// matching upstream API.\n\nexport interface BacklogPatch {\n title?: string\n description?: string\n /** Replace labels wholesale. Use addLabels/removeLabels for incremental. */\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n /** Username on the source (NOT agentId — caller resolves the mapping). */\n assigneeUsernames?: string[]\n /** Title of the milestone, or empty string to clear. */\n milestoneTitle?: string | null\n /** Open/close transition. */\n state?: \"open\" | \"closed\"\n /** Optional comment to post on the upstream issue. */\n note?: string\n}\n\nexport interface SyncBackResult {\n ok: boolean\n url?: string\n error?: string\n /** Each upstream call we attempted, in order. */\n attempts: Array<{ kind: string; ok: boolean; status?: number; error?: string }>\n}\n\n/** Resolve agentIds in a patch to upstream usernames using the daemon mappings. */\nexport function resolveAssigneesForSource(\n agentIds: string[],\n source: BacklogSource,\n daemon: DaemonConfig,\n): string[] {\n const out: string[] = []\n if (source.type === \"gitlab\") {\n const mappings = daemon.channels.gitlab?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.gitlabUsernames?.length) out.push(m.gitlabUsernames[0])\n else out.push(agentId)\n }\n } else if (source.type === \"github\") {\n const mappings = daemon.channels.github?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.githubUsernames?.length) out.push(m.githubUsernames[0])\n else out.push(agentId)\n }\n }\n return out\n}\n\n/** Push a backlog patch to the upstream source referenced by the item. */\nexport async function syncBacklogItemToSource(\n item: BacklogItem,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void = () => {},\n): Promise<SyncBackResult> {\n if (!item.source) return { ok: true, attempts: [] }\n if (item.source.type === \"gitlab\") return syncToGitLab(item.source, patch, daemon, log)\n if (item.source.type === \"github\") return syncToGitHub(item.source, patch, daemon, log)\n return { ok: false, error: `unknown source.type ${(item.source as any).type}`, attempts: [] }\n}\n\n// ---------- GitLab ----------\n\nasync function syncToGitLab(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gl = daemon.channels.gitlab\n const token = gl?.token\n const host = gl?.host || (source.host ? `https://${source.host}` : undefined)\n if (!token || !host) {\n return { ok: false, error: \"gitlab token/host not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`${host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n }\n\n const projectEnc = encodeURIComponent(source.project)\n\n // Resolve assignee usernames → ids (parallel) — only when we need them.\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames !== undefined) {\n const ids = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const res = await fetch(`${host}/api/v4/users?username=${encodeURIComponent(u)}`, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) return null\n const arr: any = await res.json()\n return Array.isArray(arr) && arr[0]?.id ? arr[0].id : null\n } catch { return null }\n }))\n assigneeIds = ids.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const res = await api(`/projects/${projectEnc}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`)\n if (res.ok) {\n const arr: any = await res.json()\n if (Array.isArray(arr) && arr[0]?.id) milestoneId = arr[0].id\n }\n } catch (e: any) {\n log(`[backlog-sync] milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // Build the PUT body.\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n if (patch.state) body.state_event = patch.state === \"closed\" ? \"close\" : \"reopen\"\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PUT issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PUT issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PUT issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}/notes`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST note\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST note\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n\n// ---------- GitHub ----------\n\nasync function syncToGitHub(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gh = daemon.channels.github\n const token = gh?.token\n if (!token) {\n return { ok: false, error: \"github token not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`https://api.github.com${path}`, {\n ...init,\n headers: {\n \"Authorization\": `Bearer ${token}`,\n \"Accept\": \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...(init.headers || {}),\n },\n })\n }\n\n const repo = source.project // GitHub IDs use \"owner/repo\" verbatim in the URL path.\n\n // Resolve milestone title → number.\n let milestoneNumber: number | null | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneNumber = null\n } else {\n try {\n const res = await api(`/repos/${repo}/milestones?state=all&per_page=100`)\n if (res.ok) {\n const arr: any = await res.json()\n const found = Array.isArray(arr) ? arr.find((m: any) => m.title === patch.milestoneTitle) : null\n if (found?.number) milestoneNumber = found.number\n }\n } catch (e: any) {\n log(`[backlog-sync] github milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // GitHub doesn't support add/remove labels in the issue PATCH the way\n // GitLab does — labels is a wholesale set. If only addLabels/removeLabels\n // were provided we read+merge.\n let nextLabels: string[] | undefined\n if (patch.labels !== undefined) {\n nextLabels = patch.labels\n } else if (patch.addLabels?.length || patch.removeLabels?.length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`)\n if (res.ok) {\n const issue: any = await res.json()\n const current: string[] = Array.isArray(issue.labels)\n ? issue.labels.map((l: any) => (typeof l === \"string\" ? l : l.name))\n : []\n const adds = new Set([...current, ...(patch.addLabels ?? [])])\n for (const r of patch.removeLabels ?? []) adds.delete(r)\n nextLabels = [...adds]\n }\n } catch (e: any) {\n log(`[backlog-sync] github label fetch failed: ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.body = patch.description\n if (nextLabels !== undefined) body.labels = nextLabels\n if (patch.assigneeUsernames !== undefined) body.assignees = patch.assigneeUsernames\n if (milestoneNumber !== undefined) body.milestone = milestoneNumber\n if (patch.state) body.state = patch.state\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PATCH issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PATCH issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PATCH issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}/comments`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST comment\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST comment\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync } from \"fs\"\nimport { resolve, relative, dirname, join, extname, basename } from \"path\"\n\n// --- Scoped file operations for the admin panel ---\n//\n// Lets the dashboard read/write identity files (CLAUDE.md / SOUL.md /\n// IDENTITY.md) and skill files (.claude/skills/<slug>/SKILL.md) WITHIN an\n// agent's workspace without ever touching the rest of the filesystem.\n// Every public function resolves the requested path against the agent's\n// workspace and refuses anything that would escape via .. symlinks, etc.\n\nconst MAX_FILE_BYTES = 200 * 1024 // 200 KB — plenty for CLAUDE.md / skills.\nconst ALLOWED_EXTENSIONS = new Set([\".md\", \".markdown\", \".txt\"])\n/** Extensions the skill-tree viewer is allowed to read/write. Broader than\n * identity editing — skills include shell scripts, lockfiles, configs. Kept\n * as an allow-list so a malicious prompt can't touch an arbitrary binary. */\nconst SKILL_EDITABLE_EXTENSIONS = new Set([\n \".md\", \".markdown\", \".txt\", \".json\", \".jsonc\", \".yaml\", \".yml\", \".toml\",\n \".sh\", \".bash\", \".zsh\", \".py\", \".js\", \".mjs\", \".ts\", \".tsx\", \".jsx\",\n \".css\", \".html\", \".lock\", \".cfg\", \".ini\", \".env\", \".gitignore\",\n])\n/** Depth limit when walking a skill dir tree — defensive against loops. */\nconst SKILL_TREE_MAX_DEPTH = 6\nconst IDENTITY_FILENAMES = new Set([\n \"CLAUDE.md\", \"SOUL.md\", \"IDENTITY.md\", \"PERSONA.md\", \"AGENT.md\", \"BOOTSTRAP.md\",\n])\n\nexport interface FileOverview {\n identity: Array<{\n path: string // relative to workspace, e.g. \"CLAUDE.md\"\n title: string\n size: number\n exists: boolean // canonical files can be listed as templates even if missing\n }>\n skills: Array<{\n slug: string // dir name under .claude/skills/\n title: string // first # heading in SKILL.md or fallback to slug\n size: number\n path: string // relative to workspace, e.g. \".claude/skills/foo/SKILL.md\"\n }>\n workspace: string // absolute, for display only\n}\n\n/**\n * Resolve a user-supplied path to an absolute path strictly within `workspace`.\n * Throws if the result would escape. Returns the normalised absolute path.\n */\nfunction resolveInWorkspace(workspace: string, relPath: string): string {\n if (!workspace) throw new Error(\"Agent workspace is not configured.\")\n const wsAbs = resolve(workspace)\n if (!existsSync(wsAbs)) {\n // Create the workspace lazily — a fresh agent scaffolded via the wizard\n // may have an empty directory created but not yet with file structure.\n mkdirSync(wsAbs, { recursive: true })\n }\n // Reject absolute-path attempts outright.\n if (!relPath || relPath.startsWith(\"/\") || relPath.match(/^[a-zA-Z]:/)) {\n throw new Error(`Path must be relative to the workspace: ${relPath}`)\n }\n const target = resolve(wsAbs, relPath)\n const rel = relative(wsAbs, target)\n // rel === \"\" means target IS the workspace dir — fine for list ops.\n // rel starts with \"..\" iff target is outside the workspace.\n if (rel.startsWith(\"..\")) {\n throw new Error(`Path escapes the workspace: ${relPath}`)\n }\n return target\n}\n\nfunction assertEditable(absPath: string): void {\n const ext = extname(absPath).toLowerCase()\n if (!ALLOWED_EXTENSIONS.has(ext)) {\n throw new Error(`Only text files (.md, .markdown, .txt) can be edited from the admin panel (got ${ext}).`)\n }\n}\n\nfunction extractTitle(markdown: string, fallback: string): string {\n const line = markdown.split(/\\r?\\n/).find((l) => l.trim().startsWith(\"#\"))\n if (!line) return fallback\n return line.replace(/^#+\\s*/, \"\").trim().slice(0, 120) || fallback\n}\n\n/**\n * List the identity files + skills for an agent. Identity \"canonical\" names\n * that don't exist are still reported (exists: false) so the UI can offer to\n * create them with a sensible default.\n */\nexport function listAgentFiles(workspace: string): FileOverview {\n const wsAbs = resolveInWorkspace(workspace, \".\")\n const identity: FileOverview[\"identity\"] = []\n const seen = new Set<string>()\n\n // Every .md in the workspace root is identity material; canonical names\n // sort first.\n let rootEntries: string[] = []\n try { rootEntries = readdirSync(wsAbs) } catch { /* empty */ }\n for (const name of IDENTITY_FILENAMES) {\n const abs = join(wsAbs, name)\n if (existsSync(abs) && statSync(abs).isFile()) {\n const size = statSync(abs).size\n const content = size > 0 && size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size, exists: true })\n seen.add(name)\n } else {\n identity.push({ path: name, title: name, size: 0, exists: false })\n seen.add(name)\n }\n }\n for (const name of rootEntries) {\n if (seen.has(name)) continue\n const abs = join(wsAbs, name)\n try {\n const st = statSync(abs)\n if (!st.isFile()) continue\n if (!ALLOWED_EXTENSIONS.has(extname(name).toLowerCase())) continue\n const content = st.size > 0 && st.size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size: st.size, exists: true })\n } catch { /* skip unreadable */ }\n }\n\n // Skills live under .claude/skills/<slug>/SKILL.md\n const skills: FileOverview[\"skills\"] = []\n const skillsRoot = join(wsAbs, \".claude\", \"skills\")\n if (existsSync(skillsRoot)) {\n try {\n for (const slug of readdirSync(skillsRoot)) {\n const dir = join(skillsRoot, slug)\n let st\n try { st = statSync(dir) } catch { continue }\n if (!st.isDirectory()) continue\n const file = join(dir, \"SKILL.md\")\n if (!existsSync(file)) continue\n const stf = statSync(file)\n const content = stf.size < MAX_FILE_BYTES ? safeRead(file) : \"\"\n skills.push({\n slug,\n title: extractTitle(content, slug),\n size: stf.size,\n path: `.claude/skills/${slug}/SKILL.md`,\n })\n }\n } catch { /* */ }\n }\n skills.sort((a, b) => a.slug.localeCompare(b.slug))\n\n return { identity, skills, workspace: wsAbs }\n}\n\nfunction safeRead(abs: string): string {\n try { return readFileSync(abs, \"utf-8\") } catch { return \"\" }\n}\n\n/**\n * Read a single file within the workspace. Throws if the path is outside the\n * workspace, has a disallowed extension, or doesn't exist.\n */\nexport function readAgentFile(workspace: string, relPath: string): { path: string; content: string } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n if (!existsSync(abs)) return { path: relPath, content: \"\" }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File is too large to edit in the admin panel (> ${MAX_FILE_BYTES / 1024} KB).`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/**\n * Write a file. Creates parent dirs as needed. Rejects extensions / size\n * outside the allow-list the same way readAgentFile does.\n */\nexport function writeAgentFile(workspace: string, relPath: string, content: string): { path: string; bytes: number } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/**\n * Create a new skill scaffold at .claude/skills/<slug>/SKILL.md.\n */\nexport function createAgentSkill(\n workspace: string,\n slug: string,\n opts: { title?: string; content?: string } = {},\n): { slug: string; path: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) {\n throw new Error(\"Skill slug must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n const relPath = `.claude/skills/${slug}/SKILL.md`\n const abs = resolveInWorkspace(workspace, relPath)\n if (existsSync(abs)) throw new Error(`Skill \"${slug}\" already exists.`)\n mkdirSync(dirname(abs), { recursive: true })\n const body = opts.content?.trim()\n ? opts.content\n : defaultSkillTemplate(slug, opts.title)\n writeFileSync(abs, body)\n return { slug, path: relPath }\n}\n\n/**\n * Remove a skill directory (.claude/skills/<slug>). Scoped to the workspace\n * so we can never touch anything else.\n */\nexport function deleteAgentSkill(workspace: string, slug: string): { slug: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(abs)) throw new Error(`Skill \"${slug}\" not found.`)\n const st = statSync(abs)\n if (!st.isDirectory()) throw new Error(`Skill path is not a directory: ${slug}`)\n rmSync(abs, { recursive: true, force: true })\n return { slug }\n}\n\n// --- Skill directory tree + per-file I/O ---\n//\n// A skill is `.claude/skills/<slug>/` — usually SKILL.md plus scripts,\n// lockfiles, configs. The admin UI lets the operator browse the directory\n// tree, edit any text file, and dispatch \"install deps\" through the agent.\n\nexport interface SkillTreeEntry {\n /** Path relative to the skill dir root. Directories end with \"/\". */\n path: string\n kind: \"file\" | \"dir\"\n size: number\n editable: boolean // false for binaries or disallowed extensions\n}\n\nexport interface SkillDepsHint {\n /** Which install command the operator should run (or have an agent run). */\n manager: \"npm\" | \"pnpm\" | \"yarn\" | \"pip\" | \"bundle\" | null\n /** Filename that triggered the detection, relative to skill dir. */\n file: string | null\n /** The exact command we'd recommend. */\n command: string | null\n}\n\n/** List every file in `.claude/skills/<slug>/`, recursively. Scoped so the\n * caller can't escape the skill dir via symlinks or \"..\" segments. */\nexport function listSkillTree(workspace: string, slug: string): SkillTreeEntry[] {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const out: SkillTreeEntry[] = []\n walk(skillRoot, \"\", 0, out)\n out.sort((a, b) => {\n // dirs first, then alphabetical\n if (a.kind !== b.kind) return a.kind === \"dir\" ? -1 : 1\n return a.path.localeCompare(b.path)\n })\n return out\n\n function walk(absDir: string, relDir: string, depth: number, acc: SkillTreeEntry[]): void {\n if (depth > SKILL_TREE_MAX_DEPTH) return\n let entries: string[] = []\n try { entries = readdirSync(absDir) } catch { return }\n for (const name of entries) {\n if (name === \"node_modules\" || name === \".git\") continue\n const abs = join(absDir, name)\n const rel = relDir ? `${relDir}/${name}` : name\n let st\n try { st = statSync(abs) } catch { continue }\n if (st.isDirectory()) {\n acc.push({ path: `${rel}/`, kind: \"dir\", size: 0, editable: false })\n walk(abs, rel, depth + 1, acc)\n } else if (st.isFile()) {\n const ext = extname(name).toLowerCase()\n // name-only allowlist for dotfiles like .env / .gitignore (no extension)\n const allowedByName = SKILL_EDITABLE_EXTENSIONS.has(ext) ||\n SKILL_EDITABLE_EXTENSIONS.has(name.toLowerCase())\n acc.push({\n path: rel,\n kind: \"file\",\n size: st.size,\n editable: allowedByName && st.size < MAX_FILE_BYTES,\n })\n }\n }\n }\n}\n\n/** Read any file in a skill dir. Respects the skill-editable allowlist. */\nexport function readSkillFile(workspace: string, slug: string, relPath: string): { path: string; content: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n if (!existsSync(abs) || !statSync(abs).isFile()) {\n throw new Error(`File not found: ${relPath}`)\n }\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File too large to edit (> ${MAX_FILE_BYTES / 1024} KB): ${relPath}`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/** Write any allowed file inside a skill dir. Creates parents. */\nexport function writeSkillFile(\n workspace: string,\n slug: string,\n relPath: string,\n content: string,\n): { path: string; bytes: number } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/** Detect which dependency manager a skill needs based on the files present. */\nexport function detectSkillDeps(workspace: string, slug: string): SkillDepsHint {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const check = (name: string): boolean => existsSync(join(skillRoot, name))\n if (check(\"pnpm-lock.yaml\")) return { manager: \"pnpm\", file: \"pnpm-lock.yaml\", command: \"pnpm install\" }\n if (check(\"yarn.lock\")) return { manager: \"yarn\", file: \"yarn.lock\", command: \"yarn install\" }\n if (check(\"package.json\")) return { manager: \"npm\", file: \"package.json\", command: \"npm install\" }\n if (check(\"requirements.txt\")) return { manager: \"pip\", file: \"requirements.txt\", command: \"pip install -r requirements.txt\" }\n if (check(\"Gemfile\")) return { manager: \"bundle\", file: \"Gemfile\", command: \"bundle install\" }\n return { manager: null, file: null, command: null }\n}\n\nfunction defaultSkillTemplate(slug: string, title?: string): string {\n const displayTitle = title?.trim() || slug\n return `# ${displayTitle}\n\nDescribe what this skill does and when the agent should use it. Keep it under ~5 lines — the agent reads this to decide whether to invoke the skill.\n\n## When to use\n- Trigger condition 1\n- Trigger condition 2\n\n## How to use\nDocument the arguments, commands, or files the skill expects. Example:\n\n\\`\\`\\`bash\n# Example invocation\nclaude-code ${basename(slug)} --arg value\n\\`\\`\\`\n`\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","readdirSync","statSync","resolve","join","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","z","sourceSchema","itemSchema","fileSchema","BacklogStore","path","resolve","existsSync","p","raw","readFileSync","e","file","jsonP","mkdirSync","dirname","writeFileSync","id","i","input","now","item","inputs","existing","added","patch","idx","updated","before","lines","buckets","sections","key","label","it","check","mention","time","src","sourceBacklogId","source","resolveAssigneesForSource","agentIds","source","daemon","out","mappings","agentId","m","x","syncBacklogItemToSource","item","patch","log","syncToGitLab","syncToGitHub","gl","token","host","attempts","api","path","init","projectEnc","assigneeIds","u","res","arr","milestoneId","e","body","text","a","repo","milestoneNumber","found","nextLabels","issue","current","l","adds","r","CHECK_RE","MENTION_RE","TIME_RE","parseDuration","s","hMatch","mMatch","BacklogWorkSource","path","daemon","log","BacklogStore","full","resolve","existsSync","readFileSync","lines","writeFileSync","agentId","it","items","i","m","body","mention","timeMatch","title","_opts","itemId","item","updated","usernames","resolveAssigneesForSource","syncBacklogItemToSource","e","update","patch","sourcePatch","lineNo","line","note","stage","WikiWorkSource","root","glob","dir","out","entry","readdirSync","join","statSync","files","file","parts","GitLabWorkSource","host","token","projects","agentUsernames","username","names","issue","project","ns","labels","labelDetails","l","assignees","firstUsername","init","res","projectList","q","issues","iid","type","seg","mins","dur","opts","sinceDays","updatedAfter","params","state","result","deny","prefix","to","assigneeIds","u","users","x","milestoneId","ms","byUsername","members","a","b","byTitle","all","input","assignee_ids","milestone_id","toLabel","fromLabel","add","remove","createWorkSource","business","ws","gl","mapping","r","existsSync","readFileSync","writeFileSync","mkdirSync","readdirSync","rmSync","statSync","resolve","relative","dirname","join","extname","basename","MAX_FILE_BYTES","ALLOWED_EXTENSIONS","SKILL_EDITABLE_EXTENSIONS","SKILL_TREE_MAX_DEPTH","IDENTITY_FILENAMES","resolveInWorkspace","workspace","relPath","wsAbs","target","assertEditable","absPath","ext","extractTitle","markdown","fallback","line","l","listAgentFiles","identity","seen","rootEntries","name","abs","size","content","safeRead","st","skills","skillsRoot","slug","dir","file","stf","a","b","readAgentFile","writeAgentFile","buf","createAgentSkill","opts","body","defaultSkillTemplate","deleteAgentSkill","listSkillTree","skillRoot","out","walk","absDir","relDir","depth","acc","entries","rel","allowedByName","readSkillFile","writeSkillFile","detectSkillDeps","check","title"]}
|
|
@@ -17,7 +17,7 @@ SKIP:
|
|
|
17
17
|
For each fact, output a JSON array:
|
|
18
18
|
[{"category":"fact|secret|preference|commitment|task-state","content":"concise fact, 1-2 sentences","keywords":["keyword1","keyword2"]}]
|
|
19
19
|
|
|
20
|
-
If nothing worth remembering, output: []`;async function h(t,s,a,n,r){if(s.length<20&&a.length<50)return;let{createProvider:i}=await import("./providers-
|
|
20
|
+
If nothing worth remembering, output: []`;async function h(t,s,a,n,r){if(s.length<20&&a.length<50)return;let{createProvider:i}=await import("./providers-L27BJ5A6.js"),c=(await i("claude").generate([{role:"system",content:f},{role:"user",content:`${n.sender} (via ${n.channel}): ${s}
|
|
21
21
|
|
|
22
22
|
Agent: ${a}`}],{model:m,maxTokens:512})).content.trim().match(/\[[\s\S]*\]/);if(!c)return;let o=JSON.parse(c[0]);if(!Array.isArray(o)||o.length===0)return;let d=new Date().toISOString().slice(0,10);for(let e of o)r.hasSimilar(t,e.content)||r.addMemory(t,{agentId:t,category:e.category,content:e.content,keywords:e.keywords||[],source:{...n,date:d},expiresAt:e.category==="task-state"?new Date(Date.now()+7*24*60*60*1e3).toISOString():void 0});Math.random()<.02&&r.prune(t)}export{h as a};
|
|
23
|
-
//# sourceMappingURL=chunk-
|
|
23
|
+
//# sourceMappingURL=chunk-5U35WAZF.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import{execFile as m}from"child_process";import{existsSync as d}from"fs";import{resolve as f}from"path";var R=["mcp__codegraph__codegraph_search","mcp__codegraph__codegraph_context","mcp__codegraph__codegraph_callers","mcp__codegraph__codegraph_callees","mcp__codegraph__codegraph_impact","mcp__codegraph__codegraph_node","mcp__codegraph__codegraph_status","mcp__codegraph__codegraph_files"],p={command:"codegraph",args:["serve","--mcp"]};function O(){return["## CodeGraph","","This workspace has CodeGraph initialized \u2014 a semantic code-knowledge-graph indexed via tree-sitter. Use it instead of grep/find/Read for exploration.","","**ALWAYS spawn an Explore subagent for `codegraph_explore` / `codegraph_context`** \u2014 those tools return large source sections that would otherwise bloat the main session. Include this in the Explore prompt:","","> Use `codegraph_explore` as your PRIMARY tool \u2014 one call returns full source from all relevant files. Do not re-Read files codegraph_explore already returned. Fall back to grep/glob/Read only if codegraph returned no results.","","**In the main session, use these lightweight codegraph tools directly:**","","| Tool | Use for |","|---|---|","| `codegraph_search` | Find symbols by name across the codebase |","| `codegraph_callers` / `codegraph_callees` | Trace call flow |","| `codegraph_impact` | Check what's affected before editing a symbol |","| `codegraph_node` | Get a single symbol's details |","| `codegraph_files` | Indexed file structure (faster than ls) |","| `codegraph_status` | Index health |",""].join(`
|
|
2
|
+
`)}function P(e){let t=e.mcp??{};return!e.codegraph||t.codegraph?t:{...t,codegraph:{command:p.command,args:[...p.args]}}}var _=".codegraph",x=10*60*1e3,g=2;function y(e){let t=Date.now();return new Promise((r,n)=>{m("codegraph",["init","--index","--quiet",e],{timeout:x,maxBuffer:4*1024*1024},o=>{o?n(o):r(Date.now()-t)})})}async function k(e,t=console.error){let r=[];for(let[i,s]of Object.entries(e))s.codegraph&&(s.tier!=="claude-code"&&s.tier!=="codex-cli"||d(s.workspace)&&(d(f(s.workspace,_))||r.push({agentId:i,workspace:s.workspace})));if(r.length===0)return;t(`codegraph: indexing ${r.length} workspace(s) in background (max ${g} parallel)`);let n=0,o=[];for(let i=0;i<Math.min(g,r.length);i++)o.push((async()=>{for(;;){let s=n++;if(s>=r.length)return;let{agentId:c,workspace:h}=r[s];try{let a=await y(h);t(` codegraph: indexed ${c} in ${(a/1e3).toFixed(1)}s`)}catch(a){let u=a?.code==="ENOENT"?" (codegraph CLI not on PATH; install with `npm i -g @colbymchenry/codegraph`)":"";t(` codegraph: ${c} index failed: ${a?.message??a}${u}`)}}})());await Promise.allSettled(o)}import{readFileSync as S,existsSync as b,unlinkSync as w,readdirSync as v}from"fs";import{resolve as l}from"path";function L(e,t){let r={},n=o=>{let i=l(e,o);if(b(i))try{return S(i,"utf-8").trim()||void 0}catch{return}};if(t){let o=n(`SOUL.${t}.md`);o?(r.soul=o,r.soulProfile=t):r.soul=n("SOUL.md")}else r.soul=n("SOUL.md");if(r.identity=n("IDENTITY.md"),r.user=n("USER.md"),r.agents=n("AGENTS.md"),r.bootstrap=n("BOOTSTRAP.md"),r.bootstrap)try{w(l(e,"BOOTSTRAP.md"))}catch{}return r}function D(e){let t=[];return e.identity&&t.push(`[Identity]
|
|
3
|
+
${e.identity}`),e.soul&&t.push(`[Personality & Boundaries]
|
|
4
|
+
${e.soul}`),e.user&&t.push(`[User Profile]
|
|
5
|
+
${e.user}`),e.bootstrap&&t.push(`[First-Run Instructions \u2014 one time only]
|
|
6
|
+
${e.bootstrap}`),t.length===0?"":t.join(`
|
|
7
|
+
|
|
8
|
+
`)}function F(e){return`# ${e.name}
|
|
9
|
+
|
|
10
|
+
> Agent ID: \`${e.id}\` | Tier: ${e.tier||"claude-code"}
|
|
11
|
+
|
|
12
|
+
## Role
|
|
13
|
+
|
|
14
|
+
${e.role||"AI agent managed by AgentX orchestrator."}
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
This workspace is managed by AgentX. The agent runs via:
|
|
19
|
+
\`\`\`bash
|
|
20
|
+
# Direct execution
|
|
21
|
+
agentx daemon send ${e.id} "your task here"
|
|
22
|
+
|
|
23
|
+
# Or via any connected channel (Telegram, WhatsApp, GitLab)
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
## Code Style
|
|
27
|
+
|
|
28
|
+
- Follow existing patterns in the codebase
|
|
29
|
+
- Use the language and framework conventions already present
|
|
30
|
+
- Keep changes minimal and focused
|
|
31
|
+
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
Run tests before committing:
|
|
35
|
+
\`\`\`bash
|
|
36
|
+
npm test # or the project's test command
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
## PR Guidelines
|
|
40
|
+
|
|
41
|
+
- One logical change per commit
|
|
42
|
+
- Reference issue numbers when applicable
|
|
43
|
+
- Keep PR descriptions concise
|
|
44
|
+
|
|
45
|
+
## AgentX Integration
|
|
46
|
+
|
|
47
|
+
This agent is part of an AgentX mesh. It can:
|
|
48
|
+
- Receive tasks from messaging channels (Telegram, WhatsApp, Discord)
|
|
49
|
+
- Respond to GitLab @mentions on issues and MRs
|
|
50
|
+
- Delegate to other agents by mentioning their handles
|
|
51
|
+
- Send cross-channel messages via the /send API
|
|
52
|
+
- Access its wiki knowledge base for context
|
|
53
|
+
`}function U(e){let t=e.match(/\/soul\s+(\w+)/i);return t?t[1].toLowerCase():/\/soul\s*$/i.test(e)?"default":null}export{R as a,O as b,P as c,k as d,L as e,D as f,F as g,U as h};
|
|
54
|
+
//# sourceMappingURL=chunk-67Q4DCPS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/codegraph-bootstrap.ts","../src/agents/bootstrap.ts"],"sourcesContent":["import { execFile } from \"child_process\"\nimport { existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { AgentDef } from \"@/daemon/config\"\nimport type { McpServerMap } from \"./agent-mcp\"\n\n// --- CodeGraph integration: per-agent semantic code-graph index ---\n//\n// CodeGraph (https://github.com/colbymchenry/codegraph) is a local\n// tree-sitter → SQLite knowledge graph exposed to Claude Code / Codex via\n// MCP. Opt-in per agent with `codegraph: true` on the agent's config.\n//\n// This module owns three responsibilities:\n// 1. `effectiveMcpConfig(def)` — synthesize the codegraph MCP server\n// entry into the agent's MCP map. Used by the daemon's\n// installAgentMcpConfig pass so the workspace's .mcp.json picks\n// up `codegraph serve --mcp` automatically.\n// 2. `bootstrapCodegraphIndexes(agents, log)` — at daemon boot, fire\n// off `codegraph init --index --quiet` in the background for any\n// opted-in workspace whose `.codegraph/` is missing. Throttled to\n// 2 parallel jobs so a large fleet doesn't thrash the host.\n// Returns immediately; the indexes finish on their own.\n// 3. Export the constant tool/CLI names that workspace-setup needs\n// to keep CLAUDE.md and settings.json in sync.\n\n/** MCP tool names exposed by `codegraph serve --mcp`. Used to extend the\n * per-workspace `.claude/settings.json` permissions.allow so claude\n * doesn't prompt for permission on every call. Keep in sync with the\n * upstream tool surface — additions are harmless, removals leave a\n * stale allow entry (also harmless). */\nexport const CODEGRAPH_TOOLS = [\n \"mcp__codegraph__codegraph_search\",\n \"mcp__codegraph__codegraph_context\",\n \"mcp__codegraph__codegraph_callers\",\n \"mcp__codegraph__codegraph_callees\",\n \"mcp__codegraph__codegraph_impact\",\n \"mcp__codegraph__codegraph_node\",\n \"mcp__codegraph__codegraph_status\",\n \"mcp__codegraph__codegraph_files\",\n] as const\n\n/** MCP server stanza we inject into `.mcp.json` for opted-in agents. */\nexport const CODEGRAPH_MCP_SERVER: { command: string; args: string[] } = {\n command: \"codegraph\",\n args: [\"serve\", \"--mcp\"],\n}\n\n/** Markdown block appended to managed CLAUDE.md when codegraph is on.\n * Tells the agent to prefer codegraph_* over grep, and to always spawn\n * an Explore subagent for codegraph_explore so the large source-dump\n * doesn't bloat the main session context. Mirrors the upstream-\n * recommended wording from the README's \"Global Instructions\" block. */\nexport function codegraphClaudeMdSection(): string {\n return [\n \"## CodeGraph\",\n \"\",\n \"This workspace has CodeGraph initialized — a semantic code-knowledge-graph indexed via tree-sitter. Use it instead of grep/find/Read for exploration.\",\n \"\",\n \"**ALWAYS spawn an Explore subagent for `codegraph_explore` / `codegraph_context`** — those tools return large source sections that would otherwise bloat the main session. Include this in the Explore prompt:\",\n \"\",\n \"> Use `codegraph_explore` as your PRIMARY tool — one call returns full source from all relevant files. Do not re-Read files codegraph_explore already returned. Fall back to grep/glob/Read only if codegraph returned no results.\",\n \"\",\n \"**In the main session, use these lightweight codegraph tools directly:**\",\n \"\",\n \"| Tool | Use for |\",\n \"|---|---|\",\n \"| `codegraph_search` | Find symbols by name across the codebase |\",\n \"| `codegraph_callers` / `codegraph_callees` | Trace call flow |\",\n \"| `codegraph_impact` | Check what's affected before editing a symbol |\",\n \"| `codegraph_node` | Get a single symbol's details |\",\n \"| `codegraph_files` | Indexed file structure (faster than ls) |\",\n \"| `codegraph_status` | Index health |\",\n \"\",\n ].join(\"\\n\")\n}\n\n/** Return the MCP server map an agent should expose to .mcp.json,\n * layering codegraph on top of the operator-declared `def.mcp` when\n * `def.codegraph === true`. Operator-declared entries win on key\n * collision (operator can override by declaring their own\n * `mcp.codegraph` block with different args). */\nexport function effectiveMcpConfig(def: AgentDef): McpServerMap {\n const base: McpServerMap = ((def as any).mcp ?? {}) as McpServerMap\n if (!def.codegraph) return base\n if (base.codegraph) return base\n return { ...base, codegraph: { command: CODEGRAPH_MCP_SERVER.command, args: [...CODEGRAPH_MCP_SERVER.args] } }\n}\n\nconst INDEX_DIR = \".codegraph\"\nconst INIT_TIMEOUT_MS = 10 * 60 * 1000 // 10 min — Swift Compiler benchmark indexed 25k files in ~4 min\nconst MAX_PARALLEL = 2\n\n/** Spawn `codegraph init --index --quiet` for one workspace. Returns the\n * duration in ms on success, throws on failure. Caller decides whether\n * to surface the error. */\nfunction indexWorkspace(workspace: string): Promise<number> {\n const started = Date.now()\n return new Promise<number>((res, rej) => {\n execFile(\n \"codegraph\",\n [\"init\", \"--index\", \"--quiet\", workspace],\n { timeout: INIT_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 },\n (err) => {\n if (err) rej(err)\n else res(Date.now() - started)\n },\n )\n })\n}\n\n/** Boot-time helper: kick off `codegraph init --index` for every\n * opted-in workspace whose `.codegraph/` is missing. Fire-and-forget —\n * the returned promise resolves once all background jobs settle, but\n * callers (daemon start) don't need to await it. Indexing happens\n * while the daemon is already serving traffic; cold dispatches before\n * the index lands gracefully fall through to grep (the MCP server\n * returns an empty result, and CLAUDE.md tells the agent to fall back).\n *\n * Throttled to MAX_PARALLEL concurrent jobs so a fleet boot doesn't\n * saturate CPU/disk. */\nexport async function bootstrapCodegraphIndexes(\n agents: Record<string, AgentDef>,\n log: (...args: unknown[]) => void = console.error,\n): Promise<void> {\n const pending: Array<{ agentId: string; workspace: string }> = []\n for (const [agentId, def] of Object.entries(agents)) {\n if (!def.codegraph) continue\n if (def.tier !== \"claude-code\" && def.tier !== \"codex-cli\") continue\n if (!existsSync(def.workspace)) continue\n if (existsSync(resolve(def.workspace, INDEX_DIR))) continue\n pending.push({ agentId, workspace: def.workspace })\n }\n\n if (pending.length === 0) return\n\n log(`codegraph: indexing ${pending.length} workspace(s) in background (max ${MAX_PARALLEL} parallel)`)\n\n let i = 0\n const runners: Promise<void>[] = []\n for (let slot = 0; slot < Math.min(MAX_PARALLEL, pending.length); slot++) {\n runners.push((async () => {\n while (true) {\n const idx = i++\n if (idx >= pending.length) return\n const { agentId, workspace } = pending[idx]\n try {\n const ms = await indexWorkspace(workspace)\n log(` codegraph: indexed ${agentId} in ${(ms / 1000).toFixed(1)}s`)\n } catch (err: any) {\n // codegraph binary missing surfaces as ENOENT — recoverable hint\n const hint = err?.code === \"ENOENT\"\n ? \" (codegraph CLI not on PATH; install with `npm i -g @colbymchenry/codegraph`)\"\n : \"\"\n log(` codegraph: ${agentId} index failed: ${err?.message ?? err}${hint}`)\n }\n }\n })())\n }\n await Promise.allSettled(runners)\n}\n","import { readFileSync, existsSync, unlinkSync, readdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Bootstrap Identity Files ---\n//\n// Support for structured workspace identity files (inspired by OpenClaw).\n// These files live in the agent's workspace and are auto-loaded into context.\n//\n// Supported files:\n// SOUL.md — default personality, tone, boundaries\n// SOUL.{profile}.md — named soul profiles (e.g. SOUL.finance.md, SOUL.legal.md)\n// IDENTITY.md — name, role, emoji, tagline (persistent)\n// USER.md — user profile, preferences (persistent)\n// AGENTS.md — operating rules, standing orders (persistent)\n// BOOTSTRAP.md — one-time first-run ritual (auto-deleted after first load)\n//\n// Soul switching: send \"/soul finance\" to swap to SOUL.finance.md mid-session.\n\nexport interface BootstrapFiles {\n soul?: string\n /** Active soul profile name (undefined = default SOUL.md) */\n soulProfile?: string\n identity?: string\n user?: string\n agents?: string\n bootstrap?: string\n}\n\nconst BOOTSTRAP_FILE_NAMES = [\n \"SOUL.md\",\n \"IDENTITY.md\",\n \"USER.md\",\n \"AGENTS.md\",\n \"BOOTSTRAP.md\",\n] as const\n\n/**\n * Load bootstrap identity files from an agent's workspace.\n * @param soulProfile — optional soul profile name (e.g. \"finance\" loads SOUL.finance.md)\n */\nexport function loadBootstrapFiles(workspace: string, soulProfile?: string): BootstrapFiles {\n const result: BootstrapFiles = {}\n\n const tryLoad = (filename: string): string | undefined => {\n const filePath = resolve(workspace, filename)\n if (!existsSync(filePath)) return undefined\n try {\n const content = readFileSync(filePath, \"utf-8\").trim()\n return content || undefined\n } catch {\n return undefined\n }\n }\n\n // Load soul: profile-specific if requested, fallback to default\n if (soulProfile) {\n const profileSoul = tryLoad(`SOUL.${soulProfile}.md`)\n if (profileSoul) {\n result.soul = profileSoul\n result.soulProfile = soulProfile\n } else {\n // Profile not found — fall back to default and note it\n result.soul = tryLoad(\"SOUL.md\")\n }\n } else {\n result.soul = tryLoad(\"SOUL.md\")\n }\n\n result.identity = tryLoad(\"IDENTITY.md\")\n result.user = tryLoad(\"USER.md\")\n result.agents = tryLoad(\"AGENTS.md\")\n result.bootstrap = tryLoad(\"BOOTSTRAP.md\")\n\n // BOOTSTRAP.md is one-time: delete after loading\n if (result.bootstrap) {\n try {\n unlinkSync(resolve(workspace, \"BOOTSTRAP.md\"))\n } catch {\n // Best-effort deletion\n }\n }\n\n return result\n}\n\n/**\n * Build context string from bootstrap files.\n * Returns empty string if no files found.\n */\nexport function buildBootstrapContext(files: BootstrapFiles): string {\n const sections: string[] = []\n\n if (files.identity) {\n sections.push(`[Identity]\\n${files.identity}`)\n }\n\n if (files.soul) {\n sections.push(`[Personality & Boundaries]\\n${files.soul}`)\n }\n\n // AGENTS.md is intentionally NOT injected into AgentX's --append-system-prompt\n // anymore. It's loaded natively by Claude Code via `@AGENTS.md` in the\n // workspace's CLAUDE.md (generated by workspace-setup.ts for new workspaces;\n // added to existing workspaces by the 2026-04-24 distribution). Injecting it\n // here on top of the native CLAUDE.md path would double the bytes. Keeping\n // the load in BootstrapFiles for diagnostic/listing purposes only.\n\n if (files.user) {\n sections.push(`[User Profile]\\n${files.user}`)\n }\n\n if (files.bootstrap) {\n sections.push(`[First-Run Instructions — one time only]\\n${files.bootstrap}`)\n }\n\n if (sections.length === 0) return \"\"\n\n return sections.join(\"\\n\\n\")\n}\n\n/**\n * Check which bootstrap files exist in a workspace (for status/diagnostics).\n */\nexport function listBootstrapFiles(workspace: string): string[] {\n return BOOTSTRAP_FILE_NAMES.filter((name) =>\n existsSync(resolve(workspace, name))\n )\n}\n\n/**\n * List available soul profiles in a workspace.\n * Returns profile names (e.g. [\"finance\", \"legal\", \"creative\"]).\n */\nexport function listSoulProfiles(workspace: string): string[] {\n try {\n return readdirSync(workspace)\n .filter(f => f.startsWith(\"SOUL.\") && f.endsWith(\".md\") && f !== \"SOUL.md\")\n .map(f => f.slice(5, -3)) // \"SOUL.finance.md\" → \"finance\"\n } catch {\n return []\n }\n}\n\n/**\n * Generate a standard AGENTS.md for a new agent workspace.\n * Follows the agents.md community convention (https://agents.md/).\n * Readable by Claude Code, Codex, Cursor, Amp, Gemini CLI, etc.\n */\nexport function generateAgentsMd(agent: {\n name: string\n id: string\n role?: string\n workspace: string\n tier?: string\n}): string {\n return `# ${agent.name}\n\n> Agent ID: \\`${agent.id}\\` | Tier: ${agent.tier || \"claude-code\"}\n\n## Role\n\n${agent.role || \"AI agent managed by AgentX orchestrator.\"}\n\n## Setup\n\nThis workspace is managed by AgentX. The agent runs via:\n\\`\\`\\`bash\n# Direct execution\nagentx daemon send ${agent.id} \"your task here\"\n\n# Or via any connected channel (Telegram, WhatsApp, GitLab)\n\\`\\`\\`\n\n## Code Style\n\n- Follow existing patterns in the codebase\n- Use the language and framework conventions already present\n- Keep changes minimal and focused\n\n## Testing\n\nRun tests before committing:\n\\`\\`\\`bash\nnpm test # or the project's test command\n\\`\\`\\`\n\n## PR Guidelines\n\n- One logical change per commit\n- Reference issue numbers when applicable\n- Keep PR descriptions concise\n\n## AgentX Integration\n\nThis agent is part of an AgentX mesh. It can:\n- Receive tasks from messaging channels (Telegram, WhatsApp, Discord)\n- Respond to GitLab @mentions on issues and MRs\n- Delegate to other agents by mentioning their handles\n- Send cross-channel messages via the /send API\n- Access its wiki knowledge base for context\n`\n}\n\n/**\n * Detect a /soul command in a message.\n * Returns the requested profile name, or null if no /soul command found.\n * \"/soul finance\" → \"finance\"\n * \"/soul\" or \"/soul default\" → \"default\" (reset to SOUL.md)\n */\nexport function detectSoulSwitch(message: string): string | null {\n const match = message.match(/\\/soul\\s+(\\w+)/i)\n if (match) return match[1].toLowerCase()\n if (/\\/soul\\s*$/i.test(message)) return \"default\"\n return null\n}\n"],"mappings":"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","names":["execFile","existsSync","resolve","CODEGRAPH_TOOLS","CODEGRAPH_MCP_SERVER","codegraphClaudeMdSection","effectiveMcpConfig","def","base","INDEX_DIR","INIT_TIMEOUT_MS","MAX_PARALLEL","indexWorkspace","workspace","started","res","rej","err","bootstrapCodegraphIndexes","agents","log","pending","agentId","i","runners","slot","idx","ms","hint","readFileSync","existsSync","unlinkSync","readdirSync","resolve","loadBootstrapFiles","workspace","soulProfile","result","tryLoad","filename","filePath","resolve","existsSync","readFileSync","profileSoul","unlinkSync","buildBootstrapContext","files","sections","generateAgentsMd","agent","detectSoulSwitch","message","match"]}
|