agentix-cli 0.128.0 → 0.130.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/dist/{App-BOLQUKCN.js → App-DWSHIDN6.js} +2 -2
- package/dist/{ChatApp-72HXQV24.js → ChatApp-CROSI7JM.js} +2 -2
- package/dist/{absorb-eval-O3HA6XA5.js → absorb-eval-MNGQ752C.js} +2 -2
- package/dist/{agent-S2DXXALS.js → agent-4AQOFPRD.js} +2 -2
- package/dist/{board-dashboard-ZKXXK5CG.js → board-dashboard-54A3XTV5.js} +256 -236
- package/dist/board-dashboard-54A3XTV5.js.map +1 -0
- package/dist/{chunk-ZLC4TSXF.js → chunk-242MPMJX.js} +2 -2
- package/dist/{chunk-6KIUVVKX.js → chunk-5KKXJZZ4.js} +2 -2
- package/dist/{chunk-5DAEJF5S.js → chunk-5VKFIBXY.js} +3 -3
- package/dist/{chunk-5DAEJF5S.js.map → chunk-5VKFIBXY.js.map} +1 -1
- package/dist/{chunk-DQ2LNQ2D.js → chunk-AERIU4AH.js} +13 -13
- package/dist/chunk-AERIU4AH.js.map +1 -0
- package/dist/{chunk-O2BS6N3T.js → chunk-HF4OXA5L.js} +2 -2
- package/dist/{chunk-YWKLUQDC.js → chunk-I4GT27UO.js} +2 -2
- package/dist/{chunk-FSW4FUMW.js → chunk-I4OQQRY2.js} +3 -3
- package/dist/{chunk-HKW56YZK.js → chunk-J4I5ZJ2V.js} +2 -2
- package/dist/chunk-JE5EJGGD.js +4 -0
- package/dist/chunk-JE5EJGGD.js.map +1 -0
- package/dist/chunk-KBJNJGBP.js +2 -0
- package/dist/chunk-KBJNJGBP.js.map +1 -0
- package/dist/{chunk-6GGGXCOY.js → chunk-LJWEJ6UZ.js} +23 -14
- package/dist/{chunk-6GGGXCOY.js.map → chunk-LJWEJ6UZ.js.map} +1 -1
- package/dist/{chunk-FXBMFGOV.js → chunk-LLLBH2HH.js} +2 -2
- package/dist/{chunk-6W74CHNW.js → chunk-MEFPXHQJ.js} +13 -13
- package/dist/chunk-MEFPXHQJ.js.map +1 -0
- package/dist/{chunk-TPNTFCSQ.js → chunk-MZJY26FW.js} +2 -2
- package/dist/{chunk-LDS4SZBU.js → chunk-N2YSVZML.js} +2 -2
- package/dist/{chunk-P4XGM7ZM.js → chunk-NR7WLMNI.js} +2 -2
- package/dist/{chunk-4V7SWL2S.js → chunk-Q5FEWCS5.js} +2 -2
- package/dist/{chunk-D2WX3BET.js → chunk-QPYN2UTV.js} +2 -2
- package/dist/{chunk-C5MTLW7O.js → chunk-QXZLYCEZ.js} +2 -2
- package/dist/{chunk-QFZU5CEO.js → chunk-RKB7FQJF.js} +2 -2
- package/dist/{chunk-Z3W7D5RR.js → chunk-TJDNS5G6.js} +2 -2
- package/dist/{chunk-DN3RLR5Z.js → chunk-TN5A7PLU.js} +2 -2
- package/dist/{chunk-TG6CT54D.js → chunk-U2EHJZAZ.js} +2 -2
- package/dist/{chunk-EJT4MA3J.js → chunk-UBA2QJCY.js} +238 -238
- package/dist/chunk-UBA2QJCY.js.map +1 -0
- package/dist/chunk-UUFJQMHE.js +54 -0
- package/dist/chunk-UUFJQMHE.js.map +1 -0
- package/dist/{chunk-QACEFESP.js → chunk-W2HEJKJR.js} +30 -30
- package/dist/chunk-W2HEJKJR.js.map +1 -0
- package/dist/{chunk-QQFURUMS.js → chunk-WX67I2KN.js} +2 -2
- package/dist/{chunk-2SV4RKRW.js → chunk-YHCPBMRH.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/{client-VBV7Q43P.js → client-KGWYROLX.js} +2 -2
- package/dist/{config-52C3DJD2.js → config-FNTLKUGA.js} +2 -2
- package/dist/{config-mutate-DKQESY6F.js → config-mutate-7BLOYRZF.js} +2 -2
- package/dist/config-mutator-NCLHYPJ3.js +2 -0
- package/dist/{contributions-5OEZWYXI.js → contributions-DLUQEAGL.js} +2 -2
- package/dist/{doctor-VLRC4HW2.js → doctor-32YP26SJ.js} +2 -2
- package/dist/{follow-up-tool-235LDEMP.js → follow-up-tool-AQ7NYZZR.js} +2 -2
- package/dist/{hub-5QK7KYTZ.js → hub-Q3I4ALQ2.js} +2 -2
- package/dist/index.d.ts +91 -2
- package/dist/index.js +1 -1
- package/dist/note-reader-LPPIYYLT.js +8 -0
- package/dist/note-reader-LPPIYYLT.js.map +1 -0
- package/dist/{notes-YLTNRMB5.js → notes-TMAGEDUS.js} +2 -2
- package/dist/notes-settings-WMOH2V75.js +2 -0
- package/dist/{program-MVD57PKP.js → program-FESSM5TY.js} +285 -262
- package/dist/program-FESSM5TY.js.map +1 -0
- package/dist/{promote-UPPQ4BQE.js → promote-R7FKN5SY.js} +2 -2
- package/dist/{query-XQS5JN6E.js → query-YTKTNXCQ.js} +2 -2
- package/dist/{registry-MOMTLJ6R.js → registry-7L4RUATA.js} +2 -2
- package/dist/{request-planner-QGZJEAKY.js → request-planner-LEUMSI2K.js} +2 -2
- package/dist/{review-candidates-PAKF2JQF.js → review-candidates-MOSGA4IS.js} +2 -2
- package/dist/{routing-JG3EV4O4.js → routing-FSJ42RZH.js} +2 -2
- package/dist/schedule-tool-4T2VZIDW.js +2 -0
- package/dist/{scheduler-LMPBAA34.js → scheduler-6AQDHQI4.js} +2 -2
- package/dist/{seat-JN3IVZ3G.js → seat-O37EQRHE.js} +2 -2
- package/dist/{select-ZMMRFMNO.js → select-XO2STSQN.js} +2 -2
- package/dist/{set-enabled-7Y3TW6Z4.js → set-enabled-ZNEPEFGF.js} +2 -2
- package/dist/{tool-S7QI6HRA.js → tool-6KMEAY4U.js} +2 -2
- package/dist/{tool-OCYXCDOK.js → tool-PH45YCO5.js} +2 -2
- package/dist/wiki-4HD4FZ4W.js +2 -0
- package/package.json +1 -1
- package/dist/board-dashboard-ZKXXK5CG.js.map +0 -1
- package/dist/chunk-52SIHG45.js +0 -4
- package/dist/chunk-52SIHG45.js.map +0 -1
- package/dist/chunk-6W74CHNW.js.map +0 -1
- package/dist/chunk-DQ2LNQ2D.js.map +0 -1
- package/dist/chunk-EJT4MA3J.js.map +0 -1
- package/dist/chunk-MRYA4J5F.js +0 -54
- package/dist/chunk-MRYA4J5F.js.map +0 -1
- package/dist/chunk-QACEFESP.js.map +0 -1
- package/dist/chunk-QUXMLLX5.js +0 -2
- package/dist/chunk-QUXMLLX5.js.map +0 -1
- package/dist/config-mutator-BF4D4QGN.js +0 -2
- package/dist/notes-settings-X63D5HPM.js +0 -2
- package/dist/program-MVD57PKP.js.map +0 -1
- package/dist/schedule-tool-O7ZZ2A7X.js +0 -2
- package/dist/wiki-OCLFFZG6.js +0 -2
- /package/dist/{App-BOLQUKCN.js.map → App-DWSHIDN6.js.map} +0 -0
- /package/dist/{ChatApp-72HXQV24.js.map → ChatApp-CROSI7JM.js.map} +0 -0
- /package/dist/{absorb-eval-O3HA6XA5.js.map → absorb-eval-MNGQ752C.js.map} +0 -0
- /package/dist/{agent-S2DXXALS.js.map → agent-4AQOFPRD.js.map} +0 -0
- /package/dist/{chunk-ZLC4TSXF.js.map → chunk-242MPMJX.js.map} +0 -0
- /package/dist/{chunk-6KIUVVKX.js.map → chunk-5KKXJZZ4.js.map} +0 -0
- /package/dist/{chunk-O2BS6N3T.js.map → chunk-HF4OXA5L.js.map} +0 -0
- /package/dist/{chunk-YWKLUQDC.js.map → chunk-I4GT27UO.js.map} +0 -0
- /package/dist/{chunk-FSW4FUMW.js.map → chunk-I4OQQRY2.js.map} +0 -0
- /package/dist/{chunk-HKW56YZK.js.map → chunk-J4I5ZJ2V.js.map} +0 -0
- /package/dist/{chunk-FXBMFGOV.js.map → chunk-LLLBH2HH.js.map} +0 -0
- /package/dist/{chunk-TPNTFCSQ.js.map → chunk-MZJY26FW.js.map} +0 -0
- /package/dist/{chunk-LDS4SZBU.js.map → chunk-N2YSVZML.js.map} +0 -0
- /package/dist/{chunk-P4XGM7ZM.js.map → chunk-NR7WLMNI.js.map} +0 -0
- /package/dist/{chunk-4V7SWL2S.js.map → chunk-Q5FEWCS5.js.map} +0 -0
- /package/dist/{chunk-D2WX3BET.js.map → chunk-QPYN2UTV.js.map} +0 -0
- /package/dist/{chunk-C5MTLW7O.js.map → chunk-QXZLYCEZ.js.map} +0 -0
- /package/dist/{chunk-QFZU5CEO.js.map → chunk-RKB7FQJF.js.map} +0 -0
- /package/dist/{chunk-Z3W7D5RR.js.map → chunk-TJDNS5G6.js.map} +0 -0
- /package/dist/{chunk-DN3RLR5Z.js.map → chunk-TN5A7PLU.js.map} +0 -0
- /package/dist/{chunk-TG6CT54D.js.map → chunk-U2EHJZAZ.js.map} +0 -0
- /package/dist/{chunk-QQFURUMS.js.map → chunk-WX67I2KN.js.map} +0 -0
- /package/dist/{chunk-2SV4RKRW.js.map → chunk-YHCPBMRH.js.map} +0 -0
- /package/dist/{client-VBV7Q43P.js.map → client-KGWYROLX.js.map} +0 -0
- /package/dist/{config-52C3DJD2.js.map → config-FNTLKUGA.js.map} +0 -0
- /package/dist/{config-mutate-DKQESY6F.js.map → config-mutate-7BLOYRZF.js.map} +0 -0
- /package/dist/{config-mutator-BF4D4QGN.js.map → config-mutator-NCLHYPJ3.js.map} +0 -0
- /package/dist/{contributions-5OEZWYXI.js.map → contributions-DLUQEAGL.js.map} +0 -0
- /package/dist/{doctor-VLRC4HW2.js.map → doctor-32YP26SJ.js.map} +0 -0
- /package/dist/{follow-up-tool-235LDEMP.js.map → follow-up-tool-AQ7NYZZR.js.map} +0 -0
- /package/dist/{hub-5QK7KYTZ.js.map → hub-Q3I4ALQ2.js.map} +0 -0
- /package/dist/{notes-YLTNRMB5.js.map → notes-TMAGEDUS.js.map} +0 -0
- /package/dist/{notes-settings-X63D5HPM.js.map → notes-settings-WMOH2V75.js.map} +0 -0
- /package/dist/{promote-UPPQ4BQE.js.map → promote-R7FKN5SY.js.map} +0 -0
- /package/dist/{query-XQS5JN6E.js.map → query-YTKTNXCQ.js.map} +0 -0
- /package/dist/{registry-MOMTLJ6R.js.map → registry-7L4RUATA.js.map} +0 -0
- /package/dist/{request-planner-QGZJEAKY.js.map → request-planner-LEUMSI2K.js.map} +0 -0
- /package/dist/{review-candidates-PAKF2JQF.js.map → review-candidates-MOSGA4IS.js.map} +0 -0
- /package/dist/{routing-JG3EV4O4.js.map → routing-FSJ42RZH.js.map} +0 -0
- /package/dist/{schedule-tool-O7ZZ2A7X.js.map → schedule-tool-4T2VZIDW.js.map} +0 -0
- /package/dist/{scheduler-LMPBAA34.js.map → scheduler-6AQDHQI4.js.map} +0 -0
- /package/dist/{seat-JN3IVZ3G.js.map → seat-O37EQRHE.js.map} +0 -0
- /package/dist/{select-ZMMRFMNO.js.map → select-XO2STSQN.js.map} +0 -0
- /package/dist/{set-enabled-7Y3TW6Z4.js.map → set-enabled-ZNEPEFGF.js.map} +0 -0
- /package/dist/{tool-S7QI6HRA.js.map → tool-6KMEAY4U.js.map} +0 -0
- /package/dist/{tool-OCYXCDOK.js.map → tool-PH45YCO5.js.map} +0 -0
- /package/dist/{wiki-OCLFFZG6.js.map → wiki-4HD4FZ4W.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wiki/promote.ts","../src/wiki/prompts.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { AgentMemory, type MemoryRecord, type MemoryType } from \"../agents/agent-memory\"\nimport { WikiStore } from \"./store\"\nimport { buildMemoryPromotePrompt } from \"./prompts\"\nimport { isWikiArticleType, type WikiIndex } from \"./types\"\nimport { newProposalId, readProposal, saveProposal, type PromotionProposal, type ProposalSource } from \"./proposals\"\n\n// --- Memory → wiki promotion ---\n//\n// Finishes the architecture documented in agent-memory.ts: memory is\n// experiential / per-agent; the wiki is authoritative / cross-agent. This\n// module reads per-agent memories, decides (via an LLM judge) which are\n// durable and fleet-relevant, and PROPOSES them as [[wikilinked]] articles\n// for the SHARED wiki store. Nothing reaches the wiki until an operator\n// approves the proposal (proposals.ts): a wrong or planted lesson would\n// otherwise reach every agent overnight.\n//\n// Idempotency has two layers, both keyed on the versioned stamp\n// `memory:<agentId>/<type>_<name>@<updatedAt>`:\n//\n// 1. Promoted memories: the stamp is recorded in the target article's\n// `sources[]` frontmatter (same pattern as getUnabsorbedEntries).\n// 2. Skipped memories: recorded in `.agentx/wiki/_memory-promotions.json`\n// so permanently-irrelevant memories aren't re-judged every night.\n//\n// A bumped `updatedAt` beats both — an edited memory re-enters the\n// pipeline, and mergeSources() replaces the stale stamp for its key so\n// frontmatter stays bounded (one stamp per memory, not per edit).\n\nexport const PROMOTER_OWNER = \"memory-promoter\"\nexport const DEFAULT_PROMOTE_TYPES: MemoryType[] = [\"project\", \"reference\", \"feedback\"]\nexport const PROMOTION_LEDGER_FILE = \"_memory-promotions.json\"\n\nconst STAMP_PREFIX = \"memory:\"\nconst FAILURE_PREFIX = \"failure:\"\n\n/** `failure:<signature digest>@<recurrence bucket>` without the bucket. */\nfunction failureIdentity(stamp: string): string {\n const at = stamp.lastIndexOf(\"@\")\n return at === -1 ? stamp : stamp.slice(0, at)\n}\nconst MEMORY_TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface MemoryCandidate {\n agentId: string\n memory: MemoryRecord\n /** Identity without version: `<agentId>/<type>_<name>` */\n key: string\n /** Identity + version: `memory:<key>@<updatedAt>`. Other sources use\n * their own namespace (`review:`, `failure:`). */\n stamp: string\n /** Distinct sessions a review finding or failure recurred in. More\n * occurrences rank first. */\n occurrences?: number\n /** Some of those sessions, kept as proposal evidence. */\n sessions?: string[]\n /** Some of the runs (task ids) behind a failure, kept as evidence. */\n tasks?: string[]\n /** What a recurring failure has in common: its signature and run count. */\n failure?: { tool: string; errorClass: string; runs: number }\n}\n\nexport interface PromotionLedgerEntry {\n stamp: string\n /** proposed: waiting for an operator; rejected: an operator said no. Both\n * keep the memory from being judged again until it changes. */\n decision: \"promoted\" | \"skipped\" | \"proposed\" | \"rejected\"\n /** Article path when promoted or proposed. */\n article?: string\n /** Proposal id when proposed or decided through one. */\n proposal?: string\n /** LLM skip reason. */\n reason?: string\n /** ISO run timestamp. */\n at: string\n}\n\nexport type PromotionLedger = PromotionLedgerEntry[]\n\n// --- Stamps -------------------------------------------------------------\n\nexport function memoryKey(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\">): string {\n return `${agentId}/${m.type}_${m.name}`\n}\n\nexport function memoryStamp(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\" | \"updatedAt\">): string {\n return `${STAMP_PREFIX}${memoryKey(agentId, m)}@${m.updatedAt}`\n}\n\n/** Parse a `memory:<agentId>/<type>_<name>@<updatedAt>` source stamp.\n * Returns null for anything else (raw entry ids, malformed stamps) so\n * callers can filter article `sources[]` safely. */\nexport function parseMemoryStamp(s: string): {\n agentId: string\n type: MemoryType\n name: string\n updatedAt: string\n key: string\n} | null {\n if (!s.startsWith(STAMP_PREFIX)) return null\n const rest = s.slice(STAMP_PREFIX.length)\n const slash = rest.indexOf(\"/\")\n const at = rest.lastIndexOf(\"@\")\n if (slash <= 0 || at <= slash) return null\n const agentId = rest.slice(0, slash)\n const typeName = rest.slice(slash + 1, at)\n const updatedAt = rest.slice(at + 1)\n if (!updatedAt) return null\n const underscore = typeName.indexOf(\"_\")\n if (underscore <= 0) return null\n const type = typeName.slice(0, underscore) as MemoryType\n const name = typeName.slice(underscore + 1)\n if (!MEMORY_TYPES.includes(type) || !name) return null\n return { agentId, type, name, updatedAt, key: `${agentId}/${typeName}` }\n}\n\n/** Merge new promotion stamps into an article's existing sources: a new\n * stamp REPLACES any prior stamp sharing its key (one stamp per memory,\n * not one per edit); everything else (raw entry ids, other memories'\n * stamps) is kept. Deduped, order-stable. */\nexport function mergeSources(existing: string[] | undefined, newStamps: string[]): string[] {\n const newKeys = new Set<string>()\n for (const s of newStamps) {\n const parsed = parseMemoryStamp(s)\n if (parsed) newKeys.add(parsed.key)\n }\n const out: string[] = []\n for (const s of existing ?? []) {\n const parsed = parseMemoryStamp(s)\n if (parsed && newKeys.has(parsed.key)) continue // superseded\n if (!out.includes(s)) out.push(s)\n }\n for (const s of newStamps) {\n if (!out.includes(s)) out.push(s)\n }\n return out\n}\n\n// --- Cross-agent memory enumeration --------------------------------------\n\n/** List every agent's memories. AgentMemory has no cross-agent API — we\n * enumerate the agent subdirs of `.agentx/agent-memory/` ourselves.\n * `memoryRoot` is the `.agentx` directory (AgentMemory's baseDir root). */\nexport function listAllAgentMemories(\n memoryRoot: string = resolve(process.cwd(), \".agentx\"),\n): Array<{ agentId: string; memory: MemoryRecord }> {\n const store = new AgentMemory({ baseDir: memoryRoot })\n if (!existsSync(store.baseDir)) return []\n const out: Array<{ agentId: string; memory: MemoryRecord }> = []\n for (const entry of readdirSync(store.baseDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith(\"_\") || entry.name.startsWith(\".\")) continue\n for (const memory of store.list(entry.name)) {\n out.push({ agentId: entry.name, memory })\n }\n }\n return out\n}\n\n// --- Skip ledger ----------------------------------------------------------\n\nconst LEDGER_DECISIONS = new Set([\"promoted\", \"skipped\", \"proposed\", \"rejected\"])\n\nexport function readPromotionLedger(wikiBaseDir: string): PromotionLedger {\n const path = resolve(wikiBaseDir, PROMOTION_LEDGER_FILE)\n if (!existsSync(path)) return []\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\"))\n if (!Array.isArray(parsed)) return []\n return parsed.filter(\n (e): e is PromotionLedgerEntry =>\n e && typeof e.stamp === \"string\" && LEDGER_DECISIONS.has(e.decision),\n )\n } catch {\n // Corrupt ledger → treat as empty. Worst case: skipped memories are\n // re-judged once. Promoted memories stay deduped via article sources.\n return []\n }\n}\n\n/** Append entries and compact: keep only the newest entry per memory key\n * (per exact stamp for `review:` and `failure:` sources, where the later\n * entry wins) so the ledger stays bounded. */\nexport function appendPromotionLedger(wikiBaseDir: string, entries: PromotionLedgerEntry[]): void {\n const merged = [...readPromotionLedger(wikiBaseDir), ...entries]\n const byKey = new Map<string, PromotionLedgerEntry>()\n for (const e of merged) {\n const parsed = parseMemoryStamp(e.stamp)\n const key = parsed?.key ?? e.stamp\n const prev = byKey.get(key)\n // A non-memory stamp has no version to compare: the later decision\n // (rejected after proposed) must replace the earlier one.\n if (!prev || !parsed || (parseMemoryStamp(prev.stamp)?.updatedAt ?? \"\") <= parsed.updatedAt) {\n byKey.set(key, e)\n }\n }\n writeFileSync(\n resolve(wikiBaseDir, PROMOTION_LEDGER_FILE),\n JSON.stringify([...byKey.values()], null, 2),\n )\n}\n\n// --- Clustering -------------------------------------------------------------\n\nexport interface PromotionCluster {\n /** ≥1 candidate; >1 when the same `<type>_<name>` exists on several agents. */\n candidates: MemoryCandidate[]\n corroboratingAgents: string[]\n /** 0.5 base + 0.15 per extra corroborating agent + 0.05 for\n * project|reference types, capped at 0.95. Advisory only — shown to\n * the LLM judge, never a hard gate (mirrors clusterWorkflowCandidates). */\n confidence: number\n}\n\n/** Group candidates that share `<type>_<name>` across agents. Exact-match\n * clustering only — two agents naming a memory identically is treated as\n * corroboration; fuzzy matching is future work. */\nexport function groupCandidates(cands: MemoryCandidate[]): PromotionCluster[] {\n const byName = new Map<string, MemoryCandidate[]>()\n for (const c of cands) {\n const k = `${c.memory.type}_${c.memory.name}`\n const list = byName.get(k) ?? []\n list.push(c)\n byName.set(k, list)\n }\n return [...byName.values()].map((candidates) => {\n const corroboratingAgents = [...new Set(candidates.map((c) => c.agentId))].sort()\n const typeBoost = [\"project\", \"reference\"].includes(candidates[0].memory.type) ? 0.05 : 0\n const confidence = Math.min(0.95, 0.5 + 0.15 * (corroboratingAgents.length - 1) + typeBoost)\n return { candidates, corroboratingAgents, confidence }\n })\n}\n\n// --- LLM response parsing ----------------------------------------------------\n\nexport interface PromotedArticle {\n path: string\n title: string\n type?: string\n related?: string[]\n tags: string[]\n content: string\n /** Validated stamps of the memories this article was promoted from. */\n promotedFrom: string[]\n}\n\nexport interface PromotionResponse {\n articles: PromotedArticle[]\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n /** Stamp refs the LLM emitted that weren't in the offered set, plus\n * articles dropped for having zero valid stamps. For the report. */\n warnings: string[]\n}\n\n/** Parse the promotion LLM's reply. Balanced-JSON extraction (adapted from\n * the wiki absorb CLI): find the outermost `{...}`, depth-scan to its\n * close, JSON.parse, then validate. Stamps not present in `offeredStamps`\n * are hallucinations — dropped; an article left with zero valid stamps is\n * dropped entirely (nothing to dedupe against on the next run). Any\n * failure returns `{error}` — the caller must write nothing. */\nexport function parsePromotionResponse(\n text: string,\n offeredStamps: Set<string>,\n): PromotionResponse | { error: string } {\n const start = text.indexOf(\"{\")\n if (start === -1) return { error: \"no JSON object in response\" }\n let depth = 0\n let end = -1\n let inString = false\n let escaped = false\n for (let i = start; i < text.length; i++) {\n const ch = text[i]\n if (escaped) { escaped = false; continue }\n if (ch === \"\\\\\") { escaped = true; continue }\n if (ch === '\"') { inString = !inString; continue }\n if (inString) continue\n if (ch === \"{\") depth++\n else if (ch === \"}\") { depth--; if (depth === 0) { end = i + 1; break } }\n }\n if (end === -1) return { error: \"unbalanced JSON in response\" }\n\n let parsed: any\n try {\n parsed = JSON.parse(text.slice(start, end))\n } catch (e: any) {\n return { error: `JSON parse error: ${e.message}` }\n }\n if (!parsed || typeof parsed !== \"object\" || !Array.isArray(parsed.articles)) {\n return { error: \"response JSON missing articles array\" }\n }\n\n const warnings: string[] = []\n const articles: PromotedArticle[] = []\n for (const a of parsed.articles) {\n if (!a || typeof a.path !== \"string\" || typeof a.title !== \"string\" || typeof a.content !== \"string\") {\n warnings.push(`article missing path/title/content — dropped`)\n continue\n }\n const raw = Array.isArray(a.promotedFrom) ? a.promotedFrom.filter((s: unknown) => typeof s === \"string\") : []\n const promotedFrom = raw.filter((s: string) => offeredStamps.has(s))\n for (const s of raw) {\n if (!offeredStamps.has(s)) warnings.push(`article \"${a.path}\" references unknown stamp \"${s}\" — dropped`)\n }\n if (promotedFrom.length === 0) {\n warnings.push(`article \"${a.path}\" has no valid promotedFrom stamps — dropped`)\n continue\n }\n articles.push({\n path: a.path,\n title: a.title,\n type: typeof a.type === \"string\" ? a.type : undefined,\n related: Array.isArray(a.related) ? a.related.filter((r: unknown) => typeof r === \"string\") : undefined,\n tags: Array.isArray(a.tags) ? a.tags.filter((t: unknown) => typeof t === \"string\") : [],\n content: a.content,\n promotedFrom,\n })\n }\n\n const skipped: Array<{ stamp: string; reason: string }> = []\n for (const s of Array.isArray(parsed.skipped) ? parsed.skipped : []) {\n const stamp = typeof s?.memory === \"string\" ? s.memory : typeof s?.stamp === \"string\" ? s.stamp : \"\"\n if (!offeredStamps.has(stamp)) {\n if (stamp) warnings.push(`skip references unknown stamp \"${stamp}\" — dropped`)\n continue\n }\n skipped.push({ stamp, reason: typeof s.reason === \"string\" ? s.reason : \"\" })\n }\n\n const gaps = Array.isArray(parsed.gaps) ? parsed.gaps.filter((g: unknown) => typeof g === \"string\") : []\n return { articles, skipped, gaps, warnings }\n}\n\n// --- Candidate selection ---------------------------------------------------\n\nexport interface UnpromotedOptions {\n /** Memory types to consider. Default: project, reference, feedback. */\n types?: MemoryType[]\n /** Only memories updated within this window (ms before `now`). */\n sinceMs?: number\n /** Only this agent's memories. */\n agentFilter?: string\n /** Reference clock for `sinceMs` (injectable for tests). Default Date.now(). */\n now?: number\n /** Cap on returned candidates (newest first). Default 20. */\n max?: number\n}\n\n/** Diff all agent memories against what's already been promoted (article\n * `sources[]` stamps across the shared index) or deliberately skipped\n * (ledger). A memory is a candidate iff its key is unseen OR its\n * `updatedAt` is newer than the recorded one — ISO-8601 strings compare\n * lexicographically, so plain `>` is safe. */\nexport function getUnpromotedMemories(\n all: Array<\n // `stamp` is supplied by non-memory sources so their namespace\n // survives; the evidence fields travel through to the proposal.\n Pick<MemoryCandidate, \"agentId\" | \"memory\"> & Partial<Omit<MemoryCandidate, \"agentId\" | \"memory\" | \"key\">>\n >,\n index: WikiIndex,\n ledger: PromotionLedger,\n opts: UnpromotedOptions = {},\n): MemoryCandidate[] {\n const types = opts.types ?? DEFAULT_PROMOTE_TYPES\n const now = opts.now ?? Date.now()\n const max = opts.max ?? 20\n\n // key → newest updatedAt already handled (promoted or skipped)\n const seen = new Map<string, string>()\n // Exact stamps already handled. Non-memory sources (`review:`,\n // `failure:`) have no parseable key/version, so this is how they are\n // recognised as proposed, promoted, skipped or rejected instead of\n // being offered again every night.\n const handledStamps = new Set<string>()\n const record = (stamp: string) => {\n handledStamps.add(stamp)\n const parsed = parseMemoryStamp(stamp)\n if (!parsed) return\n const prev = seen.get(parsed.key)\n if (!prev || parsed.updatedAt > prev) seen.set(parsed.key, parsed.updatedAt)\n }\n for (const article of index.articles) {\n for (const s of article.sources ?? []) record(s)\n }\n for (const e of ledger) record(e.stamp)\n // A failure's stamp moves on as it recurs more (failure-candidates.ts);\n // while one proposal for it waits for review, don't open a second.\n const pendingFailures = new Set(ledger\n .filter((e) => e.decision === \"proposed\" && e.stamp.startsWith(FAILURE_PREFIX))\n .map((e) => failureIdentity(e.stamp)))\n\n const candidates: MemoryCandidate[] = []\n for (const { agentId, memory, stamp, occurrences, sessions, tasks, failure } of all) {\n if (!types.includes(memory.type)) continue\n if (opts.agentFilter && agentId !== opts.agentFilter) continue\n if (opts.sinceMs !== undefined) {\n const updated = Date.parse(memory.updatedAt)\n if (!Number.isFinite(updated) || updated < now - opts.sinceMs) continue\n }\n if (stamp && handledStamps.has(stamp)) continue\n if (stamp?.startsWith(FAILURE_PREFIX) && pendingFailures.has(failureIdentity(stamp))) continue\n const key = memoryKey(agentId, memory)\n const handled = seen.get(key)\n if (handled && memory.updatedAt <= handled) continue\n // A caller-supplied stamp is kept as-is. Re-deriving it would\n // rewrite a `review:` stamp into the `memory:` namespace and lose\n // the provenance the ledger needs to tell the two sources apart.\n candidates.push({\n agentId, memory, key, stamp: stamp ?? memoryStamp(agentId, memory), occurrences,\n ...(sessions?.length ? { sessions } : {}),\n ...(tasks?.length ? { tasks } : {}),\n ...(failure ? { failure } : {}),\n })\n }\n\n // Recurrence first where a source provides it, then recency. Memory\n // candidates carry no count, so their ordering is unchanged.\n return candidates\n .sort((a, b) =>\n (b.occurrences ?? 0) - (a.occurrences ?? 0) ||\n b.memory.updatedAt.localeCompare(a.memory.updatedAt))\n .slice(0, max)\n}\n\n// --- LLM transport -----------------------------------------------------------\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\"\nconst ANTHROPIC_VERSION = \"2023-06-01\"\n\nexport interface PromotionLlmOptions {\n /** Route through the daemon: POST /task to this agent. No API key needed —\n * the agent's own session handles auth (mirrors architectWorkflowViaAgent). */\n viaAgent?: string\n /** Direct Anthropic API model. Requires ANTHROPIC_API_KEY. */\n model?: string\n daemonUrl?: string\n timeoutMs?: number\n fetchImpl?: typeof fetch\n /** Stable chat id so a retry's --resume replay sees the first reply. */\n chatId?: string\n}\n\n/** Run the promotion prompt through an LLM and return the raw reply text.\n * Throws on transport failure — the caller decides whether to retry.\n * `feedback` turns the call into a correction round: the via-agent branch\n * resumes the same session; the direct branch replays the conversation. */\nexport async function callPromotionLlm(\n prompt: string,\n opts: PromotionLlmOptions,\n feedback: string | null = null,\n): Promise<string> {\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 180_000\n const ctrl = new AbortController()\n const timer = setTimeout(() => ctrl.abort(), timeoutMs)\n try {\n if (opts.viaAgent) {\n const daemonUrl = (opts.daemonUrl ?? \"http://127.0.0.1:18800\").replace(/\\/+$/, \"\")\n const message = feedback\n ? `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object — same structure, fixed issues, valid JSON only.`\n : prompt\n const res = await fetchImpl(`${daemonUrl}/task`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n agent: opts.viaAgent,\n message,\n // First call gets a fresh session; the retry resumes it so the\n // agent can fix its own output instead of starting over.\n freshSession: feedback === null,\n context: { channel: \"api\", chatId: opts.chatId ?? \"memory-promote\" },\n }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`agent /task ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n if (body.error) throw new Error(`agent reported error: ${body.error}`)\n const content = String(body.content ?? \"\").trim()\n if (!content) throw new Error(\"agent returned empty content\")\n return content\n }\n\n if (!opts.model) throw new Error(\"callPromotionLlm: viaAgent or model is required\")\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) throw new Error(\"ANTHROPIC_API_KEY not set; use --via <agentId> instead\")\n const messages: Array<{ role: string; content: string }> = [{ role: \"user\", content: prompt }]\n if (feedback) {\n messages.push({ role: \"assistant\", content: \"(previous attempt; will retry with corrections)\" })\n messages.push({ role: \"user\", content: `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object.` })\n }\n const res = await fetchImpl(ANTHROPIC_API, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_VERSION,\n },\n body: JSON.stringify({ model: opts.model, max_tokens: 8000, messages }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`anthropic API ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n const blocks = Array.isArray(body.content) ? body.content : []\n const text = blocks.filter((b: any) => b?.type === \"text\").map((b: any) => b.text).join(\"\\n\").trim()\n if (!text) throw new Error(\"anthropic API returned no text content\")\n return text\n } finally {\n clearTimeout(timer)\n }\n}\n\n// --- Orchestrator ------------------------------------------------------------\n\nexport interface RunPromotionOptions extends PromotionLlmOptions {\n /** Wiki base dir (the shared store). Default `.agentx/wiki`. */\n wikiDir?: string\n /** The `.agentx` dir holding `agent-memory/`. Default `.agentx`. */\n memoryRoot?: string\n sinceMs?: number\n agentFilter?: string\n types?: MemoryType[]\n max?: number\n /** Candidates from somewhere other than agent memory — session-monitor\n * reviews and recurring failures. They join the same pool so they inherit\n * the ledger, the dedupe and the judge rather than getting a second\n * pipeline that drifts from this one. */\n extraCandidates?: MemoryCandidate[]\n /** Write proposals + ledger. Default false (dry-run). */\n commit?: boolean\n /** Upper bound on the judge prompt, in estimated tokens. Clusters are\n * dropped from the end (lowest ranked) until it fits; they stay\n * unledgered, so the next run picks them up. Default 60 000. */\n budgetTokens?: number\n log?: (msg: string) => void\n /** Injectable clock (tests). */\n now?: number\n}\n\nexport interface PromotionReport {\n candidates: MemoryCandidate[]\n clusters: PromotionCluster[]\n /** Proposals written this run; nothing reaches the wiki until approved. */\n proposed: Array<{ id: string; path: string; title: string; type?: string; stamps: string[]; agents: string[]; occurrences: number }>\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n warnings: string[]\n errors: string[]\n dryRun: boolean\n}\n\n/** End-to-end promotion run: enumerate memories → diff against promoted/\n * skipped → cluster → (dry-run stops here) → LLM judge → write articles\n * with merged sources → ledger → rebuild index. Parses the full response\n * before any write — a bad reply writes nothing, and idempotency makes\n * the next scheduled run retry for free. */\nexport async function runPromotion(opts: RunPromotionOptions = {}): Promise<PromotionReport> {\n const wikiDir = opts.wikiDir ?? resolve(process.cwd(), \".agentx\", \"wiki\")\n const memoryRoot = opts.memoryRoot ?? resolve(process.cwd(), \".agentx\")\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now()\n const report: PromotionReport = {\n candidates: [], clusters: [], proposed: [], skipped: [],\n gaps: [], warnings: [], errors: [], dryRun: !opts.commit,\n }\n\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const index = store.rebuildIndex()\n const ledger = readPromotionLedger(wikiDir)\n const all = [...listAllAgentMemories(memoryRoot), ...(opts.extraCandidates ?? [])]\n\n report.candidates = getUnpromotedMemories(all, index, ledger, {\n types: opts.types,\n sinceMs: opts.sinceMs,\n agentFilter: opts.agentFilter,\n now,\n max: opts.max,\n })\n if (report.candidates.length === 0) {\n log(\"no unpromoted memories in window\")\n return report\n }\n report.clusters = groupCandidates(report.candidates)\n if (report.dryRun) return report\n\n const articleList = index.articles.map((a) => ({ title: a.title, path: a.path, type: a.type }))\n const worldview = store.getWorldview() ?? \"\"\n const budget = opts.budgetTokens ?? DEFAULT_BUDGET_TOKENS\n let clusters = report.clusters\n let prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n while (estimateTokens(prompt) > budget && clusters.length > 1) {\n clusters = clusters.slice(0, -1)\n prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n }\n if (estimateTokens(prompt) > budget) {\n report.errors.push(`judge prompt (~${estimateTokens(prompt)} tokens) exceeds the ${budget}-token budget even with one cluster`)\n return report\n }\n if (clusters.length < report.clusters.length) {\n report.warnings.push(`token budget: judged ${clusters.length} of ${report.clusters.length} clusters; the rest wait for the next run`)\n }\n const judged = clusters.flatMap((c) => c.candidates)\n const offered = new Set(judged.map((c) => c.stamp))\n const chatId = opts.chatId ?? `memory-promote-${new Date(now).toISOString().slice(0, 10)}`\n\n let parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }), offered)\n if (\"error\" in parsed) {\n log(`parse failed (${parsed.error}) — retrying with feedback`)\n parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }, parsed.error), offered)\n }\n if (\"error\" in parsed) {\n report.errors.push(`LLM response unusable after retry: ${parsed.error}`)\n return report\n }\n report.warnings.push(...parsed.warnings)\n report.gaps = parsed.gaps\n\n const at = new Date(now).toISOString()\n const ledgerEntries: PromotionLedgerEntry[] = []\n\n const byStamp = new Map(judged.map((c) => [c.stamp, c]))\n for (const article of parsed.articles) {\n const existing = store.readArticle(article.path)\n if (existing && !store.canWrite(existing.meta, PROMOTER_OWNER)) {\n // Owned by another agent — a human resolves; stamps stay unledgered\n // so the memory is retried after the standoff clears.\n report.errors.push(`write denied for \"${article.path}\" (owned by ${existing.meta.owner ?? \"?\"})`)\n continue\n }\n const evidence = evidenceFor(article.promotedFrom, byStamp)\n const proposal: PromotionProposal = {\n id: newProposalId(now, article.path),\n createdAt: at,\n status: \"pending\",\n article,\n evidence,\n ...(existing ? { replaces: { lastUpdated: existing.meta.lastUpdated, contentHash: contentHash(existing.content) } } : {}),\n }\n saveProposal(wikiDir, proposal)\n report.proposed.push({\n id: proposal.id, path: article.path, title: article.title, type: article.type,\n stamps: article.promotedFrom, agents: evidence.agents, occurrences: evidence.occurrences,\n })\n for (const stamp of article.promotedFrom) {\n ledgerEntries.push({ stamp, decision: \"proposed\", article: article.path, proposal: proposal.id, at })\n }\n }\n\n for (const s of parsed.skipped) {\n ledgerEntries.push({ stamp: s.stamp, decision: \"skipped\", reason: s.reason, at })\n report.skipped.push(s)\n }\n\n if (ledgerEntries.length) appendPromotionLedger(wikiDir, ledgerEntries)\n return report\n}\n\nconst DEFAULT_BUDGET_TOKENS = 60_000\n\n/** Rough token count (4 characters per token) — enough for a budget. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4)\n}\n\nfunction contentHash(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\nconst EXCERPT_CHARS = 600\n\n/** Which pipeline a stamp came from. */\nexport function sourceKind(stamp: string): ProposalSource[\"kind\"] {\n if (stamp.startsWith(\"review:\")) return \"review\"\n if (stamp.startsWith(FAILURE_PREFIX)) return \"failure\"\n return \"memory\"\n}\n\n/** What a reviewer needs to judge a proposal: each source memory, review\n * finding or recurring failure, who wrote it and in which task, how often\n * it recurred, and some of the sessions and runs it came from. */\nexport function evidenceFor(stamps: string[], byStamp: Map<string, MemoryCandidate>): PromotionProposal[\"evidence\"] {\n const sources: ProposalSource[] = []\n for (const stamp of stamps) {\n const c = byStamp.get(stamp)\n if (!c) continue\n sources.push({\n stamp,\n kind: sourceKind(stamp),\n agentId: c.agentId,\n type: c.memory.type,\n name: c.memory.name,\n description: c.memory.description,\n ...(c.occurrences ? { occurrences: c.occurrences } : {}),\n ...(c.sessions?.length ? { sessions: c.sessions } : {}),\n ...(c.tasks?.length ? { tasks: c.tasks } : {}),\n ...(c.failure ? { failure: c.failure } : {}),\n ...(c.memory.author ? { author: c.memory.author } : {}),\n ...(c.memory.taskId ? { taskId: c.memory.taskId } : {}),\n updatedAt: c.memory.updatedAt,\n excerpt: c.memory.body.slice(0, EXCERPT_CHARS),\n })\n }\n return {\n agents: [...new Set(sources.map((s) => s.agentId))].sort(),\n occurrences: Math.max(1, ...sources.map((s) => s.occurrences ?? 1)),\n sources,\n }\n}\n\n// --- Review: approve or reject a proposal ------------------------------------\n\nexport type ProposalDecisionResult =\n | { ok: true; proposal: PromotionProposal }\n | { ok: false; error: string }\n\n/** Write the proposed article into the shared wiki. Refuses when the\n * article changed (or appeared) since the proposal was made, unless\n * `force`, so an approval can't silently overwrite someone's edit. */\nexport function approveProposal(\n wikiDir: string, id: string, opts: { by?: string; force?: boolean; now?: number; log?: (m: string) => void } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n\n const log = opts.log ?? (() => {})\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const { article } = proposal\n const existing = store.readArticle(article.path)\n if (!opts.force) {\n if (existing && !proposal.replaces) {\n return { ok: false, error: `\"${article.path}\" was created after this proposal; review it, then approve with --force` }\n }\n if (existing && proposal.replaces && contentHash(existing.content) !== proposal.replaces.contentHash) {\n return { ok: false, error: `\"${article.path}\" changed since this proposal; review it, then approve with --force` }\n }\n }\n const now = opts.now ?? Date.now()\n const today = new Date(now).toISOString().slice(0, 10)\n const ok = store.writeArticle(\n article.path,\n {\n title: article.title,\n type: isWikiArticleType(article.type) ? article.type : undefined,\n related: article.related,\n tags: article.tags,\n owner: PROMOTER_OWNER,\n access: \"public\",\n created: existing?.meta.created ?? today,\n lastUpdated: today,\n sources: mergeSources(existing?.meta.sources, article.promotedFrom),\n },\n article.content,\n PROMOTER_OWNER,\n )\n if (!ok) return { ok: false, error: `write denied for \"${article.path}\" (owned by ${existing?.meta.owner ?? \"?\"})` }\n\n const at = new Date(now).toISOString()\n appendPromotionLedger(wikiDir, article.promotedFrom.map((stamp) => ({\n stamp, decision: \"promoted\" as const, article: article.path, proposal: id, at,\n })))\n store.rebuildIndex()\n const decided: PromotionProposal = { ...proposal, status: \"approved\", decidedBy: opts.by ?? \"operator\", decidedAt: at }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n\n/** Decline a proposal. Its memories aren't judged again until they change. */\nexport function rejectProposal(\n wikiDir: string, id: string, opts: { by?: string; reason?: string; now?: number } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n const at = new Date(opts.now ?? Date.now()).toISOString()\n appendPromotionLedger(wikiDir, proposal.article.promotedFrom.map((stamp) => ({\n stamp, decision: \"rejected\" as const, article: proposal.article.path, proposal: id, reason: opts.reason, at,\n })))\n const decided: PromotionProposal = {\n ...proposal, status: \"rejected\", decidedBy: opts.by ?? \"operator\", decidedAt: at,\n ...(opts.reason ? { reason: opts.reason } : {}),\n }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n","import { fieldChecklistMarkdown, tierLadderMarkdown } from \"@/decisions/seats/article-fields\"\nimport type { WikiMode } from \"./hub\"\nimport type { PromotionCluster } from \"./promote\"\n\n/**\n * Farzapedia-faithful absorb prompt.\n *\n * Replaces the legacy three-mode design (flat/graph/unified) with one prompt\n * that mirrors what Karpathy and Farza describe: article `type` is the\n * organizational spine, `[[wikilinks]]` are the navigation surface, tags are\n * a secondary hint. The LLM chooses paths and cross-references; it does not\n * produce a sprawling per-article tag cloud.\n *\n * The mode parameter is kept in the signature for backwards compatibility but\n * all values resolve to the same prompt — retained callers (hub.ts, wiki CLI)\n * still pass a mode but it no longer changes behavior.\n */\nexport function buildAbsorbPrompt(\n _mode: WikiMode,\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n coveringBlock = \"\",\n): string {\n return buildFarzapediaPrompt(agentId, worldview, existingArticles, entryTexts, entryCount, factsBlock, coveringBlock)\n}\n\nfunction buildFarzapediaPrompt(\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n coveringBlock = \"\",\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n // Group the existing index by type so the LLM sees the structure.\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const existingList = existingArticles.length === 0 ? \"\" :\n \"\\n## Existing Articles (the current catalog)\\n\\n\" +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n return `You are compiling a personal wiki for the \"${agentId}\" agent in the Karpathy / Farzapedia pattern.\n\n## The Pattern (read this first — it shapes every decision below)\n\nThe wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \\`type\\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.\n\n**Three rules, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` frontmatter field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint, not a retrieval spine.** 2–4 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \\`event\\`. Do NOT tag with generic terms (\"deploy\", \"work\") that will match half the corpus.\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## How to process entries\n\nFor each of the ${entryCount} raw entries below, ask in this order:\n\n1. **Does it extend an existing article?** If a person/project/concept/event mentioned in the entry already has an article in the catalog, produce an UPDATE at that article's path with the full merged content. Prefer merging over proliferating. When the article is shown in full below, keep every fact it already has.\n2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.\n3. **Does it belong in an existing \\`event\\` or \\`decision\\`?** Most work entries fold into one of these.\n4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere — skip. The wiki is curated, not exhaustive.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, and their actual contact details — not a log of every interaction. History goes last, and only where it still bears on the present.\n- **Length: 20–100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.\n- **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.\n\n## Build in layers\n\nFacts are not equally load-bearing. Write them in this order, and never\nspend length on a lower layer while a higher one is missing:\n\n${tierLadderMarkdown()}\n\nAn article is a building, not a transcript. The common failure is to\nfurnish a slab: a person's page that records an invoice thread in detail\nand never states their role, their organisation, or how to reach them.\nThat article cannot answer \"who is this\" or \"how do I reach them\" — the\ntwo questions it exists for. History is the last layer, and it is the\nonly one you can safely leave thin.\n\n## Identity before history\n\nAn entity article must first establish **what the entity is**, and only then\nwhat happened to it.\n\nBefore the narrative, every entity article carries an **Identity** section\nwith the fields for its type. State a field even when the entries do not\nanswer it, as \\`unknown\\`, so the gap is visible and a later pass can fill it.\nA field silently omitted is indistinguishable from a field that does not\napply.\n\n${fieldChecklistMarkdown()}\n\nFields in **bold** are the ones an article of that type is unusable without.\nEvery article of any type also states what it is, why it matters to our work,\nand which named people, projects or systems it connects to.\n\nExtract identifiers **verbatim** from the entries. A phone number, an\naccount number, a URL or a path is the single most useful thing an article\ncan carry, and it is exactly what summarising discards. Never paraphrase or\nredact one that appears in the source; if a value appears only in part, say\nso rather than dropping it.\n\n## Wikilink discipline\n\n- Use \\`[[Article Title]]\\` **inside the body** every time another tracked entity is referenced.\n- List those same targets in the \\`related\\` frontmatter field.\n- If you reference something that has no article yet (\\`[[New Thing]]\\`), add \"New Thing\" to the output \\`gaps\\` array so the next pass can create it.\n\n## Article frontmatter (exact shape — do not invent fields)\n\n\\`\\`\\`yaml\n---\ntitle: \"Article Title\"\ntype: person | project | place | concept | event | decision | pattern\nrelated: [\"Other Article\", \"Another Article\"]\ntags: [\"2-4-specific-tags\", \"no-dates-unless-event\"]\nowner: ${agentId}\naccess: public | shared | private\ncreated: YYYY-MM-DD\nlast_updated: YYYY-MM-DD\nsources: [\"entry-id-1\", \"entry-id-2\"]\n---\n\\`\\`\\`\n\nAccess guidance: default \\`public\\`; \\`private\\` only for sensitive credentials or agent-specific learnings; \\`shared\\` with specific agent IDs when the article matters only to a subset.\n${worldviewSection}${existingList}${coveringBlock}${factsBlock}\n## Gap Detection\n\nAfter compiling, populate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific:\n\n- Good: \"Karim Rahmouni — coach for Tunis Padel club, mentioned twice but no \\`people/\\` article\"\n- Bad: \"We need more content about deployments\"\n\n## Output — valid JSON only, no markdown fencing\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"projects/globex-system-v2.md\",\n \"title\": \"Globex System V2\",\n \"type\": \"project\",\n \"related\": [\"Alex Rivera\", \"Laravel\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"laravel\", \"react\"],\n \"content\": \"Globex System V2 is [[Alex Rivera]]'s production Laravel + React app for …\",\n \"sources\": [\"entry-id-1\", \"entry-id-2\"]\n }\n ],\n \"gaps\": [\n \"Karim Rahmouni — Tunis Padel coach, mentioned in entries but no people/ article\"\n ]\n}\n\\`\\`\\`\n\nENTRIES (${entryCount}):\n\n${entryTexts}`\n}\n\n/** Max chars of a memory body shown to the promotion judge. */\nexport const PROMOTE_BODY_LIMIT = 2000\n\n/** Past this catalog size, list titles only (no paths) to bound the prompt. */\nconst PROMOTE_CATALOG_LIMIT = 300\n\n/**\n * Memory→wiki promotion prompt. Mirrors the Farzapedia absorb prompt's\n * skeleton (type spine, path convention, wikilink discipline, gaps) but\n * judges per-agent MEMORIES instead of raw entries: promote only durable,\n * cross-agent-relevant facts into the shared wiki.\n *\n * The LLM does NOT emit frontmatter — the caller sets owner/access/dates/\n * sources itself, which removes a whole class of hallucination. Candidates\n * are referenced only by their exact stamp strings.\n */\nexport function buildMemoryPromotePrompt(\n ownerId: string,\n clusters: PromotionCluster[],\n existingArticles: Array<{ title: string; path: string; type?: string }>,\n worldview: string,\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const titlesOnly = existingArticles.length > PROMOTE_CATALOG_LIMIT\n const existingList = existingArticles.length === 0 ? \"\" :\n `\\n## Existing Articles (the current catalog${titlesOnly ? `, ${existingArticles.length} articles — titles only` : \"\"})\\n\\n` +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => titlesOnly ? `- [[${a.title}]]` : `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n const candidateCount = clusters.reduce((n, c) => n + c.candidates.length, 0)\n const candidateBlocks = clusters.map(cluster => {\n const corroboration = cluster.corroboratingAgents.length > 1\n ? `corroborated by: ${cluster.corroboratingAgents.join(\", \")} (${cluster.corroboratingAgents.length} agents)`\n : `single agent: ${cluster.corroboratingAgents[0]}`\n return cluster.candidates.map(c => {\n const body = c.memory.body.length > PROMOTE_BODY_LIMIT\n ? c.memory.body.slice(0, PROMOTE_BODY_LIMIT) + \"\\n[… truncated]\"\n : c.memory.body\n return [\n `--- CANDIDATE ${c.stamp} ---`,\n `type: ${c.memory.type} | agent: ${c.agentId} | ${corroboration} | confidence: ${cluster.confidence.toFixed(2)}`,\n `description: ${c.memory.description}`,\n body,\n `--- END CANDIDATE ---`,\n ].join(\"\\n\")\n }).join(\"\\n\\n\")\n }).join(\"\\n\\n\")\n\n return `You are promoting per-agent experiential memories into the shared, authoritative AgentX wiki (owner: \"${ownerId}\").\n\n## The Job\n\nMemory is \"what one agent learned\"; the wiki is \"what every agent should know.\" For each candidate memory below, decide: promote it into a wiki article, or skip it with a reason.\n\n**The promotion test — apply in order, skip on the first \"no\":**\n\n1. **Durable?** Will this still be true in 3 months? Transient state, one-off task context, and dated status pings fail here.\n2. **Cross-agent?** Would a DIFFERENT agent benefit from knowing this? Facts about shared infrastructure, projects, people, and conventions pass; one agent's private workflow habits fail.\n3. **A fact, not a preference?** Personal style preferences private to one agent's operator relationship fail.\n4. **Never a secret.** Credentials, tokens, keys, and passwords are NEVER promoted — skip with reason \"contains secret\". Pointers to where a secret lives (e.g. \"SSH config is in X\") are fine; the secret itself is not.\n\nCorroboration across agents (shown per candidate) is a confidence signal, not a requirement — a single-agent memory that passes the test above should be promoted.\n\nCandidates stamped \\`failure:\\` are not memories: they are the same failure recurring across several sessions, with example runs. The recurrence is what makes them durable. Promote one only when the examples share a cause, as a \\`pattern\\` article saying what fails, why, and what to do instead (including a tool or configuration change, if that is the fix). Never copy task text or error output verbatim beyond what the lesson needs.\n\n**Three rules for the articles you write, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, skip the memory instead.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` output field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint.** 2–4 specific tags max. No dates unless the article is an \\`event\\`. No generic terms (\"deploy\", \"work\").\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## Merge over create\n\nIf a candidate extends an article in the catalog below, emit an UPDATE at the EXISTING path with the full merged content. Prefer merging over proliferating. Several candidates about the same entity should fold into ONE article.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** Write about the entity's role in our work, not a diary of how the memory was learned.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Length: 20–100 lines.**\n- **Every paragraph earns its place.**\n\n## Gap Detection\n\nPopulate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific (\"Karim Rahmouni — coach mentioned twice, no people/ article\"), not vague (\"need more deploy content\").\n${worldviewSection}${existingList}\n## Output — valid JSON only, no markdown fencing\n\nReference candidates ONLY by their exact stamp strings (the \\`memory:...\\` line in each CANDIDATE header). Every candidate must appear in exactly one of \\`articles[].promotedFrom\\` or \\`skipped\\`. Do NOT emit frontmatter, owner, access, dates, or sources — the caller sets those.\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"concepts/globex-staging-deploy.md\",\n \"title\": \"Globex Staging Deploy\",\n \"type\": \"concept\",\n \"related\": [\"Globex System V2\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"deploy\"],\n \"content\": \"Deploys to [[Globex System V2]] staging go through …\",\n \"promotedFrom\": [\"memory:coder-agent/project_globex_deploy@2026-07-01T22:14:03.000Z\"]\n }\n ],\n \"skipped\": [\n { \"memory\": \"memory:cx/feedback_short_replies@2026-06-01T00:00:00.000Z\", \"reason\": \"agent-specific style preference, not a cross-agent fact\" }\n ],\n \"gaps\": []\n}\n\\`\\`\\`\n\nCANDIDATES (${candidateCount} memories in ${clusters.length} clusters):\n\n${candidateBlocks}`\n}\n"],"mappings":"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","names":["createHash","existsSync","readdirSync","readFileSync","writeFileSync","resolve","buildAbsorbPrompt","_mode","agentId","worldview","existingArticles","entryTexts","entryCount","factsBlock","coveringBlock","buildFarzapediaPrompt","worldviewSection","byType","a","t","list","existingList","type","arr","x","y","tierLadderMarkdown","fieldChecklistMarkdown","PROMOTE_BODY_LIMIT","PROMOTE_CATALOG_LIMIT","buildMemoryPromotePrompt","ownerId","clusters","titlesOnly","candidateCount","n","c","candidateBlocks","cluster","corroboration","body","PROMOTER_OWNER","DEFAULT_PROMOTE_TYPES","PROMOTION_LEDGER_FILE","STAMP_PREFIX","FAILURE_PREFIX","failureIdentity","stamp","at","MEMORY_TYPES","memoryKey","agentId","m","memoryStamp","parseMemoryStamp","s","rest","slash","typeName","updatedAt","underscore","type","name","mergeSources","existing","newStamps","newKeys","parsed","out","listAllAgentMemories","memoryRoot","resolve","store","AgentMemory","existsSync","entry","readdirSync","memory","LEDGER_DECISIONS","readPromotionLedger","wikiBaseDir","path","readFileSync","e","appendPromotionLedger","entries","merged","byKey","key","prev","writeFileSync","groupCandidates","cands","byName","c","k","list","candidates","corroboratingAgents","typeBoost","confidence","parsePromotionResponse","text","offeredStamps","start","depth","end","inString","escaped","i","ch","warnings","articles","a","raw","promotedFrom","r","t","skipped","gaps","g","getUnpromotedMemories","all","index","ledger","opts","types","now","max","seen","handledStamps","record","article","pendingFailures","occurrences","sessions","tasks","failure","updated","handled","b","ANTHROPIC_API","ANTHROPIC_VERSION","callPromotionLlm","prompt","feedback","fetchImpl","timeoutMs","ctrl","timer","daemonUrl","message","res","errBody","body","content","apiKey","messages","runPromotion","wikiDir","log","report","WikiStore","args","articleList","worldview","budget","DEFAULT_BUDGET_TOKENS","clusters","buildMemoryPromotePrompt","estimateTokens","judged","offered","chatId","ledgerEntries","byStamp","evidence","evidenceFor","proposal","newProposalId","contentHash","saveProposal","createHash","EXCERPT_CHARS","sourceKind","stamps","sources","approveProposal","id","readProposal","today","isWikiArticleType","decided","rejectProposal"]}
|
|
1
|
+
{"version":3,"sources":["../src/wiki/promote.ts","../src/wiki/prompts.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { AgentMemory, type MemoryRecord, type MemoryType } from \"../agents/agent-memory\"\nimport { WikiStore } from \"./store\"\nimport { buildMemoryPromotePrompt } from \"./prompts\"\nimport { isWikiArticleType, type WikiIndex } from \"./types\"\nimport { newProposalId, readProposal, saveProposal, type PromotionProposal, type ProposalSource } from \"./proposals\"\n\n// --- Memory → wiki promotion ---\n//\n// Finishes the architecture documented in agent-memory.ts: memory is\n// experiential / per-agent; the wiki is authoritative / cross-agent. This\n// module reads per-agent memories, decides (via an LLM judge) which are\n// durable and fleet-relevant, and PROPOSES them as [[wikilinked]] articles\n// for the SHARED wiki store. Nothing reaches the wiki until an operator\n// approves the proposal (proposals.ts): a wrong or planted lesson would\n// otherwise reach every agent overnight.\n//\n// Idempotency has two layers, both keyed on the versioned stamp\n// `memory:<agentId>/<type>_<name>@<updatedAt>`:\n//\n// 1. Promoted memories: the stamp is recorded in the target article's\n// `sources[]` frontmatter (same pattern as getUnabsorbedEntries).\n// 2. Skipped memories: recorded in `.agentx/wiki/_memory-promotions.json`\n// so permanently-irrelevant memories aren't re-judged every night.\n//\n// A bumped `updatedAt` beats both — an edited memory re-enters the\n// pipeline, and mergeSources() replaces the stale stamp for its key so\n// frontmatter stays bounded (one stamp per memory, not per edit).\n\nexport const PROMOTER_OWNER = \"memory-promoter\"\nexport const DEFAULT_PROMOTE_TYPES: MemoryType[] = [\"project\", \"reference\", \"feedback\"]\nexport const PROMOTION_LEDGER_FILE = \"_memory-promotions.json\"\n\nconst STAMP_PREFIX = \"memory:\"\nconst FAILURE_PREFIX = \"failure:\"\n\n/** `failure:<signature digest>@<recurrence bucket>` without the bucket. */\nfunction failureIdentity(stamp: string): string {\n const at = stamp.lastIndexOf(\"@\")\n return at === -1 ? stamp : stamp.slice(0, at)\n}\nconst MEMORY_TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface MemoryCandidate {\n agentId: string\n memory: MemoryRecord\n /** Identity without version: `<agentId>/<type>_<name>` */\n key: string\n /** Identity + version: `memory:<key>@<updatedAt>`. Other sources use\n * their own namespace (`review:`, `failure:`). */\n stamp: string\n /** Distinct sessions a review finding or failure recurred in. More\n * occurrences rank first. */\n occurrences?: number\n /** Some of those sessions, kept as proposal evidence. */\n sessions?: string[]\n /** Some of the runs (task ids) behind a failure, kept as evidence. */\n tasks?: string[]\n /** What a recurring failure has in common: its signature and run count. */\n failure?: { tool: string; errorClass: string; runs: number }\n}\n\nexport interface PromotionLedgerEntry {\n stamp: string\n /** proposed: waiting for an operator; rejected: an operator said no. Both\n * keep the memory from being judged again until it changes. */\n decision: \"promoted\" | \"skipped\" | \"proposed\" | \"rejected\"\n /** Article path when promoted or proposed. */\n article?: string\n /** Proposal id when proposed or decided through one. */\n proposal?: string\n /** LLM skip reason. */\n reason?: string\n /** ISO run timestamp. */\n at: string\n}\n\nexport type PromotionLedger = PromotionLedgerEntry[]\n\n// --- Stamps -------------------------------------------------------------\n\nexport function memoryKey(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\">): string {\n return `${agentId}/${m.type}_${m.name}`\n}\n\nexport function memoryStamp(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\" | \"updatedAt\">): string {\n return `${STAMP_PREFIX}${memoryKey(agentId, m)}@${m.updatedAt}`\n}\n\n/** Parse a `memory:<agentId>/<type>_<name>@<updatedAt>` source stamp.\n * Returns null for anything else (raw entry ids, malformed stamps) so\n * callers can filter article `sources[]` safely. */\nexport function parseMemoryStamp(s: string): {\n agentId: string\n type: MemoryType\n name: string\n updatedAt: string\n key: string\n} | null {\n if (!s.startsWith(STAMP_PREFIX)) return null\n const rest = s.slice(STAMP_PREFIX.length)\n const slash = rest.indexOf(\"/\")\n const at = rest.lastIndexOf(\"@\")\n if (slash <= 0 || at <= slash) return null\n const agentId = rest.slice(0, slash)\n const typeName = rest.slice(slash + 1, at)\n const updatedAt = rest.slice(at + 1)\n if (!updatedAt) return null\n const underscore = typeName.indexOf(\"_\")\n if (underscore <= 0) return null\n const type = typeName.slice(0, underscore) as MemoryType\n const name = typeName.slice(underscore + 1)\n if (!MEMORY_TYPES.includes(type) || !name) return null\n return { agentId, type, name, updatedAt, key: `${agentId}/${typeName}` }\n}\n\n/** Merge new promotion stamps into an article's existing sources: a new\n * stamp REPLACES any prior stamp sharing its key (one stamp per memory,\n * not one per edit); everything else (raw entry ids, other memories'\n * stamps) is kept. Deduped, order-stable. */\nexport function mergeSources(existing: string[] | undefined, newStamps: string[]): string[] {\n const newKeys = new Set<string>()\n for (const s of newStamps) {\n const parsed = parseMemoryStamp(s)\n if (parsed) newKeys.add(parsed.key)\n }\n const out: string[] = []\n for (const s of existing ?? []) {\n const parsed = parseMemoryStamp(s)\n if (parsed && newKeys.has(parsed.key)) continue // superseded\n if (!out.includes(s)) out.push(s)\n }\n for (const s of newStamps) {\n if (!out.includes(s)) out.push(s)\n }\n return out\n}\n\n// --- Cross-agent memory enumeration --------------------------------------\n\n/** List every agent's memories. AgentMemory has no cross-agent API — we\n * enumerate the agent subdirs of `.agentx/agent-memory/` ourselves.\n * `memoryRoot` is the `.agentx` directory (AgentMemory's baseDir root). */\nexport function listAllAgentMemories(\n memoryRoot: string = resolve(process.cwd(), \".agentx\"),\n): Array<{ agentId: string; memory: MemoryRecord }> {\n const store = new AgentMemory({ baseDir: memoryRoot })\n if (!existsSync(store.baseDir)) return []\n const out: Array<{ agentId: string; memory: MemoryRecord }> = []\n for (const entry of readdirSync(store.baseDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith(\"_\") || entry.name.startsWith(\".\")) continue\n for (const memory of store.list(entry.name)) {\n out.push({ agentId: entry.name, memory })\n }\n }\n return out\n}\n\n// --- Skip ledger ----------------------------------------------------------\n\nconst LEDGER_DECISIONS = new Set([\"promoted\", \"skipped\", \"proposed\", \"rejected\"])\n\nexport function readPromotionLedger(wikiBaseDir: string): PromotionLedger {\n const path = resolve(wikiBaseDir, PROMOTION_LEDGER_FILE)\n if (!existsSync(path)) return []\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\"))\n if (!Array.isArray(parsed)) return []\n return parsed.filter(\n (e): e is PromotionLedgerEntry =>\n e && typeof e.stamp === \"string\" && LEDGER_DECISIONS.has(e.decision),\n )\n } catch {\n // Corrupt ledger → treat as empty. Worst case: skipped memories are\n // re-judged once. Promoted memories stay deduped via article sources.\n return []\n }\n}\n\n/** Append entries and compact: keep only the newest entry per memory key\n * (per exact stamp for `review:` and `failure:` sources, where the later\n * entry wins) so the ledger stays bounded. */\nexport function appendPromotionLedger(wikiBaseDir: string, entries: PromotionLedgerEntry[]): void {\n const merged = [...readPromotionLedger(wikiBaseDir), ...entries]\n const byKey = new Map<string, PromotionLedgerEntry>()\n for (const e of merged) {\n const parsed = parseMemoryStamp(e.stamp)\n const key = parsed?.key ?? e.stamp\n const prev = byKey.get(key)\n // A non-memory stamp has no version to compare: the later decision\n // (rejected after proposed) must replace the earlier one.\n if (!prev || !parsed || (parseMemoryStamp(prev.stamp)?.updatedAt ?? \"\") <= parsed.updatedAt) {\n byKey.set(key, e)\n }\n }\n writeFileSync(\n resolve(wikiBaseDir, PROMOTION_LEDGER_FILE),\n JSON.stringify([...byKey.values()], null, 2),\n )\n}\n\n// --- Clustering -------------------------------------------------------------\n\nexport interface PromotionCluster {\n /** ≥1 candidate; >1 when the same `<type>_<name>` exists on several agents. */\n candidates: MemoryCandidate[]\n corroboratingAgents: string[]\n /** 0.5 base + 0.15 per extra corroborating agent + 0.05 for\n * project|reference types, capped at 0.95. Advisory only — shown to\n * the LLM judge, never a hard gate (mirrors clusterWorkflowCandidates). */\n confidence: number\n}\n\n/** Group candidates that share `<type>_<name>` across agents. Exact-match\n * clustering only — two agents naming a memory identically is treated as\n * corroboration; fuzzy matching is future work. */\nexport function groupCandidates(cands: MemoryCandidate[]): PromotionCluster[] {\n const byName = new Map<string, MemoryCandidate[]>()\n for (const c of cands) {\n const k = `${c.memory.type}_${c.memory.name}`\n const list = byName.get(k) ?? []\n list.push(c)\n byName.set(k, list)\n }\n return [...byName.values()].map((candidates) => {\n const corroboratingAgents = [...new Set(candidates.map((c) => c.agentId))].sort()\n const typeBoost = [\"project\", \"reference\"].includes(candidates[0].memory.type) ? 0.05 : 0\n const confidence = Math.min(0.95, 0.5 + 0.15 * (corroboratingAgents.length - 1) + typeBoost)\n return { candidates, corroboratingAgents, confidence }\n })\n}\n\n// --- LLM response parsing ----------------------------------------------------\n\nexport interface PromotedArticle {\n path: string\n title: string\n type?: string\n related?: string[]\n tags: string[]\n content: string\n /** Validated stamps of the memories this article was promoted from. */\n promotedFrom: string[]\n}\n\nexport interface PromotionResponse {\n articles: PromotedArticle[]\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n /** Stamp refs the LLM emitted that weren't in the offered set, plus\n * articles dropped for having zero valid stamps. For the report. */\n warnings: string[]\n}\n\n/** Parse the promotion LLM's reply. Balanced-JSON extraction (adapted from\n * the wiki absorb CLI): find the outermost `{...}`, depth-scan to its\n * close, JSON.parse, then validate. Stamps not present in `offeredStamps`\n * are hallucinations — dropped; an article left with zero valid stamps is\n * dropped entirely (nothing to dedupe against on the next run). Any\n * failure returns `{error}` — the caller must write nothing. */\nexport function parsePromotionResponse(\n text: string,\n offeredStamps: Set<string>,\n): PromotionResponse | { error: string } {\n const start = text.indexOf(\"{\")\n if (start === -1) return { error: \"no JSON object in response\" }\n let depth = 0\n let end = -1\n let inString = false\n let escaped = false\n for (let i = start; i < text.length; i++) {\n const ch = text[i]\n if (escaped) { escaped = false; continue }\n if (ch === \"\\\\\") { escaped = true; continue }\n if (ch === '\"') { inString = !inString; continue }\n if (inString) continue\n if (ch === \"{\") depth++\n else if (ch === \"}\") { depth--; if (depth === 0) { end = i + 1; break } }\n }\n if (end === -1) return { error: \"unbalanced JSON in response\" }\n\n let parsed: any\n try {\n parsed = JSON.parse(text.slice(start, end))\n } catch (e: any) {\n return { error: `JSON parse error: ${e.message}` }\n }\n if (!parsed || typeof parsed !== \"object\" || !Array.isArray(parsed.articles)) {\n return { error: \"response JSON missing articles array\" }\n }\n\n const warnings: string[] = []\n const articles: PromotedArticle[] = []\n for (const a of parsed.articles) {\n if (!a || typeof a.path !== \"string\" || typeof a.title !== \"string\" || typeof a.content !== \"string\") {\n warnings.push(`article missing path/title/content — dropped`)\n continue\n }\n const raw = Array.isArray(a.promotedFrom) ? a.promotedFrom.filter((s: unknown) => typeof s === \"string\") : []\n const promotedFrom = raw.filter((s: string) => offeredStamps.has(s))\n for (const s of raw) {\n if (!offeredStamps.has(s)) warnings.push(`article \"${a.path}\" references unknown stamp \"${s}\" — dropped`)\n }\n if (promotedFrom.length === 0) {\n warnings.push(`article \"${a.path}\" has no valid promotedFrom stamps — dropped`)\n continue\n }\n articles.push({\n path: a.path,\n title: a.title,\n type: typeof a.type === \"string\" ? a.type : undefined,\n related: Array.isArray(a.related) ? a.related.filter((r: unknown) => typeof r === \"string\") : undefined,\n tags: Array.isArray(a.tags) ? a.tags.filter((t: unknown) => typeof t === \"string\") : [],\n content: a.content,\n promotedFrom,\n })\n }\n\n const skipped: Array<{ stamp: string; reason: string }> = []\n for (const s of Array.isArray(parsed.skipped) ? parsed.skipped : []) {\n const stamp = typeof s?.memory === \"string\" ? s.memory : typeof s?.stamp === \"string\" ? s.stamp : \"\"\n if (!offeredStamps.has(stamp)) {\n if (stamp) warnings.push(`skip references unknown stamp \"${stamp}\" — dropped`)\n continue\n }\n skipped.push({ stamp, reason: typeof s.reason === \"string\" ? s.reason : \"\" })\n }\n\n const gaps = Array.isArray(parsed.gaps) ? parsed.gaps.filter((g: unknown) => typeof g === \"string\") : []\n return { articles, skipped, gaps, warnings }\n}\n\n// --- Candidate selection ---------------------------------------------------\n\nexport interface UnpromotedOptions {\n /** Memory types to consider. Default: project, reference, feedback. */\n types?: MemoryType[]\n /** Only memories updated within this window (ms before `now`). */\n sinceMs?: number\n /** Only this agent's memories. */\n agentFilter?: string\n /** Reference clock for `sinceMs` (injectable for tests). Default Date.now(). */\n now?: number\n /** Cap on returned candidates (newest first). Default 20. */\n max?: number\n}\n\n/** Diff all agent memories against what's already been promoted (article\n * `sources[]` stamps across the shared index) or deliberately skipped\n * (ledger). A memory is a candidate iff its key is unseen OR its\n * `updatedAt` is newer than the recorded one — ISO-8601 strings compare\n * lexicographically, so plain `>` is safe. */\nexport function getUnpromotedMemories(\n all: Array<\n // `stamp` is supplied by non-memory sources so their namespace\n // survives; the evidence fields travel through to the proposal.\n Pick<MemoryCandidate, \"agentId\" | \"memory\"> & Partial<Omit<MemoryCandidate, \"agentId\" | \"memory\" | \"key\">>\n >,\n index: WikiIndex,\n ledger: PromotionLedger,\n opts: UnpromotedOptions = {},\n): MemoryCandidate[] {\n const types = opts.types ?? DEFAULT_PROMOTE_TYPES\n const now = opts.now ?? Date.now()\n const max = opts.max ?? 20\n\n // key → newest updatedAt already handled (promoted or skipped)\n const seen = new Map<string, string>()\n // Exact stamps already handled. Non-memory sources (`review:`,\n // `failure:`) have no parseable key/version, so this is how they are\n // recognised as proposed, promoted, skipped or rejected instead of\n // being offered again every night.\n const handledStamps = new Set<string>()\n const record = (stamp: string) => {\n handledStamps.add(stamp)\n const parsed = parseMemoryStamp(stamp)\n if (!parsed) return\n const prev = seen.get(parsed.key)\n if (!prev || parsed.updatedAt > prev) seen.set(parsed.key, parsed.updatedAt)\n }\n for (const article of index.articles) {\n for (const s of article.sources ?? []) record(s)\n }\n for (const e of ledger) record(e.stamp)\n // A failure's stamp moves on as it recurs more (failure-candidates.ts);\n // while one proposal for it waits for review, don't open a second.\n const pendingFailures = new Set(ledger\n .filter((e) => e.decision === \"proposed\" && e.stamp.startsWith(FAILURE_PREFIX))\n .map((e) => failureIdentity(e.stamp)))\n\n const candidates: MemoryCandidate[] = []\n for (const { agentId, memory, stamp, occurrences, sessions, tasks, failure } of all) {\n if (!types.includes(memory.type)) continue\n if (opts.agentFilter && agentId !== opts.agentFilter) continue\n if (opts.sinceMs !== undefined) {\n const updated = Date.parse(memory.updatedAt)\n if (!Number.isFinite(updated) || updated < now - opts.sinceMs) continue\n }\n if (stamp && handledStamps.has(stamp)) continue\n if (stamp?.startsWith(FAILURE_PREFIX) && pendingFailures.has(failureIdentity(stamp))) continue\n const key = memoryKey(agentId, memory)\n const handled = seen.get(key)\n if (handled && memory.updatedAt <= handled) continue\n // A caller-supplied stamp is kept as-is. Re-deriving it would\n // rewrite a `review:` stamp into the `memory:` namespace and lose\n // the provenance the ledger needs to tell the two sources apart.\n candidates.push({\n agentId, memory, key, stamp: stamp ?? memoryStamp(agentId, memory), occurrences,\n ...(sessions?.length ? { sessions } : {}),\n ...(tasks?.length ? { tasks } : {}),\n ...(failure ? { failure } : {}),\n })\n }\n\n // Recurrence first where a source provides it, then recency. Memory\n // candidates carry no count, so their ordering is unchanged.\n return candidates\n .sort((a, b) =>\n (b.occurrences ?? 0) - (a.occurrences ?? 0) ||\n b.memory.updatedAt.localeCompare(a.memory.updatedAt))\n .slice(0, max)\n}\n\n// --- LLM transport -----------------------------------------------------------\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\"\nconst ANTHROPIC_VERSION = \"2023-06-01\"\n\nexport interface PromotionLlmOptions {\n /** Route through the daemon: POST /task to this agent. No API key needed —\n * the agent's own session handles auth (mirrors architectWorkflowViaAgent). */\n viaAgent?: string\n /** Direct Anthropic API model. Requires ANTHROPIC_API_KEY. */\n model?: string\n daemonUrl?: string\n timeoutMs?: number\n fetchImpl?: typeof fetch\n /** Stable chat id so a retry's --resume replay sees the first reply. */\n chatId?: string\n}\n\n/** Run the promotion prompt through an LLM and return the raw reply text.\n * Throws on transport failure — the caller decides whether to retry.\n * `feedback` turns the call into a correction round: the via-agent branch\n * resumes the same session; the direct branch replays the conversation. */\nexport async function callPromotionLlm(\n prompt: string,\n opts: PromotionLlmOptions,\n feedback: string | null = null,\n): Promise<string> {\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 180_000\n const ctrl = new AbortController()\n const timer = setTimeout(() => ctrl.abort(), timeoutMs)\n try {\n if (opts.viaAgent) {\n const daemonUrl = (opts.daemonUrl ?? \"http://127.0.0.1:18800\").replace(/\\/+$/, \"\")\n const message = feedback\n ? `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object — same structure, fixed issues, valid JSON only.`\n : prompt\n const res = await fetchImpl(`${daemonUrl}/task`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n agent: opts.viaAgent,\n message,\n // First call gets a fresh session; the retry resumes it so the\n // agent can fix its own output instead of starting over.\n freshSession: feedback === null,\n context: { channel: \"api\", chatId: opts.chatId ?? \"memory-promote\" },\n }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`agent /task ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n if (body.error) throw new Error(`agent reported error: ${body.error}`)\n const content = String(body.content ?? \"\").trim()\n if (!content) throw new Error(\"agent returned empty content\")\n return content\n }\n\n if (!opts.model) throw new Error(\"callPromotionLlm: viaAgent or model is required\")\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) throw new Error(\"ANTHROPIC_API_KEY not set; use --via <agentId> instead\")\n const messages: Array<{ role: string; content: string }> = [{ role: \"user\", content: prompt }]\n if (feedback) {\n messages.push({ role: \"assistant\", content: \"(previous attempt; will retry with corrections)\" })\n messages.push({ role: \"user\", content: `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object.` })\n }\n const res = await fetchImpl(ANTHROPIC_API, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_VERSION,\n },\n body: JSON.stringify({ model: opts.model, max_tokens: 8000, messages }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`anthropic API ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n const blocks = Array.isArray(body.content) ? body.content : []\n const text = blocks.filter((b: any) => b?.type === \"text\").map((b: any) => b.text).join(\"\\n\").trim()\n if (!text) throw new Error(\"anthropic API returned no text content\")\n return text\n } finally {\n clearTimeout(timer)\n }\n}\n\n// --- Orchestrator ------------------------------------------------------------\n\nexport interface RunPromotionOptions extends PromotionLlmOptions {\n /** Wiki base dir (the shared store). Default `.agentx/wiki`. */\n wikiDir?: string\n /** The `.agentx` dir holding `agent-memory/`. Default `.agentx`. */\n memoryRoot?: string\n sinceMs?: number\n agentFilter?: string\n types?: MemoryType[]\n max?: number\n /** Candidates from somewhere other than agent memory — session-monitor\n * reviews and recurring failures. They join the same pool so they inherit\n * the ledger, the dedupe and the judge rather than getting a second\n * pipeline that drifts from this one. */\n extraCandidates?: MemoryCandidate[]\n /** Write proposals + ledger. Default false (dry-run). */\n commit?: boolean\n /** Upper bound on the judge prompt, in estimated tokens. Clusters are\n * dropped from the end (lowest ranked) until it fits; they stay\n * unledgered, so the next run picks them up. Default 60 000. */\n budgetTokens?: number\n log?: (msg: string) => void\n /** Injectable clock (tests). */\n now?: number\n}\n\nexport interface PromotionReport {\n candidates: MemoryCandidate[]\n clusters: PromotionCluster[]\n /** Proposals written this run; nothing reaches the wiki until approved. */\n proposed: Array<{ id: string; path: string; title: string; type?: string; stamps: string[]; agents: string[]; occurrences: number }>\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n warnings: string[]\n errors: string[]\n dryRun: boolean\n}\n\n/** End-to-end promotion run: enumerate memories → diff against promoted/\n * skipped → cluster → (dry-run stops here) → LLM judge → write articles\n * with merged sources → ledger → rebuild index. Parses the full response\n * before any write — a bad reply writes nothing, and idempotency makes\n * the next scheduled run retry for free. */\nexport async function runPromotion(opts: RunPromotionOptions = {}): Promise<PromotionReport> {\n const wikiDir = opts.wikiDir ?? resolve(process.cwd(), \".agentx\", \"wiki\")\n const memoryRoot = opts.memoryRoot ?? resolve(process.cwd(), \".agentx\")\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now()\n const report: PromotionReport = {\n candidates: [], clusters: [], proposed: [], skipped: [],\n gaps: [], warnings: [], errors: [], dryRun: !opts.commit,\n }\n\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const index = store.rebuildIndex()\n const ledger = readPromotionLedger(wikiDir)\n const all = [...listAllAgentMemories(memoryRoot), ...(opts.extraCandidates ?? [])]\n\n report.candidates = getUnpromotedMemories(all, index, ledger, {\n types: opts.types,\n sinceMs: opts.sinceMs,\n agentFilter: opts.agentFilter,\n now,\n max: opts.max,\n })\n if (report.candidates.length === 0) {\n log(\"no unpromoted memories in window\")\n return report\n }\n report.clusters = groupCandidates(report.candidates)\n if (report.dryRun) return report\n\n const articleList = index.articles.map((a) => ({ title: a.title, path: a.path, type: a.type }))\n const worldview = store.getWorldview() ?? \"\"\n const budget = opts.budgetTokens ?? DEFAULT_BUDGET_TOKENS\n let clusters = report.clusters\n let prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n while (estimateTokens(prompt) > budget && clusters.length > 1) {\n clusters = clusters.slice(0, -1)\n prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n }\n if (estimateTokens(prompt) > budget) {\n report.errors.push(`judge prompt (~${estimateTokens(prompt)} tokens) exceeds the ${budget}-token budget even with one cluster`)\n return report\n }\n if (clusters.length < report.clusters.length) {\n report.warnings.push(`token budget: judged ${clusters.length} of ${report.clusters.length} clusters; the rest wait for the next run`)\n }\n const judged = clusters.flatMap((c) => c.candidates)\n const offered = new Set(judged.map((c) => c.stamp))\n const chatId = opts.chatId ?? `memory-promote-${new Date(now).toISOString().slice(0, 10)}`\n\n let parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }), offered)\n if (\"error\" in parsed) {\n log(`parse failed (${parsed.error}) — retrying with feedback`)\n parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }, parsed.error), offered)\n }\n if (\"error\" in parsed) {\n report.errors.push(`LLM response unusable after retry: ${parsed.error}`)\n return report\n }\n report.warnings.push(...parsed.warnings)\n report.gaps = parsed.gaps\n\n const at = new Date(now).toISOString()\n const ledgerEntries: PromotionLedgerEntry[] = []\n\n const byStamp = new Map(judged.map((c) => [c.stamp, c]))\n for (const article of parsed.articles) {\n const existing = store.readArticle(article.path)\n if (existing && !store.canWrite(existing.meta, PROMOTER_OWNER)) {\n // Owned by another agent — a human resolves; stamps stay unledgered\n // so the memory is retried after the standoff clears.\n report.errors.push(`write denied for \"${article.path}\" (owned by ${existing.meta.owner ?? \"?\"})`)\n continue\n }\n const evidence = evidenceFor(article.promotedFrom, byStamp)\n const proposal: PromotionProposal = {\n id: newProposalId(now, article.path),\n createdAt: at,\n status: \"pending\",\n article,\n evidence,\n ...(existing ? { replaces: { lastUpdated: existing.meta.lastUpdated, contentHash: contentHash(existing.content) } } : {}),\n }\n saveProposal(wikiDir, proposal)\n report.proposed.push({\n id: proposal.id, path: article.path, title: article.title, type: article.type,\n stamps: article.promotedFrom, agents: evidence.agents, occurrences: evidence.occurrences,\n })\n for (const stamp of article.promotedFrom) {\n ledgerEntries.push({ stamp, decision: \"proposed\", article: article.path, proposal: proposal.id, at })\n }\n }\n\n for (const s of parsed.skipped) {\n ledgerEntries.push({ stamp: s.stamp, decision: \"skipped\", reason: s.reason, at })\n report.skipped.push(s)\n }\n\n if (ledgerEntries.length) appendPromotionLedger(wikiDir, ledgerEntries)\n return report\n}\n\nconst DEFAULT_BUDGET_TOKENS = 60_000\n\n/** Rough token count (4 characters per token) — enough for a budget. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4)\n}\n\nfunction contentHash(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\nconst EXCERPT_CHARS = 600\n\n/** Which pipeline a stamp came from. */\nexport function sourceKind(stamp: string): ProposalSource[\"kind\"] {\n if (stamp.startsWith(\"review:\")) return \"review\"\n if (stamp.startsWith(FAILURE_PREFIX)) return \"failure\"\n return \"memory\"\n}\n\n/** What a reviewer needs to judge a proposal: each source memory, review\n * finding or recurring failure, who wrote it and in which task, how often\n * it recurred, and some of the sessions and runs it came from. */\nexport function evidenceFor(stamps: string[], byStamp: Map<string, MemoryCandidate>): PromotionProposal[\"evidence\"] {\n const sources: ProposalSource[] = []\n for (const stamp of stamps) {\n const c = byStamp.get(stamp)\n if (!c) continue\n sources.push({\n stamp,\n kind: sourceKind(stamp),\n agentId: c.agentId,\n type: c.memory.type,\n name: c.memory.name,\n description: c.memory.description,\n ...(c.occurrences ? { occurrences: c.occurrences } : {}),\n ...(c.sessions?.length ? { sessions: c.sessions } : {}),\n ...(c.tasks?.length ? { tasks: c.tasks } : {}),\n ...(c.failure ? { failure: c.failure } : {}),\n ...(c.memory.author ? { author: c.memory.author } : {}),\n ...(c.memory.taskId ? { taskId: c.memory.taskId } : {}),\n updatedAt: c.memory.updatedAt,\n excerpt: c.memory.body.slice(0, EXCERPT_CHARS),\n })\n }\n return {\n agents: [...new Set(sources.map((s) => s.agentId))].sort(),\n occurrences: Math.max(1, ...sources.map((s) => s.occurrences ?? 1)),\n sources,\n }\n}\n\n// --- Review: approve or reject a proposal ------------------------------------\n\nexport type ProposalDecisionResult =\n | { ok: true; proposal: PromotionProposal }\n | { ok: false; error: string }\n\n/** Write the proposed article into the shared wiki. Refuses when the\n * article changed (or appeared) since the proposal was made, unless\n * `force`, so an approval can't silently overwrite someone's edit. */\nexport function approveProposal(\n wikiDir: string, id: string, opts: { by?: string; force?: boolean; now?: number; log?: (m: string) => void } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n\n const log = opts.log ?? (() => {})\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const { article } = proposal\n const existing = store.readArticle(article.path)\n if (!opts.force) {\n if (existing && !proposal.replaces) {\n return { ok: false, error: `\"${article.path}\" was created after this proposal; review it, then approve with --force` }\n }\n if (existing && proposal.replaces && contentHash(existing.content) !== proposal.replaces.contentHash) {\n return { ok: false, error: `\"${article.path}\" changed since this proposal; review it, then approve with --force` }\n }\n }\n const now = opts.now ?? Date.now()\n const today = new Date(now).toISOString().slice(0, 10)\n const ok = store.writeArticle(\n article.path,\n {\n title: article.title,\n type: isWikiArticleType(article.type) ? article.type : undefined,\n related: article.related,\n tags: article.tags,\n owner: PROMOTER_OWNER,\n access: \"public\",\n created: existing?.meta.created ?? today,\n lastUpdated: today,\n sources: mergeSources(existing?.meta.sources, article.promotedFrom),\n },\n article.content,\n PROMOTER_OWNER,\n )\n if (!ok) return { ok: false, error: `write denied for \"${article.path}\" (owned by ${existing?.meta.owner ?? \"?\"})` }\n\n const at = new Date(now).toISOString()\n appendPromotionLedger(wikiDir, article.promotedFrom.map((stamp) => ({\n stamp, decision: \"promoted\" as const, article: article.path, proposal: id, at,\n })))\n store.rebuildIndex()\n const decided: PromotionProposal = { ...proposal, status: \"approved\", decidedBy: opts.by ?? \"operator\", decidedAt: at }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n\n/** Decline a proposal. Its memories aren't judged again until they change. */\nexport function rejectProposal(\n wikiDir: string, id: string, opts: { by?: string; reason?: string; now?: number } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n const at = new Date(opts.now ?? Date.now()).toISOString()\n appendPromotionLedger(wikiDir, proposal.article.promotedFrom.map((stamp) => ({\n stamp, decision: \"rejected\" as const, article: proposal.article.path, proposal: id, reason: opts.reason, at,\n })))\n const decided: PromotionProposal = {\n ...proposal, status: \"rejected\", decidedBy: opts.by ?? \"operator\", decidedAt: at,\n ...(opts.reason ? { reason: opts.reason } : {}),\n }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n","import { fieldChecklistMarkdown, tierLadderMarkdown } from \"@/decisions/seats/article-fields\"\nimport type { WikiMode } from \"./hub\"\nimport type { PromotionCluster } from \"./promote\"\n\n/**\n * Farzapedia-faithful absorb prompt.\n *\n * Replaces the legacy three-mode design (flat/graph/unified) with one prompt\n * that mirrors what Karpathy and Farza describe: article `type` is the\n * organizational spine, `[[wikilinks]]` are the navigation surface, tags are\n * a secondary hint. The LLM chooses paths and cross-references; it does not\n * produce a sprawling per-article tag cloud.\n *\n * The mode parameter is kept in the signature for backwards compatibility but\n * all values resolve to the same prompt — retained callers (hub.ts, wiki CLI)\n * still pass a mode but it no longer changes behavior.\n */\nexport function buildAbsorbPrompt(\n _mode: WikiMode,\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n coveringBlock = \"\",\n notesBlock = \"\",\n): string {\n return buildFarzapediaPrompt(agentId, worldview, existingArticles, entryTexts, entryCount, factsBlock, coveringBlock, notesBlock)\n}\n\nfunction buildFarzapediaPrompt(\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n coveringBlock = \"\",\n notesBlock = \"\",\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n // Group the existing index by type so the LLM sees the structure.\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const existingList = existingArticles.length === 0 ? \"\" :\n \"\\n## Existing Articles (the current catalog)\\n\\n\" +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n return `You are compiling a personal wiki for the \"${agentId}\" agent in the Karpathy / Farzapedia pattern.\n\n## The Pattern (read this first — it shapes every decision below)\n\nThe wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \\`type\\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.\n\n**Three rules, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` frontmatter field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint, not a retrieval spine.** 2–4 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \\`event\\`. Do NOT tag with generic terms (\"deploy\", \"work\") that will match half the corpus.\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## How to process entries\n\nFor each of the ${entryCount} raw entries below, ask in this order:\n\n1. **Does it extend an existing article?** If a person/project/concept/event mentioned in the entry already has an article in the catalog, produce an UPDATE at that article's path with the full merged content. Prefer merging over proliferating. When the article is shown in full below, keep every fact it already has.\n2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.\n3. **Does it belong in an existing \\`event\\` or \\`decision\\`?** Most work entries fold into one of these.\n4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere — skip. The wiki is curated, not exhaustive.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, and their actual contact details — not a log of every interaction. History goes last, and only where it still bears on the present.\n- **Length: 20–100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.\n- **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.\n\n## Build in layers\n\nFacts are not equally load-bearing. Write them in this order, and never\nspend length on a lower layer while a higher one is missing:\n\n${tierLadderMarkdown()}\n\nAn article is a building, not a transcript. The common failure is to\nfurnish a slab: a person's page that records an invoice thread in detail\nand never states their role, their organisation, or how to reach them.\nThat article cannot answer \"who is this\" or \"how do I reach them\" — the\ntwo questions it exists for. History is the last layer, and it is the\nonly one you can safely leave thin.\n\n## Identity before history\n\nAn entity article must first establish **what the entity is**, and only then\nwhat happened to it.\n\nBefore the narrative, every entity article carries an **Identity** section\nwith the fields for its type. State a field even when the entries do not\nanswer it, as \\`unknown\\`, so the gap is visible and a later pass can fill it.\nA field silently omitted is indistinguishable from a field that does not\napply.\n\n${fieldChecklistMarkdown()}\n\nFields in **bold** are the ones an article of that type is unusable without.\nEvery article of any type also states what it is, why it matters to our work,\nand which named people, projects or systems it connects to.\n\nExtract identifiers **verbatim** from the entries. A phone number, an\naccount number, a URL or a path is the single most useful thing an article\ncan carry, and it is exactly what summarising discards. Never paraphrase or\nredact one that appears in the source; if a value appears only in part, say\nso rather than dropping it.\n\n## Wikilink discipline\n\n- Use \\`[[Article Title]]\\` **inside the body** every time another tracked entity is referenced.\n- List those same targets in the \\`related\\` frontmatter field.\n- If you reference something that has no article yet (\\`[[New Thing]]\\`), add \"New Thing\" to the output \\`gaps\\` array so the next pass can create it.\n\n## Article frontmatter (exact shape — do not invent fields)\n\n\\`\\`\\`yaml\n---\ntitle: \"Article Title\"\ntype: person | project | place | concept | event | decision | pattern\nrelated: [\"Other Article\", \"Another Article\"]\ntags: [\"2-4-specific-tags\", \"no-dates-unless-event\"]\nowner: ${agentId}\naccess: public | shared | private\ncreated: YYYY-MM-DD\nlast_updated: YYYY-MM-DD\nsources: [\"entry-id-1\", \"entry-id-2\"]\n---\n\\`\\`\\`\n\nAccess guidance: default \\`public\\`; \\`private\\` only for sensitive credentials or agent-specific learnings; \\`shared\\` with specific agent IDs when the article matters only to a subset.\n${worldviewSection}${existingList}${coveringBlock}${factsBlock}${notesBlock}\n## Gap Detection\n\nAfter compiling, populate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific:\n\n- Good: \"Karim Rahmouni — coach for Tunis Padel club, mentioned twice but no \\`people/\\` article\"\n- Bad: \"We need more content about deployments\"\n\n## Output — valid JSON only, no markdown fencing\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"projects/globex-system-v2.md\",\n \"title\": \"Globex System V2\",\n \"type\": \"project\",\n \"related\": [\"Alex Rivera\", \"Laravel\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"laravel\", \"react\"],\n \"content\": \"Globex System V2 is [[Alex Rivera]]'s production Laravel + React app for …\",\n \"sources\": [\"entry-id-1\", \"entry-id-2\"]\n }\n ],\n \"gaps\": [\n \"Karim Rahmouni — Tunis Padel coach, mentioned in entries but no people/ article\"\n ]${notesBlock ? `,\n \"notes\": [\n {\n \"id\": \"3f2a91c04b7e\",\n \"outcome\": \"patched\",\n \"reason\": \"Entry 2026-10-07 confirms the move\",\n \"edits\": [{ \"path\": \"concepts/staging-deployment.md\", \"find\": \"Staging runs on the old server.\", \"replace\": \"Staging runs on the new server (moved 2026-10-07).\" }]\n },\n { \"id\": \"9b1c2d3e4f50\", \"outcome\": \"rejected\", \"reason\": \"The article and entry 2026-10-06 both name a different owner\", \"edits\": [] }\n ]` : \"\"}\n}\n\\`\\`\\`\n\nENTRIES (${entryCount}):\n\n${entryTexts}`\n}\n\n/** Max chars of a memory body shown to the promotion judge. */\nexport const PROMOTE_BODY_LIMIT = 2000\n\n/** Past this catalog size, list titles only (no paths) to bound the prompt. */\nconst PROMOTE_CATALOG_LIMIT = 300\n\n/**\n * Memory→wiki promotion prompt. Mirrors the Farzapedia absorb prompt's\n * skeleton (type spine, path convention, wikilink discipline, gaps) but\n * judges per-agent MEMORIES instead of raw entries: promote only durable,\n * cross-agent-relevant facts into the shared wiki.\n *\n * The LLM does NOT emit frontmatter — the caller sets owner/access/dates/\n * sources itself, which removes a whole class of hallucination. Candidates\n * are referenced only by their exact stamp strings.\n */\nexport function buildMemoryPromotePrompt(\n ownerId: string,\n clusters: PromotionCluster[],\n existingArticles: Array<{ title: string; path: string; type?: string }>,\n worldview: string,\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const titlesOnly = existingArticles.length > PROMOTE_CATALOG_LIMIT\n const existingList = existingArticles.length === 0 ? \"\" :\n `\\n## Existing Articles (the current catalog${titlesOnly ? `, ${existingArticles.length} articles — titles only` : \"\"})\\n\\n` +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => titlesOnly ? `- [[${a.title}]]` : `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n const candidateCount = clusters.reduce((n, c) => n + c.candidates.length, 0)\n const candidateBlocks = clusters.map(cluster => {\n const corroboration = cluster.corroboratingAgents.length > 1\n ? `corroborated by: ${cluster.corroboratingAgents.join(\", \")} (${cluster.corroboratingAgents.length} agents)`\n : `single agent: ${cluster.corroboratingAgents[0]}`\n return cluster.candidates.map(c => {\n const body = c.memory.body.length > PROMOTE_BODY_LIMIT\n ? c.memory.body.slice(0, PROMOTE_BODY_LIMIT) + \"\\n[… truncated]\"\n : c.memory.body\n return [\n `--- CANDIDATE ${c.stamp} ---`,\n `type: ${c.memory.type} | agent: ${c.agentId} | ${corroboration} | confidence: ${cluster.confidence.toFixed(2)}`,\n `description: ${c.memory.description}`,\n body,\n `--- END CANDIDATE ---`,\n ].join(\"\\n\")\n }).join(\"\\n\\n\")\n }).join(\"\\n\\n\")\n\n return `You are promoting per-agent experiential memories into the shared, authoritative AgentX wiki (owner: \"${ownerId}\").\n\n## The Job\n\nMemory is \"what one agent learned\"; the wiki is \"what every agent should know.\" For each candidate memory below, decide: promote it into a wiki article, or skip it with a reason.\n\n**The promotion test — apply in order, skip on the first \"no\":**\n\n1. **Durable?** Will this still be true in 3 months? Transient state, one-off task context, and dated status pings fail here.\n2. **Cross-agent?** Would a DIFFERENT agent benefit from knowing this? Facts about shared infrastructure, projects, people, and conventions pass; one agent's private workflow habits fail.\n3. **A fact, not a preference?** Personal style preferences private to one agent's operator relationship fail.\n4. **Never a secret.** Credentials, tokens, keys, and passwords are NEVER promoted — skip with reason \"contains secret\". Pointers to where a secret lives (e.g. \"SSH config is in X\") are fine; the secret itself is not.\n\nCorroboration across agents (shown per candidate) is a confidence signal, not a requirement — a single-agent memory that passes the test above should be promoted.\n\nCandidates stamped \\`failure:\\` are not memories: they are the same failure recurring across several sessions, with example runs. The recurrence is what makes them durable. Promote one only when the examples share a cause, as a \\`pattern\\` article saying what fails, why, and what to do instead (including a tool or configuration change, if that is the fix). Never copy task text or error output verbatim beyond what the lesson needs.\n\n**Three rules for the articles you write, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, skip the memory instead.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` output field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint.** 2–4 specific tags max. No dates unless the article is an \\`event\\`. No generic terms (\"deploy\", \"work\").\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## Merge over create\n\nIf a candidate extends an article in the catalog below, emit an UPDATE at the EXISTING path with the full merged content. Prefer merging over proliferating. Several candidates about the same entity should fold into ONE article.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** Write about the entity's role in our work, not a diary of how the memory was learned.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Length: 20–100 lines.**\n- **Every paragraph earns its place.**\n\n## Gap Detection\n\nPopulate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific (\"Karim Rahmouni — coach mentioned twice, no people/ article\"), not vague (\"need more deploy content\").\n${worldviewSection}${existingList}\n## Output — valid JSON only, no markdown fencing\n\nReference candidates ONLY by their exact stamp strings (the \\`memory:...\\` line in each CANDIDATE header). Every candidate must appear in exactly one of \\`articles[].promotedFrom\\` or \\`skipped\\`. Do NOT emit frontmatter, owner, access, dates, or sources — the caller sets those.\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"concepts/globex-staging-deploy.md\",\n \"title\": \"Globex Staging Deploy\",\n \"type\": \"concept\",\n \"related\": [\"Globex System V2\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"deploy\"],\n \"content\": \"Deploys to [[Globex System V2]] staging go through …\",\n \"promotedFrom\": [\"memory:coder-agent/project_globex_deploy@2026-07-01T22:14:03.000Z\"]\n }\n ],\n \"skipped\": [\n { \"memory\": \"memory:cx/feedback_short_replies@2026-06-01T00:00:00.000Z\", \"reason\": \"agent-specific style preference, not a cross-agent fact\" }\n ],\n \"gaps\": []\n}\n\\`\\`\\`\n\nCANDIDATES (${candidateCount} memories in ${clusters.length} clusters):\n\n${candidateBlocks}`\n}\n"],"mappings":"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","names":["createHash","existsSync","readdirSync","readFileSync","writeFileSync","resolve","buildAbsorbPrompt","_mode","agentId","worldview","existingArticles","entryTexts","entryCount","factsBlock","coveringBlock","notesBlock","buildFarzapediaPrompt","worldviewSection","byType","a","t","list","existingList","type","arr","x","y","tierLadderMarkdown","fieldChecklistMarkdown","PROMOTE_BODY_LIMIT","PROMOTE_CATALOG_LIMIT","buildMemoryPromotePrompt","ownerId","clusters","titlesOnly","candidateCount","n","c","candidateBlocks","cluster","corroboration","body","PROMOTER_OWNER","DEFAULT_PROMOTE_TYPES","PROMOTION_LEDGER_FILE","STAMP_PREFIX","FAILURE_PREFIX","failureIdentity","stamp","at","MEMORY_TYPES","memoryKey","agentId","m","memoryStamp","parseMemoryStamp","s","rest","slash","typeName","updatedAt","underscore","type","name","mergeSources","existing","newStamps","newKeys","parsed","out","listAllAgentMemories","memoryRoot","resolve","store","AgentMemory","existsSync","entry","readdirSync","memory","LEDGER_DECISIONS","readPromotionLedger","wikiBaseDir","path","readFileSync","e","appendPromotionLedger","entries","merged","byKey","key","prev","writeFileSync","groupCandidates","cands","byName","c","k","list","candidates","corroboratingAgents","typeBoost","confidence","parsePromotionResponse","text","offeredStamps","start","depth","end","inString","escaped","i","ch","warnings","articles","a","raw","promotedFrom","r","t","skipped","gaps","g","getUnpromotedMemories","all","index","ledger","opts","types","now","max","seen","handledStamps","record","article","pendingFailures","occurrences","sessions","tasks","failure","updated","handled","b","ANTHROPIC_API","ANTHROPIC_VERSION","callPromotionLlm","prompt","feedback","fetchImpl","timeoutMs","ctrl","timer","daemonUrl","message","res","errBody","body","content","apiKey","messages","runPromotion","wikiDir","log","report","WikiStore","args","articleList","worldview","budget","DEFAULT_BUDGET_TOKENS","clusters","buildMemoryPromotePrompt","estimateTokens","judged","offered","chatId","ledgerEntries","byStamp","evidence","evidenceFor","proposal","newProposalId","contentHash","saveProposal","createHash","EXCERPT_CHARS","sourceKind","stamps","sources","approveProposal","id","readProposal","today","isWikiArticleType","decided","rejectProposal"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{d as h}from"./chunk-
|
|
1
|
+
import{d as h}from"./chunk-TN5A7PLU.js";var d=["main contact","primary contact","point of contact","billing contact","technical contact","decision maker"],f=/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g,w=/\b(role|title|position|job)\s*\**\s*[::]\s*\**\s*([^\n;|]+)/gi;function L(t,c,e=[]){let l=[c,...e].join(`
|
|
2
2
|
`),a=l.toLowerCase(),i=h(t,l);for(let s of p(t.match(f)??[]))a.includes(s.toLowerCase())||i.push(`email ${s}`);let m=t.toLowerCase();for(let s of d)m.includes(s)&&!a.includes(s)&&i.push(`marker "${s}"`);for(let s of t.matchAll(w)){let n=s[2].replace(/\*+/g,"").trim();n&&!a.includes(n.toLowerCase())&&i.push(`${s[1].toLowerCase()} "${n}"`)}return p(i)}function p(t){return[...new Set(t)]}var $=.9,g=/^\s*(?:wait\b|here(?:'s| is) the (?:updated|modified|patched|revised)|i (?:must|need to|will|should) (?:output|return|write)|(?:okay|ok|sure)[,.!]\s|let me\b|as requested\b)/im;function k(t,c){let e=[],l=t.split(`
|
|
3
3
|
`).filter(o=>o.trim()).length,a=c.split(`
|
|
4
4
|
`).filter(o=>o.trim()).length;l>=5&&a<Math.floor(l*$)&&e.push(`the article shrinks from ${l} to ${a} lines`);let i=c.match(g);i&&!t.match(g)&&e.push(`it contains the model's own commentary ("${i[0].trim().slice(0,40)}")`);let m=o=>{let r=new Map;for(let u of o.split(`
|
|
5
5
|
`))/^#{1,6} /.test(u)&&r.set(u.trim(),(r.get(u.trim())??0)+1);return r},s=m(t);for(let[o,r]of m(c))r>1&&r>(s.get(o)??0)&&e.push(`the heading "${o}" appears ${r} times`);let n=L(t,c);return n.length&&e.push(`it drops ${n.length} fact(s): ${n.slice(0,6).join(", ")}${n.length>6?", \u2026":""}`),e}export{L as a,k as b};
|
|
6
|
-
//# sourceMappingURL=chunk-
|
|
6
|
+
//# sourceMappingURL=chunk-LLLBH2HH.js.map
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import{existsSync as M,mkdirSync as G,readdirSync as H,readFileSync as J,unlinkSync as
|
|
1
|
+
import{existsSync as M,mkdirSync as G,readdirSync as H,readFileSync as J,unlinkSync as O,writeFileSync as T}from"fs";import{resolve as u}from"path";import{createHash as V}from"crypto";import{existsSync as A,mkdirSync as U,readdirSync as F,readFileSync as X,renameSync as L,unlinkSync as q,writeFileSync as W}from"fs";import{basename as C,dirname as D,resolve as g}from"path";var z=20,$=class extends Error{constructor(n){super(n?"memory changed since you read it (etag mismatch)":"memory does not exist (etag mismatch)");this.currentEtag=n;this.name="MemoryConflictError"}};function h(r){return V("sha256").update(r).digest("hex").slice(0,16)}function R(r,e){if(!e)return;let n=r===null?null:h(r);if(e.ifNoneMatch==="*"&&n!==null)throw new $(n);if(e.ifMatch!==void 0&&e.ifMatch!==n)throw new $(n)}function _(r,e){let n=g(D(r),`.${C(r)}.${process.pid}.${Date.now()}.tmp`);W(n,e),L(n,r)}function j(r){return g(D(r),"_versions",C(r,".md"))}function E(r,e){if(e===null)return;let n=j(r);U(n,{recursive:!0});let i=new Date().toISOString().replace(/[:.]/g,"-"),t=g(n,`${i}.md`);for(let o=1;A(t);o++)t=g(n,`${i}-${o}.md`);W(t,e),B(n)}function B(r){let e=F(r).filter(n=>n.endsWith(".md")).sort();for(let n of e.slice(0,Math.max(0,e.length-z)))try{q(g(r,n))}catch{}}function P(r){let e=j(r);return A(e)?F(e).filter(n=>n.endsWith(".md")).sort().reverse().map(n=>({id:n.slice(0,-3),content:X(g(e,n),"utf-8")})):[]}var m=["user","feedback","project","reference"],N=class{baseDir;constructor(e={}){let n=e.baseDir??u(process.cwd(),".agentx");this.baseDir=u(n,"agent-memory")}dirOf(e){return u(this.baseDir,v(e))}dirFor(e){let n=v(e),i=u(this.baseDir,n);return G(i,{recursive:!0}),i}fileFor(e,n,i){return u(this.dirFor(e),`${n}_${v(i)}.md`)}indexPath(e){return u(this.dirFor(e),"MEMORY.md")}save(e,n){if(!m.includes(e.type))throw new Error(`invalid memory type: ${e.type}`);if(!e.name.trim())throw new Error("memory name is required");if(!e.description.trim())throw new Error("memory description is required");let i=this.fileFor(e.agentId,e.type,e.name),t=y(i);R(t,n);let o=new Date().toISOString(),s=o;if(t){let a=b(t);a&&(s=a.createdAt)}let c={name:v(e.name),type:e.type,description:e.description.trim(),body:e.body.trimEnd(),createdAt:s,updatedAt:o,...e.author?{author:e.author}:{},...e.taskId?{taskId:e.taskId}:{}},d=Q(c);return E(i,t),_(i,d),this.rewriteIndex(e.agentId),{...c,etag:h(d)}}append(e,n){let i=this.get(e.agentId,e.name),t=i?`${i.body.trimEnd()}
|
|
2
2
|
|
|
3
|
-
${e.body.trim()}`:e.body;return this.save({...e,type:i?.type??e.type,body:t},n)}get(e,n){for(let i of m){let t=this.fileFor(e,i,n);if(!M(t))continue;let o=y(t)??"",s=b(o);if(s)return{...s,etag:h(o)}}return null}list(e){let n=this.dirFor(e);if(!M(n))return[];let i=[];for(let t of H(n,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".md")||t.name==="MEMORY.md")continue;let o=y(
|
|
4
|
-
`):M(o)&&
|
|
3
|
+
${e.body.trim()}`:e.body;return this.save({...e,type:i?.type??e.type,body:t},n)}get(e,n){for(let i of m){let t=this.fileFor(e,i,n);if(!M(t))continue;let o=y(t)??"",s=b(o);if(s)return{...s,etag:h(o)}}return null}list(e){let n=this.dirFor(e);if(!M(n))return[];let i=[];for(let t of H(n,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".md")||t.name==="MEMORY.md")continue;let o=y(u(n,t.name))??"",s=b(o);s&&i.push({...s,etag:h(o)})}return i.sort((t,o)=>{let s=m.indexOf(t.type),c=m.indexOf(o.type);return s!==c?s-c:t.name.localeCompare(o.name)})}remove(e,n,i){let t=m.map(o=>this.fileFor(e,o,n)).filter(o=>M(o));if(t.length===0)return!1;i&&R(y(t[0]),i);for(let o of t)E(o,y(o)),O(o);return this.rewriteIndex(e),!0}versions(e,n){return m.flatMap(i=>P(this.fileFor(e,i,n))).sort((i,t)=>t.id.localeCompare(i.id)).map(i=>{let t=b(i.content);return{id:i.id,record:t?{...t,etag:h(i.content)}:null}})}restore(e,n,i,t={}){let o=this.versions(e,n).find(a=>a.id===i);if(!o?.record)return null;let{type:s,description:c,body:d}=o.record;return this.save({agentId:e,type:s,name:n,description:c,body:d,...t})}indexMarkdown(e,n=0){let i=this.indexPath(e);return Y(y(i)??"",n)}syncToWorkspace(e,n,i=0){if(!n)return;let t=this.indexMarkdown(e).trim(),o=u(n,".agentx-memory.md");t?T(o,t+`
|
|
4
|
+
`):M(o)&&O(o);let s=Y(t,i),c=u(n,"CLAUDE.md"),d="<!-- AGENTX-MEMORY-START \u2014 auto-managed by agentx; edit via `agentx memory` -->",a="<!-- AGENTX-MEMORY-END -->",l=s?`${d}
|
|
5
5
|
${s}
|
|
6
|
-
${a}`:"",f=y(c)??"",p=f.indexOf(d),x=f.indexOf(a),
|
|
6
|
+
${a}`:"",f=y(c)??"",p=f.indexOf(d),x=f.indexOf(a),k;if(p>=0&&x>p){let S=f.slice(0,p).replace(/\n+$/,""),w=f.slice(x+a.length).replace(/^\n+/,"");k=l?[S,l,w].filter(Boolean).join(`
|
|
7
7
|
|
|
8
8
|
`)+`
|
|
9
|
-
`:[
|
|
9
|
+
`:[S,w].filter(Boolean).join(`
|
|
10
10
|
|
|
11
|
-
`)+(
|
|
12
|
-
`:"")}else if(
|
|
11
|
+
`)+(S||w?`
|
|
12
|
+
`:"")}else if(l)k=f?f.replace(/\n+$/,"")+`
|
|
13
13
|
|
|
14
|
-
`+
|
|
15
|
-
`:
|
|
16
|
-
`;else return;T(c,
|
|
14
|
+
`+l+`
|
|
15
|
+
`:l+`
|
|
16
|
+
`;else return;T(c,k)}rewriteIndex(e){let n=this.list(e);if(n.length===0){let o=this.indexPath(e);M(o)&&O(o);return}let i={user:[],feedback:[],project:[],reference:[]};for(let o of n)i[o.type].push(o);let t=[];t.push(`# Memory \u2014 ${e}`,""),t.push("This is the agent's experiential memory: user preferences,"),t.push("feedback, project-specific context, and external references."),t.push("Updated via `agentx memory` CLI or by the agent itself.");for(let o of m){let s=i[o];if(s.length){t.push("",`## ${ee(o)}`,"");for(let c of s)t.push(`- **${c.name}** \u2014 ${c.description} _(updated ${c.updatedAt.slice(0,10)})_`)}}t.push(""),T(this.indexPath(e),t.join(`
|
|
17
17
|
`))}},K="memories not shown here. The full index is in .agentx-memory.md in this workspace, or run `agentx memory index`.";function Y(r,e){if(!e||e<=0||r.length<=e)return r;let n=r.replace(/\n+$/,"").split(`
|
|
18
|
-
`),i=p=>p.startsWith("- "),t=n.filter(i).length,o=p=>`_(${p} more ${K})_`,s=o(t).length+2,c=[],d=0,a=0;for(let p of n){let x=p.length+1;if(d+x>e-s)break;c.push(p),d+=x,i(p)&&a++}for(;c.length&&!i(c[c.length-1]);)c.pop();let
|
|
18
|
+
`),i=p=>p.startsWith("- "),t=n.filter(i).length,o=p=>`_(${p} more ${K})_`,s=o(t).length+2,c=[],d=0,a=0;for(let p of n){let x=p.length+1;if(d+x>e-s)break;c.push(p),d+=x,i(p)&&a++}for(;c.length&&!i(c[c.length-1]);)c.pop();let l=t-a;return(l>0?[...c,"",o(l)]:c).join(`
|
|
19
19
|
`).replace(/^\n+/,"")}function Q(r){return["---",`name: ${r.name}`,`description: ${I(r.description)}`,`type: ${r.type}`,`created: ${r.createdAt}`,`updated: ${r.updatedAt}`,...r.author?[`author: ${I(r.author)}`]:[],...r.taskId?[`task: ${I(r.taskId)}`]:[],"---",""].join(`
|
|
20
20
|
`)+r.body+`
|
|
21
21
|
`}function b(r){if(!r.startsWith(`---
|
|
22
22
|
`))return null;let e=r.indexOf(`
|
|
23
23
|
---
|
|
24
24
|
`,4);if(e<0)return null;let n=r.slice(4,e),i=r.slice(e+5).replace(/^\n+/,"").trimEnd(),t={};for(let o of n.split(`
|
|
25
|
-
`)){let s=o.indexOf(":");if(s<0)continue;let c=o.slice(0,s).trim(),d=Z(o.slice(s+1).trim());t[c]=d}return!t.name||!t.type||!t.description||!m.includes(t.type)?null:{name:t.name,type:t.type,description:t.description,body:i,createdAt:t.created??"",updatedAt:t.updated??t.created??"",...t.author?{author:t.author}:{},...t.task?{taskId:t.task}:{}}}function I(r){return/[:#\n]/.test(r)?`"${r.replace(/"/g,'\\"')}"`:r}function Z(r){return r.startsWith('"')&&r.endsWith('"')&&r.length>=2?r.slice(1,-1).replace(/\\"/g,'"'):r}function
|
|
26
|
-
//# sourceMappingURL=chunk-
|
|
25
|
+
`)){let s=o.indexOf(":");if(s<0)continue;let c=o.slice(0,s).trim(),d=Z(o.slice(s+1).trim());t[c]=d}return!t.name||!t.type||!t.description||!m.includes(t.type)?null:{name:t.name,type:t.type,description:t.description,body:i,createdAt:t.created??"",updatedAt:t.updated??t.created??"",...t.author?{author:t.author}:{},...t.task?{taskId:t.task}:{}}}function I(r){return/[:#\n]/.test(r)?`"${r.replace(/"/g,'\\"')}"`:r}function Z(r){return r.startsWith('"')&&r.endsWith('"')&&r.length>=2?r.slice(1,-1).replace(/\\"/g,'"'):r}function v(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"unnamed"}function ee(r){return r.charAt(0).toUpperCase()+r.slice(1)}function y(r){try{return J(r,"utf-8")}catch{return null}}export{$ as a,N as b};
|
|
26
|
+
//# sourceMappingURL=chunk-MEFPXHQJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/agent-memory.ts","../src/agents/memory-versions.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { atomicWrite, checkCondition, etagOf, readVersions, snapshot, type WriteCondition } from \"./memory-versions\"\n\n// --- AgentMemory: Claude-Code-style structured memory per agent ---\n//\n// Four memory kinds, each a separate file with YAML frontmatter + markdown\n// body, gathered under a per-agent root index (MEMORY.md):\n//\n// user/ — \"who this human is, what they do, what they know\"\n// feedback/ — \"rules the user gave me; what to do / avoid\"\n// project/ — \"facts about current work the code can't reveal\"\n// reference/ — \"pointers to external systems (linear board, grafana, …)\"\n//\n// Layout on disk:\n// .agentx/agent-memory/<agentId>/\n// MEMORY.md — one-line hook per memory, auto-maintained\n// user_deep_backend_expertise.md\n// feedback_no_mock_database.md\n// ...\n// _versions/<type>_<name>/<timestamp>.md — prior versions (memory-versions.ts)\n//\n// Philosophy: the wiki is authoritative / cross-agent / documented.\n// Memory is experiential / per-agent / \"what I learned across runs.\"\n// Durable, cross-agent-relevant memories get promoted to shared wiki\n// articles by `agentx wiki promote` (src/wiki/promote.ts) — corroboration\n// across agents boosts confidence but isn't required. This module stays\n// read-only from the promotion pipeline's perspective; authors keep both\n// layers parallel.\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\"\n\nexport interface MemoryRecord {\n /** Slug-safe name, unique within an agent. */\n name: string\n type: MemoryType\n /** One-line hook — used as the MEMORY.md index entry. */\n description: string\n /** The body, excluding frontmatter. */\n body: string\n createdAt: string\n updatedAt: string\n /** Who wrote this version: an agent id verified against its running\n * task, \"operator\" (CLI), or \"unverified\" (a caller we couldn't check). */\n author?: string\n /** The running task that wrote it; opens on the Task page. */\n taskId?: string\n /** Content hash of the file as stored. Pass back as `ifMatch` to change\n * it only if nobody else has since. Not stored in the file. */\n etag?: string\n}\n\n/** Who is making a change, recorded in the memory's frontmatter. */\nexport interface MemoryProvenance {\n author?: string\n taskId?: string\n}\n\n/** One entry in a memory's history. */\nexport interface MemoryVersion {\n id: string\n record: MemoryRecord | null\n}\n\nconst TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface AgentMemoryOptions {\n baseDir?: string\n}\n\nexport class AgentMemory {\n readonly baseDir: string\n\n constructor(opts: AgentMemoryOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.baseDir = resolve(root, \"agent-memory\")\n }\n\n /** The agent's note folder, without creating it. */\n dirOf(agentId: string): string {\n return resolve(this.baseDir, slug(agentId))\n }\n\n private dirFor(agentId: string): string {\n const safe = slug(agentId)\n const dir = resolve(this.baseDir, safe)\n mkdirSync(dir, { recursive: true })\n return dir\n }\n\n private fileFor(agentId: string, type: MemoryType, name: string): string {\n return resolve(this.dirFor(agentId), `${type}_${slug(name)}.md`)\n }\n\n private indexPath(agentId: string): string {\n return resolve(this.dirFor(agentId), \"MEMORY.md\")\n }\n\n /** Save (create or update) a memory. Keeps createdAt stable across\n * updates; bumps updatedAt. The replaced version goes to history first.\n * With `cond`, throws MemoryConflictError instead of overwriting a\n * version the caller hasn't seen. Always rewrites MEMORY.md. */\n save(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n } & MemoryProvenance, cond?: WriteCondition): MemoryRecord {\n if (!TYPES.includes(args.type)) throw new Error(`invalid memory type: ${args.type}`)\n if (!args.name.trim()) throw new Error(\"memory name is required\")\n if (!args.description.trim()) throw new Error(\"memory description is required\")\n\n const path = this.fileFor(args.agentId, args.type, args.name)\n const existing = safeRead(path)\n checkCondition(existing, cond)\n const now = new Date().toISOString()\n let createdAt = now\n if (existing) {\n const parsed = parseMemoryFile(existing)\n if (parsed) createdAt = parsed.createdAt\n }\n const record: MemoryRecord = {\n name: slug(args.name),\n type: args.type,\n description: args.description.trim(),\n body: args.body.trimEnd(),\n createdAt,\n updatedAt: now,\n ...(args.author ? { author: args.author } : {}),\n ...(args.taskId ? { taskId: args.taskId } : {}),\n }\n const content = serialize(record)\n snapshot(path, existing)\n atomicWrite(path, content)\n this.rewriteIndex(args.agentId)\n return { ...record, etag: etagOf(content) }\n }\n\n /** Add to the end of an existing memory's body (or create it) in one\n * step, so two appends can't lose each other. */\n append(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n } & MemoryProvenance, cond?: WriteCondition): MemoryRecord {\n const existing = this.get(args.agentId, args.name)\n const body = existing ? `${existing.body.trimEnd()}\\n\\n${args.body.trim()}` : args.body\n return this.save({ ...args, type: existing?.type ?? args.type, body }, cond)\n }\n\n get(agentId: string, name: string): MemoryRecord | null {\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (!existsSync(path)) continue\n const raw = safeRead(path) ?? \"\"\n const parsed = parseMemoryFile(raw)\n if (parsed) return { ...parsed, etag: etagOf(raw) }\n }\n return null\n }\n\n /** Every memory for an agent. Sorted by type then name for stable\n * index rendering. */\n list(agentId: string): MemoryRecord[] {\n const dir = this.dirFor(agentId)\n if (!existsSync(dir)) return []\n const out: MemoryRecord[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name === \"MEMORY.md\") continue\n const raw = safeRead(resolve(dir, entry.name)) ?? \"\"\n const parsed = parseMemoryFile(raw)\n if (parsed) out.push({ ...parsed, etag: etagOf(raw) })\n }\n return out.sort((a, b) => {\n const ta = TYPES.indexOf(a.type)\n const tb = TYPES.indexOf(b.type)\n if (ta !== tb) return ta - tb\n return a.name.localeCompare(b.name)\n })\n }\n\n /** Remove a memory by name. The removed version stays in history, so\n * `restore` can bring it back. With `cond.ifMatch`, throws\n * MemoryConflictError if it changed since the caller read it. Returns\n * true on success, false if none found. */\n remove(agentId: string, name: string, cond?: WriteCondition): boolean {\n const paths = TYPES.map((t) => this.fileFor(agentId, t, name)).filter((p) => existsSync(p))\n if (paths.length === 0) return false\n if (cond) checkCondition(safeRead(paths[0]), cond)\n for (const path of paths) {\n snapshot(path, safeRead(path))\n unlinkSync(path)\n }\n this.rewriteIndex(agentId)\n return true\n }\n\n /** Prior versions of a memory, newest first, including ones kept after\n * it was deleted. */\n versions(agentId: string, name: string): MemoryVersion[] {\n return TYPES\n .flatMap((t) => readVersions(this.fileFor(agentId, t, name)))\n .sort((a, b) => b.id.localeCompare(a.id))\n .map((v) => {\n const parsed = parseMemoryFile(v.content)\n return { id: v.id, record: parsed ? { ...parsed, etag: etagOf(v.content) } : null }\n })\n }\n\n /** Put a prior version back as the current one. The version it replaces\n * goes to history, so a restore can itself be undone. */\n restore(agentId: string, name: string, versionId: string, by: MemoryProvenance = {}): MemoryRecord | null {\n const version = this.versions(agentId, name).find((v) => v.id === versionId)\n if (!version?.record) return null\n const { type, description, body } = version.record\n return this.save({ agentId, type, name, description, body, ...by })\n }\n\n /** The content of MEMORY.md — used at prompt-build time to inline into\n * the agent's system prompt. Returns \"\" when the agent has no\n * memories (so the prompt builder can no-op). With `maxChars` > 0 the\n * index is cut to that size (#615): whole lines only, and a closing line\n * says how many memories were left out and where the full index is. */\n indexMarkdown(agentId: string, maxChars = 0): string {\n const path = this.indexPath(agentId)\n return trimMemoryIndex(safeRead(path) ?? \"\", maxChars)\n }\n\n /** Push the agent's memory into its workspace so Claude Code sessions\n * pick it up automatically. Does two things:\n *\n * 1. Writes `<workspace>/.agentx-memory.md` — full MEMORY.md content,\n * explicit file the skill can `Read` at session start.\n * 2. Merges that same content into `<workspace>/CLAUDE.md` between\n * sentinel comments so Claude Code auto-loads it into the cached\n * system context on every session. Idempotent: subsequent syncs\n * replace the sentinel block.\n *\n * Never touches content outside the sentinel block — if the file\n * doesn't exist, creates it with just the sentinels. Safe to call\n * on any save / delete. */\n syncToWorkspace(agentId: string, workspacePath: string, maxChars = 0): void {\n if (!workspacePath) return\n const full = this.indexMarkdown(agentId).trim()\n\n // 1. Explicit file for the `remember` skill to Read if it wants to.\n // Always the whole index: it is what the capped block points at.\n const explicitPath = resolve(workspacePath, \".agentx-memory.md\")\n if (full) {\n writeFileSync(explicitPath, full + \"\\n\")\n } else if (existsSync(explicitPath)) {\n unlinkSync(explicitPath)\n }\n\n // 2. Sentinel-merged into CLAUDE.md for auto-inject, capped when the\n // operator set session.memoryIndexMaxChars (#615).\n const memory = trimMemoryIndex(full, maxChars)\n const claudePath = resolve(workspacePath, \"CLAUDE.md\")\n const start = \"<!-- AGENTX-MEMORY-START — auto-managed by agentx; edit via `agentx memory` -->\"\n const end = \"<!-- AGENTX-MEMORY-END -->\"\n const block = memory\n ? `${start}\\n${memory}\\n${end}`\n : \"\"\n\n const existing = safeRead(claudePath) ?? \"\"\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n\n let next: string\n if (startIdx >= 0 && endIdx > startIdx) {\n // Replace existing sentinel block (or remove it if memory is empty).\n const before = existing.slice(0, startIdx).replace(/\\n+$/, \"\")\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, \"\")\n next = block\n ? [before, block, after].filter(Boolean).join(\"\\n\\n\") + \"\\n\"\n : [before, after].filter(Boolean).join(\"\\n\\n\") + (before || after ? \"\\n\" : \"\")\n } else if (block) {\n // No prior block — append to the end of CLAUDE.md (creates the file\n // if needed). Keeps any existing content untouched.\n next = existing\n ? existing.replace(/\\n+$/, \"\") + \"\\n\\n\" + block + \"\\n\"\n : block + \"\\n\"\n } else {\n // Empty memory + no prior block → nothing to do.\n return\n }\n writeFileSync(claudePath, next)\n }\n\n /** Regenerate MEMORY.md from the on-disk files. Called automatically\n * after every save / remove; safe to call manually. */\n rewriteIndex(agentId: string): void {\n const entries = this.list(agentId)\n if (entries.length === 0) {\n const p = this.indexPath(agentId)\n if (existsSync(p)) unlinkSync(p)\n return\n }\n const grouped: Record<MemoryType, MemoryRecord[]> = {\n user: [], feedback: [], project: [], reference: [],\n }\n for (const r of entries) grouped[r.type].push(r)\n\n const lines: string[] = []\n lines.push(`# Memory — ${agentId}`, \"\")\n lines.push(\"This is the agent's experiential memory: user preferences,\")\n lines.push(\"feedback, project-specific context, and external references.\")\n lines.push(\"Updated via `agentx memory` CLI or by the agent itself.\")\n for (const type of TYPES) {\n const group = grouped[type]\n if (!group.length) continue\n lines.push(\"\", `## ${titleCase(type)}`, \"\")\n for (const r of group) {\n lines.push(`- **${r.name}** — ${r.description} _(updated ${r.updatedAt.slice(0, 10)})_`)\n }\n }\n lines.push(\"\")\n writeFileSync(this.indexPath(agentId), lines.join(\"\\n\"))\n }\n}\n\n/** What a cut index ends with: how many entries are missing and where the\n * whole index is (the workspace file, or `agentx memory index`). */\nexport const MEMORY_INDEX_CUT_NOTE = \"memories not shown here. The full index is in .agentx-memory.md in this workspace, or run `agentx memory index`.\"\n\n/** Cut a MEMORY.md index down to `maxChars` (#615). 0 or a size the index\n * already fits in returns it unchanged. Lines are kept whole, in order,\n * until the budget is spent; a type heading left with no entry under it\n * is dropped; the closing note counts the entries left out. The budget\n * includes that note, so the result never exceeds `maxChars`. */\nexport function trimMemoryIndex(markdown: string, maxChars: number): string {\n if (!maxChars || maxChars <= 0 || markdown.length <= maxChars) return markdown\n const lines = markdown.replace(/\\n+$/, \"\").split(\"\\n\")\n const isEntry = (line: string) => line.startsWith(\"- \")\n const total = lines.filter(isEntry).length\n // The note is longest when every entry is left out; reserve for that.\n const noteFor = (left: number) => `_(${left} more ${MEMORY_INDEX_CUT_NOTE})_`\n const reserve = noteFor(total).length + 2\n const kept: string[] = []\n let used = 0\n let entries = 0\n for (const line of lines) {\n const cost = line.length + 1\n if (used + cost > maxChars - reserve) break\n kept.push(line)\n used += cost\n if (isEntry(line)) entries++\n }\n // A heading (or blank) at the tail with no entry under it says nothing.\n while (kept.length && !isEntry(kept[kept.length - 1])) kept.pop()\n const left = total - entries\n const out = left > 0 ? [...kept, \"\", noteFor(left)] : kept\n return out.join(\"\\n\").replace(/^\\n+/, \"\")\n}\n\n// --- Serialisation helpers ---------------------------------------------\n\nfunction serialize(r: MemoryRecord): string {\n const frontmatter = [\n \"---\",\n `name: ${r.name}`,\n `description: ${escapeYaml(r.description)}`,\n `type: ${r.type}`,\n `created: ${r.createdAt}`,\n `updated: ${r.updatedAt}`,\n ...(r.author ? [`author: ${escapeYaml(r.author)}`] : []),\n ...(r.taskId ? [`task: ${escapeYaml(r.taskId)}`] : []),\n \"---\",\n \"\",\n ].join(\"\\n\")\n return frontmatter + r.body + \"\\n\"\n}\n\n/** Minimal frontmatter parser. Accepts `key: value` lines between `---`\n * markers; body is everything after. No YAML flow syntax — if we need\n * it later, swap for a proper parser. */\nexport function parseMemoryFile(raw: string): MemoryRecord | null {\n if (!raw.startsWith(\"---\\n\")) return null\n const end = raw.indexOf(\"\\n---\\n\", 4)\n if (end < 0) return null\n const header = raw.slice(4, end)\n const body = raw.slice(end + 5).replace(/^\\n+/, \"\").trimEnd()\n const fm: Record<string, string> = {}\n for (const line of header.split(\"\\n\")) {\n const colon = line.indexOf(\":\")\n if (colon < 0) continue\n const key = line.slice(0, colon).trim()\n const val = unescapeYaml(line.slice(colon + 1).trim())\n fm[key] = val\n }\n if (!fm.name || !fm.type || !fm.description) return null\n if (!TYPES.includes(fm.type as MemoryType)) return null\n return {\n name: fm.name,\n type: fm.type as MemoryType,\n description: fm.description,\n body,\n createdAt: fm.created ?? \"\",\n updatedAt: fm.updated ?? fm.created ?? \"\",\n ...(fm.author ? { author: fm.author } : {}),\n ...(fm.task ? { taskId: fm.task } : {}),\n }\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n]/.test(s)) return `\"${s.replace(/\"/g, \"\\\\\\\"\")}\"`\n return s\n}\n\nfunction unescapeYaml(s: string): string {\n if (s.startsWith(\"\\\"\") && s.endsWith(\"\\\"\") && s.length >= 2) {\n return s.slice(1, -1).replace(/\\\\\"/g, \"\\\"\")\n }\n return s\n}\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"unnamed\"\n}\n\nfunction titleCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction safeRead(path: string): string | null {\n try { return readFileSync(path, \"utf-8\") } catch { return null }\n}\n","import { createHash } from \"crypto\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { basename, dirname, resolve } from \"path\"\n\n// --- Version history and write safety for AgentMemory files ---\n//\n// Every overwrite or delete of a memory file first copies the current file\n// into `_versions/<file stem>/<timestamp>.md` next to it, so any change can\n// be rolled back. Writes go through a temp file and a rename, so a reader\n// never sees half a file. An etag (content hash) lets callers make\n// conditional writes: two agents editing the same memory can no longer\n// silently overwrite each other.\n\n/** Old versions kept per memory. The oldest are pruned past this. */\nexport const MAX_VERSIONS = 20\n\nexport class MemoryConflictError extends Error {\n constructor(readonly currentEtag: string | null) {\n super(currentEtag\n ? \"memory changed since you read it (etag mismatch)\"\n : \"memory does not exist (etag mismatch)\")\n this.name = \"MemoryConflictError\"\n }\n}\n\n/** Conditions for a write, mirroring HTTP If-Match / If-None-Match. */\nexport interface WriteCondition {\n /** Write only when the current file has this etag. */\n ifMatch?: string\n /** \"*\": write only when no memory exists yet (create, never overwrite). */\n ifNoneMatch?: \"*\"\n}\n\nexport function etagOf(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\n/** Throws MemoryConflictError when `current` (file content, or null when\n * absent) doesn't satisfy `cond`. No condition always passes. */\nexport function checkCondition(current: string | null, cond: WriteCondition | undefined): void {\n if (!cond) return\n const etag = current === null ? null : etagOf(current)\n if (cond.ifNoneMatch === \"*\" && etag !== null) throw new MemoryConflictError(etag)\n if (cond.ifMatch !== undefined && cond.ifMatch !== etag) throw new MemoryConflictError(etag)\n}\n\nexport function atomicWrite(path: string, content: string): void {\n const tmp = resolve(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`)\n writeFileSync(tmp, content)\n renameSync(tmp, path)\n}\n\nfunction versionsDir(memoryFile: string): string {\n return resolve(dirname(memoryFile), \"_versions\", basename(memoryFile, \".md\"))\n}\n\n/** Copy the current file into its history before it is replaced or\n * removed. No-op when the file doesn't exist yet. */\nexport function snapshot(memoryFile: string, current: string | null): void {\n if (current === null) return\n const dir = versionsDir(memoryFile)\n mkdirSync(dir, { recursive: true })\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\")\n let path = resolve(dir, `${stamp}.md`)\n for (let n = 1; existsSync(path); n++) path = resolve(dir, `${stamp}-${n}.md`)\n writeFileSync(path, current)\n prune(dir)\n}\n\nfunction prune(dir: string): void {\n const files = readdirSync(dir).filter((f) => f.endsWith(\".md\")).sort()\n for (const f of files.slice(0, Math.max(0, files.length - MAX_VERSIONS))) {\n try { unlinkSync(resolve(dir, f)) } catch { /* already gone */ }\n }\n}\n\nexport interface StoredVersion {\n /** File stem, e.g. 2026-09-26T07-30-00-123Z. Pass to restore. */\n id: string\n content: string\n}\n\n/** Stored versions of a memory file, newest first. */\nexport function readVersions(memoryFile: string): StoredVersion[] {\n const dir = versionsDir(memoryFile)\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .sort()\n .reverse()\n .map((f) => ({ id: f.slice(0, -3), content: readFileSync(resolve(dir, f), \"utf-8\") }))\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","createHash","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","unlinkSync","writeFileSync","basename","dirname","resolve","MAX_VERSIONS","MemoryConflictError","currentEtag","etagOf","content","checkCondition","current","cond","etag","atomicWrite","path","tmp","versionsDir","memoryFile","snapshot","dir","stamp","n","prune","files","f","readVersions","TYPES","AgentMemory","opts","root","resolve","agentId","slug","safe","dir","mkdirSync","type","name","args","cond","path","existing","safeRead","checkCondition","now","createdAt","parsed","parseMemoryFile","record","content","serialize","snapshot","atomicWrite","etagOf","body","existsSync","raw","out","entry","readdirSync","a","b","ta","tb","paths","t","p","unlinkSync","readVersions","v","versionId","by","version","description","maxChars","trimMemoryIndex","workspacePath","full","explicitPath","writeFileSync","memory","claudePath","start","end","block","startIdx","endIdx","next","before","after","entries","grouped","r","lines","group","titleCase","MEMORY_INDEX_CUT_NOTE","markdown","isEntry","line","total","noteFor","left","reserve","kept","used","cost","escapeYaml","header","fm","colon","key","val","unescapeYaml","s","readFileSync"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{oa as l,ua as m}from"./chunk-
|
|
1
|
+
import{oa as l,ua as m}from"./chunk-UUFJQMHE.js";m();import{existsSync as g,readFileSync as u,writeFileSync as a,copyFileSync as y}from"fs";import{resolve as p}from"path";function P(e,n={}){let t=n.configPath||p(process.cwd(),"agentx.json");if(!g(t))throw new Error(`agentx.json not found at ${t}`);let i=JSON.parse(u(t,"utf-8")),o=e(i),s=typeof o=="string"?{summary:o}:o,c=JSON.stringify(i,null,2)+`
|
|
2
2
|
`,f=t+".tmp";a(f,c,"utf-8");try{JSON.parse(u(f,"utf-8"))}catch(d){throw new Error(`config did not round-trip cleanly: ${d.message}`)}let r;return n.skipBackup||(r=`${t}.bak.${Date.now()}`,y(t,r)),a(t,c,"utf-8"),n.skipReload||h().catch(()=>{}),{summary:s.summary,payload:s.payload,backupPath:r}}function S(e,n={}){let t=n.configPath||p(process.cwd(),"agentx.json");if(g(t)&&!n.overwrite)throw new Error(`agentx.json already exists at ${t}. Pass overwrite=true to replace it.`);return a(t,JSON.stringify(e,null,2)+`
|
|
3
3
|
`,"utf-8"),{file:t}}async function h(){try{let n=l().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${n}/reload`,{method:"POST"}).catch(()=>null)}catch{}}export{P as a,S as b};
|
|
4
|
-
//# sourceMappingURL=chunk-
|
|
4
|
+
//# sourceMappingURL=chunk-MZJY26FW.js.map
|