toolnet-memory 0.3.16 → 0.3.17

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 (45) hide show
  1. package/.env.example +74 -0
  2. package/.release-target +1 -1
  3. package/README.md +165 -7
  4. package/bin/toolnet-memory +12 -0
  5. package/bundle/agy-hook.js +59 -59
  6. package/bundle/agy.js +58 -58
  7. package/bundle/api.js +15 -15
  8. package/bundle/audit.js +2 -0
  9. package/bundle/background-refresh.js +7 -7
  10. package/bundle/code-capabilities.js +2 -0
  11. package/bundle/codex.js +63 -63
  12. package/bundle/context-runtime.js +41 -41
  13. package/bundle/context.js +15 -15
  14. package/bundle/copilot-hook.js +73 -73
  15. package/bundle/cursor-hook.js +73 -73
  16. package/bundle/docker-healthcheck.js +3 -0
  17. package/bundle/doctor.js +4 -4
  18. package/bundle/full-index.js +20 -19
  19. package/bundle/gc.js +5 -3
  20. package/bundle/graph-cli.js +1 -1
  21. package/bundle/graph-index.js +5 -4
  22. package/bundle/graph-ui.js +4 -3
  23. package/bundle/grok-hook.js +52 -52
  24. package/bundle/handoff-refresh.js +2 -2
  25. package/bundle/help-cli.js +4 -4
  26. package/bundle/impact.js +5 -4
  27. package/bundle/incremental.js +5 -5
  28. package/bundle/init.js +35 -35
  29. package/bundle/kiro-hook.js +74 -74
  30. package/bundle/learner.js +8 -6
  31. package/bundle/mcp.js +19 -17
  32. package/bundle/memory-agent.js +4 -4
  33. package/bundle/memory-query.js +3 -3
  34. package/bundle/opencode.js +73 -73
  35. package/bundle/project-manual.js +7 -7
  36. package/bundle/public/index.html +51 -2
  37. package/bundle/runtime.js +16 -13
  38. package/bundle/semantic.js +6 -5
  39. package/bundle/service.js +11 -6
  40. package/bundle/snapshot.js +6 -4
  41. package/bundle/status-cli.js +4 -4
  42. package/bundle/toolnet-cli.js +49 -49
  43. package/bundle/work-continuity.js +7 -7
  44. package/package.json +26 -2
  45. package/release-manifest.json +94 -5
@@ -1,12 +1,12 @@
1
- import{existsSync as Oe,readFileSync as Ne}from"node:fs";import{homedir as Ke}from"node:os";import{join as $e}from"node:path";function De(r){let e=r.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Le(){let r=process.env.TOOLNET_GLOBAL_ENV??$e(Ke(),".config","toolnet-memory",".env");if(!Oe(r))return;let e=Ne(r,"utf8");for(let t of e.split(/\r?\n/)){let n=t.trim();if(!n||n.startsWith("#"))continue;n.startsWith("export ")&&(n=n.slice(7));let o=n.indexOf("=");if(o<=0)continue;let s=n.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)&&process.env[s]===void 0&&(process.env[s]=De(n.slice(o+1)))}}Le();function x(r,e){return r===void 0?e:["1","true","yes","on"].includes(r.toLowerCase())}function w(r,e){if(!r)return e;let t=Number(r);return Number.isFinite(t)?t:e}function oe(){return{memory:{autoCapture:x(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:x(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:x(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:x(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:w(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:w(process.env.MEMORY_RERANK_TOP,10),finalContext:w(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:w(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:x(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:w(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as He}from"node:crypto";import{existsSync as C,mkdirSync as Je,readFileSync as Ue,renameSync as Ye,writeFileSync as qe}from"node:fs";import{basename as Ze,dirname as E,join as j,parse as pe,resolve as S}from"node:path";import{createHash as ae}from"node:crypto";import{spawnSync as ze}from"node:child_process";var v="git-remote-v1",Ve=new Set(["github.com","gitlab.com","bitbucket.org"]);function ce(r,e){let t=e.replaceAll("\\","/").replace(/^\/+/u,"").replace(/\/+$/u,"").replace(/\.git$/iu,"").replace(/\/+/gu,"/");return!t||t==="."||t===".."||t.split("/").some(n=>!n||n==="."||n==="..")?null:(Ve.has(r)&&(t=t.toLowerCase()),t)}function Fe(r){let e;try{e=new URL(r)}catch{return null}if(!["https:","http:","ssh:","git:"].includes(e.protocol))return null;let t=e.hostname.trim().toLowerCase();if(!t)return null;let n=e.protocol==="https:"&&e.port==="443"||e.protocol==="http:"&&e.port==="80"||e.protocol==="ssh:"&&e.port==="22",o=e.port&&!n?`${t}:${e.port}`:t,s=ce(t,e.pathname);return s?`${o}/${s}`:null}function We(r){let e=r.match(/^(?:[^@\s/:]+@)?([^:/\s]+):(.+)$/u);if(!e)return null;let t=e[1]?.trim().toLowerCase();if(!t||t.length===1)return null;let n=ce(t,e[2]??"");return n?`${t}/${n}`:null}function se(r){let e=r.trim();return e?e.includes("://")?Fe(e):We(e):null}function Be(r){return ae("sha256").update(`${v}:${r}`).digest("hex")}function de(r){return ae("sha256").update(`toolnet-project:${v}:${r}`).digest("hex").slice(0,16)}function Ge(r){return r.split("/").filter(Boolean).at(-1)?.trim()||null}function z(r,e){let t=ze("git",["-C",r,...e],{encoding:"utf8",windowsHide:!0,stdio:["ignore","pipe","ignore"]});return t.error||t.status!==0?null:t.stdout?.trim()||null}function ie(r,e){let t=Ge(r);return t?{scheme:v,canonicalRemote:r,fingerprint:Be(r),repositoryName:t,source:e}:null}function ue(r){let e=z(r,["remote","get-url","origin"]);if(e){let o=se(e);if(o)return ie(o,"origin")}let t=z(r,["remote"]);if(!t)return null;let n=new Set;for(let o of t.split(/\r?\n/u).map(s=>s.trim()).filter(Boolean)){let s=z(r,["remote","get-url",o]);if(!s)continue;let a=se(s);a&&n.add(a)}return n.size!==1?null:ie([...n][0],"unique-remote")}var me=".toolnet",Qe="project.json";function Xe(r){return He("sha256").update(r).digest("hex").slice(0,16)}function b(r){return j(r,me,Qe)}function fe(r){return C(b(r))}function le(r,e){let t=S(r),n=pe(t).root;for(;;){if(fe(t))return t;if(t===n||e&&t===S(e))break;let o=E(t);if(o===t)break;t=o}return null}function V(r){let e=S(r),t=pe(e).root,n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(n.some(s=>C(j(e,s))))return e;if(e===t)break;let o=E(e);if(o===e)break;e=o}return S(r)}function I(r){let e;try{e=JSON.parse(Ue(r,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${r}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${r}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${r}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${r}`);let n=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:E(E(r)),createdAt:typeof t.createdAt=="string"?t.createdAt:n,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:n,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function P(r,e){let t=j(r,me);Je(t,{recursive:!0});let n=b(r),o=`${n}.tmp-${process.pid}`;qe(o,JSON.stringify(e,null,2)+`
3
- `,{encoding:"utf8",mode:384}),Ye(o,n)}function h(r,e){return{id:r.id,name:r.name,remote:r.remote,rootPath:e,createdAt:r.createdAt,updatedAt:r.updatedAt,graphVersion:r.graphVersion,memoryVersion:r.memoryVersion,metadata:r.metadata}}function F(r){return{version:1,scheme:v,canonicalRemote:r.canonicalRemote,fingerprint:r.fingerprint,repositoryName:r.repositoryName}}function et(r){let e=r.metadata?.toolnetIdentity;if(!e||typeof e!="object"||Array.isArray(e))return null;let t=e;return typeof t.fingerprint=="string"?t.fingerprint:null}var T=class{adopt(e,t){let n=V(S(e));if(!t.id.trim())throw new Error("PROJECT_ADOPTION_INVALID_ID");if(!t.name.trim())throw new Error("PROJECT_ADOPTION_INVALID_NAME");if(!t.remote.trim())throw new Error("PROJECT_ADOPTION_INVALID_REMOTE");if(fe(n)){let c=I(b(n));if(c.id!==t.id)throw new Error(["PROJECT_IDENTITY_ALREADY_EXISTS",`existing=${c.id}`,`requested=${t.id}`].join(" "));return h(c,n)}let o=new Date().toISOString(),s={...t.metadata};t.gitIdentity&&(s.toolnetIdentity=F(t.gitIdentity));let a={version:1,id:t.id.trim(),name:t.name.trim(),remote:t.remote.trim(),rootPath:n,createdAt:t.createdAt??o,updatedAt:o,graphVersion:t.graphVersion??0,memoryVersion:t.memoryVersion??0,metadata:Object.keys(s).length?s:void 0};return P(n,a),h(a,n)}recordGitIdentity(e,t,n={}){let o=this.requireExisting(e),s=b(o.rootPath),a=I(s),c=et(a);if(c&&c!==t.fingerprint&&!n.allowRebind)throw new Error(["PROJECT_GIT_REMOTE_CHANGED",`existing=${c}`,`current=${t.fingerprint}`,"Use explicit rebind only when this repository identity change is intentional."].join(" "));let d=a.metadata?.toolnetIdentity;return d&&typeof d=="object"&&!Array.isArray(d)&&d.fingerprint===t.fingerprint||(a.metadata={...a.metadata,toolnetIdentity:F(t)},a.updatedAt=new Date().toISOString(),P(o.rootPath,a)),h(a,o.rootPath)}findExisting(e=process.cwd()){let t=S(e),n=V(t),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(d=>C(j(n,d))),a=le(t,s?n:void 0);if(!a)return null;let c=I(b(a));return h(c,a)}requireExisting(e=process.cwd()){let t=this.findExisting(e);if(!t)throw new Error("PROJECT_NOT_INITIALIZED");return t}detect(e=process.cwd()){let t=S(e),n=V(t),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(k=>C(j(n,k))),a=le(t,s?n:void 0);if(a){let k=b(a),g=I(k);return g.rootPath!==a&&(g.rootPath=a,g.updatedAt=new Date().toISOString(),P(a,g)),h(g,a)}let c=new Date().toISOString(),d=Ze(n),u=ue(n),f={version:1,id:u?de(u.canonicalRemote):Xe(n),name:d,remote:u?.repositoryName??d,rootPath:n,createdAt:c,updatedAt:c,graphVersion:0,memoryVersion:0,metadata:u?{toolnetIdentity:F(u)}:void 0};return P(n,f),h(f,n)}};var tt=[{type:"openai_key",regex:/\bsk-[A-Za-z0-9_-]{20,}\b/g,confidence:"exact"},{type:"huggingface_token",regex:/\bhf_[A-Za-z0-9]{20,}\b/g,confidence:"exact"},{type:"hf_s3_access_key",regex:/\bHFAK[A-Za-z0-9]{8,}\b/g,confidence:"exact"},{type:"aws_access_key",regex:/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,confidence:"exact"},{type:"github_token",regex:/\b(?:gh[pousr]_[A-Za-z0-9]{30,255}|github_pat_[A-Za-z0-9_]{40,255})\b/g,confidence:"exact"},{type:"stripe_secret_key",regex:/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,confidence:"exact"},{type:"google_api_key",regex:/\bAIza[A-Za-z0-9_-]{30,}\b/g,confidence:"exact"},{type:"slack_token",regex:/\bxox[baprs]-[A-Za-z0-9-]{16,}\b/g,confidence:"exact"},{type:"npm_token",regex:/\bnpm_[A-Za-z0-9]{20,}\b/g,confidence:"exact"},{type:"bearer_token",regex:/\bBearer\s+[A-Za-z0-9._~+/=-]{16,}\b/gi,confidence:"high"},{type:"jwt",regex:/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,confidence:"exact"},{type:"private_key",regex:/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,confidence:"exact"},{type:"password_assignment",regex:/\b(?:password|passwd|pwd)\s*[:=]\s*["']?[^"' \t\r\n]{6,}["']?/gi,confidence:"high"},{type:"secret_assignment",regex:/\b(?:secret|api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|private[_-]?key)\s*[:=]\s*["']?[^"' \t\r\n]{8,}["']?/gi,confidence:"high"},{type:"cookie",regex:/\b(?:cookie|set-cookie)\s*[:=]\s*[^;\n]{8,}/gi,confidence:"high"},{type:"url_credentials",regex:/\bhttps?:\/\/[^:/@\s]+:[^/@\s]{4,}@[^/\s]+/gi,confidence:"high"}],rt=new Set(["example","example-key","example-token","changeme","change-me","password","secret","your-api-key","your-token","<token>","<secret>","<password>","[redacted]"]);function ge(r){return r.normalize("NFKC").trim().toLowerCase()}function nt(r){if(r.length===0)return 0;let e=new Map;for(let n of r)e.set(n,(e.get(n)??0)+1);let t=0;for(let n of e.values()){let o=n/r.length;t-=o*Math.log2(o)}return t}function ot(r){return/^[a-f0-9]{32}$/iu.test(r)||/^[a-f0-9]{40}$/iu.test(r)||/^[a-f0-9]{64}$/iu.test(r)}function st(r,e,t){let n=r.slice(Math.max(0,e-48),e),o=r.slice(t,Math.min(r.length,t+16));return/\b(?:token|secret|key|credential|authorization|password|passwd|apikey|api_key|access[_-]?key)\b/iu.test(`${n} ${o}`)}function it(r,e){return r.start<e.end&&e.start<r.end}function ye(r){return r.sort((e,t)=>e.start!==t.start?e.start-t.start:t.end-t.start-(e.end-e.start))}var _=class{allowValues=new Set;enableEntropyHeuristic;constructor(e={}){for(let t of e.allowValues??[]){let n=ge(t);n&&this.allowValues.add(n)}this.enableEntropyHeuristic=e.enableEntropyHeuristic??!0}scan(e){let t=[];for(let s of tt){let a=new RegExp(s.regex.source,s.regex.flags);for(let c of e.matchAll(a))c.index===void 0||!c[0]||this.allowed(c[0])||t.push({type:s.type,value:c[0],start:c.index,end:c.index+c[0].length,confidence:s.confidence})}this.enableEntropyHeuristic&&t.push(...this.entropyMatches(e));let n=ye(t),o=[];for(let s of n)o.some(a=>it(a,s))||o.push(s);return ye(o)}hasSecrets(e){return this.scan(e).length>0}allowed(e){let t=ge(e);return rt.has(t)?!0:this.allowValues.has(t)}entropyMatches(e){let t=[],n=/[A-Za-z0-9_+/=-]{32,160}/g;for(let o of e.matchAll(n)){if(o.index===void 0||!o[0])continue;let s=o[0];this.allowed(s)||ot(s)||!/[A-Za-z]/u.test(s)||!/[0-9]/u.test(s)||st(e,o.index,o.index+s.length)&&(nt(s)<3.7||t.push({type:"high_entropy_secret",value:s,start:o.index,end:o.index+s.length,confidence:"heuristic"}))}return t}};var O=class{scanner;constructor(e={}){this.scanner=new _(e)}sanitize(e){let t=this.scanner.scan(e);if(t.length===0)return{text:e,redacted:0,secretTypes:[]};let n=e,o=[...t].sort((a,c)=>c.start-a.start),s=new Set;for(let a of o)s.add(a.type),n=n.slice(0,a.start)+`[REDACTED:${a.type}]`+n.slice(a.end);return{text:n,redacted:t.length,secretTypes:[...s].sort()}}sanitizeValue(e){if(typeof e=="string")return this.sanitize(e).text;if(Array.isArray(e))return e.map(t=>this.sanitizeValue(t));if(e&&typeof e=="object"){let t={};for(let[n,o]of Object.entries(e)){let s=n.normalize("NFKC").toLowerCase().replace(/[^a-z0-9]+/g,"");if(s.includes("password")||s.includes("passwd")||s==="pwd"||s.includes("secret")||s.includes("token")||s.includes("cookie")||s.includes("authorization")||s.includes("apikey")||s.includes("accesskey")||s.includes("privatekey")||s.includes("clientsecret")||s.includes("credential")){t[n]="[REDACTED]";continue}t[n]=this.sanitizeValue(o)}return t}return e}};var dr=new O;import{homedir as Pt}from"node:os";import{join as Ct}from"node:path";import{DeleteObjectCommand as at,GetObjectCommand as ct,HeadObjectCommand as dt,ListObjectsV2Command as ut,PutObjectCommand as lt,S3Client as pt}from"@aws-sdk/client-s3";import{getSignedUrl as mt}from"@aws-sdk/s3-request-presigner";var N=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new pt({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,n="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new lt({Bucket:this.bucket,Key:e,Body:o,ContentType:n}))}async get(e){let t=await mt(this.client,new ct({Bucket:this.bucket,Key:e}),{expiresIn:60}),n=await fetch(t,{redirect:"follow"});if(n.status===404)return null;if(!n.ok)throw new Error(`HF download failed: ${n.status} ${n.statusText}`);return new Uint8Array(await n.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new dt({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new at({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],n;do{let o=await this.client.send(new ut({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:n}));for(let s of o.Contents??[])s.Key&&t.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});n=o.IsTruncated?o.NextContinuationToken:void 0}while(n);return t}};import{access as he,mkdir as ft,readFile as gt,readdir as yt,rm as ht,stat as Se,writeFile as St}from"node:fs/promises";import{dirname as kt,join as bt,relative as ke,resolve as xt}from"node:path";var A=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return xt(this.root,t)}async put(e,t){let n=this.path(e);await ft(kt(n),{recursive:!0}),await St(n,t)}async get(e){try{return await gt(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await he(this.path(e)),!0}catch{return!1}}async delete(e){await ht(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),n=[];try{await he(t)}catch{return n}let o=async a=>{let c=await yt(a,{withFileTypes:!0});for(let d of c){let u=bt(a,d.name);if(d.isDirectory()){await o(u);continue}let f=await Se(u);n.push({key:ke(this.root,u),size:f.size,updatedAt:f.mtime.toISOString()})}},s=await Se(t);return s.isDirectory()?await o(t):n.push({key:ke(this.root,t),size:s.size,updatedAt:s.mtime.toISOString()}),n}};import{DeleteObjectCommand as wt,GetObjectCommand as vt,HeadObjectCommand as jt,ListObjectsV2Command as At,PutObjectCommand as Mt,S3Client as Rt}from"@aws-sdk/client-s3";import{getSignedUrl as It}from"@aws-sdk/s3-request-presigner";var M=class{name;client;bucket;constructor(e){this.name=e.name??"s3",this.bucket=e.bucket,this.client=new Rt({region:e.region??"us-east-1",endpoint:e.endpoint||void 0,forcePathStyle:e.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,n="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Mt({Bucket:this.bucket,Key:e,Body:o,ContentType:n}))}async get(e){let t=await It(this.client,new vt({Bucket:this.bucket,Key:e}),{expiresIn:60}),n=await fetch(t,{redirect:"follow"});if(n.status===404)return null;if(!n.ok)throw new Error(`${this.name} download failed: ${n.status} ${n.statusText}`);return new Uint8Array(await n.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new jt({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new wt({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],n;do{let o=await this.client.send(new At({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:n}));for(let s of o.Contents??[])s.Key&&t.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});n=o.IsTruncated?o.NextContinuationToken:void 0}while(n);return t}};function W(r,e){return console.warn(e),new A(r)}function be(r){let e=r.localRoot??Ct(Pt(),".toolnet-memory","storage");if(r.provider==="r2"){let t=r.r2;return t?.accountId&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new M({name:"r2",endpoint:`https://${t.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:t.bucket,forcePathStyle:!0,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):W(e,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(r.provider==="s3"){let t=r.s3;return t?.bucket&&t.accessKeyId&&t.secretAccessKey?new M({name:"s3",endpoint:t.endpoint,region:t.region??"us-east-1",bucket:t.bucket,forcePathStyle:t.forcePathStyle??!1,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):W(e,"[storage] S3 credentials missing. Using local fallback.")}if(r.provider==="huggingface"){let t=r.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?new N({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}):W(e,"[storage] Hugging Face credentials missing. Using local fallback.")}return new A(e)}function Et(r){return new Promise(e=>setTimeout(e,r))}async function xe(r,e={}){let t=Math.max(1,e.attempts??3),n=e.baseDelayMs??150,o=e.maxDelayMs??2e3,s;for(let a=1;a<=t;a++)try{return await r()}catch(c){if(s=c,a>=t)break;let d=Math.min(o,n*2**(a-1)),u=Math.floor(Math.random()*Math.max(1,d*.2));await Et(d+u)}throw s}var Tt=new Set(["put","get","getText","delete","list"]);function we(r,e={}){return new Proxy(r,{get(t,n){let o=Reflect.get(t,n,t);return typeof o!="function"?o:Tt.has(n)?(...s)=>xe(()=>Promise.resolve(o.apply(t,s)),e):o.bind(t)}})}function ve(r){let e=r.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function je(r){let e=r.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let n=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(n)return`${n[1]}memory/records/${n[2]}`;if(n=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),n)return`${n[1]}memory/vectors/${n[2]}`;if(n=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),n)return`${n[1]}code/graph/${n[2]}`;let o=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return o?`${o[1]}memory/records/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),o?`${o[1]}memory/vectors/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),o?`${o[1]}code/graph/${o[2]}`:e))}var K=class{constructor(e,t,n,o){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=n,this.folder=ve(o??n),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),n=t,o=await this.provider.getText(e);if(o){let a;try{a=JSON.parse(o)}catch(c){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${c instanceof Error?c.message:String(c)}`)}if(typeof a.id=="string"&&a.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${a.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof a.createdAt=="string"&&(n=a.createdAt)}let s={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:n,updatedAt:t};await this.provider.put(e,JSON.stringify(s,null,2)+`
4
- `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=je(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,n){return await this.ensureRegistered(),this.provider.put(this.key(e),t,n)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};import{join as Re}from"node:path";import{mkdirSync as Dt}from"node:fs";import{createHash as _t}from"node:crypto";import{dirname as Ot}from"node:path";import{mkdirSync as Nt,readFileSync as eo,renameSync as Kt,writeFileSync as $t}from"node:fs";function Ae(r){return _t("sha256").update(r).digest("hex")}function Me(r,e){Nt(Ot(r),{recursive:!0});let t=`${r}.${process.pid}.tmp`;$t(t,JSON.stringify(e,null,2)+`
5
- `,{encoding:"utf8",mode:384}),Kt(t,r)}function Pe(r){return r.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").replace(/\s+/g," ").trim()}function p(r,e=20){let t=[],n=new Set;for(let o of r.slice().reverse()){let s=Pe(o);if(!(!s||n.has(s))&&(n.add(s),t.push(o),t.length>=e))break}return t.reverse()}function Lt(r,e=20){let t=new Map;for(let n of r){let o=`${n.kind}|${Pe(n.command)}`;t.delete(o),t.set(o,n)}return Array.from(t.values()).slice(-e)}function zt(r){return r.kind==="phase"?/^Phase\s+\d+$/iu.test(r.text):r.kind==="task"?/^(?:TODO|Task|Việc)\s+\d+$/iu.test(r.text):!1}function Ie(r,e){let t=e.status??r?.status??"pending",n=t;r&&(r.status==="completed"&&t!=="completed"?n="completed":t==="pending"&&(r.status==="in_progress"||r.status==="blocked")&&(n=r.status));let o=r&&zt(e)?r.title:e.text;return{id:r?.id??Ae(e.key).slice(0,24),title:o,status:n,order:e.order??r?.order,confidence:Math.max(e.confidence,r?.confidence??0),updatedAt:e.occurredAt,updatedBy:{agent:e.agent,nativeSessionId:e.nativeSessionId,eventId:e.eventId}}}async function Ce(r,e){let t=`projects/${r.id}/work/observations/`,n=await e.list(t),o=[];for(let s of n.filter(a=>a.key.endsWith(".json")).sort((a,c)=>a.key.localeCompare(c.key))){let a=await e.getText(s.key);if(a)try{let c=JSON.parse(a);c.version===1&&Array.isArray(c.observations)&&o.push(c)}catch{}}return o}async function B(r,e){let n=(await Ce(r,e)).flatMap(i=>i.observations).sort((i,l)=>{let re=i.occurredAt.localeCompare(l.occurredAt);if(re!==0)return re;let ne=i.sequence-l.sequence;return ne!==0?ne:i.id.localeCompare(l.id)}),o=new Map,s=new Map,a,c,d,u,f,k=[],g=[],H=[],J=[],U=[],$=new Map,Y=[],q=[],Z=[],Q=[],X=[],ee=[];for(let i of n)switch(i.kind){case"request":a=i.text;break;case"activity":c=i.text;break;case"goal":d=i.text;break;case"plan":u=i.text;break;case"phase":o.set(i.key,Ie(o.get(i.key),i));break;case"task":s.set(i.key,Ie(s.get(i.key),i));break;case"decision":k.push(i.text);break;case"blocker":g.push(i.text);break;case"warning":H.push(i.text);break;case"next_action":J.push(i.text);break;case"file":{U.push(i.text);let l=i.fileAction??"active";$.delete(i.text),$.set(i.text,l),l==="modified"?Y.push(i.text):l==="created"?q.push(i.text):l==="deleted"&&Z.push(i.text);break}case"command":Q.push(i.text);break;case"test":X.push(i.text),i.checkKind&&ee.push({kind:i.checkKind,command:i.text,status:i.checkStatus??"unknown",updatedAt:i.occurredAt,agent:i.agent,nativeSessionId:i.nativeSessionId});break;case"session":f={agent:i.agent,nativeSessionId:i.nativeSessionId,sessionKey:i.sessionKey,updatedAt:i.occurredAt};break}let y=Array.from(o.values()).sort((i,l)=>(i.order??Number.MAX_SAFE_INTEGER)-(l.order??Number.MAX_SAFE_INTEGER)),m=Array.from(s.values()).sort((i,l)=>(i.order??Number.MAX_SAFE_INTEGER)-(l.order??Number.MAX_SAFE_INTEGER)),D=y.find(i=>i.status==="in_progress")??y.find(i=>i.status==="blocked")??y.find(i=>i.status==="pending"),R=m.find(i=>i.status==="in_progress")??m.find(i=>i.status==="blocked")??m.find(i=>i.status==="pending"),Te=p([...J,...R?[R.title]:[],...!R&&D?[D.title]:[],...m.filter(i=>i.status==="pending").slice(0,5).map(i=>i.title)],8),_e=p([...g,...y.filter(i=>i.status==="blocked").map(i=>i.title),...m.filter(i=>i.status==="blocked").map(i=>i.title)],20),L={version:1,projectId:r.id,projectName:r.name,currentRequest:a,currentActivity:c,goal:d,plan:u,phases:y,tasks:m,decisions:p(k,20),blockers:_e,warnings:p(H,20),nextActions:Te,filesTouched:p(U,30),activeFiles:Array.from($.entries()).filter(([,i])=>i!=="deleted").map(([i])=>i).slice(-5),modifiedFiles:p(Y,30),createdFiles:p(q,30),deletedFiles:p(Z,30),commands:p(Q,20),tests:p(X,20),checks:Lt(ee,20),currentPhase:D,currentTask:R,progress:{phasesTotal:y.length,phasesCompleted:y.filter(i=>i.status==="completed").length,tasksTotal:m.length,tasksCompleted:m.filter(i=>i.status==="completed").length,blocked:y.filter(i=>i.status==="blocked").length+m.filter(i=>i.status==="blocked").length},lastSession:f,updatedAt:n.length?n[n.length-1].occurredAt:new Date().toISOString()},te=Re(r.rootPath,".toolnet","work");return Dt(te,{recursive:!0}),Me(Re(te,"current.json"),L),await e.put(`projects/${r.id}/work/current.json`,JSON.stringify(L,null,2)+`
6
- `,"application/json"),L}async function G(r,e){if((await Ce(r,e)).length>0)return B(r,e);let n=await e.getText(`projects/${r.id}/work/current.json`);if(!n)return null;try{return JSON.parse(n)}catch{return null}}function Vt(r,e){let t=r.indexOf(e);return t>=0?r[t+1]:void 0}function Ft(r){let e=oe(),t=we(be({provider:e.storage.provider,huggingface:e.storage.huggingface,localRoot:e.storage.localRoot}),{attempts:3});return new K(t,r.id,r.name,r.remote??r.name)}function Ee(r){console.log(),console.log(`Project: ${r.projectName}`),r.goal&&console.log(`Goal: ${r.goal}`),r.currentPhase&&console.log(`Current phase: ${r.currentPhase.title} [${r.currentPhase.status}]`),r.currentTask&&console.log(`Current task: ${r.currentTask.title} [${r.currentTask.status}]`),console.log(`Phases: ${r.progress.phasesCompleted}/${r.progress.phasesTotal}`),console.log(`Tasks: ${r.progress.tasksCompleted}/${r.progress.tasksTotal}`),r.nextActions.length&&(console.log(),console.log("Next actions:"),r.nextActions.forEach((e,t)=>console.log(`${t+1}. ${e}`))),r.blockers.length&&(console.log(),console.log("Blockers:"),r.blockers.forEach(e=>console.log(`- ${e}`))),r.lastSession&&(console.log(),console.log(`Last session: ${r.lastSession.agent} / ${r.lastSession.nativeSessionId}`))}async function Wt(){let[r="help",...e]=process.argv.slice(2),t=new T().detect(Vt(e,"--project")??process.cwd()),n=Ft(t);if(r==="reconcile"){let o=await B(t,n);Ee(o);return}if(r==="status"){let o=await G(t,n);if(!o){console.log("No active work state yet.");return}Ee(o);return}if(r==="json"){let o=await G(t,n);console.log(JSON.stringify(o,null,2));return}console.log(`Work Continuity
1
+ import{existsSync as et,readFileSync as tt}from"node:fs";import{homedir as rt}from"node:os";import{join as nt}from"node:path";function ot(r){let e=r.trim();return e.length>=2&&e.startsWith('"')&&e.endsWith('"')?(e=e.slice(1,-1),e.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):e.length>=2&&e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function st(){let r=process.env.TOOLNET_GLOBAL_ENV??nt(rt(),".config","toolnet-memory",".env");if(!et(r))return;let e=tt(r,"utf8");for(let t of e.split(/\r?\n/)){let n=t.trim();if(!n||n.startsWith("#"))continue;n.startsWith("export ")&&(n=n.slice(7));let o=n.indexOf("=");if(o<=0)continue;let s=n.slice(0,o).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)&&process.env[s]===void 0&&(process.env[s]=ot(n.slice(o+1)))}}st();function E(r,e){return r===void 0?e:["1","true","yes","on"].includes(r.toLowerCase())}function v(r,e){if(!r)return e;let t=Number(r);return Number.isFinite(t)?t:e}function fe(){return{memory:{autoCapture:E(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:E(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:E(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:E(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:v(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:v(process.env.MEMORY_RERANK_TOP,10),finalContext:v(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:v(process.env.MEMORY_TOKEN_BUDGET,2e3)},storage:{provider:process.env.MEMORY_STORAGE_PROVIDER??"huggingface",r2:{accountId:process.env.R2_ACCOUNT_ID,bucket:process.env.R2_BUCKET,accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY},s3:{endpoint:process.env.S3_ENDPOINT,region:process.env.S3_REGION,bucket:process.env.S3_BUCKET,accessKeyId:process.env.S3_ACCESS_KEY_ID,secretAccessKey:process.env.S3_SECRET_ACCESS_KEY,forcePathStyle:E(process.env.S3_FORCE_PATH_STYLE,!1)},huggingface:{namespace:process.env.HF_NAMESPACE,bucket:process.env.HF_BUCKET,accessKeyId:process.env.HF_S3_ACCESS_KEY_ID,secretAccessKey:process.env.HF_S3_SECRET_ACCESS_KEY},localRoot:process.env.MEMORY_LOCAL_STORAGE_PATH},cache:{maxMb:v(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as pt}from"node:crypto";import{existsSync as N,mkdirSync as mt,readFileSync as ft,renameSync as gt,writeFileSync as yt}from"node:fs";import{basename as ht,dirname as K,join as A,parse as we,resolve as x}from"node:path";import{createHash as he}from"node:crypto";import{spawnSync as it}from"node:child_process";var R="git-remote-v1",at=new Set(["github.com","gitlab.com","bitbucket.org"]);function Se(r,e){let t=e.replaceAll("\\","/").replace(/^\/+/u,"").replace(/\/+$/u,"").replace(/\.git$/iu,"").replace(/\/+/gu,"/");return!t||t==="."||t===".."||t.split("/").some(n=>!n||n==="."||n==="..")?null:(at.has(r)&&(t=t.toLowerCase()),t)}function ct(r){let e;try{e=new URL(r)}catch{return null}if(!["https:","http:","ssh:","git:"].includes(e.protocol))return null;let t=e.hostname.trim().toLowerCase();if(!t)return null;let n=e.protocol==="https:"&&e.port==="443"||e.protocol==="http:"&&e.port==="80"||e.protocol==="ssh:"&&e.port==="22",o=e.port&&!n?`${t}:${e.port}`:t,s=Se(t,e.pathname);return s?`${o}/${s}`:null}function ut(r){let e=r.match(/^(?:[^@\s/:]+@)?([^:/\s]+):(.+)$/u);if(!e)return null;let t=e[1]?.trim().toLowerCase();if(!t||t.length===1)return null;let n=Se(t,e[2]??"");return n?`${t}/${n}`:null}function ge(r){let e=r.trim();return e?e.includes("://")?ct(e):ut(e):null}function dt(r){return he("sha256").update(`${R}:${r}`).digest("hex")}function be(r){return he("sha256").update(`toolnet-project:${R}:${r}`).digest("hex").slice(0,16)}function lt(r){return r.split("/").filter(Boolean).at(-1)?.trim()||null}function U(r,e){let t=it("git",["-C",r,...e],{encoding:"utf8",windowsHide:!0,stdio:["ignore","pipe","ignore"]});return t.error||t.status!==0?null:t.stdout?.trim()||null}function ye(r,e){let t=lt(r);return t?{scheme:R,canonicalRemote:r,fingerprint:dt(r),repositoryName:t,source:e}:null}function xe(r){let e=U(r,["remote","get-url","origin"]);if(e){let o=ge(e);if(o)return ye(o,"origin")}let t=U(r,["remote"]);if(!t)return null;let n=new Set;for(let o of t.split(/\r?\n/u).map(s=>s.trim()).filter(Boolean)){let s=U(r,["remote","get-url",o]);if(!s)continue;let a=ge(s);a&&n.add(a)}return n.size!==1?null:ye([...n][0],"unique-remote")}var Ee=".toolnet",St="project.json";function bt(r){return pt("sha256").update(r).digest("hex").slice(0,16)}function w(r){return A(r,Ee,St)}function ve(r){return N(w(r))}function ke(r,e){let t=x(r),n=we(t).root;for(;;){if(ve(t))return t;if(t===n||e&&t===x(e))break;let o=K(t);if(o===t)break;t=o}return null}function G(r){let e=x(r),t=we(e).root,n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(n.some(s=>N(A(e,s))))return e;if(e===t)break;let o=K(e);if(o===e)break;e=o}return x(r)}function _(r){let e;try{e=JSON.parse(ft(r,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${r}: ${o instanceof Error?o.message:String(o)}`)}if(!e||typeof e!="object")throw new Error(`Invalid ToolNet project manifest: ${r}`);let t=e;if(typeof t.id!="string"||!t.id.trim())throw new Error(`ToolNet project manifest is missing id: ${r}`);if(typeof t.name!="string"||!t.name.trim())throw new Error(`ToolNet project manifest is missing name: ${r}`);let n=new Date().toISOString();return{version:1,id:t.id,name:t.name,remote:typeof t.remote=="string"&&t.remote.trim()?t.remote:t.name,rootPath:typeof t.rootPath=="string"?t.rootPath:K(K(r)),createdAt:typeof t.createdAt=="string"?t.createdAt:n,updatedAt:typeof t.updatedAt=="string"?t.updatedAt:n,graphVersion:typeof t.graphVersion=="number"?t.graphVersion:0,memoryVersion:typeof t.memoryVersion=="number"?t.memoryVersion:0,metadata:t.metadata&&typeof t.metadata=="object"?t.metadata:void 0}}function O(r,e){let t=A(r,Ee);mt(t,{recursive:!0});let n=w(r),o=`${n}.tmp-${process.pid}`;yt(o,JSON.stringify(e,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),gt(o,n)}function b(r,e){return{id:r.id,name:r.name,remote:r.remote,rootPath:e,createdAt:r.createdAt,updatedAt:r.updatedAt,graphVersion:r.graphVersion,memoryVersion:r.memoryVersion,metadata:r.metadata}}function W(r){return{version:1,scheme:R,canonicalRemote:r.canonicalRemote,fingerprint:r.fingerprint,repositoryName:r.repositoryName}}function xt(r){let e=r.metadata?.toolnetIdentity;if(!e||typeof e!="object"||Array.isArray(e))return null;let t=e;return typeof t.fingerprint=="string"?t.fingerprint:null}var $=class{adopt(e,t){let n=G(x(e));if(!t.id.trim())throw new Error("PROJECT_ADOPTION_INVALID_ID");if(!t.name.trim())throw new Error("PROJECT_ADOPTION_INVALID_NAME");if(!t.remote.trim())throw new Error("PROJECT_ADOPTION_INVALID_REMOTE");if(ve(n)){let c=_(w(n));if(c.id!==t.id)throw new Error(["PROJECT_IDENTITY_ALREADY_EXISTS",`existing=${c.id}`,`requested=${t.id}`].join(" "));return b(c,n)}let o=new Date().toISOString(),s={...t.metadata};t.gitIdentity&&(s.toolnetIdentity=W(t.gitIdentity));let a={version:1,id:t.id.trim(),name:t.name.trim(),remote:t.remote.trim(),rootPath:n,createdAt:t.createdAt??o,updatedAt:o,graphVersion:t.graphVersion??0,memoryVersion:t.memoryVersion??0,metadata:Object.keys(s).length?s:void 0};return O(n,a),b(a,n)}recordGitIdentity(e,t,n={}){let o=this.requireExisting(e),s=w(o.rootPath),a=_(s),c=xt(a);if(c&&c!==t.fingerprint&&!n.allowRebind)throw new Error(["PROJECT_GIT_REMOTE_CHANGED",`existing=${c}`,`current=${t.fingerprint}`,"Use explicit rebind only when this repository identity change is intentional."].join(" "));let u=a.metadata?.toolnetIdentity;return u&&typeof u=="object"&&!Array.isArray(u)&&u.fingerprint===t.fingerprint||(a.metadata={...a.metadata,toolnetIdentity:W(t)},a.updatedAt=new Date().toISOString(),O(o.rootPath,a)),b(a,o.rootPath)}findExisting(e=process.cwd()){let t=x(e),n=G(t),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(u=>N(A(n,u))),a=ke(t,s?n:void 0);if(!a)return null;let c=_(w(a));return b(c,a)}requireExisting(e=process.cwd()){let t=this.findExisting(e);if(!t)throw new Error("PROJECT_NOT_INITIALIZED");return t}detect(e=process.cwd()){let t=x(e),n=G(t),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(m=>N(A(n,m))),a=ke(t,s?n:void 0);if(a){let m=w(a),h=_(m);return h.rootPath!==a&&(h.rootPath=a,h.updatedAt=new Date().toISOString(),O(a,h)),b(h,a)}let c=new Date().toISOString(),u=ht(n),d=xe(n),f={version:1,id:d?be(d.canonicalRemote):bt(n),name:u,remote:d?.repositoryName??u,rootPath:n,createdAt:c,updatedAt:c,graphVersion:0,memoryVersion:0,metadata:d?{toolnetIdentity:W(d)}:void 0};return O(n,f),b(f,n)}};var kt=[{type:"openai_key",regex:/\bsk-[A-Za-z0-9_-]{20,}\b/g,confidence:"exact"},{type:"huggingface_token",regex:/\bhf_[A-Za-z0-9]{20,}\b/g,confidence:"exact"},{type:"hf_s3_access_key",regex:/\bHFAK[A-Za-z0-9]{8,}\b/g,confidence:"exact"},{type:"aws_access_key",regex:/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,confidence:"exact"},{type:"github_token",regex:/\b(?:gh[pousr]_[A-Za-z0-9]{30,255}|github_pat_[A-Za-z0-9_]{40,255})\b/g,confidence:"exact"},{type:"stripe_secret_key",regex:/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,confidence:"exact"},{type:"google_api_key",regex:/\bAIza[A-Za-z0-9_-]{30,}\b/g,confidence:"exact"},{type:"slack_token",regex:/\bxox[baprs]-[A-Za-z0-9-]{16,}\b/g,confidence:"exact"},{type:"npm_token",regex:/\bnpm_[A-Za-z0-9]{20,}\b/g,confidence:"exact"},{type:"bearer_token",regex:/\bBearer\s+[A-Za-z0-9._~+/=-]{16,}\b/gi,confidence:"high"},{type:"jwt",regex:/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,confidence:"exact"},{type:"private_key",regex:/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,confidence:"exact"},{type:"password_assignment",regex:/\b(?:password|passwd|pwd)\s*[:=]\s*["']?[^"' \t\r\n]{6,}["']?/gi,confidence:"high"},{type:"secret_assignment",regex:/\b(?:secret|api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|private[_-]?key)\s*[:=]\s*["']?[^"' \t\r\n]{8,}["']?/gi,confidence:"high"},{type:"cookie",regex:/\b(?:cookie|set-cookie)\s*[:=]\s*[^;\n]{8,}/gi,confidence:"high"},{type:"url_credentials",regex:/\bhttps?:\/\/[^:/@\s]+:[^/@\s]{4,}@[^/\s]+/gi,confidence:"high"}],wt=new Set(["example","example-key","example-token","changeme","change-me","password","secret","your-api-key","your-token","<token>","<secret>","<password>","[redacted]"]);function Re(r){return r.normalize("NFKC").trim().toLowerCase()}function Et(r){if(r.length===0)return 0;let e=new Map;for(let n of r)e.set(n,(e.get(n)??0)+1);let t=0;for(let n of e.values()){let o=n/r.length;t-=o*Math.log2(o)}return t}function vt(r){return/^[a-f0-9]{32}$/iu.test(r)||/^[a-f0-9]{40}$/iu.test(r)||/^[a-f0-9]{64}$/iu.test(r)}function Rt(r,e,t){let n=r.slice(Math.max(0,e-48),e),o=r.slice(t,Math.min(r.length,t+16));return/\b(?:token|secret|key|credential|authorization|password|passwd|apikey|api_key|access[_-]?key)\b/iu.test(`${n} ${o}`)}function At(r,e){return r.start<e.end&&e.start<r.end}function Ae(r){return r.sort((e,t)=>e.start!==t.start?e.start-t.start:t.end-t.start-(e.end-e.start))}var D=class{allowValues=new Set;enableEntropyHeuristic;constructor(e={}){for(let t of e.allowValues??[]){let n=Re(t);n&&this.allowValues.add(n)}this.enableEntropyHeuristic=e.enableEntropyHeuristic??!0}scan(e){let t=[];for(let s of kt){let a=new RegExp(s.regex.source,s.regex.flags);for(let c of e.matchAll(a))c.index===void 0||!c[0]||this.allowed(c[0])||t.push({type:s.type,value:c[0],start:c.index,end:c.index+c[0].length,confidence:s.confidence})}this.enableEntropyHeuristic&&t.push(...this.entropyMatches(e));let n=Ae(t),o=[];for(let s of n)o.some(a=>At(a,s))||o.push(s);return Ae(o)}hasSecrets(e){return this.scan(e).length>0}allowed(e){let t=Re(e);return wt.has(t)?!0:this.allowValues.has(t)}entropyMatches(e){let t=[],n=/[A-Za-z0-9_+/=-]{32,160}/g;for(let o of e.matchAll(n)){if(o.index===void 0||!o[0])continue;let s=o[0];this.allowed(s)||vt(s)||!/[A-Za-z]/u.test(s)||!/[0-9]/u.test(s)||Rt(e,o.index,o.index+s.length)&&(Et(s)<3.7||t.push({type:"high_entropy_secret",value:s,start:o.index,end:o.index+s.length,confidence:"heuristic"}))}return t}};var L=class{scanner;constructor(e={}){this.scanner=new D(e)}sanitize(e){let t=this.scanner.scan(e);if(t.length===0)return{text:e,redacted:0,secretTypes:[]};let n=e,o=[...t].sort((a,c)=>c.start-a.start),s=new Set;for(let a of o)s.add(a.type),n=n.slice(0,a.start)+`[REDACTED:${a.type}]`+n.slice(a.end);return{text:n,redacted:t.length,secretTypes:[...s].sort()}}sanitizeValue(e){if(typeof e=="string")return this.sanitize(e).text;if(Array.isArray(e))return e.map(t=>this.sanitizeValue(t));if(e&&typeof e=="object"){let t={};for(let[n,o]of Object.entries(e)){let s=n.normalize("NFKC").toLowerCase().replace(/[^a-z0-9]+/g,"");if(s.includes("password")||s.includes("passwd")||s==="pwd"||s.includes("secret")||s.includes("token")||s.includes("cookie")||s.includes("authorization")||s.includes("apikey")||s.includes("accesskey")||s.includes("privatekey")||s.includes("clientsecret")||s.includes("credential")){t[n]="[REDACTED]";continue}t[n]=this.sanitizeValue(o)}return t}return e}};var $r=new L;import{homedir as nr}from"node:os";import{join as or}from"node:path";import{DeleteObjectCommand as Mt,GetObjectCommand as It,HeadObjectCommand as Pt,ListObjectsV2Command as Tt,PutObjectCommand as jt,S3Client as Ct}from"@aws-sdk/client-s3";import{getSignedUrl as _t}from"@aws-sdk/s3-request-presigner";var z=class{name="huggingface";client;bucket;constructor(e){this.bucket=e.bucket,this.client=new Ct({region:"us-east-1",endpoint:`https://s3.hf.co/${e.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,n="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new jt({Bucket:this.bucket,Key:e,Body:o,ContentType:n}))}async get(e){let t=await _t(this.client,new It({Bucket:this.bucket,Key:e}),{expiresIn:60}),n=await fetch(t,{redirect:"follow"});if(n.status===404)return null;if(!n.ok)throw new Error(`HF download failed: ${n.status} ${n.statusText}`);return new Uint8Array(await n.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new Pt({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Mt({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],n;do{let o=await this.client.send(new Tt({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:n}));for(let s of o.Contents??[])s.Key&&t.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});n=o.IsTruncated?o.NextContinuationToken:void 0}while(n);return t}};import{access as Me,mkdir as Ot,readFile as Nt,readdir as Kt,rm as $t,stat as Ie,writeFile as Dt}from"node:fs/promises";import{dirname as Lt,join as zt,relative as Pe,resolve as Vt}from"node:path";var M=class{constructor(e){this.root=e}root;name="local";path(e){let t=e.replace(/^\/+/,"");return Vt(this.root,t)}async put(e,t){let n=this.path(e);await Ot(Lt(n),{recursive:!0}),await Dt(n,t)}async get(e){try{return await Nt(this.path(e))}catch(t){if(typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT")return null;throw t}}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await Me(this.path(e)),!0}catch{return!1}}async delete(e){await $t(this.path(e),{force:!0})}async list(e=""){let t=this.path(e),n=[];try{await Me(t)}catch{return n}let o=async a=>{let c=await Kt(a,{withFileTypes:!0});for(let u of c){let d=zt(a,u.name);if(u.isDirectory()){await o(d);continue}let f=await Ie(d);n.push({key:Pe(this.root,d),size:f.size,updatedAt:f.mtime.toISOString()})}},s=await Ie(t);return s.isDirectory()?await o(t):n.push({key:Pe(this.root,t),size:s.size,updatedAt:s.mtime.toISOString()}),n}};import{DeleteObjectCommand as Bt,GetObjectCommand as Yt,HeadObjectCommand as Ft,ListObjectsV2Command as Ut,PutObjectCommand as Gt,S3Client as Wt}from"@aws-sdk/client-s3";import{getSignedUrl as Ht}from"@aws-sdk/s3-request-presigner";var I=class{name;client;bucket;constructor(e){this.name=e.name??"s3",this.bucket=e.bucket,this.client=new Wt({region:e.region??"us-east-1",endpoint:e.endpoint||void 0,forcePathStyle:e.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})}async put(e,t,n="application/octet-stream"){let o=typeof t=="string"?Buffer.from(t,"utf8"):t;await this.client.send(new Gt({Bucket:this.bucket,Key:e,Body:o,ContentType:n}))}async get(e){let t=await Ht(this.client,new Yt({Bucket:this.bucket,Key:e}),{expiresIn:60}),n=await fetch(t,{redirect:"follow"});if(n.status===404)return null;if(!n.ok)throw new Error(`${this.name} download failed: ${n.status} ${n.statusText}`);return new Uint8Array(await n.arrayBuffer())}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){try{return await this.client.send(new Ft({Bucket:this.bucket,Key:e})),!0}catch(t){if(typeof t=="object"&&t!==null&&"$metadata"in t&&t.$metadata?.httpStatusCode===404)return!1;throw t}}async delete(e){await this.client.send(new Bt({Bucket:this.bucket,Key:e}))}async list(e=""){let t=[],n;do{let o=await this.client.send(new Ut({Bucket:this.bucket,Prefix:e||void 0,ContinuationToken:n}));for(let s of o.Contents??[])s.Key&&t.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});n=o.IsTruncated?o.NextContinuationToken:void 0}while(n);return t}};import{createCipheriv as Jt,createDecipheriv as qt,createHash as Zt,randomBytes as Qt,timingSafeEqual as Xt}from"node:crypto";import{readFileSync as er}from"node:fs";var k=Buffer.from("TNMEME01","ascii"),je=1,P=8,T=12,H=16,Ce=k.length+1+P+T+H,tr="toolnet-memory:remote-encryption:v1:",_e="aes-256-gcm",J=32,l=class extends Error{constructor(t,n){super(n);this.code=t;this.name="RemoteEncryptionError"}code};function rr(r){return r?["1","true","yes","on","enabled"].includes(r.trim().toLowerCase()):!1}function q(r=process.env){return rr(r.TOOLNET_REMOTE_ENCRYPTION)}function Te(r){let e=r.trim();if(!e)throw new l("REMOTE_ENCRYPTION_KEY_EMPTY","Remote encryption key is empty.");let t;if(e.startsWith("hex:")){let n=e.slice(4);if(!/^[0-9a-f]{64}$/iu.test(n))throw new l("REMOTE_ENCRYPTION_KEY_INVALID","hex: remote encryption key must contain exactly 64 hexadecimal characters.");t=Buffer.from(n,"hex")}else if(/^[0-9a-f]{64}$/iu.test(e))t=Buffer.from(e,"hex");else{let n=e.startsWith("base64:")?e.slice(7):e;if(!/^[A-Za-z0-9+/_-]+={0,2}$/u.test(n))throw new l("REMOTE_ENCRYPTION_KEY_INVALID","Remote encryption key must be 32 raw bytes encoded as hexadecimal or base64.");t=Buffer.from(n,n.includes("-")||n.includes("_")?"base64url":"base64")}if(t.length!==J)throw new l("REMOTE_ENCRYPTION_KEY_INVALID_LENGTH",`Remote encryption key must decode to exactly ${J} bytes.`);return t}function Oe(r=process.env){let e=r.TOOLNET_REMOTE_ENCRYPTION_KEY?.trim(),t=r.TOOLNET_REMOTE_ENCRYPTION_KEY_FILE?.trim();if(e&&t)throw new l("REMOTE_ENCRYPTION_KEY_AMBIGUOUS","Configure either TOOLNET_REMOTE_ENCRYPTION_KEY or TOOLNET_REMOTE_ENCRYPTION_KEY_FILE, not both.");if(e)return Te(e);if(t){let n;try{n=er(t,"utf8")}catch(o){throw new l("REMOTE_ENCRYPTION_KEY_FILE_READ_FAILED",[`Unable to read remote encryption key file: ${t}.`,o instanceof Error?o.message:String(o)].join(" "))}return Te(n)}}function Ne(r){return Zt("sha256").update(r).digest().subarray(0,P)}function Ke(r){return Buffer.from(`${tr}${r}`,"utf8")}function Z(r){return r.byteLength<k.length?!1:Buffer.from(r).subarray(0,k.length).equals(k)}function $e(r,e,t){if(t.byteLength!==J)throw new l("REMOTE_ENCRYPTION_KEY_INVALID_LENGTH","AES-256-GCM requires a 32-byte key.");let n=typeof e=="string"?Buffer.from(e,"utf8"):Buffer.from(e),o=Qt(T),s=Jt(_e,t,o);s.setAAD(Ke(r));let a=Buffer.concat([s.update(n),s.final()]),c=s.getAuthTag(),u=Buffer.alloc(Ce),d=0;return k.copy(u,d),d+=k.length,u.writeUInt8(je,d),d+=1,Ne(t).copy(u,d),d+=P,o.copy(u,d),d+=T,c.copy(u,d),Buffer.concat([u,a])}function De(r,e,t){let n=Buffer.from(e);if(!Z(n))throw new l("REMOTE_ENCRYPTION_ENVELOPE_REQUIRED","Payload is not a ToolNet encrypted remote object.");if(n.length<Ce)throw new l("REMOTE_ENCRYPTION_ENVELOPE_TRUNCATED","Encrypted remote payload is truncated.");let o=k.length,s=n.readUInt8(o);if(o+=1,s!==je)throw new l("REMOTE_ENCRYPTION_VERSION_UNSUPPORTED",`Unsupported remote encryption envelope version: ${s}.`);let a=n.subarray(o,o+P);o+=P;let c=Ne(t);if(!Xt(a,c))throw new l("REMOTE_ENCRYPTION_KEY_MISMATCH","Configured remote encryption key does not match this encrypted object.");let u=n.subarray(o,o+T);o+=T;let d=n.subarray(o,o+H);o+=H;let f=n.subarray(o),m=qt(_e,t,u);m.setAAD(Ke(r)),m.setAuthTag(d);try{return Buffer.concat([m.update(f),m.final()])}catch{throw new l("REMOTE_ENCRYPTION_AUTH_FAILED","Encrypted remote object failed AES-GCM authentication.")}}var Q=class{constructor(e,t){this.inner=e;this.options=t;if(this.name=e.name,t.enabled&&!t.key)throw new l("REMOTE_ENCRYPTION_KEY_REQUIRED","Remote client-side encryption is enabled but no encryption key is configured.")}inner;options;name;async put(e,t,n){if(!this.options.enabled){await this.inner.put(e,t,n);return}let o=this.options.key;if(!o)throw new l("REMOTE_ENCRYPTION_KEY_REQUIRED","Remote encryption key is unavailable.");let s=$e(e,t,o);await this.inner.put(e,s,"application/octet-stream")}async get(e){let t=await this.inner.get(e);if(!t)return null;if(!Z(t))return t;if(!this.options.enabled)throw new l("REMOTE_ENCRYPTION_REQUIRED",["Remote object is client-side encrypted.","Enable TOOLNET_REMOTE_ENCRYPTION and configure the matching key."].join(" "));let n=this.options.key;if(!n)throw new l("REMOTE_ENCRYPTION_KEY_REQUIRED","Remote object is encrypted but no decryption key is configured.");return De(e,t,n)}async getText(e){let t=await this.get(e);return t?Buffer.from(t).toString("utf8"):null}async exists(e){return this.inner.exists(e)}async delete(e){await this.inner.delete(e)}async list(e=""){return this.inner.list(e)}};function Le(r,e=process.env){if(r.name==="local")return q(e)&&console.warn("[storage] Remote encryption requested but active storage provider is local; local data remains unchanged."),r;let t=q(e),n=t?Oe(e):void 0;return new Q(r,{enabled:t,key:n})}function j(r){return Le(r)}function X(r,e){return console.warn(e),j(new M(r))}function ze(r){let e=r.localRoot??or(nr(),".toolnet-memory","storage");if(r.provider==="r2"){let t=r.r2;return t?.accountId&&t.bucket&&t.accessKeyId&&t.secretAccessKey?j(new I({name:"r2",endpoint:`https://${t.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:t.bucket,forcePathStyle:!0,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey})):X(e,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(r.provider==="s3"){let t=r.s3;return t?.bucket&&t.accessKeyId&&t.secretAccessKey?j(new I({name:"s3",endpoint:t.endpoint,region:t.region??"us-east-1",bucket:t.bucket,forcePathStyle:t.forcePathStyle??!1,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey})):X(e,"[storage] S3 credentials missing. Using local fallback.")}if(r.provider==="huggingface"){let t=r.huggingface;return t?.namespace&&t.bucket&&t.accessKeyId&&t.secretAccessKey?j(new z({namespace:t.namespace,bucket:t.bucket,accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey})):X(e,"[storage] Hugging Face credentials missing. Using local fallback.")}return j(new M(e))}function sr(r){return new Promise(e=>setTimeout(e,r))}async function Ve(r,e={}){let t=Math.max(1,e.attempts??3),n=e.baseDelayMs??150,o=e.maxDelayMs??2e3,s;for(let a=1;a<=t;a++)try{return await r()}catch(c){if(s=c,a>=t)break;let u=Math.min(o,n*2**(a-1)),d=Math.floor(Math.random()*Math.max(1,u*.2));await sr(u+d)}throw s}var ir=new Set(["put","get","getText","delete","list"]);function Be(r,e={}){return new Proxy(r,{get(t,n){let o=Reflect.get(t,n,t);return typeof o!="function"?o:ir.has(n)?(...s)=>Ve(()=>Promise.resolve(o.apply(t,s)),e):o.bind(t)}})}function Ye(r){let e=r.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!e||e==="."||e==="..")throw new Error("Invalid project storage folder");return e}function Fe(r){let e=r.replace(/^\/+/,"");if(e.startsWith("memories/"))return"memory/records/"+e.slice(9);if(e.startsWith("vectors/"))return"memory/vectors/"+e.slice(8);if(e.startsWith("graph/"))return"code/graph/"+e.slice(6);let t=e.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(t)return`${t[1]}memory/records/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),t)return`${t[1]}memory/vectors/${t[2]}`;if(t=e.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),t)return`${t[1]}code/graph/${t[2]}`;let n=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(n)return`${n[1]}memory/records/${n[2]}`;if(n=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),n)return`${n[1]}memory/vectors/${n[2]}`;if(n=e.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),n)return`${n[1]}code/graph/${n[2]}`;let o=e.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return o?`${o[1]}memory/records/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),o?`${o[1]}memory/vectors/${o[2]}`:(o=e.match(/^(projects\/[^/]+\/)graph\/(.+)$/),o?`${o[1]}code/graph/${o[2]}`:e))}var V=class{constructor(e,t,n,o){this.provider=e;this.name=e.name,this.projectId=t,this.projectName=n,this.folder=Ye(o??n),this.sourcePrefix=`projects/${t}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let e=`${this.targetPrefix}/project.json`,t=new Date().toISOString(),n=t,o=await this.provider.getText(e);if(o){let a;try{a=JSON.parse(o)}catch(c){throw new Error(`Invalid remote ToolNet project manifest at ${e}: ${c instanceof Error?c.message:String(c)}`)}if(typeof a.id=="string"&&a.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${a.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof a.createdAt=="string"&&(n=a.createdAt)}let s={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:n,updatedAt:t};await this.provider.put(e,JSON.stringify(s,null,2)+`
4
+ `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(e){if(e=Fe(e),e===this.sourcePrefix)return this.targetPrefix;if(e.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+e.slice(this.sourcePrefix.length);if(e===this.targetPrefix||e.startsWith(`${this.targetPrefix}/`))return e;if(e.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${e}`].join(" "));return e}async put(e,t,n){return await this.ensureRegistered(),this.provider.put(this.key(e),t,n)}async get(e){return await this.ensureRegistered(),this.provider.get(this.key(e))}async getText(e){return await this.ensureRegistered(),this.provider.getText(this.key(e))}async delete(e){return await this.ensureRegistered(),this.provider.delete(this.key(e))}async exists(e){return await this.ensureRegistered(),this.provider.exists(this.key(e))}async list(e){return await this.ensureRegistered(),this.provider.list(this.key(e))}};import{join as We}from"node:path";import{mkdirSync as pr}from"node:fs";import{createHash as ar}from"node:crypto";import{dirname as cr}from"node:path";import{mkdirSync as ur,readFileSync as Ko,renameSync as dr,writeFileSync as lr}from"node:fs";function Ue(r){return ar("sha256").update(r).digest("hex")}function Ge(r,e){ur(cr(r),{recursive:!0});let t=`${r}.${process.pid}.tmp`;lr(t,JSON.stringify(e,null,2)+`
5
+ `,{encoding:"utf8",mode:384}),dr(t,r)}function Je(r){return r.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").replace(/\s+/g," ").trim()}function g(r,e=20){let t=[],n=new Set;for(let o of r.slice().reverse()){let s=Je(o);if(!(!s||n.has(s))&&(n.add(s),t.push(o),t.length>=e))break}return t.reverse()}function mr(r,e=20){let t=new Map;for(let n of r){let o=`${n.kind}|${Je(n.command)}`;t.delete(o),t.set(o,n)}return Array.from(t.values()).slice(-e)}function fr(r){return r.kind==="phase"?/^Phase\s+\d+$/iu.test(r.text):r.kind==="task"?/^(?:TODO|Task|Việc)\s+\d+$/iu.test(r.text):!1}function He(r,e){let t=e.status??r?.status??"pending",n=t;r&&(r.status==="completed"&&t!=="completed"?n="completed":t==="pending"&&(r.status==="in_progress"||r.status==="blocked")&&(n=r.status));let o=r&&fr(e)?r.title:e.text;return{id:r?.id??Ue(e.key).slice(0,24),title:o,status:n,order:e.order??r?.order,confidence:Math.max(e.confidence,r?.confidence??0),updatedAt:e.occurredAt,updatedBy:{agent:e.agent,nativeSessionId:e.nativeSessionId,eventId:e.eventId}}}async function qe(r,e){let t=`projects/${r.id}/work/observations/`,n=await e.list(t),o=[];for(let s of n.filter(a=>a.key.endsWith(".json")).sort((a,c)=>a.key.localeCompare(c.key))){let a=await e.getText(s.key);if(a)try{let c=JSON.parse(a);c.version===1&&Array.isArray(c.observations)&&o.push(c)}catch{}}return o}async function ee(r,e){let n=(await qe(r,e)).flatMap(i=>i.observations).sort((i,p)=>{let pe=i.occurredAt.localeCompare(p.occurredAt);if(pe!==0)return pe;let me=i.sequence-p.sequence;return me!==0?me:i.id.localeCompare(p.id)}),o=new Map,s=new Map,a,c,u,d,f,m=[],h=[],re=[],ne=[],oe=[],B=new Map,se=[],ie=[],ae=[],ce=[],ue=[],de=[];for(let i of n)switch(i.kind){case"request":a=i.text;break;case"activity":c=i.text;break;case"goal":u=i.text;break;case"plan":d=i.text;break;case"phase":o.set(i.key,He(o.get(i.key),i));break;case"task":s.set(i.key,He(s.get(i.key),i));break;case"decision":m.push(i.text);break;case"blocker":h.push(i.text);break;case"warning":re.push(i.text);break;case"next_action":ne.push(i.text);break;case"file":{oe.push(i.text);let p=i.fileAction??"active";B.delete(i.text),B.set(i.text,p),p==="modified"?se.push(i.text):p==="created"?ie.push(i.text):p==="deleted"&&ae.push(i.text);break}case"command":ce.push(i.text);break;case"test":ue.push(i.text),i.checkKind&&de.push({kind:i.checkKind,command:i.text,status:i.checkStatus??"unknown",updatedAt:i.occurredAt,agent:i.agent,nativeSessionId:i.nativeSessionId});break;case"session":f={agent:i.agent,nativeSessionId:i.nativeSessionId,sessionKey:i.sessionKey,updatedAt:i.occurredAt};break}let S=Array.from(o.values()).sort((i,p)=>(i.order??Number.MAX_SAFE_INTEGER)-(p.order??Number.MAX_SAFE_INTEGER)),y=Array.from(s.values()).sort((i,p)=>(i.order??Number.MAX_SAFE_INTEGER)-(p.order??Number.MAX_SAFE_INTEGER)),Y=S.find(i=>i.status==="in_progress")??S.find(i=>i.status==="blocked")??S.find(i=>i.status==="pending"),C=y.find(i=>i.status==="in_progress")??y.find(i=>i.status==="blocked")??y.find(i=>i.status==="pending"),Qe=g([...ne,...C?[C.title]:[],...!C&&Y?[Y.title]:[],...y.filter(i=>i.status==="pending").slice(0,5).map(i=>i.title)],8),Xe=g([...h,...S.filter(i=>i.status==="blocked").map(i=>i.title),...y.filter(i=>i.status==="blocked").map(i=>i.title)],20),F={version:1,projectId:r.id,projectName:r.name,currentRequest:a,currentActivity:c,goal:u,plan:d,phases:S,tasks:y,decisions:g(m,20),blockers:Xe,warnings:g(re,20),nextActions:Qe,filesTouched:g(oe,30),activeFiles:Array.from(B.entries()).filter(([,i])=>i!=="deleted").map(([i])=>i).slice(-5),modifiedFiles:g(se,30),createdFiles:g(ie,30),deletedFiles:g(ae,30),commands:g(ce,20),tests:g(ue,20),checks:mr(de,20),currentPhase:Y,currentTask:C,progress:{phasesTotal:S.length,phasesCompleted:S.filter(i=>i.status==="completed").length,tasksTotal:y.length,tasksCompleted:y.filter(i=>i.status==="completed").length,blocked:S.filter(i=>i.status==="blocked").length+y.filter(i=>i.status==="blocked").length},lastSession:f,updatedAt:n.length?n[n.length-1].occurredAt:new Date().toISOString()},le=We(r.rootPath,".toolnet","work");return pr(le,{recursive:!0}),Ge(We(le,"current.json"),F),await e.put(`projects/${r.id}/work/current.json`,JSON.stringify(F,null,2)+`
6
+ `,"application/json"),F}async function te(r,e){if((await qe(r,e)).length>0)return ee(r,e);let n=await e.getText(`projects/${r.id}/work/current.json`);if(!n)return null;try{return JSON.parse(n)}catch{return null}}function gr(r,e){let t=r.indexOf(e);return t>=0?r[t+1]:void 0}function yr(r){let e=fe(),t=Be(ze({provider:e.storage.provider,huggingface:e.storage.huggingface,localRoot:e.storage.localRoot}),{attempts:3});return new V(t,r.id,r.name,r.remote??r.name)}function Ze(r){console.log(),console.log(`Project: ${r.projectName}`),r.goal&&console.log(`Goal: ${r.goal}`),r.currentPhase&&console.log(`Current phase: ${r.currentPhase.title} [${r.currentPhase.status}]`),r.currentTask&&console.log(`Current task: ${r.currentTask.title} [${r.currentTask.status}]`),console.log(`Phases: ${r.progress.phasesCompleted}/${r.progress.phasesTotal}`),console.log(`Tasks: ${r.progress.tasksCompleted}/${r.progress.tasksTotal}`),r.nextActions.length&&(console.log(),console.log("Next actions:"),r.nextActions.forEach((e,t)=>console.log(`${t+1}. ${e}`))),r.blockers.length&&(console.log(),console.log("Blockers:"),r.blockers.forEach(e=>console.log(`- ${e}`))),r.lastSession&&(console.log(),console.log(`Last session: ${r.lastSession.agent} / ${r.lastSession.nativeSessionId}`))}async function hr(){let[r="help",...e]=process.argv.slice(2),t=new $().detect(gr(e,"--project")??process.cwd()),n=yr(t);if(r==="reconcile"){let o=await ee(t,n);Ze(o);return}if(r==="status"){let o=await te(t,n);if(!o){console.log("No active work state yet.");return}Ze(o);return}if(r==="json"){let o=await te(t,n);console.log(JSON.stringify(o,null,2));return}console.log(`Work Continuity
7
7
 
8
8
  Commands:
9
9
  status [--project PATH]
10
10
  json [--project PATH]
11
11
  reconcile [--project PATH]
12
- `)}Wt().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});
12
+ `)}hr().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolnet-memory",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "Persistent project memory, work continuity, and code intelligence for AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -124,7 +124,30 @@
124
124
  "memory:conflict-v2:audit": "node scripts/conflict-v2-audit.mjs",
125
125
  "memory:conflict-v2:test": "vitest run tests/memory/conflict-v2.test.ts",
126
126
  "release:verify:v0316": "node scripts/verify-v0316-contract.mjs",
127
- "project:identity:audit": "node scripts/project-identity-audit.mjs"
127
+ "project:identity:audit": "node scripts/project-identity-audit.mjs",
128
+ "graph:security:audit": "node scripts/graph-security-audit.mjs",
129
+ "graph:security:test": "vitest run tests/visualization/graph-security.test.ts",
130
+ "docker:contract:audit": "node scripts/docker-contract-audit.mjs",
131
+ "docker:contract:test": "vitest run tests/production/docker-contract.test.ts",
132
+ "docker:build": "docker build -t toolnet-memory:local .",
133
+ "docker:smoke": "bash scripts/docker-smoke.sh",
134
+ "security:remote-encryption:audit": "node scripts/remote-encryption-audit.mjs",
135
+ "security:remote-encryption:test": "vitest run tests/security/remote-encryption.test.ts",
136
+ "storage:scope:audit": "node scripts/storage-scope-audit.mjs",
137
+ "standalone:build": "node scripts/build-standalone.mjs",
138
+ "standalone:smoke": "bash scripts/standalone-smoke.sh",
139
+ "standalone:routes:audit": "node scripts/standalone-route-audit.mjs",
140
+ "standalone:contract:test": "vitest run tests/production/standalone-contract.test.ts",
141
+ "standalone:contract:audit": "node scripts/standalone-contract-audit.mjs",
142
+ "audit:test": "vitest run tests/audit/audit-log.test.ts",
143
+ "audit:verify": "node bundle/audit.js verify",
144
+ "retention:auto-gc:test": "vitest run tests/retention/auto-gc-scheduler.test.ts",
145
+ "phase29:audit": "node scripts/phase29-audit.mjs",
146
+ "code:non-ts:test": "vitest run tests/code-intelligence/non-ts-foundation.test.ts",
147
+ "phase30:audit": "node scripts/phase30-audit.mjs",
148
+ "code:capabilities": "node bundle/code-capabilities.js",
149
+ "release:verify:v0317": "node scripts/verify-v0317-contract.mjs",
150
+ "release:certify:v0317": "bash scripts/final-release-certify-v0317.sh"
128
151
  },
129
152
  "dependencies": {
130
153
  "@aws-sdk/client-s3": "^3.1102.0",
@@ -138,6 +161,7 @@
138
161
  "@types/node": "^24.0.0",
139
162
  "@typescript-eslint/eslint-plugin": "^8.66.0",
140
163
  "@typescript-eslint/parser": "^8.66.0",
164
+ "@yao-pkg/pkg": "6.22.0",
141
165
  "3d-force-graph": "^1.80.0",
142
166
  "esbuild": "^0.28.1",
143
167
  "eslint": "^10.8.1",
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "toolnet-memory",
4
- "version": "0.3.16",
4
+ "version": "0.3.17",
5
5
  "releaseSeries": "0.3.x",
6
6
  "runtime": {
7
7
  "node": ">=22",
8
8
  "localMemoryRuntime": true,
9
9
  "requiresLlm": false,
10
10
  "requiresEmbeddings": false,
11
- "retrieval": ["BM25", "SQLite FTS5"],
11
+ "retrieval": [
12
+ "BM25",
13
+ "SQLite FTS5"
14
+ ],
12
15
  "sharedProjectMemory": true,
13
16
  "multiHostConvergence": true,
14
17
  "storageImplementationFrozen": true
@@ -93,7 +96,11 @@
93
96
  "remoteDistributedLock": false
94
97
  },
95
98
  "largeRepository": {
96
- "benchmarkProfiles": ["10k", "50k", "100k"],
99
+ "benchmarkProfiles": [
100
+ "10k",
101
+ "50k",
102
+ "100k"
103
+ ],
97
104
  "maxCodeFileBytes": 2097152,
98
105
  "symlinkTraversal": false,
99
106
  "boundedParseConcurrency": true,
@@ -104,7 +111,16 @@
104
111
  "codeIntelligence": {
105
112
  "parserEngine": "typescript-compiler-api",
106
113
  "treeSitterRuntime": false,
107
- "supportedExtensions": [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"],
114
+ "supportedExtensions": [
115
+ ".ts",
116
+ ".tsx",
117
+ ".js",
118
+ ".jsx",
119
+ ".mts",
120
+ ".cts",
121
+ ".mjs",
122
+ ".cjs"
123
+ ],
108
124
  "tsconfigPaths": true,
109
125
  "baseUrlResolution": true,
110
126
  "projectReferences": true,
@@ -160,7 +176,15 @@
160
176
  "deterministic": true,
161
177
  "llmMerge": false,
162
178
  "embeddingMatching": false,
163
- "kinds": ["rule", "decision", "todo", "next_action", "fix", "context", "architecture"],
179
+ "kinds": [
180
+ "rule",
181
+ "decision",
182
+ "todo",
183
+ "next_action",
184
+ "fix",
185
+ "context",
186
+ "architecture"
187
+ ],
164
188
  "topicMatching": true,
165
189
  "entityMatching": true,
166
190
  "lexicalSubjectMatching": true,
@@ -169,6 +193,71 @@
169
193
  "conflictingMemoryActive": false,
170
194
  "completedMemoryActive": false,
171
195
  "resolvedMemoryActive": false
196
+ },
197
+ "graphExposure": {
198
+ "localhostDefault": true,
199
+ "optionalBearerAuthentication": true,
200
+ "hostHeaderProtection": true,
201
+ "sameOriginApiProtection": true,
202
+ "minimalUnauthenticatedHealth": true
203
+ },
204
+ "docker": {
205
+ "officialDockerfile": true,
206
+ "nodeRuntime": 22,
207
+ "multiStage": true,
208
+ "nonRoot": true,
209
+ "ipcHealthcheck": true,
210
+ "ghcr": true,
211
+ "multiArch": [
212
+ "linux/amd64",
213
+ "linux/arm64"
214
+ ]
215
+ },
216
+ "remoteEncryption": {
217
+ "supported": true,
218
+ "defaultEnabled": false,
219
+ "keyRequiredByDefault": false,
220
+ "algorithm": "AES-256-GCM",
221
+ "existingPlaintextReadable": true,
222
+ "automaticMigration": false
223
+ },
224
+ "standalone": {
225
+ "supported": true,
226
+ "nodeRequiredOnTarget": false,
227
+ "linuxX64": true,
228
+ "linuxArm64": true,
229
+ "macosX64": true,
230
+ "macosArm64": true,
231
+ "windowsX64": true
232
+ },
233
+ "auditLog": {
234
+ "projectScoped": true,
235
+ "appendOnly": true,
236
+ "sha256HashChain": true,
237
+ "durableSanitization": true,
238
+ "rawMemoryContent": false,
239
+ "rawGuardCommand": false
240
+ },
241
+ "autoGc": {
242
+ "supported": true,
243
+ "enabledByDefault": false,
244
+ "defaultIntervalHours": 168,
245
+ "remoteGcEnabledByDefault": false,
246
+ "implicitProjectInit": false,
247
+ "filesystemProjectScan": false
248
+ },
249
+ "nonTsIntelligence": {
250
+ "localCodeSearch": [
251
+ "python",
252
+ "go",
253
+ "rust",
254
+ "c",
255
+ "cpp"
256
+ ],
257
+ "engine": "sqlite-fts5-bm25",
258
+ "structuralParserSupport": false,
259
+ "lspFoundation": "detection-only",
260
+ "automaticLspDownload": false
172
261
  }
173
262
  }
174
263
  }