agentix-cli 0.69.0 → 0.69.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{agent-VQIC7O5T.js → agent-P2UO35ZW.js} +2 -2
  2. package/dist/{board-dashboard-ZPJHXX3U.js → board-dashboard-3WVKEGRZ.js} +6 -5
  3. package/dist/{board-dashboard-ZPJHXX3U.js.map → board-dashboard-3WVKEGRZ.js.map} +1 -1
  4. package/dist/{chunk-ART666G6.js → chunk-2V7IQUEA.js} +2 -2
  5. package/dist/{chunk-PNWNVOEZ.js → chunk-4DRNEOOW.js} +2 -2
  6. package/dist/{chunk-NVJHA63C.js → chunk-4UMYRIEI.js} +10 -10
  7. package/dist/chunk-4UMYRIEI.js.map +1 -0
  8. package/dist/{chunk-HEJOWP7O.js → chunk-56AQBWEJ.js} +2 -2
  9. package/dist/{chunk-JHWBCDD2.js → chunk-A5ZCMRB3.js} +12 -9
  10. package/dist/chunk-A5ZCMRB3.js.map +1 -0
  11. package/dist/{chunk-BYKF3UTL.js → chunk-AIV547JW.js} +2 -2
  12. package/dist/chunk-ILEC3IAV.js +3 -0
  13. package/dist/chunk-ILEC3IAV.js.map +1 -0
  14. package/dist/{chunk-B65FS265.js → chunk-JSF2THM4.js} +2 -2
  15. package/dist/chunk-KA2UIZXG.js +3 -0
  16. package/dist/chunk-KA2UIZXG.js.map +1 -0
  17. package/dist/chunk-KXR6GVL7.js +5 -0
  18. package/dist/chunk-KXR6GVL7.js.map +1 -0
  19. package/dist/{chunk-QDRAKJSY.js → chunk-L2K4N3DA.js} +4 -4
  20. package/dist/chunk-M6HJDSLS.js +109 -0
  21. package/dist/chunk-M6HJDSLS.js.map +1 -0
  22. package/dist/{chunk-4NXIJEYU.js → chunk-U3XQ7USK.js} +9 -9
  23. package/dist/{chunk-4NXIJEYU.js.map → chunk-U3XQ7USK.js.map} +1 -1
  24. package/dist/{chunk-S4ASFOJU.js → chunk-VXDM7JAT.js} +2 -2
  25. package/dist/{chunk-5AMQFDLE.js → chunk-X4FD5OBK.js} +63 -63
  26. package/dist/{chunk-5AMQFDLE.js.map → chunk-X4FD5OBK.js.map} +1 -1
  27. package/dist/{chunk-NEC4RXFT.js → chunk-XMW422NO.js} +2 -2
  28. package/dist/{chunk-LPWPFEIE.js → chunk-YBLITM6C.js} +2 -2
  29. package/dist/cli.js +1 -1
  30. package/dist/fact-proposals-LZWS5KFO.js +2 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.js +1 -1
  33. package/dist/ledger-CH7R756S.js +2 -0
  34. package/dist/{migrate-v2-5VGHWPBW.js → migrate-v2-KOMPDC6U.js} +2 -2
  35. package/dist/{program-6PFRDQFT.js → program-GUBHJFZS.js} +2 -2
  36. package/dist/{promote-XRKH73IB.js → promote-7FMPZKXN.js} +2 -2
  37. package/dist/questions-ZATLU5AW.js +2 -0
  38. package/dist/{registry-KP3C4EZ4.js → registry-MUMQPZDM.js} +2 -2
  39. package/dist/{rotation-memo-store-X63HYIMV.js → rotation-memo-store-A4YW6FSK.js} +2 -2
  40. package/dist/{schedule-tool-ZVKBZOVG.js → schedule-tool-BY72YNNU.js} +2 -2
  41. package/dist/{scheduler-5NBO3J2G.js → scheduler-XWYFGG2S.js} +2 -2
  42. package/dist/{tool-ZENVUVBO.js → tool-ZUGNQZGI.js} +2 -2
  43. package/dist/{wiki-W6WI6HL3.js → wiki-LLL7VWRJ.js} +2 -2
  44. package/package.json +1 -1
  45. package/dist/chunk-AU6FFC4I.js +0 -5
  46. package/dist/chunk-AU6FFC4I.js.map +0 -1
  47. package/dist/chunk-CUZWGTXT.js +0 -109
  48. package/dist/chunk-CUZWGTXT.js.map +0 -1
  49. package/dist/chunk-JHWBCDD2.js.map +0 -1
  50. package/dist/chunk-NVJHA63C.js.map +0 -1
  51. package/dist/chunk-ST6KPQOY.js +0 -3
  52. package/dist/chunk-ST6KPQOY.js.map +0 -1
  53. package/dist/chunk-ZXR6GNEK.js +0 -3
  54. package/dist/chunk-ZXR6GNEK.js.map +0 -1
  55. package/dist/fact-proposals-ERRQXV2J.js +0 -2
  56. package/dist/ledger-DHYRP4TN.js +0 -2
  57. package/dist/questions-QGITDLJY.js +0 -2
  58. /package/dist/{agent-VQIC7O5T.js.map → agent-P2UO35ZW.js.map} +0 -0
  59. /package/dist/{chunk-ART666G6.js.map → chunk-2V7IQUEA.js.map} +0 -0
  60. /package/dist/{chunk-PNWNVOEZ.js.map → chunk-4DRNEOOW.js.map} +0 -0
  61. /package/dist/{chunk-HEJOWP7O.js.map → chunk-56AQBWEJ.js.map} +0 -0
  62. /package/dist/{chunk-BYKF3UTL.js.map → chunk-AIV547JW.js.map} +0 -0
  63. /package/dist/{chunk-B65FS265.js.map → chunk-JSF2THM4.js.map} +0 -0
  64. /package/dist/{chunk-QDRAKJSY.js.map → chunk-L2K4N3DA.js.map} +0 -0
  65. /package/dist/{chunk-S4ASFOJU.js.map → chunk-VXDM7JAT.js.map} +0 -0
  66. /package/dist/{chunk-NEC4RXFT.js.map → chunk-XMW422NO.js.map} +0 -0
  67. /package/dist/{chunk-LPWPFEIE.js.map → chunk-YBLITM6C.js.map} +0 -0
  68. /package/dist/{fact-proposals-ERRQXV2J.js.map → fact-proposals-LZWS5KFO.js.map} +0 -0
  69. /package/dist/{ledger-DHYRP4TN.js.map → ledger-CH7R756S.js.map} +0 -0
  70. /package/dist/{migrate-v2-5VGHWPBW.js.map → migrate-v2-KOMPDC6U.js.map} +0 -0
  71. /package/dist/{program-6PFRDQFT.js.map → program-GUBHJFZS.js.map} +0 -0
  72. /package/dist/{promote-XRKH73IB.js.map → promote-7FMPZKXN.js.map} +0 -0
  73. /package/dist/{questions-QGITDLJY.js.map → questions-ZATLU5AW.js.map} +0 -0
  74. /package/dist/{registry-KP3C4EZ4.js.map → registry-MUMQPZDM.js.map} +0 -0
  75. /package/dist/{rotation-memo-store-X63HYIMV.js.map → rotation-memo-store-A4YW6FSK.js.map} +0 -0
  76. /package/dist/{schedule-tool-ZVKBZOVG.js.map → schedule-tool-BY72YNNU.js.map} +0 -0
  77. /package/dist/{scheduler-5NBO3J2G.js.map → scheduler-XWYFGG2S.js.map} +0 -0
  78. /package/dist/{tool-ZENVUVBO.js.map → tool-ZUGNQZGI.js.map} +0 -0
  79. /package/dist/{wiki-W6WI6HL3.js.map → wiki-LLL7VWRJ.js.map} +0 -0
@@ -1,3 +0,0 @@
1
- import{b,c as k,d as F,f as A}from"./chunk-GQ76J72O.js";import{a as B,b as v}from"./chunk-ZXR6GNEK.js";import{createHash as P}from"crypto";import{existsSync as _,statSync as q}from"fs";import{resolve as J}from"path";import{closeSync as L,existsSync as M,mkdirSync as S,openSync as C,readFileSync as E,renameSync as T,statSync as O,unlinkSync as $,writeFileSync as R}from"fs";import{dirname as x}from"path";var y=class extends Error{constructor(e,s){super(`fact ledger ${e} is unreadable (${s}). Nothing was written to it. Repair the JSON, or move the file aside to start an empty ledger.`);this.file=e;this.name="LedgerCorruptError"}};function j(r){if(!M(r))return null;let t;try{t=JSON.parse(E(r,"utf-8"))}catch(e){throw new y(r,String(e?.message??e))}if(!t||typeof t!="object"||!Array.isArray(t.facts)||t.proposals!==void 0&&!Array.isArray(t.proposals))throw new y(r,"not a ledger: expected { facts: [], proposals: [] }");return{facts:t.facts,proposals:t.proposals??[]}}function W(r,t){S(x(r),{recursive:!0});let e=`${r}.${process.pid}.tmp`;R(e,JSON.stringify(t,null,2)+`
2
- `),T(e,r)}var N=r=>Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,r);function I(r,t,e={}){let s=`${r}.lock`,i=e.timeoutMs??3e3,l=e.staleMs??15e3;S(x(s),{recursive:!0});let o=Date.now()+i,c;for(;c===void 0;)try{c=C(s,"wx")}catch(d){if(d?.code!=="EEXIST")throw d;try{if(Date.now()-O(s).mtimeMs>l){$(s);continue}}catch{continue}if(Date.now()>o)throw new Error(`fact ledger is busy (${s}); try again`);N(25)}try{return t()}finally{L(c);try{$(s)}catch{}}}var n=r=>r.toLowerCase().replace(/\s+/g," ").trim();function H(r,t){return"f-"+P("sha1").update(`${n(r)}\0${n(t)}`).digest("hex").slice(0,10)}var D=class{constructor(t){this.wikiDir=t;this.file=J(t,"_facts.json")}file;cache;reported;load(){if(!_(this.file))return{version:1,facts:[],proposals:[]};let t=q(this.file).mtimeMs;if(this.cache?.mtimeMs===t)return structuredClone(this.cache.data);let e;try{e=j(this.file)}catch(i){throw this.reported!==t&&(this.reported=t,console.error(`[wiki-facts] ${i.message}`)),i}let s={version:1,facts:e?.facts??[],proposals:e?.proposals??[]};return this.cache={mtimeMs:t,data:s},structuredClone(s)}update(t){return I(this.file,()=>{this.cache=void 0;let e=this.load(),{save:s,result:i}=t(e);return s&&(W(this.file,e),this.cache=void 0),i})}list(){return this.load().facts}get(t){return this.list().find(e=>e.id===t||t.length>=6&&e.id.startsWith(t))??null}write(t,e={}){let s=e.now??Date.now(),i=new Date(s).toISOString();if(!t.source?.trim())throw new Error('a fact needs a source: a system, URL, command or "owner said"');if(!t.subject?.trim()||!t.attribute?.trim()||!t.value?.trim())throw new Error("a fact needs a subject, an attribute and a value");let l=t.verifiedAt==="now"?s:Date.parse(t.verifiedAt??""),o=Number.isFinite(l),c=o?new Date(Math.min(l,s)).toISOString():i,d=b(t.volatility)?t.volatility:F(t.attribute,`${t.subject} ${t.attribute} is ${t.value}`),g={source:t.source.trim(),verifiedAt:c,verifiedBy:t.verifiedBy||"unknown",volatility:d,...typeof t.ttlDays=="number"?{ttlDays:t.ttlDays}:{}},h=H(t.subject,t.attribute),u=this.update(f=>{let a=f.facts.find(m=>m.id===h);if(!a){let m={id:h,subject:t.subject.trim(),attribute:t.attribute.trim(),value:t.value.trim(),...g,createdAt:i,updatedAt:i,...e.confirmedBy?{confirmedBy:e.confirmedBy}:{}};return f.facts.push(m),{save:!0,result:{status:"created",fact:m}}}let p=Date.parse(a.verifiedAt),w=o&&(!Number.isFinite(p)||Date.parse(c)>p);return n(a.value)===n(t.value)?!w&&!e.confirmedBy?{save:!1,result:{status:"unchanged",fact:a}}:(Object.assign(a,g,{updatedAt:i},e.confirmedBy?{confirmedBy:e.confirmedBy}:{}),{save:!0,result:{status:"verified",fact:a}}):!e.confirmedBy&&(!w||a.confirmedBy)?{save:!1,result:{contradiction:a}}:(a.history=[...a.history??[],{value:a.value,source:a.source,verifiedAt:a.verifiedAt,verifiedBy:a.verifiedBy,replacedAt:i}].slice(-10),a.value=t.value.trim(),Object.assign(a,g,{updatedAt:i}),e.confirmedBy?a.confirmedBy=e.confirmedBy:delete a.confirmedBy,{save:!0,result:{status:"updated",fact:a}})});if("contradiction"in u){let f=Q(this.wikiDir,u.contradiction,{...t,verifiedAt:o?c:"undated"});return{status:"contradiction",fact:u.contradiction,questionId:f}}return u}};function Q(r,t,e){let s=new v(r),i={kind:"contradiction",agentId:e.verifiedBy||"unknown",path:"_facts.json",subject:t.subject,field:`${t.attribute}=${n(e.value)}`,tier:"pillar",factId:t.id,proposed:{value:e.value.trim(),source:e.source.trim(),verifiedAt:e.verifiedAt,verifiedBy:e.verifiedBy},question:`${t.subject} \xB7 ${t.attribute}: the wiki says "${t.value}" (${t.source}, checked ${t.verifiedAt.slice(0,10)}), but ${e.verifiedBy} reports "${e.value.trim()}" (${e.source.trim()}, checked ${e.verifiedAt.slice(0,10)}). Which is true?`};return s.add([i]),B(i.kind,i.agentId,i.subject,i.field)}function et(r){return new v(r).list("open").filter(t=>t.kind==="contradiction").map(t=>({type:"contradiction",articles:[`_facts.json#${t.factId??"?"}`],message:`[${t.id}] ${t.question}`,confidence:"high"}))}function rt(r,t=Date.now()){return A(r,t)}function it(r,t){let e=n(r),s=null;for(let i of t){let l=n(i.subject);if(l.length<3||!e.includes(l))continue;let o=n(i.attribute).split(" ").filter(d=>d.length>2);(e.includes(n(i.value))||i.volatility!=="stable"&&k(r)===i.volatility||o.length>0&&o.every(d=>e.includes(d)))&&(!s||i.subject.length>s.subject.length)&&(s=i)}return s}export{H as a,D as b,et as c,rt as d,it as e};
3
- //# sourceMappingURL=chunk-ST6KPQOY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/wiki/facts/ledger.ts","../src/wiki/facts/ledger-file.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, statSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { readLedger, withLock, writeLedger } from \"./ledger-file\"\nimport { classifyAttribute, classifyFact, isFactClass, isPastTtl, type FactClass, type Provenance } from \"@/agents/fact-freshness\"\nimport type { ContradictionIssue } from \"../lint-contradictions\"\nimport { QuestionStore, questionId } from \"../questions\"\n\n// --- The wiki fact ledger: durable facts with provenance (#273) ---\n//\n// Articles are prose. A fact an agent states to a person (\"the vendor\n// account is past due\") needs more than prose: where it came from, when\n// it was last checked, by whom, and how long it stays true. This is that\n// record — one small JSON sidecar beside `_questions.json`, skipped by the\n// article index like every `_` file.\n//\n// Every other store references it: memory lines that restate a ledger\n// fact are replaced by a reference at injection (agents/memory-context),\n// and memo claims wait here as proposals (fact-proposals.ts) instead of\n// becoming memory.\n//\n// Overwrite safety: a write that disagrees with the current value needs a\n// check newer than the current one, or a person's confirmation. Otherwise\n// the old value stays and a contradiction question joins the wiki's\n// question queue (`agentx wiki questions`), where the answer is written\n// back through here.\n\nexport interface WikiFact extends Provenance {\n id: string\n subject: string\n attribute: string\n value: string\n createdAt: string\n updatedAt: string\n /** Set when a person confirmed the value over a newer-dated one. */\n confirmedBy?: string\n /** Earlier values, newest last. Nothing is replaced silently. */\n history?: Array<{ value: string; source: string; verifiedAt: string; verifiedBy: string; replacedAt: string }>\n}\n\nexport interface FactInput {\n subject: string\n attribute: string\n value: string\n source: string\n verifiedBy: string\n /** When the source was checked (ISO date, or \"now\"). Without it the\n * write is undated and never counts as newer than an existing fact. */\n verifiedAt?: string\n volatility?: FactClass\n ttlDays?: number\n}\n\nexport type WriteStatus = \"created\" | \"verified\" | \"unchanged\" | \"updated\" | \"contradiction\"\n\nexport interface WriteResult {\n status: WriteStatus\n fact: WikiFact\n /** The queued question, for a contradiction. */\n questionId?: string\n}\n\nexport interface LedgerFile {\n version: 1\n facts: WikiFact[]\n proposals: import(\"./fact-proposals\").FactProposal[]\n}\n\nconst norm = (s: string) => s.toLowerCase().replace(/\\s+/g, \" \").trim()\n\n/** Stable per subject and attribute, so a re-check lands on the same fact. */\nexport function factId(subject: string, attribute: string): string {\n return \"f-\" + createHash(\"sha1\").update(`${norm(subject)}\\u0000${norm(attribute)}`).digest(\"hex\").slice(0, 10)\n}\n\nexport class FactLedger {\n readonly file: string\n private cache?: { mtimeMs: number; data: LedgerFile }\n private reported?: number\n\n constructor(readonly wikiDir: string) {\n this.file = resolve(wikiDir, \"_facts.json\")\n }\n\n /** The ledger as stored. Throws LedgerCorruptError on a file that does\n * not parse: it is never read as empty, so it is never overwritten. */\n load(): LedgerFile {\n if (!existsSync(this.file)) return { version: 1, facts: [], proposals: [] }\n const mtimeMs = statSync(this.file).mtimeMs\n if (this.cache?.mtimeMs === mtimeMs) return structuredClone(this.cache.data)\n let raw\n try {\n raw = readLedger(this.file)\n } catch (e) {\n // Once per version of the bad file, not once per prompt.\n if (this.reported !== mtimeMs) {\n this.reported = mtimeMs\n console.error(`[wiki-facts] ${(e as Error).message}`)\n }\n throw e\n }\n const data: LedgerFile = { version: 1, facts: (raw?.facts ?? []) as WikiFact[], proposals: (raw?.proposals ?? []) as LedgerFile[\"proposals\"] }\n this.cache = { mtimeMs, data }\n return structuredClone(data)\n }\n\n /**\n * Load, change and save under the ledger's lock, so concurrent writers\n * (a daemon proposing memo claims, a CLI `set`) can't drop each other's\n * change. `fn` returns whether to save, and a result.\n */\n update<T>(fn: (data: LedgerFile) => { save: boolean; result: T }): T {\n return withLock(this.file, () => {\n this.cache = undefined\n const data = this.load()\n const { save, result } = fn(data)\n if (save) {\n writeLedger(this.file, data)\n this.cache = undefined\n }\n return result\n })\n }\n\n list(): WikiFact[] { return this.load().facts }\n\n get(id: string): WikiFact | null {\n return this.list().find((f) => f.id === id || (id.length >= 6 && f.id.startsWith(id))) ?? null\n }\n\n /**\n * Record a checked fact. Refuses a fact with no source. A different value\n * replaces the current one only with a dated check newer than the current\n * one, or `confirmedBy`. A value a person confirmed is replaced only by\n * another confirmation. Otherwise the current value stays and a\n * contradiction question is raised.\n *\n * A write with no `verifiedAt` is undated: it can create a fact, but it\n * never counts as newer than a fact already there.\n */\n write(input: FactInput, opts: { confirmedBy?: string; now?: number } = {}): WriteResult {\n const now = opts.now ?? Date.now()\n const nowIso = new Date(now).toISOString()\n if (!input.source?.trim()) throw new Error(\"a fact needs a source: a system, URL, command or \\\"owner said\\\"\")\n if (!input.subject?.trim() || !input.attribute?.trim() || !input.value?.trim()) {\n throw new Error(\"a fact needs a subject, an attribute and a value\")\n }\n const checked = input.verifiedAt === \"now\" ? now : Date.parse(input.verifiedAt ?? \"\")\n const dated = Number.isFinite(checked)\n // A check can't be dated after it happened.\n const verifiedAt = dated ? new Date(Math.min(checked, now)).toISOString() : nowIso\n const volatility = isFactClass(input.volatility)\n ? input.volatility\n : classifyAttribute(input.attribute, `${input.subject} ${input.attribute} is ${input.value}`)\n const provenance: Provenance = {\n source: input.source.trim(), verifiedAt, verifiedBy: input.verifiedBy || \"unknown\", volatility,\n ...(typeof input.ttlDays === \"number\" ? { ttlDays: input.ttlDays } : {}),\n }\n const id = factId(input.subject, input.attribute)\n\n const out = this.update<WriteResult | { contradiction: WikiFact }>((data) => {\n const existing = data.facts.find((f) => f.id === id)\n if (!existing) {\n const fact: WikiFact = {\n id, subject: input.subject.trim(), attribute: input.attribute.trim(), value: input.value.trim(),\n ...provenance, createdAt: nowIso, updatedAt: nowIso,\n ...(opts.confirmedBy ? { confirmedBy: opts.confirmedBy } : {}),\n }\n data.facts.push(fact)\n return { save: true, result: { status: \"created\", fact } }\n }\n\n const prior = Date.parse(existing.verifiedAt)\n const newer = dated && (!Number.isFinite(prior) || Date.parse(verifiedAt) > prior)\n if (norm(existing.value) === norm(input.value)) {\n if (!newer && !opts.confirmedBy) return { save: false, result: { status: \"unchanged\", fact: existing } }\n Object.assign(existing, provenance, { updatedAt: nowIso }, opts.confirmedBy ? { confirmedBy: opts.confirmedBy } : {})\n return { save: true, result: { status: \"verified\", fact: existing } }\n }\n\n // A person's confirmation outranks any agent's date.\n if (!opts.confirmedBy && (!newer || existing.confirmedBy)) {\n return { save: false, result: { contradiction: existing } }\n }\n\n existing.history = [...(existing.history ?? []), {\n value: existing.value, source: existing.source, verifiedAt: existing.verifiedAt,\n verifiedBy: existing.verifiedBy, replacedAt: nowIso,\n }].slice(-10)\n existing.value = input.value.trim()\n Object.assign(existing, provenance, { updatedAt: nowIso })\n if (opts.confirmedBy) existing.confirmedBy = opts.confirmedBy\n else delete existing.confirmedBy\n return { save: true, result: { status: \"updated\", fact: existing } }\n })\n\n if (\"contradiction\" in out) {\n const questionId = raiseContradiction(this.wikiDir, out.contradiction, { ...input, verifiedAt: dated ? verifiedAt : \"undated\" })\n return { status: \"contradiction\", fact: out.contradiction, questionId }\n }\n return out\n }\n}\n\nfunction raiseContradiction(wikiDir: string, current: WikiFact, input: FactInput & { verifiedAt: string }): string {\n const store = new QuestionStore(wikiDir)\n const item = {\n kind: \"contradiction\" as const,\n agentId: input.verifiedBy || \"unknown\",\n path: \"_facts.json\",\n subject: current.subject,\n // One question per refused value: a second, different claim is a new\n // question, while repeating the same claim does not ask twice.\n field: `${current.attribute}=${norm(input.value)}`,\n tier: \"pillar\",\n factId: current.id,\n proposed: { value: input.value.trim(), source: input.source.trim(), verifiedAt: input.verifiedAt, verifiedBy: input.verifiedBy },\n question:\n `${current.subject} · ${current.attribute}: the wiki says \"${current.value}\" ` +\n `(${current.source}, checked ${current.verifiedAt.slice(0, 10)}), but ${input.verifiedBy} reports ` +\n `\"${input.value.trim()}\" (${input.source.trim()}, checked ${input.verifiedAt.slice(0, 10)}). Which is true?`,\n }\n store.add([item])\n return questionId(item.kind, item.agentId, item.subject, item.field)\n}\n\n/** Open fact contradictions, in the shape `wiki lint` reports. */\nexport function factContradictions(wikiDir: string): ContradictionIssue[] {\n return new QuestionStore(wikiDir).list(\"open\")\n .filter((q) => q.kind === \"contradiction\")\n .map((q) => ({\n type: \"contradiction\" as const,\n articles: [`_facts.json#${q.factId ?? \"?\"}`],\n message: `[${q.id}] ${q.question}`,\n confidence: \"high\" as const,\n }))\n}\n\n/** A fact is stale when it is past its class TTL. */\nexport function isStaleFact(f: WikiFact, now = Date.now()): boolean {\n return isPastTtl(f, now)\n}\n\n/**\n * The ledger fact a memory line restates, if any. The line must name the\n * fact's subject, and either repeat its value, share its volatility class,\n * or name its attribute.\n */\nexport function matchFact(content: string, facts: WikiFact[]): WikiFact | null {\n const text = norm(content)\n let best: WikiFact | null = null\n for (const f of facts) {\n const subject = norm(f.subject)\n if (subject.length < 3 || !text.includes(subject)) continue\n const attrWords = norm(f.attribute).split(\" \").filter((w) => w.length > 2)\n const hit = text.includes(norm(f.value))\n || (f.volatility !== \"stable\" && classifyFact(content) === f.volatility)\n || (attrWords.length > 0 && attrWords.every((w) => text.includes(w)))\n if (hit && (!best || f.subject.length > best.subject.length)) best = f\n }\n return best\n}\n","import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from \"fs\"\nimport { dirname } from \"path\"\n\n// --- The fact ledger's file: strict reads, locked writes (#273) ---\n//\n// Two rules keep the ledger from losing facts with nobody watching:\n// 1. A file that does not parse is never treated as empty. Reading it\n// throws LedgerCorruptError, so no write can replace it; the file is\n// left exactly as it is for a person to repair.\n// 2. load → modify → save runs under a lockfile, so a daemon proposing\n// memo claims and a CLI `set` or `approve` can't drop each other's\n// write.\n\nexport class LedgerCorruptError extends Error {\n constructor(readonly file: string, cause: string) {\n super(\n `fact ledger ${file} is unreadable (${cause}). Nothing was written to it. ` +\n `Repair the JSON, or move the file aside to start an empty ledger.`,\n )\n this.name = \"LedgerCorruptError\"\n }\n}\n\nexport interface RawLedger {\n facts: unknown[]\n proposals: unknown[]\n}\n\n/** Null when the file does not exist; throws when it exists but is not a ledger. */\nexport function readLedger(file: string): RawLedger | null {\n if (!existsSync(file)) return null\n let raw: any\n try {\n raw = JSON.parse(readFileSync(file, \"utf-8\"))\n } catch (e) {\n throw new LedgerCorruptError(file, String((e as Error)?.message ?? e))\n }\n if (!raw || typeof raw !== \"object\" || !Array.isArray(raw.facts) || (raw.proposals !== undefined && !Array.isArray(raw.proposals))) {\n throw new LedgerCorruptError(file, \"not a ledger: expected { facts: [], proposals: [] }\")\n }\n return { facts: raw.facts, proposals: raw.proposals ?? [] }\n}\n\nexport function writeLedger(file: string, data: unknown): void {\n mkdirSync(dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(data, null, 2) + \"\\n\")\n renameSync(tmp, file)\n}\n\nconst sleep = (ms: number) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n\n/**\n * Run `fn` holding `<file>.lock`. Waits up to `timeoutMs` for another\n * holder; a lock older than `staleMs` is a crashed holder and is taken over.\n */\nexport function withLock<T>(file: string, fn: () => T, opts: { timeoutMs?: number; staleMs?: number } = {}): T {\n const lock = `${file}.lock`\n const timeoutMs = opts.timeoutMs ?? 3000\n const staleMs = opts.staleMs ?? 15_000\n mkdirSync(dirname(lock), { recursive: true })\n const deadline = Date.now() + timeoutMs\n let fd: number | undefined\n while (fd === undefined) {\n try {\n fd = openSync(lock, \"wx\")\n } catch (e: any) {\n if (e?.code !== \"EEXIST\") throw e\n try {\n if (Date.now() - statSync(lock).mtimeMs > staleMs) { unlinkSync(lock); continue }\n } catch { continue }\n if (Date.now() > deadline) throw new Error(`fact ledger is busy (${lock}); try again`)\n sleep(25)\n }\n }\n try {\n return fn()\n } finally {\n closeSync(fd)\n try { unlinkSync(lock) } catch { /* already gone */ }\n }\n}\n"],"mappings":"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","names":["createHash","existsSync","statSync","resolve","closeSync","existsSync","mkdirSync","openSync","readFileSync","renameSync","statSync","unlinkSync","writeFileSync","dirname","LedgerCorruptError","file","cause","readLedger","raw","writeLedger","data","tmp","sleep","ms","withLock","fn","opts","lock","timeoutMs","staleMs","deadline","fd","e","norm","s","factId","subject","attribute","createHash","FactLedger","wikiDir","resolve","existsSync","mtimeMs","statSync","raw","readLedger","e","data","fn","withLock","save","result","writeLedger","id","f","input","opts","now","nowIso","checked","dated","verifiedAt","volatility","isFactClass","classifyAttribute","provenance","out","existing","fact","prior","newer","questionId","raiseContradiction","current","store","QuestionStore","item","factContradictions","q","isStaleFact","isPastTtl","matchFact","content","facts","text","best","attrWords","w","classifyFact"]}
@@ -1,3 +0,0 @@
1
- import{createHash as l}from"crypto";import{existsSync as f,mkdirSync as c,readFileSync as g,writeFileSync as p}from"fs";import{dirname as h,resolve as v}from"path";function m(u,i,s,t){return l("sha1").update([u,i,s.toLowerCase().trim(),t??""].join("\0")).digest("hex").slice(0,12)}var d=class{file;constructor(i){this.file=v(i,"_questions.json")}get path(){return this.file}load(){if(!f(this.file))return{version:1,questions:[]};try{let i=JSON.parse(g(this.file,"utf-8"));return Array.isArray(i?.questions)?{version:1,questions:i.questions}:{version:1,questions:[]}}catch{return{version:1,questions:[]}}}save(i){c(h(this.file),{recursive:!0}),p(this.file,`${JSON.stringify(i,null,2)}
2
- `)}list(i){let s=this.load().questions;return i?s.filter(t=>t.status===i):s}add(i){let s=this.load(),t=new Set(s.questions.map(n=>n.id)),r=0,e=0,o=new Date().toISOString();for(let n of i){let a=m(n.kind,n.agentId,n.subject,n.field);if(t.has(a)){e++;continue}t.add(a),s.questions.push({...n,id:a,status:"open",asked:o}),r++}return r>0&&this.save(s),{added:r,skipped:e}}resolve(i,s,t){let r=this.load(),e=r.questions.find(o=>o.id===i||o.id.startsWith(i));return e?(e.status=s,e.answered=new Date().toISOString(),t!==void 0&&(e.answer=t),this.save(r),e):null}};export{m as a,d as b};
3
- //# sourceMappingURL=chunk-ZXR6GNEK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/wiki/questions.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\n\n// The gaps a lookup cannot close.\n//\n// Two things were producing gap signal and throwing it away. Absorb has\n// always emitted a `gaps` array — entities it referenced but has no\n// article for — and the command printed it and moved on, so the signal\n// lived in a cron log nobody reads. And the field grader can now name a\n// missing `role` or `ourOwner` that no system of record holds.\n//\n// Both are the same thing: a question only a person can answer. They\n// get one queue rather than two, because a second parallel store is\n// precisely what the wiki-as-source-of-truth decision rules out.\n//\n// Stored as JSON beside the other wiki sidecars (_index.json,\n// _backlinks.json), because this is derived state that is rebuilt and\n// rewritten, not knowledge. The knowledge is what lands in the article\n// once the question is answered.\n\nexport type QuestionStatus = \"open\" | \"answered\" | \"dismissed\"\n\nexport interface WikiQuestion {\n id: string\n /** \"field\" — a named gap in an existing article.\n * \"article\" — absorb referenced something with no article at all.\n * \"contradiction\" — a write disagreed with a wiki fact and could not\n * replace it (facts/ledger.ts); the answer is the true value. */\n kind: \"field\" | \"article\" | \"contradiction\"\n agentId: string\n /** Article path for a field question; empty for a missing article. */\n path: string\n /** Article title, or the referenced name for a missing article. */\n subject: string\n /** Required-field key, for `kind: \"field\"`. */\n field?: string\n /** What tier of the build this gap sits at, so asking can be ordered. */\n tier?: string\n question: string\n /** For a contradiction: the wiki fact and the value that was refused. */\n factId?: string\n proposed?: { value: string; source: string; verifiedAt: string; verifiedBy: string }\n status: QuestionStatus\n asked: string\n answered?: string\n answer?: string\n}\n\nexport interface QuestionsFile {\n version: 1\n questions: WikiQuestion[]\n}\n\n/**\n * Stable across runs, so re-grading the same article does not ask the\n * same question twice and cannot resurrect one already dismissed.\n */\nexport function questionId(kind: string, agentId: string, subject: string, field?: string): string {\n return createHash(\"sha1\")\n .update([kind, agentId, subject.toLowerCase().trim(), field ?? \"\"].join(\"\\u0000\"))\n .digest(\"hex\")\n .slice(0, 12)\n}\n\nexport class QuestionStore {\n private readonly file: string\n\n constructor(wikiDir: string) {\n this.file = resolve(wikiDir, \"_questions.json\")\n }\n\n get path(): string { return this.file }\n\n load(): QuestionsFile {\n if (!existsSync(this.file)) return { version: 1, questions: [] }\n try {\n const parsed = JSON.parse(readFileSync(this.file, \"utf-8\")) as Partial<QuestionsFile>\n if (!Array.isArray(parsed?.questions)) return { version: 1, questions: [] }\n return { version: 1, questions: parsed.questions as WikiQuestion[] }\n } catch {\n // A corrupt queue must not stop an absorb. Worst case the\n // questions are asked again; they are idempotent by id.\n return { version: 1, questions: [] }\n }\n }\n\n private save(f: QuestionsFile): void {\n mkdirSync(dirname(this.file), { recursive: true })\n writeFileSync(this.file, `${JSON.stringify(f, null, 2)}\\n`)\n }\n\n list(status?: QuestionStatus): WikiQuestion[] {\n const all = this.load().questions\n return status ? all.filter((q) => q.status === status) : all\n }\n\n /**\n * Add questions that are not already on the queue.\n *\n * An existing id is left completely alone — including an answered or\n * dismissed one. Re-asking something a person has already dealt with\n * is how a queue becomes noise and then becomes ignored.\n */\n add(items: Array<Omit<WikiQuestion, \"id\" | \"status\" | \"asked\">>): { added: number; skipped: number } {\n const f = this.load()\n const seen = new Set(f.questions.map((q) => q.id))\n let added = 0, skipped = 0\n const now = new Date().toISOString()\n\n for (const item of items) {\n const id = questionId(item.kind, item.agentId, item.subject, item.field)\n if (seen.has(id)) { skipped++; continue }\n seen.add(id)\n f.questions.push({ ...item, id, status: \"open\", asked: now })\n added++\n }\n if (added > 0) this.save(f)\n return { added, skipped }\n }\n\n /** Returns the question so the caller can act on its subject/field. */\n resolve(id: string, status: \"answered\" | \"dismissed\", answer?: string): WikiQuestion | null {\n const f = this.load()\n const q = f.questions.find((x) => x.id === id || x.id.startsWith(id))\n if (!q) return null\n q.status = status\n q.answered = new Date().toISOString()\n if (answer !== undefined) q.answer = answer\n this.save(f)\n return q\n }\n}\n"],"mappings":"AAAA,OAAS,cAAAA,MAAkB,SAC3B,OAAS,cAAAC,EAAY,aAAAC,EAAW,gBAAAC,EAAc,iBAAAC,MAAqB,KACnE,OAAS,WAAAC,EAAS,WAAAC,MAAe,OAwD1B,SAASC,EAAWC,EAAcC,EAAiBC,EAAiBC,EAAwB,CACjG,OAAOX,EAAW,MAAM,EACrB,OAAO,CAACQ,EAAMC,EAASC,EAAQ,YAAY,EAAE,KAAK,EAAGC,GAAS,EAAE,EAAE,KAAK,IAAQ,CAAC,EAChF,OAAO,KAAK,EACZ,MAAM,EAAG,EAAE,CAChB,CAEO,IAAMC,EAAN,KAAoB,CACR,KAEjB,YAAYC,EAAiB,CAC3B,KAAK,KAAOP,EAAQO,EAAS,iBAAiB,CAChD,CAEA,IAAI,MAAe,CAAE,OAAO,KAAK,IAAK,CAEtC,MAAsB,CACpB,GAAI,CAACZ,EAAW,KAAK,IAAI,EAAG,MAAO,CAAE,QAAS,EAAG,UAAW,CAAC,CAAE,EAC/D,GAAI,CACF,IAAMa,EAAS,KAAK,MAAMX,EAAa,KAAK,KAAM,OAAO,CAAC,EAC1D,OAAK,MAAM,QAAQW,GAAQ,SAAS,EAC7B,CAAE,QAAS,EAAG,UAAWA,EAAO,SAA4B,EADrB,CAAE,QAAS,EAAG,UAAW,CAAC,CAAE,CAE5E,MAAE,CAGA,MAAO,CAAE,QAAS,EAAG,UAAW,CAAC,CAAE,CACrC,CACF,CAEQ,KAAKC,EAAwB,CACnCb,EAAUG,EAAQ,KAAK,IAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EACjDD,EAAc,KAAK,KAAM,GAAG,KAAK,UAAUW,EAAG,KAAM,CAAC;AAAA,CAAK,CAC5D,CAEA,KAAKC,EAAyC,CAC5C,IAAMC,EAAM,KAAK,KAAK,EAAE,UACxB,OAAOD,EAASC,EAAI,OAAQC,GAAMA,EAAE,SAAWF,CAAM,EAAIC,CAC3D,CASA,IAAIE,EAAiG,CACnG,IAAMJ,EAAI,KAAK,KAAK,EACdK,EAAO,IAAI,IAAIL,EAAE,UAAU,IAAKG,GAAMA,EAAE,EAAE,CAAC,EAC7CG,EAAQ,EAAGC,EAAU,EACnBC,EAAM,IAAI,KAAK,EAAE,YAAY,EAEnC,QAAWC,KAAQL,EAAO,CACxB,IAAMM,EAAKlB,EAAWiB,EAAK,KAAMA,EAAK,QAASA,EAAK,QAASA,EAAK,KAAK,EACvE,GAAIJ,EAAK,IAAIK,CAAE,EAAG,CAAEH,IAAW,SAC/BF,EAAK,IAAIK,CAAE,EACXV,EAAE,UAAU,KAAK,CAAE,GAAGS,EAAM,GAAAC,EAAI,OAAQ,OAAQ,MAAOF,CAAI,CAAC,EAC5DF,IAEF,OAAIA,EAAQ,GAAG,KAAK,KAAKN,CAAC,EACnB,CAAE,MAAAM,EAAO,QAAAC,CAAQ,CAC1B,CAGA,QAAQG,EAAYT,EAAkCU,EAAsC,CAC1F,IAAMX,EAAI,KAAK,KAAK,EACdG,EAAIH,EAAE,UAAU,KAAMY,GAAMA,EAAE,KAAOF,GAAME,EAAE,GAAG,WAAWF,CAAE,CAAC,EACpE,OAAKP,GACLA,EAAE,OAASF,EACXE,EAAE,SAAW,IAAI,KAAK,EAAE,YAAY,EAChCQ,IAAW,SAAWR,EAAE,OAASQ,GACrC,KAAK,KAAKX,CAAC,EACJG,GALQ,IAMjB,CACF","names":["createHash","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","questionId","kind","agentId","subject","field","QuestionStore","wikiDir","parsed","f","status","all","q","items","seen","added","skipped","now","item","id","answer","x"]}
@@ -1,2 +0,0 @@
1
- import{a,b,c,d,e,f}from"./chunk-AU6FFC4I.js";import"./chunk-ST6KPQOY.js";import"./chunk-GQ76J72O.js";import"./chunk-ZXR6GNEK.js";import"./chunk-QIJLFPWE.js";export{d as approveFactProposal,c as listFactProposals,a as parseClaim,b as proposeFacts,e as rejectFactProposal,f as routeMemo};
2
- //# sourceMappingURL=fact-proposals-ERRQXV2J.js.map
@@ -1,2 +0,0 @@
1
- import{a,b,c,d,e}from"./chunk-ST6KPQOY.js";import"./chunk-GQ76J72O.js";import"./chunk-ZXR6GNEK.js";import"./chunk-QIJLFPWE.js";export{b as FactLedger,c as factContradictions,a as factId,d as isStaleFact,e as matchFact};
2
- //# sourceMappingURL=ledger-DHYRP4TN.js.map
@@ -1,2 +0,0 @@
1
- import{a,b}from"./chunk-ZXR6GNEK.js";import"./chunk-QIJLFPWE.js";export{b as QuestionStore,a as questionId};
2
- //# sourceMappingURL=questions-QGITDLJY.js.map