fuli-context 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +0 -24
  2. package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +1 -0
  3. package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +1 -0
  4. package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +1 -0
  5. package/dist/web/assets/KnowledgeOrganizerPage-CJCNI2Wd.css +1 -0
  6. package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.js +1 -0
  7. package/dist/web/assets/KnowledgePage-CNQXd15y.js +1 -0
  8. package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +4 -0
  9. package/dist/web/assets/OverviewPage-ul7g7HXh.js +1 -0
  10. package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +1 -0
  11. package/dist/web/assets/PersonalProfilePage-S8ZcG2LU.css +1 -0
  12. package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +1 -0
  13. package/dist/web/assets/PublicProjectsPage-C700sR7z.js +1 -0
  14. package/dist/web/assets/ReviewPage-7MDDUDgA.js +1 -0
  15. package/dist/web/assets/console-B_Ena2DS.js +5 -0
  16. package/dist/web/assets/identity-CjBwYSMX.js +1 -0
  17. package/dist/web/assets/index-BQRkOwPx.js +2 -0
  18. package/dist/web/assets/{index-Devf7glZ.css → index-CpLDx3of.css} +1 -1
  19. package/dist/web/assets/model-C-5RVIJ2.js +1 -0
  20. package/dist/web/assets/{paths-B790iniO.js → paths-BkULqmGI.js} +1 -1
  21. package/dist/web/assets/{vue-router-BYTAoWT3.js → vue-router-DnD3hUm7.js} +1 -1
  22. package/dist/web/index.html +5 -6
  23. package/graph-provider/fuli_graph/app.py +48 -0
  24. package/graph-provider/fuli_graph/common_knowledge.py +443 -0
  25. package/graph-provider/fuli_graph/common_knowledge_models.py +54 -0
  26. package/graph-provider/fuli_graph/graph_models.py +9 -0
  27. package/graph-provider/fuli_graph/graph_query.py +25 -0
  28. package/graph-provider/fuli_graph/knowledge_audit.py +211 -0
  29. package/graph-provider/fuli_graph/knowledge_feedback_models.py +69 -0
  30. package/graph-provider/fuli_graph/knowledge_management.py +37 -0
  31. package/graph-provider/fuli_graph/knowledge_search.py +24 -2
  32. package/graph-provider/fuli_graph/models.py +69 -0
  33. package/graph-provider/fuli_graph/search_projection.py +59 -0
  34. package/graph-provider/fuli_graph/store_knowledge.py +2 -0
  35. package/graph-provider/pyproject.toml +1 -1
  36. package/npm-shrinkwrap.json +97 -2
  37. package/package.json +2 -1
  38. package/skills/capturing-session-knowledge/SKILL.md +83 -13
  39. package/src/agent-tools/graph-definitions.js +181 -13
  40. package/src/agent-tools/graph-handlers.js +19 -0
  41. package/src/cli/command-registry.js +1 -56
  42. package/src/cli.js +2 -37
  43. package/src/graphiti/agent-knowledge-workflows.js +543 -0
  44. package/src/graphiti/federated-application.js +85 -104
  45. package/src/graphiti/knowledge-provider-mapping.js +91 -0
  46. package/src/graphiti/provider-client.js +15 -0
  47. package/src/graphiti/source-uri.js +29 -0
  48. package/src/mcp/common-knowledge-preview-tokens.js +67 -0
  49. package/src/mcp/create-mcp-server.js +43 -7
  50. package/src/mcp/instructions.js +6 -7
  51. package/src/mcp/lifecycle-audit.js +27 -0
  52. package/src/mcp/task-context-registry.js +93 -0
  53. package/src/mcp/tool-annotations.js +16 -1
  54. package/src/mcp/tool-result.js +24 -0
  55. package/src/runtime-options.js +7 -8
  56. package/src/setup/agent-installation-status.js +14 -2
  57. package/src/setup/agents.js +12 -1
  58. package/src/setup/claude-code-config.js +194 -0
  59. package/src/setup/config-backup.js +20 -5
  60. package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +0 -1
  61. package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +0 -1
  62. package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +0 -1
  63. package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +0 -1
  64. package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +0 -1
  65. package/dist/web/assets/KnowledgePage-Khuo3cNs.js +0 -1
  66. package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +0 -4
  67. package/dist/web/assets/OverviewPage-DC4C4Fhn.js +0 -1
  68. package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +0 -1
  69. package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +0 -1
  70. package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +0 -1
  71. package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +0 -1
  72. package/dist/web/assets/ReviewPage-Dztk2jDG.js +0 -1
  73. package/dist/web/assets/console-tbvTUi6p.js +0 -1
  74. package/dist/web/assets/identity-B3s5CXhv.js +0 -1
  75. package/dist/web/assets/index-DwUDhQKc.js +0 -6
  76. package/dist/web/assets/model-DVT8sjy9.js +0 -1
  77. package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +0 -1
  78. package/src/cli/commands/candidates.js +0 -17
  79. package/src/cli/commands/ingestion.js +0 -32
  80. package/src/cli/commands/reads.js +0 -50
  81. package/src/cli/commands/spaces.js +0 -20
  82. package/src/cli/invocation.js +0 -47
  83. package/src/cli/migrate-command.js +0 -71
  84. package/src/cli/migration-destination.js +0 -117
  85. package/src/storage/import-json.js +0 -70
  86. package/src/storage/legacy-record-validation.js +0 -152
  87. package/src/storage/legacy-snapshot-validation.js +0 -32
@@ -1 +0,0 @@
1
- var e=new Set([`ProjectSpace`,`PersonalSpace`,`PersonalProject`,`ProjectPurpose`,`ProjectScope`,`ProjectSource`,`ProjectBoundary`,`ProjectAssessment`,`AssessmentDimension`,`RelatedPersonalProject`]),t={ProjectSpace:`项目空间`,PersonalSpace:`个人空间`,PersonalProject:`项目档案`,ProjectPurpose:`项目目标`,ProjectScope:`项目范围`,TechnicalSummary:`技术说明`,ProjectSource:`资料来源`,ProjectBoundary:`项目边界`,ProjectAssessment:`档案评估`,AssessmentDimension:`评估维度`,RelatedPersonalProject:`关联项目`},n={known_known:`已知的已知`,known_unknown:`已知的未知`,unknown_known:`未知的已知`,unknown_unknown:`未知的未知`,unclassified:`待分类`},r={known_known:`被明确表达出来的知识或结论。`,known_unknown:`被明确提出、但仍在等待答案的问题。`,unknown_known:`从行为、案例、反馈或反应中提炼出的隐性知识。`,unknown_unknown:`在探索过程中发现、仍需判断的潜在盲点。`,unclassified:`旧内容还没有明确记录发现时所属象限。`},i={confirmed:`已确认`,agent_confirmed:`Agent 已确认`,pending:`待确认`},a={user:`用户`,agent:`Agent`,authoritative_source:`权威来源`,import:`导入记录`},o={taste:`品味`,personality:`个性`,judgment_preference:`判断偏好`},s={confirm:`确认`,update:`内容纠正`,invalidate:`标记失效`,link_replacement:`补充替代关联`,restore:`恢复有效`,scope_change:`调整生效范围`,batch_confirm:`批量确认`},c={none:`没有人工变更`,unseen:`人工改动 · Agent 未查看`,viewed:`Agent 已查看 · 待审核`,reviewed:`Agent 已审核`},l={human_change:`人工修改`,agent_view:`Agent 查看`,agent_review:`Agent 审核`,knowledge_used:`Agent 实际使用`};function u(e){return n[e??`unclassified`]??e??n.unclassified}function d(e){return r[e??`unclassified`]??`这是一个自定义认知阶段;请结合形成过程与来源判断。`}function f(e){return o[e??``]??e??``}function p(e){return s[e??``]??e??`修订`}function m(e){return c[e??`none`]}function h(e){return l[e??``]??e??`审计记录`}function g(e){return e?e.label||a[e.kind]||e.kind:`尚未记录`}function _(e){return typeof e==`object`&&e?e.id:e}function v(e){let t=e.nodes.filter(e=>!e.invalid_at),n=new Set(t.map(({id:e})=>e)),r=e.edges.filter(e=>!e.invalid_at&&n.has(_(e.source))&&n.has(_(e.target)));return{...e,nodes:t,edges:r}}function y(e){if(!e)return[];let t=new Map(e.nodes.map(e=>[e.id,e.name])),n=e.nodes.filter(e=>!E(e)).map(e=>x(e)),r=e.edges.filter(e=>!D(e)).map(e=>S(e,t));return[...n,...r].sort((e,t)=>q(t)-q(e))}function b(e){if(!e)return[];let t=new Map(e.nodes.map(e=>[e.id,e.name])),n=e.nodes.filter(E).map(e=>x(e)),r=e.edges.filter(D).map(e=>S(e,t));return[...n,...r]}function x(e){return{id:e.id,itemKind:`entity`,title:e.name,body:e.summary||`没有补充说明`,type:e.type,...W(e),raw:e}}function S(e,t){return{id:e.id,itemKind:`relationship`,title:`${t.get(_(e.source))??`未知实体`} → ${t.get(_(e.target))??`未知实体`}`,body:e.fact||`没有关系说明`,type:`关系 · ${e.type}`,...W(e),raw:e}}function C(t){return t?e.has(t.type)||O(t.id):!1}function w(e){return e.itemKind===`relationship`?`项目资料关系`:t[e.type]??e.type}function T(e){if(!e||e.itemKind!==`entity`)return null;let t=e.raw.attributes?.projectId;return typeof t==`string`&&t?t:null}function E(t){return e.has(t.type)||O(t.id)}function D(e){return O(e.id)}function O(e){return String(e).startsWith(`project-profile:`)||String(e).startsWith(`project-profile-edge:`)||String(e).startsWith(`space-edge:`)||String(e).startsWith(`personal-project-relation:`)}function k(e,t){let n=t.query?.trim().toLocaleLowerCase()??``;return e.filter(e=>t.type&&t.type!==`all`&&e.type!==t.type||t.quadrant&&t.quadrant!==`all`&&e.originQuadrant!==t.quadrant||t.profile===`profile`&&!e.profileAspect||t.profile===`regular`&&e.profileAspect||t.status===`current`&&e.invalidAt||t.status===`historical`&&!e.invalidAt||t.humanChange===`human_changed`&&!e.humanEdited||t.humanChange&&![`all`,`human_changed`].includes(t.humanChange)&&e.humanChangeStatus!==t.humanChange?!1:!n||K(e).toLocaleLowerCase().includes(n))}function A(e){let t=y(e).filter(({profileAspect:e})=>e),n=new Set(t.filter(({itemKind:e})=>e===`relationship`).flatMap(({raw:e})=>{let t=e;return[_(t.source),_(t.target)]}));return t.filter(({id:e,itemKind:t})=>t===`relationship`||!n.has(e))}function j(e){let t=e.filter(e=>!!e);return{...t[0]??{space_id:null},nodes:J(t.flatMap(e=>e.nodes)),edges:J(t.flatMap(e=>e.edges)),truncated:t.some(({truncated:e})=>e)}}function M(e){return e.revisions.at(0)?.created_at??e.createdAt??e.evidence.at(0)?.created_at??e.evidence.at(0)?.reference_time??null}function N(e){if(!e)return`未记录`;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString(`zh-CN`)}function P(e){let t=e.confirmationBasis;return!e.confirmationExplicit||!t?.confirmed_by||!t.confirmed_at?`pending`:e.confirmationStatus===`confirmed`&&[`user`,`authoritative_source`].includes(t.confirmed_by.kind)?`confirmed`:e.confirmationStatus===`agent_confirmed`&&t.confirmed_by.kind===`agent`&&t.agent_policy_version?`agent_confirmed`:`pending`}function F(e){return i[P(e)]}function I(e){let t=new Map,n=e.filter(e=>!e.invalidAt&&e.classificationExplicit&&P(e)!==`confirmed`);for(let e of n)for(let n of e.evidence)n.id&&R(t,{kind:`source`,value:n.id,label:z(n),description:n.source_description||n.summary||`同一条来源记录`},e),n.session_id&&R(t,{kind:`session`,value:n.session_id,label:B(n),description:n.source_description||n.summary||`同一次来源会话`},e);return[...t.values()].filter(({items:e})=>e.length>=2).sort((e,t)=>t.items.length-e.items.length||e.label.localeCompare(t.label,`zh-CN`))}function L(e,t){let n=e.evidence.find(e=>t.kind===`source`?e.id===t.value:e.session_id===t.value),r=e.confirmationBasis;return{existenceReason:r?.existence_reason||n?.source_description||n?.summary||`该内容由来源“${t.label}”支持。`,quadrantReason:r?.quadrant_reason||e.reasoningSummary||`该内容在发现时符合“${u(e.originQuadrant)}”:${d(e.originQuadrant)}`,proposedBy:r?.proposed_by||V(n)}}function R(e,t,n){let r=`${t.kind}:${t.value}`,i=e.get(r)??{key:r,...t,items:[]},a=`${n.itemKind}:${n.id}`;i.items.some(e=>`${e.itemKind}:${e.id}`===a)||i.items.push(n),e.set(r,i)}function z(e){return e.name||e.source_description||e.summary||`来源记录`}function B(e){let t={codex:`Codex`,claude_code:`Claude Code`,cursor:`Cursor`,kiro:`Kiro`,other:`其他来源`}[e.source_application??``];return t?`${t} · ${e.name||e.source_kind||`会话`}`:e.name||e.source_kind||`来源会话`}function V(e){let t={codex:`Codex`,claude_code:`Claude Code`,cursor:`Cursor`,kiro:`Kiro`,other:`其他 Agent`}[e?.source_application??``];return t?{kind:`agent`,label:t}:{kind:`import`,label:`历史记录`}}function H(e){if(!e.classificationExplicit)return`这条旧内容没有显式记录发现时象限;保存前需要人工补充,系统不会自动归入“已知的已知”。`;let t=e.confirmationBasis;return P(e)===`confirmed`&&t?`${g(t.confirmed_by)}于 ${N(t.confirmed_at)} 确认;本次确认同时覆盖知识内容和象限归类。`:P(e)===`agent_confirmed`&&t?`这条内容因跨任务实际使用达到策略阈值,于 ${N(t.confirmed_at)} 标记为 Agent 已确认;它仍低于人工或权威来源确认。`:t?`${g(t.proposed_by)}提出了这条内容,当前仍等待确认;来源证据不会自动升级为确认。`:`旧数据没有结构化的确认人和确认时间,已统一列入“待确认”,不会继续显示为已确认。`}function U(e){let t=e.confirmationBasis;return t?P(e)===`confirmed`?`${g(t.confirmed_by)} · ${N(t.confirmed_at)}`:P(e)===`agent_confirmed`?`Agent 使用策略 · ${N(t.confirmed_at)}`:`${g(t.proposed_by)}提出 · ${t.existence_reason}`:`旧数据 · 确认人和时间缺失`}function W(e){let t=e.epistemic_state_explicit===!0,n=e.confirmation_state_explicit===!0;return{originQuadrant:t?e.origin_quadrant??`known_known`:`unclassified`,currentQuadrant:t?e.current_quadrant??`known_known`:`unclassified`,epistemicStatus:t?e.epistemic_status??`confirmed`:`unreviewed`,classificationExplicit:t,confirmationStatus:G(e.confirmation_status),confirmationExplicit:n,confirmationBasis:e.confirmation_basis??null,reasoningSummary:e.reasoning_summary??``,profileAspect:e.profile_aspect??null,preferenceScope:e.preference_scope??(e.profile_aspect?`global`:null),preferenceProjectId:e.preference_project_id??null,inheritanceMode:e.inheritance_mode??`local_only`,inheritedProjectIds:e.inherited_project_ids??[],humanEdited:e.human_edited===!0,humanChangeStatus:e.human_change_status??`none`,humanChangeVersion:e.human_change_version??0,lastHumanChangedAt:e.last_human_changed_at??null,lastAgentViewedAt:e.last_agent_viewed_at??null,lastAgentReviewedAt:e.last_agent_reviewed_at??null,utilityScore:e.utility_score??0,confidenceScore:e.confidence_score??.5,qualifiedUseCount:e.qualified_use_count??0,distinctTaskCount:e.distinct_task_count??0,lastUsedAt:e.last_used_at??null,usageGeneration:e.usage_generation??1,invalidAt:e.invalid_at,replacedByItemId:e.replaced_by_item_id??null,replacedByItemKind:e.replaced_by_item_kind??null,createdAt:e.created_at,evidence:e.evidence??[],revisions:e.revisions??[],assignments:e.assignments??[],projectReferences:e.project_references??[],conflicts:e.conflicts??[],auditEvents:e.audit_events??[]}}function G(e){return e===`confirmed`||e===`agent_confirmed`?e:`pending`}function K(e){return[e.id,e.title,e.body,e.type,u(e.originQuadrant),f(e.profileAspect),e.reasoningSummary,e.confirmationBasis?.existence_reason,e.confirmationBasis?.quadrant_reason,g(e.confirmationBasis?.proposed_by),g(e.confirmationBasis?.confirmed_by),m(e.humanChangeStatus),...e.auditEvents.flatMap(e=>[h(e.action),e.reason,e.tool_name,e.conflict_check,e.classification_check,e.outcome]),...e.evidence.flatMap(e=>[e.name,e.summary,e.source_description,e.session_id,e.source_excerpt])].filter(Boolean).join(` `)}function q(e){let t=M(e),n=t?new Date(t).getTime():0;return Number.isNaN(n)?0:n}function J(e){let t=new Map;for(let n of e){let e=t.get(n.id);t.set(n.id,e?Y(e,n):n)}return[...t.values()]}function Y(e,t){let n={...e,...t};for(let r of[`evidence`,`revisions`,`assignments`,`project_references`,`conflicts`,`audit_events`,`episodes`]){let i=e[r],a=t[r];!i&&!a||(n[r]=X([...i??[],...a??[]]))}return n}function X(e){let t=new Set;return e.filter(e=>{let n=typeof e==`object`&&e?e.id??JSON.stringify(e):e;return t.has(n)?!1:(t.add(n),!0)})}export{w as C,p as D,F as E,f as S,u as T,M as _,U as a,A as b,k as c,C as d,h as f,P as g,y as h,g as i,N as l,x as m,I as n,v as o,S as p,H as r,_ as s,L as t,m as u,b as v,d as w,T as x,j as y};
@@ -1 +0,0 @@
1
- import{$ as e,B as t,G as n,H as r,J as i,K as a,Q as o,R as s,S as c,U as l,V as u,W as d,X as f,Y as p,Z as ee,et as m,h as te,l as h,q as ne,z as g}from"./console-tbvTUi6p.js";var _=void 0,v=typeof window<`u`&&window.trustedTypes;if(v)try{_=v.createPolicy(`vue`,{createHTML:e=>e})}catch{}var y=_?e=>_.createHTML(e):e=>e,re=`http://www.w3.org/2000/svg`,ie=`http://www.w3.org/1998/Math/MathML`,b=typeof document<`u`?document:null,x=b&&b.createElement(`template`),S={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{let t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{let i=t===`svg`?b.createElementNS(re,e):t===`mathml`?b.createElementNS(ie,e):n?b.createElement(e,{is:n}):b.createElement(e);return e===`select`&&r&&r.multiple!=null&&i.setAttribute(`multiple`,r.multiple),i},createText:e=>b.createTextNode(e),createComment:e=>b.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>b.querySelector(e),setScopeId(e,t){e.setAttribute(t,``)},insertStaticContent(e,t,n,r,i,a){let o=n?n.previousSibling:t.lastChild;if(i&&(i===a||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),!(i===a||!(i=i.nextSibling)););else{x.innerHTML=y(r===`svg`?`<svg>${e}</svg>`:r===`mathml`?`<math>${e}</math>`:e);let i=x.content;if(r===`svg`||r===`mathml`){let e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}t.insertBefore(i,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},C=Symbol(`_vtc`);function w(e,t,n){let r=e[C];r&&(t=(t?[t,...r]:[...r]).join(` `)),t==null?e.removeAttribute(`class`):n?e.setAttribute(`class`,t):e.className=t}var T=Symbol(`_vod`),ae=Symbol(`_vsh`),oe=Symbol(``),se=/(?:^|;)\s*display\s*:/;function ce(e,t,n){let r=e.style,i=f(n),a=!1;if(n&&!i){if(t)if(f(t))for(let e of t.split(`;`)){let t=e.slice(0,e.indexOf(`:`)).trim();n[t]??D(r,t,``)}else for(let e in t)n[e]??D(r,e,``);for(let i in n){i===`display`&&(a=!0);let o=n[i];o==null?D(r,i,``):ue(e,i,!f(t)&&t?t[i]:void 0,o)||D(r,i,o)}}else if(i){if(t!==n){let e=r[oe];e&&(n+=`;`+e),r.cssText=n,a=se.test(n)}}else t&&e.removeAttribute(`style`);T in e&&(e[T]=a?r.display:``,e[ae]&&(r.display=`none`))}var E=/\s*!important$/;function D(e,t,n){if(d(n))n.forEach(n=>D(e,t,n));else if(n??=``,t.startsWith(`--`))e.setProperty(t,n);else{let r=le(e,t);E.test(n)?e.setProperty(u(r),n.replace(E,``),`important`):e[r]=n}}var O=[`Webkit`,`Moz`,`ms`],k={};function le(e,t){let n=k[t];if(n)return n;let r=s(t);if(r!==`filter`&&r in e)return k[t]=r;r=g(r);for(let n=0;n<O.length;n++){let i=O[n]+r;if(i in e)return k[t]=i}return t}function ue(e,t,n,r){return e.tagName===`TEXTAREA`&&(t===`width`||t===`height`)&&f(r)&&n===r}var A=`http://www.w3.org/1999/xlink`;function j(e,t,n,i,a,o=p(t)){i&&t.startsWith(`xlink:`)?n==null?e.removeAttributeNS(A,t.slice(6,t.length)):e.setAttributeNS(A,t,n):n==null||o&&!r(n)?e.removeAttribute(t):e.setAttribute(t,o?``:ee(n)?String(n):n)}function M(e,t,n,i,a){if(t===`innerHTML`||t===`textContent`){n!=null&&(e[t]=t===`innerHTML`?y(n):n);return}let o=e.tagName;if(t===`value`&&o!==`PROGRESS`&&!o.includes(`-`)){let r=o===`OPTION`?e.getAttribute(`value`)||``:e.value,i=n==null?e.type===`checkbox`?`on`:``:String(n);(r!==i||!(`_value`in e))&&(e.value=i),n??e.removeAttribute(t),e._value=n;return}let s=!1;if(n===``||n==null){let i=typeof e[t];i===`boolean`?n=r(n):n==null&&i===`string`?(n=``,s=!0):i===`number`&&(n=0,s=!0)}try{e[t]=n}catch{}s&&e.removeAttribute(a||t)}function N(e,t,n,r){e.addEventListener(t,n,r)}function P(e,t,n,r){e.removeEventListener(t,n,r)}var F=Symbol(`_vei`);function I(e,t,n,r,i=null){let a=e[F]||(e[F]={}),o=a[t];if(r&&o)o.value=r;else{let[n,s]=z(t);r?N(e,n,a[t]=U(r,i),s):o&&(P(e,n,o,s),a[t]=void 0)}}var L=/(Once|Passive|Capture)$/,R=/^on:?(?:Once|Passive|Capture)$/;function z(e){let t,n;for(;(n=e.match(L))&&!R.test(e);)t||={},e=e.slice(0,e.length-n[1].length),t[n[1].toLowerCase()]=!0;return[e[2]===`:`?e.slice(3):u(e.slice(2)),t]}var B=0,V=Promise.resolve(),H=()=>B||=(V.then(()=>B=0),Date.now());function U(e,t){let n=e=>{if(!e._vts)e._vts=Date.now();else if(e._vts<=n.attached)return;let r=n.value;if(d(r)){let n=e.stopImmediatePropagation;e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0};let i=r.slice(),a=[e];for(let n=0;n<i.length&&!e._stopped;n++){let e=i[n];e&&h(e,t,5,a)}}else h(r,t,5,[e])};return n.value=e,n.attached=H(),n}var W=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,de=(e,t,n,r,i,o)=>{let c=i===`svg`;t===`class`?w(e,r,c):t===`style`?ce(e,n,r):ne(t)?a(t)||I(e,t,n,r,o):(t[0]===`.`?(t=t.slice(1),!0):t[0]===`^`?(t=t.slice(1),!1):fe(e,t,r,c))?(M(e,t,r),!e.tagName.includes(`-`)&&(t===`value`||t===`checked`||t===`selected`)&&j(e,t,r,c,o,t!==`value`)):e._isVueCE&&(pe(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!f(r)))?M(e,s(t),r,o,t):(t===`true-value`?e._trueValue=r:t===`false-value`&&(e._falseValue=r),j(e,t,r,c))};function fe(e,t,r,i){if(i)return!!(t===`innerHTML`||t===`textContent`||t in e&&W(t)&&n(r));if(t===`spellcheck`||t===`draggable`||t===`translate`||t===`autocorrect`||t===`sandbox`&&e.tagName===`IFRAME`||t===`form`||t===`list`&&e.tagName===`INPUT`||t===`type`&&e.tagName===`TEXTAREA`)return!1;if(t===`width`||t===`height`){let t=e.tagName;if(t===`IMG`||t===`VIDEO`||t===`CANVAS`||t===`SOURCE`)return!1}return W(t)&&f(r)?!1:t in e}function pe(e,t){let n=e._def.props;if(!n)return!1;let r=s(t);return Array.isArray(n)?n.some(e=>s(e)===r):Object.keys(n).some(e=>s(e)===r)}var G=e=>{let t=e.props[`onUpdate:modelValue`]||!1;return d(t)?e=>l(t,e):t};function me(e){e.target.composing=!0}function K(e){let t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event(`input`)))}var q=Symbol(`_assign`);function J(e,t,n){return t&&(e=e.trim()),n&&(e=m(e)),e}var he={created(e,{modifiers:{lazy:t,trim:n,number:r}},i){e[q]=G(i);let a=r||i.props&&i.props.type===`number`;N(e,t?`change`:`input`,t=>{t.target.composing||e[q](J(e.value,n,a))}),(n||a)&&N(e,`change`,()=>{e.value=J(e.value,n,a)}),t||(N(e,`compositionstart`,me),N(e,`compositionend`,K),N(e,`change`,K))},mounted(e,{value:t}){e.value=t??``},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:i,number:a}},o){if(e[q]=G(o),e.composing)return;let s=(a||e.type===`number`)&&!/^0\d/.test(e.value)?m(e.value):e.value,c=t??``;if(s===c)return;let l=e.getRootNode();(l instanceof Document||l instanceof ShadowRoot)&&l.activeElement===e&&e.type!==`range`&&(r&&t===n||i&&e.value.trim()===c)||(e.value=c)}},ge={deep:!0,created(t,n,r){t[q]=G(r),N(t,`change`,()=>{let n=t._modelValue,r=Z(t),a=t.checked,o=t[q];if(d(n)){let t=e(n,r),i=t!==-1;if(a&&!i)o(n.concat(r));else if(!a&&i){let e=[...n];e.splice(t,1),o(e)}}else if(i(n)){let e=new Set(n);a?e.add(r):e.delete(r),o(e)}else o(Q(t,a))})},mounted:Y,beforeUpdate(e,t,n){e[q]=G(n),Y(e,t,n)}};function Y(t,{value:n,oldValue:r},a){t._modelValue=n;let s;if(d(n))s=e(n,a.props.value)>-1;else if(i(n))s=n.has(a.props.value);else{if(n===r)return;s=o(n,Q(t,!0))}t.checked!==s&&(t.checked=s)}var _e={created(e,{value:t},n){e.checked=o(t,n.props.value),e[q]=G(n),N(e,`change`,()=>{e[q](Z(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[q]=G(r),t!==n&&(e.checked=o(t,r.props.value))}},ve={deep:!0,created(e,{value:t,modifiers:{number:n}},r){e._modelValue=t,N(e,`change`,()=>{let t=Array.prototype.filter.call(e.options,e=>e.selected).map(e=>n?m(Z(e)):Z(e));e[q](e.multiple?i(e._modelValue)?new Set(t):t:t[0]),e._assigning=!0,c(()=>{e._assigning=!1})}),e[q]=G(r)},mounted(e,{value:t}){X(e,t)},beforeUpdate(e,{value:t},n){e._modelValue=t,e[q]=G(n)},updated(e,{value:t}){e._assigning||X(e,t)}};function X(t,n){let r=t.multiple,a=d(n);if(!(r&&!a&&!i(n))){for(let i=0,s=t.options.length;i<s;i++){let s=t.options[i],c=Z(s);if(r)if(a){let t=typeof c;t===`string`||t===`number`?s.selected=n.some(e=>String(e)===String(c)):s.selected=e(n,c)>-1}else s.selected=n.has(c);else if(o(Z(s),n)){t.selectedIndex!==i&&(t.selectedIndex=i);return}}!r&&t.selectedIndex!==-1&&(t.selectedIndex=-1)}}function Z(e){return`_value`in e?e._value:e.value}function Q(e,t){let n=t?`_trueValue`:`_falseValue`;return n in e?e[n]:t}var ye=[`ctrl`,`shift`,`alt`,`meta`],be={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>`button`in e&&e.button!==0,middle:e=>`button`in e&&e.button!==1,right:e=>`button`in e&&e.button!==2,exact:(e,t)=>ye.some(n=>e[`${n}Key`]&&!t.includes(n))},xe=(e,t)=>{if(!e)return e;let n=e._withMods||={},r=t.join(`.`);return n[r]||(n[r]=((n,...r)=>{for(let e=0;e<t.length;e++){let r=be[t[e]];if(r&&r(n,t))return}return e(n,...r)}))},Se=t({patchProp:de},S),Ce;function $(){return Ce||=te(Se)}var we=((...e)=>{let t=$().createApp(...e),{mount:r}=t;return t.mount=e=>{let i=Ee(e);if(!i)return;let a=t._component;!n(a)&&!a.render&&!a.template&&(a.template=i.innerHTML),i.nodeType===1&&(i.textContent=``);let o=r(i,!1,Te(i));return i instanceof Element&&(i.removeAttribute(`v-cloak`),i.setAttribute(`data-v-app`,``)),o},t});function Te(e){if(e instanceof SVGElement)return`svg`;if(typeof MathMLElement==`function`&&e instanceof MathMLElement)return`mathml`}function Ee(e){return f(e)?document.querySelector(e):e}export{he as a,ve as i,ge as n,xe as o,_e as r,we as t};
@@ -1,17 +0,0 @@
1
- import { requireSpace } from '../command-arguments.js';
2
-
3
- export function candidates(app, args) {
4
- const personal = requireSpace(app, args[0]);
5
- const pending = app.agent.listCandidates(personal.id);
6
- console.log(pending.map((candidate) => `${candidate.id} ${candidate.reason ?? ''}`.trim())
7
- .join('\n'));
8
- }
9
-
10
- export function candidate(app, args) {
11
- const [candidateId, decision] = args;
12
- if (!candidateId || !decision) {
13
- throw new Error('Usage: candidate CANDIDATE_ID sync|personal_only|ignore');
14
- }
15
- const result = app.decideCandidate(candidateId, decision);
16
- console.log(`candidate ${result.status} ${result.id}`);
17
- }
@@ -1,32 +0,0 @@
1
- import { option, requireSpace } from '../command-arguments.js';
2
-
3
- export function remember(app, args) {
4
- const [personalName, ...rest] = args;
5
- const personal = requireSpace(app, personalName);
6
- const targetName = option(rest, '--target');
7
- const target = targetName ? requireSpace(app, targetName) : null;
8
- const body = option(rest, '--text');
9
- if (!body) throw new Error('remember requires --text');
10
-
11
- const result = app.remember({
12
- personalSpaceId: personal.id,
13
- targetSpaceId: target?.id ?? null,
14
- sourceKind: option(rest, '--source-kind') ?? 'agent',
15
- body
16
- });
17
- console.log(`remembered ${result.route}`);
18
- }
19
-
20
- export function observe(app, args) {
21
- const [personalName, ...rest] = args;
22
- const personal = requireSpace(app, personalName);
23
- const targetName = option(rest, '--target');
24
- const target = targetName ? requireSpace(app, targetName) : null;
25
- const result = app.observe({
26
- personalSpaceId: personal.id,
27
- targetSpaceId: target?.id ?? null,
28
- cwd: option(rest, '--cwd') ?? process.cwd()
29
- });
30
- const suffix = result.observed.length === 1 ? 'change' : 'changes';
31
- console.log(`observed ${result.observed.length} ${suffix}`);
32
- }
@@ -1,50 +0,0 @@
1
- import { formatContextPack } from '../../context-pack.js';
2
- import { humanPredicate, requireSpace } from '../command-arguments.js';
3
-
4
- export function search(app, args) {
5
- const [personalName, ...queryParts] = args;
6
- const personal = requireSpace(app, personalName);
7
- console.log(app.search({
8
- personalSpaceId: personal.id,
9
- query: queryParts.join(' ')
10
- }).answer);
11
- }
12
-
13
- export function timeline(app, args) {
14
- const [spaceName, ...subjectParts] = args;
15
- const space = requireSpace(app, spaceName);
16
- const facts = app.agent.timeline(space.id, subjectParts.join(' ') || space.name);
17
- console.log(facts.map((fact) =>
18
- `${fact.validAt} ${fact.predicate} ${fact.object}`).join('\n'));
19
- }
20
-
21
- export function rules(app, args) {
22
- const space = requireSpace(app, args[0]);
23
- const result = app.agent.projectRules(space.id);
24
- const lines = [
25
- ...result.forbidden.map((fact) => `forbids ${fact.object}`),
26
- ...result.parameters.map((fact) => `${humanPredicate(fact.predicate)} ${fact.object}`),
27
- ...result.links.map((fact) => `url ${fact.object}`)
28
- ];
29
- console.log(lines.join('\n'));
30
- }
31
-
32
- export function history(app, args) {
33
- const [spaceName, predicate] = args;
34
- const space = requireSpace(app, spaceName);
35
- const result = app.agent.factHistory({ spaceId: space.id, predicate });
36
- console.log(result.facts.map((fact) =>
37
- `${fact.current ? 'current' : 'historical'} ` +
38
- `${humanPredicate(fact.predicate)} ${fact.object}`).join('\n'));
39
- }
40
-
41
- export function context(app, args) {
42
- const [personalName, spaceName, ...queryParts] = args;
43
- const personal = requireSpace(app, personalName);
44
- const space = requireSpace(app, spaceName);
45
- console.log(formatContextPack(app.agent.contextPack({
46
- personalSpaceId: personal.id,
47
- spaceId: space.id,
48
- query: queryParts.join(' ')
49
- })));
50
- }
@@ -1,20 +0,0 @@
1
- import { SpaceKind } from '../../models.js';
2
- import { option, requireSpace } from '../command-arguments.js';
3
-
4
- export function createSpace(app, args) {
5
- const [subcommand, name, ...rest] = args;
6
- if (subcommand !== 'create') {
7
- throw new Error('Usage: space create NAME --kind personal|public');
8
- }
9
- const kind = option(rest, '--kind') ?? SpaceKind.PUBLIC;
10
- const space = app.createSpace(name, kind);
11
- console.log(`${space.name} ${space.kind} ${space.id}`);
12
- }
13
-
14
- export function subscribe(app, args) {
15
- const [personalName, spaceName] = args;
16
- const personal = requireSpace(app, personalName);
17
- const space = requireSpace(app, spaceName);
18
- app.subscribe(personal.id, space.id);
19
- console.log(`${personal.name} subscribed ${space.name}`);
20
- }
@@ -1,47 +0,0 @@
1
- import { isCliCommand } from './command-registry.js';
2
-
3
- const RUNTIME_FLAGS = new Set(['--db', '--personal-space']);
4
-
5
- export function parseCliInvocation(argv = []) {
6
- const { runtimeArgs, commandArgs } = splitRuntimePrefix(argv);
7
- const [command, ...args] = commandArgs;
8
- return { runtimeArgs, command, args };
9
- }
10
-
11
- export function splitRuntimePrefix(argv = []) {
12
- const runtimeArgs = [];
13
- let index = 0;
14
- while (index < argv.length && RUNTIME_FLAGS.has(argv[index])) {
15
- const flag = argv[index];
16
- const value = argv[index + 1];
17
- if (!isRuntimeValue(value, argv, index + 2)) {
18
- throw new TypeError(`Missing value for ${flag}`);
19
- }
20
- runtimeArgs.push(flag, value);
21
- index += 2;
22
- }
23
- return { runtimeArgs, commandArgs: argv.slice(index) };
24
- }
25
-
26
- function isRuntimeValue(value, argv, nextIndex) {
27
- if (!isPlainValue(value)) return false;
28
- return !isCommandBoundary(value) || hasCommandBoundary(argv, nextIndex);
29
- }
30
-
31
- function hasCommandBoundary(argv, startIndex) {
32
- let index = startIndex;
33
- while (RUNTIME_FLAGS.has(argv[index])) {
34
- if (!isPlainValue(argv[index + 1])) return false;
35
- index += 2;
36
- }
37
- return isCommandBoundary(argv[index]);
38
- }
39
-
40
- function isPlainValue(value) {
41
- return typeof value === 'string' && Boolean(value.trim()) &&
42
- !value.startsWith('--') && value !== '-h';
43
- }
44
-
45
- function isCommandBoundary(value) {
46
- return isCliCommand(value);
47
- }
@@ -1,71 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
-
3
- import {
4
- applyPreparedJsonSnapshot,
5
- prepareJsonSnapshot
6
- } from '../storage/import-json.js';
7
- import { SqliteStore } from '../storage/sqlite-store.js';
8
- import {
9
- cleanupMigrationDestination,
10
- planMigrationDestination,
11
- publishMigrationDestination
12
- } from './migration-destination.js';
13
-
14
- export function migrateLegacyJson(args, { createStore = (path) => new SqliteStore(path) } = {}) {
15
- const sourcePath = option(args, '--from');
16
- const destinationPath = option(args, '--to');
17
- if (!sourcePath || !destinationPath) throw new Error('migrate requires --from and --to');
18
-
19
- const snapshot = readSnapshot(sourcePath);
20
- const prepared = prepareJsonSnapshot(snapshot, sourcePath);
21
- const destination = planMigrationDestination(destinationPath);
22
- const replace = args.includes('--replace');
23
- let store;
24
- let result;
25
- let failure;
26
- try {
27
- store = createStore(destination.storePath);
28
- result = applyPreparedJsonSnapshot(store, prepared, { replace });
29
- } catch (error) {
30
- failure = error;
31
- } finally {
32
- try {
33
- store?.close();
34
- } catch (error) {
35
- failure ??= error;
36
- }
37
- }
38
- if (!failure) {
39
- try {
40
- publishMigrationDestination(destination);
41
- } catch (error) {
42
- failure = error;
43
- }
44
- }
45
- try {
46
- cleanupMigrationDestination(destination);
47
- } catch (error) {
48
- failure ??= error;
49
- }
50
- if (failure) throw failure;
51
- return result;
52
- }
53
-
54
- function readSnapshot(sourcePath) {
55
- let source;
56
- try {
57
- source = readFileSync(sourcePath, 'utf8');
58
- } catch (error) {
59
- throw new Error(`Unable to read ${sourcePath}: ${error.message}`);
60
- }
61
- try {
62
- return JSON.parse(source);
63
- } catch (error) {
64
- throw new Error(`Invalid JSON in ${sourcePath}: ${error.message}`);
65
- }
66
- }
67
-
68
- function option(args, flag) {
69
- const index = args.indexOf(flag);
70
- return index === -1 ? null : args[index + 1] ?? null;
71
- }
@@ -1,117 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import {
3
- closeSync,
4
- existsSync,
5
- linkSync,
6
- mkdirSync,
7
- openSync,
8
- readdirSync,
9
- rmdirSync,
10
- unlinkSync
11
- } from 'node:fs';
12
- import { basename, dirname, join, resolve } from 'node:path';
13
-
14
- export function planMigrationDestination(destinationPath) {
15
- const destination = resolve(destinationPath);
16
- if (existsSync(destination)) {
17
- return {
18
- destination,
19
- storePath: destination,
20
- ownsStorePath: false,
21
- createdParents: []
22
- };
23
- }
24
-
25
- const parent = dirname(destination);
26
- const createdParents = createParents(parent);
27
- try {
28
- return {
29
- destination,
30
- storePath: reserveTemporaryPath(destination),
31
- ownsStorePath: true,
32
- createdParents
33
- };
34
- } catch (error) {
35
- removeEmptyParents(createdParents);
36
- throw error;
37
- }
38
- }
39
-
40
- export function publishMigrationDestination(plan) {
41
- if (!plan.ownsStorePath) return;
42
-
43
- try {
44
- linkSync(plan.storePath, plan.destination);
45
- } catch (error) {
46
- if (existsSync(plan.destination)) {
47
- throw new Error(
48
- `Migration destination appeared while the import was running: ${plan.destination}`,
49
- { cause: error }
50
- );
51
- }
52
- throw new Error(`Unable to publish migrated SQLite database: ${error.message}`, {
53
- cause: error
54
- });
55
- }
56
- }
57
-
58
- export function cleanupMigrationDestination(plan) {
59
- if (!plan.ownsStorePath) return;
60
-
61
- const errors = [];
62
- for (const artifact of ownedArtifacts(plan.storePath)) {
63
- try {
64
- unlinkSync(artifact);
65
- } catch (error) {
66
- if (error.code !== 'ENOENT') errors.push(error);
67
- }
68
- }
69
- removeEmptyParents(plan.createdParents, errors);
70
-
71
- if (errors.length > 0) {
72
- throw new AggregateError(errors, `Unable to clean migration temporary database: ${plan.storePath}`);
73
- }
74
- }
75
-
76
- function reserveTemporaryPath(destination) {
77
- const temporaryPath = join(
78
- dirname(destination),
79
- `.${basename(destination)}.${randomUUID()}.migration`
80
- );
81
- const descriptor = openSync(temporaryPath, 'wx');
82
- closeSync(descriptor);
83
- return temporaryPath;
84
- }
85
-
86
- function createParents(parent) {
87
- const firstCreated = mkdirSync(parent, { recursive: true });
88
- if (firstCreated === undefined) return [];
89
-
90
- const parents = [];
91
- const boundary = comparablePath(firstCreated);
92
- let current = parent;
93
- while (true) {
94
- parents.push(current);
95
- if (comparablePath(current) === boundary) return parents;
96
- current = dirname(current);
97
- }
98
- }
99
-
100
- function removeEmptyParents(parents, errors = []) {
101
- for (const parent of parents) {
102
- try {
103
- if (readdirSync(parent).length === 0) rmdirSync(parent);
104
- } catch (error) {
105
- if (error.code !== 'ENOENT' && error.code !== 'ENOTEMPTY') errors.push(error);
106
- }
107
- }
108
- }
109
-
110
- function ownedArtifacts(storePath) {
111
- return [`${storePath}-wal`, `${storePath}-shm`, storePath];
112
- }
113
-
114
- function comparablePath(path) {
115
- const resolved = resolve(path).replace(/^\\\\\?\\/, '');
116
- return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
117
- }
@@ -1,70 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
-
3
- import { nowIso } from '../models.js';
4
- import { validateLegacySnapshot } from './legacy-snapshot-validation.js';
5
- import { stableJson } from './stable-json.js';
6
-
7
- export function importJsonSnapshot(store, snapshot, sourcePath = '<memory>', options = {}) {
8
- return applyPreparedJsonSnapshot(store, prepareJsonSnapshot(snapshot, sourcePath), options);
9
- }
10
-
11
- export function prepareJsonSnapshot(snapshot, sourcePath = '<memory>') {
12
- validateSourcePath(sourcePath);
13
- const normalized = validateLegacySnapshot(snapshot);
14
- const canonicalSnapshot = JSON.parse(stableJson(normalized));
15
- const contentHash = hashSnapshot(canonicalSnapshot);
16
- return deepFreeze({ snapshot: deepFreeze(canonicalSnapshot), sourcePath, contentHash });
17
- }
18
-
19
- export function applyPreparedJsonSnapshot(store, prepared, { replace = false } = {}) {
20
- const verified = verifyPreparedSnapshot(prepared);
21
- let imported = false;
22
-
23
- store.transaction(() => {
24
- if (store.hasImport(verified.contentHash)) return;
25
- if (!replace && !isEmptySnapshot(store.exportSnapshot())) {
26
- throw new Error('Destination snapshot is not empty; pass replace: true to replace it');
27
- }
28
-
29
- store.importSnapshot(verified.snapshot);
30
- store.recordImport({
31
- contentHash: verified.contentHash,
32
- sourcePath: verified.sourcePath,
33
- importedAt: nowIso()
34
- });
35
- imported = true;
36
- }, { mode: 'immediate' });
37
-
38
- return { imported, contentHash: verified.contentHash };
39
- }
40
-
41
- function verifyPreparedSnapshot(prepared) {
42
- if (!prepared || typeof prepared !== 'object' || Array.isArray(prepared)) {
43
- throw new TypeError('Prepared snapshot must be an object');
44
- }
45
- const verified = prepareJsonSnapshot(prepared.snapshot, prepared.sourcePath);
46
- if (prepared.contentHash !== verified.contentHash) {
47
- throw new Error('Prepared snapshot content hash does not match its snapshot');
48
- }
49
- return verified;
50
- }
51
-
52
- function hashSnapshot(snapshot) {
53
- return createHash('sha256').update(stableJson(snapshot)).digest('hex');
54
- }
55
-
56
- function validateSourcePath(sourcePath) {
57
- if (typeof sourcePath !== 'string' || sourcePath.length === 0) {
58
- throw new TypeError('Import source path must be a nonempty string');
59
- }
60
- }
61
-
62
- function isEmptySnapshot(snapshot) {
63
- return Object.values(snapshot).every((collection) => Array.isArray(collection) && collection.length === 0);
64
- }
65
-
66
- function deepFreeze(value) {
67
- if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
68
- for (const child of Object.values(value)) deepFreeze(child);
69
- return Object.freeze(value);
70
- }
@@ -1,152 +0,0 @@
1
- import { CandidateStatus, FactStatus, SpaceKind } from '../models.js';
2
- import {
3
- validateCandidateRecord,
4
- validateFactRecord,
5
- validateOutboxRecord
6
- } from './record-validation.js';
7
-
8
- export function validateLegacyRecords(snapshot) {
9
- requireUnique(snapshot.spaces, 'space', (record) => record.id);
10
- requireUnique(snapshot.spaces, 'space name', (record) => record.name);
11
- requireUnique(snapshot.episodes, 'episode', (record) => record.id);
12
- requireUnique(snapshot.facts, 'fact', (record) => record.id);
13
- requireUnique(snapshot.candidates, 'candidate', (record) => record.id);
14
- requireUnique(snapshot.outbox ?? [], 'outbox', (record) => record.id);
15
- requireUnique(snapshot.imports ?? [], 'import', (record) => record.contentHash);
16
- requireUnique(snapshot.subscriptions, 'subscription', subscriptionKey);
17
- for (const space of snapshot.spaces) validateSpace(space);
18
- for (const subscription of snapshot.subscriptions) validateSubscription(subscription);
19
- for (const episode of snapshot.episodes) validateEpisode(episode);
20
- for (const fact of snapshot.facts) validateFact(fact);
21
- for (const candidate of snapshot.candidates) validateCandidate(candidate);
22
- for (const entry of snapshot.outbox ?? []) validateOutbox(entry);
23
- for (const record of snapshot.imports ?? []) {
24
- requireStrings(record, 'import', ['contentHash', 'sourcePath', 'importedAt']);
25
- }
26
- validateReferences(snapshot);
27
- }
28
- function validateSpace(space) {
29
- requireStrings(space, 'space', ['id', 'name', 'kind']);
30
- if (!Object.values(SpaceKind).includes(space.kind)) {
31
- throw new TypeError(`Invalid space kind: ${space.kind}`);
32
- }
33
- nullableString(space, 'description');
34
- optionalString(space, 'createdAt');
35
- }
36
-
37
- function validateSubscription(subscription) {
38
- requireStrings(subscription, 'subscription', ['personalSpaceId', 'spaceId']);
39
- optionalString(subscription, 'mode');
40
- optionalString(subscription, 'createdAt');
41
- }
42
-
43
- function validateEpisode(episode) {
44
- requireStrings(episode, 'episode', ['id', 'spaceId', 'sourceKind', 'body']);
45
- nullableString(episode, 'sourceUri');
46
- optionalString(episode, 'createdAt');
47
- }
48
-
49
- function validateFact(fact) {
50
- requireStrings(fact, 'fact', ['id', 'spaceId', 'subject', 'predicate', 'object', 'sourceEpisodeId']);
51
- optionalString(fact, 'validAt');
52
- nullableString(fact, 'invalidAt');
53
- nullableString(fact, 'replacedByFactId');
54
- validateFactRecord({
55
- ...fact,
56
- status: fact.status ?? FactStatus.CONFIRMED,
57
- confidence: fact.confidence ?? 1,
58
- sensitivity: fact.sensitivity ?? 'normal',
59
- scope: fact.scope ?? 'personal'
60
- });
61
- }
62
-
63
- function validateCandidate(candidate) {
64
- requireStrings(candidate, 'candidate', ['id', 'personalSpaceId', 'episodeId', 'reason']);
65
- nullableString(candidate, 'targetSpaceId');
66
- optionalString(candidate, 'createdAt');
67
- nullableString(candidate, 'decidedAt');
68
- validateCandidateRecord({ ...candidate, status: candidate.status ?? CandidateStatus.PENDING });
69
- }
70
-
71
- function validateOutbox(entry) {
72
- requireStrings(entry, 'outbox', ['id', 'kind', 'aggregateId']);
73
- optionalString(entry, 'createdAt');
74
- nullableString(entry, 'nextAttemptAt');
75
- nullableString(entry, 'sentAt');
76
- nullableString(entry, 'lastError');
77
- validateOutboxRecord({ ...entry, status: entry.status ?? 'pending', attempts: entry.attempts ?? 0 });
78
- }
79
-
80
- function validateReferences(snapshot) {
81
- const spaces = ids(snapshot.spaces);
82
- const episodes = ids(snapshot.episodes);
83
- const facts = ids(snapshot.facts);
84
- for (const subscription of snapshot.subscriptions) {
85
- requireReference(spaces, subscription.personalSpaceId, 'Subscription references missing personal space');
86
- requireReference(spaces, subscription.spaceId, 'Subscription references missing space');
87
- }
88
- for (const episode of snapshot.episodes) {
89
- requireReference(spaces, episode.spaceId, `Episode ${episode.id} references missing space`);
90
- }
91
- for (const fact of snapshot.facts) {
92
- requireReference(spaces, fact.spaceId, `Fact ${fact.id} references missing space`);
93
- requireReference(episodes, fact.sourceEpisodeId, `Fact ${fact.id} references missing episode`);
94
- if (fact.replacedByFactId) {
95
- requireReference(facts, fact.replacedByFactId, `Fact ${fact.id} references missing replacement fact`);
96
- }
97
- }
98
- for (const candidate of snapshot.candidates) {
99
- requireReference(spaces, candidate.personalSpaceId, `Candidate ${candidate.id} references missing personal space`);
100
- if (candidate.targetSpaceId) {
101
- requireReference(spaces, candidate.targetSpaceId, `Candidate ${candidate.id} references missing target space`);
102
- }
103
- requireReference(episodes, candidate.episodeId, `Candidate ${candidate.id} references missing episode`);
104
- }
105
- }
106
-
107
- function requireUnique(records, label, identifier) {
108
- const seen = new Set();
109
- for (const record of records) {
110
- const value = identifier(record);
111
- if (typeof value !== 'string' || value.length === 0) throw new TypeError(`${label} id must be a nonempty string`);
112
- if (seen.has(value)) {
113
- const suffix = label === 'subscription' || label.endsWith('name')
114
- ? `: ${value}`
115
- : ` id: ${value}`;
116
- throw new Error(`Duplicate ${label}${suffix}`);
117
- }
118
- seen.add(value);
119
- }
120
- }
121
-
122
- function requireStrings(record, label, fields) {
123
- for (const field of fields) {
124
- if (typeof record[field] !== 'string' || record[field].length === 0) {
125
- throw new TypeError(`${label} ${field} must be a nonempty string`);
126
- }
127
- }
128
- }
129
-
130
- function optionalString(record, field) {
131
- if (record[field] !== undefined && typeof record[field] !== 'string') {
132
- throw new TypeError(`${field} must be a string when provided`);
133
- }
134
- }
135
-
136
- function nullableString(record, field) {
137
- if (record[field] !== undefined && record[field] !== null && typeof record[field] !== 'string') {
138
- throw new TypeError(`${field} must be a string or null`);
139
- }
140
- }
141
-
142
- function subscriptionKey(record) {
143
- return `${record.personalSpaceId} -> ${record.spaceId}`;
144
- }
145
-
146
- function requireReference(validIds, id, message) {
147
- if (!validIds.has(id)) throw new Error(`${message}: ${id}`);
148
- }
149
-
150
- function ids(records) {
151
- return new Set(records.map((record) => record.id));
152
- }
@@ -1,32 +0,0 @@
1
- import { validateLegacyRecords } from './legacy-record-validation.js';
2
-
3
- const REQUIRED_COLLECTIONS = ['spaces', 'subscriptions', 'episodes', 'facts', 'candidates'];
4
- const OPTIONAL_COLLECTIONS = ['outbox', 'imports'];
5
-
6
- export function validateLegacySnapshot(snapshot) {
7
- if (!isRecord(snapshot)) throw new TypeError('Snapshot must be a JSON object');
8
- const collections = requireCollections(snapshot);
9
- validateLegacyRecords(collections);
10
- return collections;
11
- }
12
-
13
- function requireCollections(snapshot) {
14
- const collections = {};
15
- for (const name of REQUIRED_COLLECTIONS) collections[name] = requireArray(snapshot, name);
16
- for (const name of OPTIONAL_COLLECTIONS) {
17
- if (Object.hasOwn(snapshot, name)) collections[name] = requireArray(snapshot, name);
18
- }
19
- return collections;
20
- }
21
-
22
- function requireArray(snapshot, name) {
23
- if (!Array.isArray(snapshot[name])) throw new TypeError(`Snapshot ${name} must be an array`);
24
- return snapshot[name].map((record, index) => {
25
- if (!isRecord(record)) throw new TypeError(`Snapshot ${name}[${index}] must be an object`);
26
- return record;
27
- });
28
- }
29
-
30
- function isRecord(value) {
31
- return value !== null && typeof value === 'object' && !Array.isArray(value);
32
- }