toolnet-memory 0.3.15 → 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 (48) hide show
  1. package/.env.example +74 -0
  2. package/.release-target +1 -1
  3. package/README.md +316 -6
  4. package/bin/toolnet-memory +12 -0
  5. package/bundle/agy-hook.js +66 -66
  6. package/bundle/agy.js +56 -56
  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/claude-hook.js +49 -49
  11. package/bundle/code-capabilities.js +2 -0
  12. package/bundle/codex-context.js +29 -29
  13. package/bundle/codex.js +71 -71
  14. package/bundle/context-runtime.js +39 -39
  15. package/bundle/context.js +15 -15
  16. package/bundle/copilot-hook.js +78 -78
  17. package/bundle/cursor-hook.js +78 -78
  18. package/bundle/docker-healthcheck.js +3 -0
  19. package/bundle/doctor.js +4 -4
  20. package/bundle/full-index.js +22 -21
  21. package/bundle/gc.js +5 -3
  22. package/bundle/graph-cli.js +1 -1
  23. package/bundle/graph-index.js +5 -4
  24. package/bundle/graph-ui.js +4 -3
  25. package/bundle/grok-hook.js +56 -56
  26. package/bundle/handoff-refresh.js +10 -10
  27. package/bundle/help-cli.js +4 -4
  28. package/bundle/impact.js +5 -4
  29. package/bundle/incremental.js +5 -5
  30. package/bundle/init.js +36 -35
  31. package/bundle/kiro-hook.js +85 -85
  32. package/bundle/learner.js +8 -6
  33. package/bundle/mcp.js +19 -17
  34. package/bundle/memory-agent.js +6 -6
  35. package/bundle/memory-query.js +7 -7
  36. package/bundle/opencode.js +77 -77
  37. package/bundle/project-manual.js +7 -7
  38. package/bundle/public/index.html +51 -2
  39. package/bundle/recovery-certify.js +9 -9
  40. package/bundle/runtime.js +16 -13
  41. package/bundle/semantic.js +6 -5
  42. package/bundle/service.js +11 -6
  43. package/bundle/snapshot.js +6 -4
  44. package/bundle/status-cli.js +4 -4
  45. package/bundle/toolnet-cli.js +46 -46
  46. package/bundle/work-continuity.js +7 -7
  47. package/package.json +38 -2
  48. package/release-manifest.json +143 -4
package/bundle/context.js CHANGED
@@ -1,8 +1,8 @@
1
- import{existsSync as Ve,readFileSync as ze}from"node:fs";import{homedir as Be}from"node:os";import{join as He}from"node:path";function Je(r){let t=r.trim();return t.length>=2&&t.startsWith('"')&&t.endsWith('"')?(t=t.slice(1,-1),t.replace(/\\n/g,`
2
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):t.length>=2&&t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Ue(){let r=process.env.TOOLNET_GLOBAL_ENV??He(Be(),".config","toolnet-memory",".env");if(!Ve(r))return;let t=ze(r,"utf8");for(let e of t.split(/\r?\n/)){let n=e.trim();if(!n||n.startsWith("#"))continue;n.startsWith("export ")&&(n=n.slice(7));let s=n.indexOf("=");if(s<=0)continue;let o=n.slice(0,s).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(o)&&process.env[o]===void 0&&(process.env[o]=Je(n.slice(s+1)))}}Ue();function P(r,t){return r===void 0?t:["1","true","yes","on"].includes(r.toLowerCase())}function M(r,t){if(!r)return t;let e=Number(r);return Number.isFinite(e)?e:t}function de(){return{memory:{autoCapture:P(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:P(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:P(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:P(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:M(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:M(process.env.MEMORY_RERANK_TOP,10),finalContext:M(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:M(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:P(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:M(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as qe}from"node:crypto";import{existsSync as _,mkdirSync as Ge,readFileSync as Ye,renameSync as Qe,writeFileSync as Ze}from"node:fs";import{basename as Xe,dirname as $,join as I,parse as ge,resolve as j}from"node:path";var he=".toolnet",et="project.json";function tt(r){return qe("sha256").update(r).digest("hex").slice(0,16)}function W(r){return I(r,he,et)}function rt(r){return _(W(r))}function le(r,t){let e=j(r),n=ge(e).root;for(;;){if(rt(e))return e;if(e===n||t&&e===j(t))break;let s=$(e);if(s===e)break;e=s}return null}function pe(r){let t=j(r),e=ge(t).root,n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(n.some(o=>_(I(t,o))))return t;if(t===e)break;let s=$(t);if(s===t)break;t=s}return j(r)}function fe(r){let t;try{t=JSON.parse(Ye(r,"utf8"))}catch(s){throw new Error(`Invalid ToolNet project manifest: ${r}: ${s instanceof Error?s.message:String(s)}`)}if(!t||typeof t!="object")throw new Error(`Invalid ToolNet project manifest: ${r}`);let e=t;if(typeof e.id!="string"||!e.id.trim())throw new Error(`ToolNet project manifest is missing id: ${r}`);if(typeof e.name!="string"||!e.name.trim())throw new Error(`ToolNet project manifest is missing name: ${r}`);let n=new Date().toISOString();return{version:1,id:e.id,name:e.name,remote:typeof e.remote=="string"&&e.remote.trim()?e.remote:e.name,rootPath:typeof e.rootPath=="string"?e.rootPath:$($(r)),createdAt:typeof e.createdAt=="string"?e.createdAt:n,updatedAt:typeof e.updatedAt=="string"?e.updatedAt:n,graphVersion:typeof e.graphVersion=="number"?e.graphVersion:0,memoryVersion:typeof e.memoryVersion=="number"?e.memoryVersion:0,metadata:e.metadata&&typeof e.metadata=="object"?e.metadata:void 0}}function me(r,t){let e=I(r,he);Ge(e,{recursive:!0});let n=W(r),s=`${n}.tmp-${process.pid}`;Ze(s,JSON.stringify(t,null,2)+`
3
- `,{encoding:"utf8",mode:384}),Qe(s,n)}function Q(r,t){return{id:r.id,name:r.name,remote:r.remote,rootPath:t,createdAt:r.createdAt,updatedAt:r.updatedAt,graphVersion:r.graphVersion,memoryVersion:r.memoryVersion,metadata:r.metadata}}var K=class{findExisting(t=process.cwd()){let e=j(t),n=pe(e),o=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(u=>_(I(n,u))),a=le(e,o?n:void 0);if(!a)return null;let d=fe(W(a));return Q(d,a)}requireExisting(t=process.cwd()){let e=this.findExisting(t);if(!e)throw new Error("PROJECT_NOT_INITIALIZED");return e}detect(t=process.cwd()){let e=j(t),n=pe(e),o=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(g=>_(I(n,g))),a=le(e,o?n:void 0);if(a){let g=W(a),c=fe(g);return c.rootPath!==a&&(c.rootPath=a,c.updatedAt=new Date().toISOString(),me(a,c)),Q(c,a)}let d=new Date().toISOString(),u=Xe(n),p={version:1,id:tt(n),name:u,remote:u,rootPath:n,createdAt:d,updatedAt:d,graphVersion:0,memoryVersion:0};return me(n,p),Q(p,n)}};var nt=[{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"}],st=new Set(["example","example-key","example-token","changeme","change-me","password","secret","your-api-key","your-token","<token>","<secret>","<password>","[redacted]"]);function ye(r){return r.normalize("NFKC").trim().toLowerCase()}function ot(r){if(r.length===0)return 0;let t=new Map;for(let n of r)t.set(n,(t.get(n)??0)+1);let e=0;for(let n of t.values()){let s=n/r.length;e-=s*Math.log2(s)}return e}function it(r){return/^[a-f0-9]{32}$/iu.test(r)||/^[a-f0-9]{40}$/iu.test(r)||/^[a-f0-9]{64}$/iu.test(r)}function at(r,t,e){let n=r.slice(Math.max(0,t-48),t),s=r.slice(e,Math.min(r.length,e+16));return/\b(?:token|secret|key|credential|authorization|password|passwd|apikey|api_key|access[_-]?key)\b/iu.test(`${n} ${s}`)}function ct(r,t){return r.start<t.end&&t.start<r.end}function ke(r){return r.sort((t,e)=>t.start!==e.start?t.start-e.start:e.end-e.start-(t.end-t.start))}var D=class{allowValues=new Set;enableEntropyHeuristic;constructor(t={}){for(let e of t.allowValues??[]){let n=ye(e);n&&this.allowValues.add(n)}this.enableEntropyHeuristic=t.enableEntropyHeuristic??!0}scan(t){let e=[];for(let o of nt){let a=new RegExp(o.regex.source,o.regex.flags);for(let d of t.matchAll(a))d.index===void 0||!d[0]||this.allowed(d[0])||e.push({type:o.type,value:d[0],start:d.index,end:d.index+d[0].length,confidence:o.confidence})}this.enableEntropyHeuristic&&e.push(...this.entropyMatches(t));let n=ke(e),s=[];for(let o of n)s.some(a=>ct(a,o))||s.push(o);return ke(s)}hasSecrets(t){return this.scan(t).length>0}allowed(t){let e=ye(t);return st.has(e)?!0:this.allowValues.has(e)}entropyMatches(t){let e=[],n=/[A-Za-z0-9_+/=-]{32,160}/g;for(let s of t.matchAll(n)){if(s.index===void 0||!s[0])continue;let o=s[0];this.allowed(o)||it(o)||!/[A-Za-z]/u.test(o)||!/[0-9]/u.test(o)||at(t,s.index,s.index+o.length)&&(ot(o)<3.7||e.push({type:"high_entropy_secret",value:o,start:s.index,end:s.index+o.length,confidence:"heuristic"}))}return e}};var L=class{scanner;constructor(t={}){this.scanner=new D(t)}sanitize(t){let e=this.scanner.scan(t);if(e.length===0)return{text:t,redacted:0,secretTypes:[]};let n=t,s=[...e].sort((a,d)=>d.start-a.start),o=new Set;for(let a of s)o.add(a.type),n=n.slice(0,a.start)+`[REDACTED:${a.type}]`+n.slice(a.end);return{text:n,redacted:e.length,secretTypes:[...o].sort()}}sanitizeValue(t){if(typeof t=="string")return this.sanitize(t).text;if(Array.isArray(t))return t.map(e=>this.sanitizeValue(e));if(t&&typeof t=="object"){let e={};for(let[n,s]of Object.entries(t)){let o=n.normalize("NFKC").toLowerCase().replace(/[^a-z0-9]+/g,"");if(o.includes("password")||o.includes("passwd")||o==="pwd"||o.includes("secret")||o.includes("token")||o.includes("cookie")||o.includes("authorization")||o.includes("apikey")||o.includes("accesskey")||o.includes("privatekey")||o.includes("clientsecret")||o.includes("credential")){e[n]="[REDACTED]";continue}e[n]=this.sanitizeValue(s)}return e}return t}};var Er=new L;import{homedir as Et}from"node:os";import{join as Ot}from"node:path";import{DeleteObjectCommand as dt,GetObjectCommand as lt,HeadObjectCommand as pt,ListObjectsV2Command as ft,PutObjectCommand as mt,S3Client as gt}from"@aws-sdk/client-s3";import{getSignedUrl as ht}from"@aws-sdk/s3-request-presigner";var F=class{name="huggingface";client;bucket;constructor(t){this.bucket=t.bucket,this.client=new gt({region:"us-east-1",endpoint:`https://s3.hf.co/${t.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,n="application/octet-stream"){let s=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new mt({Bucket:this.bucket,Key:t,Body:s,ContentType:n}))}async get(t){let e=await ht(this.client,new lt({Bucket:this.bucket,Key:t}),{expiresIn:60}),n=await fetch(e,{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(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new pt({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new dt({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],n;do{let s=await this.client.send(new ft({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:n}));for(let o of s.Contents??[])o.Key&&e.push({key:o.Key,size:o.Size,updatedAt:o.LastModified?.toISOString()});n=s.IsTruncated?s.NextContinuationToken:void 0}while(n);return e}};import{access as Se,mkdir as yt,readFile as kt,readdir as St,rm as bt,stat as be,writeFile as vt}from"node:fs/promises";import{dirname as wt,join as xt,relative as ve,resolve as jt}from"node:path";var R=class{constructor(t){this.root=t}root;name="local";path(t){let e=t.replace(/^\/+/,"");return jt(this.root,e)}async put(t,e){let n=this.path(t);await yt(wt(n),{recursive:!0}),await vt(n,e)}async get(t){try{return await kt(this.path(t))}catch(e){if(typeof e=="object"&&e!==null&&"code"in e&&e.code==="ENOENT")return null;throw e}}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await Se(this.path(t)),!0}catch{return!1}}async delete(t){await bt(this.path(t),{force:!0})}async list(t=""){let e=this.path(t),n=[];try{await Se(e)}catch{return n}let s=async a=>{let d=await St(a,{withFileTypes:!0});for(let u of d){let p=xt(a,u.name);if(u.isDirectory()){await s(p);continue}let g=await be(p);n.push({key:ve(this.root,p),size:g.size,updatedAt:g.mtime.toISOString()})}},o=await be(e);return o.isDirectory()?await s(e):n.push({key:ve(this.root,e),size:o.size,updatedAt:o.mtime.toISOString()}),n}};import{DeleteObjectCommand as At,GetObjectCommand as Pt,HeadObjectCommand as Mt,ListObjectsV2Command as It,PutObjectCommand as Rt,S3Client as Tt}from"@aws-sdk/client-s3";import{getSignedUrl as Ct}from"@aws-sdk/s3-request-presigner";var T=class{name;client;bucket;constructor(t){this.name=t.name??"s3",this.bucket=t.bucket,this.client=new Tt({region:t.region??"us-east-1",endpoint:t.endpoint||void 0,forcePathStyle:t.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,n="application/octet-stream"){let s=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new Rt({Bucket:this.bucket,Key:t,Body:s,ContentType:n}))}async get(t){let e=await Ct(this.client,new Pt({Bucket:this.bucket,Key:t}),{expiresIn:60}),n=await fetch(e,{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(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new Mt({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new At({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],n;do{let s=await this.client.send(new It({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:n}));for(let o of s.Contents??[])o.Key&&e.push({key:o.Key,size:o.Size,updatedAt:o.LastModified?.toISOString()});n=s.IsTruncated?s.NextContinuationToken:void 0}while(n);return e}};function Z(r,t){return console.warn(t),new R(r)}function we(r){let t=r.localRoot??Ot(Et(),".toolnet-memory","storage");if(r.provider==="r2"){let e=r.r2;return e?.accountId&&e.bucket&&e.accessKeyId&&e.secretAccessKey?new T({name:"r2",endpoint:`https://${e.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:e.bucket,forcePathStyle:!0,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):Z(t,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(r.provider==="s3"){let e=r.s3;return e?.bucket&&e.accessKeyId&&e.secretAccessKey?new T({name:"s3",endpoint:e.endpoint,region:e.region??"us-east-1",bucket:e.bucket,forcePathStyle:e.forcePathStyle??!1,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):Z(t,"[storage] S3 credentials missing. Using local fallback.")}if(r.provider==="huggingface"){let e=r.huggingface;return e?.namespace&&e.bucket&&e.accessKeyId&&e.secretAccessKey?new F({namespace:e.namespace,bucket:e.bucket,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}):Z(t,"[storage] Hugging Face credentials missing. Using local fallback.")}return new R(t)}function Nt(r){return new Promise(t=>setTimeout(t,r))}async function xe(r,t={}){let e=Math.max(1,t.attempts??3),n=t.baseDelayMs??150,s=t.maxDelayMs??2e3,o;for(let a=1;a<=e;a++)try{return await r()}catch(d){if(o=d,a>=e)break;let u=Math.min(s,n*2**(a-1)),p=Math.floor(Math.random()*Math.max(1,u*.2));await Nt(u+p)}throw o}var _t=new Set(["put","get","getText","delete","list"]);function je(r,t={}){return new Proxy(r,{get(e,n){let s=Reflect.get(e,n,e);return typeof s!="function"?s:_t.has(n)?(...o)=>xe(()=>Promise.resolve(s.apply(e,o)),t):s.bind(e)}})}function Ae(r){let t=r.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!t||t==="."||t==="..")throw new Error("Invalid project storage folder");return t}function Pe(r){let t=r.replace(/^\/+/,"");if(t.startsWith("memories/"))return"memory/records/"+t.slice(9);if(t.startsWith("vectors/"))return"memory/vectors/"+t.slice(8);if(t.startsWith("graph/"))return"code/graph/"+t.slice(6);let e=t.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(e)return`${e[1]}memory/records/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),e)return`${e[1]}memory/vectors/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),e)return`${e[1]}code/graph/${e[2]}`;let n=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(n)return`${n[1]}memory/records/${n[2]}`;if(n=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),n)return`${n[1]}memory/vectors/${n[2]}`;if(n=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),n)return`${n[1]}code/graph/${n[2]}`;let s=t.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return s?`${s[1]}memory/records/${s[2]}`:(s=t.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),s?`${s[1]}memory/vectors/${s[2]}`:(s=t.match(/^(projects\/[^/]+\/)graph\/(.+)$/),s?`${s[1]}code/graph/${s[2]}`:t))}var V=class{constructor(t,e,n,s){this.provider=t;this.name=t.name,this.projectId=e,this.projectName=n,this.folder=Ae(s??n),this.sourcePrefix=`projects/${e}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let t=`${this.targetPrefix}/project.json`,e=new Date().toISOString(),n=e,s=await this.provider.getText(t);if(s){let a;try{a=JSON.parse(s)}catch(d){throw new Error(`Invalid remote ToolNet project manifest at ${t}: ${d instanceof Error?d.message:String(d)}`)}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 o={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:n,updatedAt:e};await this.provider.put(t,JSON.stringify(o,null,2)+`
4
- `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(t){if(t=Pe(t),t===this.sourcePrefix)return this.targetPrefix;if(t.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+t.slice(this.sourcePrefix.length);if(t===this.targetPrefix||t.startsWith(`${this.targetPrefix}/`))return t;if(t.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${t}`].join(" "));return t}async put(t,e,n){return await this.ensureRegistered(),this.provider.put(this.key(t),e,n)}async get(t){return await this.ensureRegistered(),this.provider.get(this.key(t))}async getText(t){return await this.ensureRegistered(),this.provider.getText(this.key(t))}async delete(t){return await this.ensureRegistered(),this.provider.delete(this.key(t))}async exists(t){return await this.ensureRegistered(),this.provider.exists(this.key(t))}async list(t){return await this.ensureRegistered(),this.provider.list(this.key(t))}};import{join as Ft}from"node:path";import{createHash as $t}from"node:crypto";import{dirname as Wt}from"node:path";import{mkdirSync as Kt,readFileSync as ks,renameSync as Dt,writeFileSync as Lt}from"node:fs";function S(r){return $t("sha256").update(r).digest("hex")}function A(r,t){Kt(Wt(r),{recursive:!0});let e=`${r}.${process.pid}.tmp`;Lt(e,JSON.stringify(t,null,2)+`
5
- `,{encoding:"utf8",mode:384}),Dt(e,r)}function Me(r,t){let e=r.trim(),n=e.replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,""),s=S(e).slice(0,12);if(!n||n==="."||n==="..")return`${t}--${s}`;let o=n.slice(0,100);return o===e&&e.length<=100?o:`${o.slice(0,85)}--${s}`}function Ie(r,t,e){let n=t.trim(),s=e.trim();if(!n)throw new Error("Session agent is required");if(!s)throw new Error("Native session ID is required");let o=Me(n.toLowerCase(),"agent"),a=Me(s,"session");return{projectId:r.id,projectName:r.name,projectRoot:r.rootPath,agent:n,nativeSessionId:s,sessionKey:`${n}:${s}`,remotePrefix:["projects",r.id,"runtime","sources",o,a].join("/"),localDirectory:Ft(r.rootPath,".toolnet","runtime","sources",o,a)}}import{existsSync as Ce,mkdirSync as Vt,readFileSync as zt,statSync as Re,writeFileSync as Bt}from"node:fs";import{dirname as Ht,join as Jt}from"node:path";var Te=64*1024,Ut=`# ToolNet Project Operating Manual
1
+ import{existsSync as kt,readFileSync as St}from"node:fs";import{homedir as bt}from"node:os";import{join as wt}from"node:path";function xt(r){let t=r.trim();return t.length>=2&&t.startsWith('"')&&t.endsWith('"')?(t=t.slice(1,-1),t.replace(/\\n/g,`
2
+ `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")):t.length>=2&&t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function vt(){let r=process.env.TOOLNET_GLOBAL_ENV??wt(bt(),".config","toolnet-memory",".env");if(!kt(r))return;let t=St(r,"utf8");for(let e of t.split(/\r?\n/)){let n=e.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]=xt(n.slice(o+1)))}}vt();function M(r,t){return r===void 0?t:["1","true","yes","on"].includes(r.toLowerCase())}function T(r,t){if(!r)return t;let e=Number(r);return Number.isFinite(e)?e:t}function Ae(){return{memory:{autoCapture:M(process.env.MEMORY_AUTO_CAPTURE,!0),autoRetrieve:M(process.env.MEMORY_AUTO_RETRIEVE,!0),autoSummarize:M(process.env.MEMORY_AUTO_SUMMARIZE,!0),autoSync:M(process.env.MEMORY_AUTO_SYNC,!0)},retrieval:{maxCandidates:T(process.env.MEMORY_MAX_CANDIDATES,50),rerankTop:T(process.env.MEMORY_RERANK_TOP,10),finalContext:T(process.env.MEMORY_FINAL_CONTEXT,5),tokenBudget:T(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:M(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:T(process.env.MEMORY_LOCAL_CACHE_MB,200)}}}import{createHash as Mt}from"node:crypto";import{existsSync as H,mkdirSync as Tt,readFileSync as Ct,renameSync as Ot,writeFileSync as _t}from"node:fs";import{basename as Nt,dirname as U,join as O,parse as _e,resolve as A}from"node:path";import{createHash as Ie}from"node:crypto";import{spawnSync as Et}from"node:child_process";var C="git-remote-v1",jt=new Set(["github.com","gitlab.com","bitbucket.org"]);function Me(r,t){let e=t.replaceAll("\\","/").replace(/^\/+/u,"").replace(/\/+$/u,"").replace(/\.git$/iu,"").replace(/\/+/gu,"/");return!e||e==="."||e===".."||e.split("/").some(n=>!n||n==="."||n==="..")?null:(jt.has(r)&&(e=e.toLowerCase()),e)}function At(r){let t;try{t=new URL(r)}catch{return null}if(!["https:","http:","ssh:","git:"].includes(t.protocol))return null;let e=t.hostname.trim().toLowerCase();if(!e)return null;let n=t.protocol==="https:"&&t.port==="443"||t.protocol==="http:"&&t.port==="80"||t.protocol==="ssh:"&&t.port==="22",o=t.port&&!n?`${e}:${t.port}`:e,s=Me(e,t.pathname);return s?`${o}/${s}`:null}function Pt(r){let t=r.match(/^(?:[^@\s/:]+@)?([^:/\s]+):(.+)$/u);if(!t)return null;let e=t[1]?.trim().toLowerCase();if(!e||e.length===1)return null;let n=Me(e,t[2]??"");return n?`${e}/${n}`:null}function Pe(r){let t=r.trim();return t?t.includes("://")?At(t):Pt(t):null}function Rt(r){return Ie("sha256").update(`${C}:${r}`).digest("hex")}function Te(r){return Ie("sha256").update(`toolnet-project:${C}:${r}`).digest("hex").slice(0,16)}function It(r){return r.split("/").filter(Boolean).at(-1)?.trim()||null}function ie(r,t){let e=Et("git",["-C",r,...t],{encoding:"utf8",windowsHide:!0,stdio:["ignore","pipe","ignore"]});return e.error||e.status!==0?null:e.stdout?.trim()||null}function Re(r,t){let e=It(r);return e?{scheme:C,canonicalRemote:r,fingerprint:Rt(r),repositoryName:e,source:t}:null}function Ce(r){let t=ie(r,["remote","get-url","origin"]);if(t){let o=Pe(t);if(o)return Re(o,"origin")}let e=ie(r,["remote"]);if(!e)return null;let n=new Set;for(let o of e.split(/\r?\n/u).map(s=>s.trim()).filter(Boolean)){let s=ie(r,["remote","get-url",o]);if(!s)continue;let i=Pe(s);i&&n.add(i)}return n.size!==1?null:Re([...n][0],"unique-remote")}var Ne=".toolnet",$t="project.json";function Kt(r){return Mt("sha256").update(r).digest("hex").slice(0,16)}function R(r){return O(r,Ne,$t)}function $e(r){return H(R(r))}function Oe(r,t){let e=A(r),n=_e(e).root;for(;;){if($e(e))return e;if(e===n||t&&e===A(t))break;let o=U(e);if(o===e)break;e=o}return null}function ae(r){let t=A(r),e=_e(t).root,n=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"];for(;;){if(n.some(s=>H(O(t,s))))return t;if(t===e)break;let o=U(t);if(o===t)break;t=o}return A(r)}function B(r){let t;try{t=JSON.parse(Ct(r,"utf8"))}catch(o){throw new Error(`Invalid ToolNet project manifest: ${r}: ${o instanceof Error?o.message:String(o)}`)}if(!t||typeof t!="object")throw new Error(`Invalid ToolNet project manifest: ${r}`);let e=t;if(typeof e.id!="string"||!e.id.trim())throw new Error(`ToolNet project manifest is missing id: ${r}`);if(typeof e.name!="string"||!e.name.trim())throw new Error(`ToolNet project manifest is missing name: ${r}`);let n=new Date().toISOString();return{version:1,id:e.id,name:e.name,remote:typeof e.remote=="string"&&e.remote.trim()?e.remote:e.name,rootPath:typeof e.rootPath=="string"?e.rootPath:U(U(r)),createdAt:typeof e.createdAt=="string"?e.createdAt:n,updatedAt:typeof e.updatedAt=="string"?e.updatedAt:n,graphVersion:typeof e.graphVersion=="number"?e.graphVersion:0,memoryVersion:typeof e.memoryVersion=="number"?e.memoryVersion:0,metadata:e.metadata&&typeof e.metadata=="object"?e.metadata:void 0}}function z(r,t){let e=O(r,Ne);Tt(e,{recursive:!0});let n=R(r),o=`${n}.tmp-${process.pid}`;_t(o,JSON.stringify(t,null,2)+`
3
+ `,{encoding:"utf8",mode:384}),Ot(o,n)}function j(r,t){return{id:r.id,name:r.name,remote:r.remote,rootPath:t,createdAt:r.createdAt,updatedAt:r.updatedAt,graphVersion:r.graphVersion,memoryVersion:r.memoryVersion,metadata:r.metadata}}function ce(r){return{version:1,scheme:C,canonicalRemote:r.canonicalRemote,fingerprint:r.fingerprint,repositoryName:r.repositoryName}}function Lt(r){let t=r.metadata?.toolnetIdentity;if(!t||typeof t!="object"||Array.isArray(t))return null;let e=t;return typeof e.fingerprint=="string"?e.fingerprint:null}var Y=class{adopt(t,e){let n=ae(A(t));if(!e.id.trim())throw new Error("PROJECT_ADOPTION_INVALID_ID");if(!e.name.trim())throw new Error("PROJECT_ADOPTION_INVALID_NAME");if(!e.remote.trim())throw new Error("PROJECT_ADOPTION_INVALID_REMOTE");if($e(n)){let d=B(R(n));if(d.id!==e.id)throw new Error(["PROJECT_IDENTITY_ALREADY_EXISTS",`existing=${d.id}`,`requested=${e.id}`].join(" "));return j(d,n)}let o=new Date().toISOString(),s={...e.metadata};e.gitIdentity&&(s.toolnetIdentity=ce(e.gitIdentity));let i={version:1,id:e.id.trim(),name:e.name.trim(),remote:e.remote.trim(),rootPath:n,createdAt:e.createdAt??o,updatedAt:o,graphVersion:e.graphVersion??0,memoryVersion:e.memoryVersion??0,metadata:Object.keys(s).length?s:void 0};return z(n,i),j(i,n)}recordGitIdentity(t,e,n={}){let o=this.requireExisting(t),s=R(o.rootPath),i=B(s),d=Lt(i);if(d&&d!==e.fingerprint&&!n.allowRebind)throw new Error(["PROJECT_GIT_REMOTE_CHANGED",`existing=${d}`,`current=${e.fingerprint}`,"Use explicit rebind only when this repository identity change is intentional."].join(" "));let c=i.metadata?.toolnetIdentity;return c&&typeof c=="object"&&!Array.isArray(c)&&c.fingerprint===e.fingerprint||(i.metadata={...i.metadata,toolnetIdentity:ce(e)},i.updatedAt=new Date().toISOString(),z(o.rootPath,i)),j(i,o.rootPath)}findExisting(t=process.cwd()){let e=A(t),n=ae(e),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(c=>H(O(n,c))),i=Oe(e,s?n:void 0);if(!i)return null;let d=B(R(i));return j(d,i)}requireExisting(t=process.cwd()){let e=this.findExisting(t);if(!e)throw new Error("PROJECT_NOT_INITIALIZED");return e}detect(t=process.cwd()){let e=A(t),n=ae(e),s=[".git","package.json","pyproject.toml","Cargo.toml","go.mod","composer.json"].some(u=>H(O(n,u))),i=Oe(e,s?n:void 0);if(i){let u=R(i),S=B(u);return S.rootPath!==i&&(S.rootPath=i,S.updatedAt=new Date().toISOString(),z(i,S)),j(S,i)}let d=new Date().toISOString(),c=Nt(n),l=Ce(n),g={version:1,id:l?Te(l.canonicalRemote):Kt(n),name:c,remote:l?.repositoryName??c,rootPath:n,createdAt:d,updatedAt:d,graphVersion:0,memoryVersion:0,metadata:l?{toolnetIdentity:ce(l)}:void 0};return z(n,g),j(g,n)}};var Dt=[{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 Ke(r){return r.normalize("NFKC").trim().toLowerCase()}function Ft(r){if(r.length===0)return 0;let t=new Map;for(let n of r)t.set(n,(t.get(n)??0)+1);let e=0;for(let n of t.values()){let o=n/r.length;e-=o*Math.log2(o)}return e}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 Bt(r,t,e){let n=r.slice(Math.max(0,t-48),t),o=r.slice(e,Math.min(r.length,e+16));return/\b(?:token|secret|key|credential|authorization|password|passwd|apikey|api_key|access[_-]?key)\b/iu.test(`${n} ${o}`)}function zt(r,t){return r.start<t.end&&t.start<r.end}function Le(r){return r.sort((t,e)=>t.start!==e.start?t.start-e.start:e.end-e.start-(t.end-t.start))}var J=class{allowValues=new Set;enableEntropyHeuristic;constructor(t={}){for(let e of t.allowValues??[]){let n=Ke(e);n&&this.allowValues.add(n)}this.enableEntropyHeuristic=t.enableEntropyHeuristic??!0}scan(t){let e=[];for(let s of Dt){let i=new RegExp(s.regex.source,s.regex.flags);for(let d of t.matchAll(i))d.index===void 0||!d[0]||this.allowed(d[0])||e.push({type:s.type,value:d[0],start:d.index,end:d.index+d[0].length,confidence:s.confidence})}this.enableEntropyHeuristic&&e.push(...this.entropyMatches(t));let n=Le(e),o=[];for(let s of n)o.some(i=>zt(i,s))||o.push(s);return Le(o)}hasSecrets(t){return this.scan(t).length>0}allowed(t){let e=Ke(t);return Wt.has(e)?!0:this.allowValues.has(e)}entropyMatches(t){let e=[],n=/[A-Za-z0-9_+/=-]{32,160}/g;for(let o of t.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)||Bt(t,o.index,o.index+s.length)&&(Ft(s)<3.7||e.push({type:"high_entropy_secret",value:s,start:o.index,end:o.index+s.length,confidence:"heuristic"}))}return e}};var G=class{scanner;constructor(t={}){this.scanner=new J(t)}sanitize(t){let e=this.scanner.scan(t);if(e.length===0)return{text:t,redacted:0,secretTypes:[]};let n=t,o=[...e].sort((i,d)=>d.start-i.start),s=new Set;for(let i of o)s.add(i.type),n=n.slice(0,i.start)+`[REDACTED:${i.type}]`+n.slice(i.end);return{text:n,redacted:e.length,secretTypes:[...s].sort()}}sanitizeValue(t){if(typeof t=="string")return this.sanitize(t).text;if(Array.isArray(t))return t.map(e=>this.sanitizeValue(e));if(t&&typeof t=="object"){let e={};for(let[n,o]of Object.entries(t)){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")){e[n]="[REDACTED]";continue}e[n]=this.sanitizeValue(o)}return e}return t}};var Pn=new G;import{homedir as xr}from"node:os";import{join as vr}from"node:path";import{DeleteObjectCommand as Ut,GetObjectCommand as Yt,HeadObjectCommand as Jt,ListObjectsV2Command as Gt,PutObjectCommand as qt,S3Client as Qt}from"@aws-sdk/client-s3";import{getSignedUrl as Zt}from"@aws-sdk/s3-request-presigner";var q=class{name="huggingface";client;bucket;constructor(t){this.bucket=t.bucket,this.client=new Qt({region:"us-east-1",endpoint:`https://s3.hf.co/${t.namespace}`,forcePathStyle:!0,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,n="application/octet-stream"){let o=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new qt({Bucket:this.bucket,Key:t,Body:o,ContentType:n}))}async get(t){let e=await Zt(this.client,new Yt({Bucket:this.bucket,Key:t}),{expiresIn:60}),n=await fetch(e,{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(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new Jt({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new Ut({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],n;do{let o=await this.client.send(new Gt({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:n}));for(let s of o.Contents??[])s.Key&&e.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});n=o.IsTruncated?o.NextContinuationToken:void 0}while(n);return e}};import{access as De,mkdir as Xt,readFile as er,readdir as tr,rm as rr,stat as We,writeFile as nr}from"node:fs/promises";import{dirname as or,join as sr,relative as Fe,resolve as ir}from"node:path";var _=class{constructor(t){this.root=t}root;name="local";path(t){let e=t.replace(/^\/+/,"");return ir(this.root,e)}async put(t,e){let n=this.path(t);await Xt(or(n),{recursive:!0}),await nr(n,e)}async get(t){try{return await er(this.path(t))}catch(e){if(typeof e=="object"&&e!==null&&"code"in e&&e.code==="ENOENT")return null;throw e}}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await De(this.path(t)),!0}catch{return!1}}async delete(t){await rr(this.path(t),{force:!0})}async list(t=""){let e=this.path(t),n=[];try{await De(e)}catch{return n}let o=async i=>{let d=await tr(i,{withFileTypes:!0});for(let c of d){let l=sr(i,c.name);if(c.isDirectory()){await o(l);continue}let g=await We(l);n.push({key:Fe(this.root,l),size:g.size,updatedAt:g.mtime.toISOString()})}},s=await We(e);return s.isDirectory()?await o(e):n.push({key:Fe(this.root,e),size:s.size,updatedAt:s.mtime.toISOString()}),n}};import{DeleteObjectCommand as ar,GetObjectCommand as cr,HeadObjectCommand as ur,ListObjectsV2Command as dr,PutObjectCommand as lr,S3Client as pr}from"@aws-sdk/client-s3";import{getSignedUrl as fr}from"@aws-sdk/s3-request-presigner";var N=class{name;client;bucket;constructor(t){this.name=t.name??"s3",this.bucket=t.bucket,this.client=new pr({region:t.region??"us-east-1",endpoint:t.endpoint||void 0,forcePathStyle:t.forcePathStyle??!1,requestChecksumCalculation:"WHEN_REQUIRED",responseChecksumValidation:"WHEN_REQUIRED",credentials:{accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey}})}async put(t,e,n="application/octet-stream"){let o=typeof e=="string"?Buffer.from(e,"utf8"):e;await this.client.send(new lr({Bucket:this.bucket,Key:t,Body:o,ContentType:n}))}async get(t){let e=await fr(this.client,new cr({Bucket:this.bucket,Key:t}),{expiresIn:60}),n=await fetch(e,{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(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){try{return await this.client.send(new ur({Bucket:this.bucket,Key:t})),!0}catch(e){if(typeof e=="object"&&e!==null&&"$metadata"in e&&e.$metadata?.httpStatusCode===404)return!1;throw e}}async delete(t){await this.client.send(new ar({Bucket:this.bucket,Key:t}))}async list(t=""){let e=[],n;do{let o=await this.client.send(new dr({Bucket:this.bucket,Prefix:t||void 0,ContinuationToken:n}));for(let s of o.Contents??[])s.Key&&e.push({key:s.Key,size:s.Size,updatedAt:s.LastModified?.toISOString()});n=o.IsTruncated?o.NextContinuationToken:void 0}while(n);return e}};import{createCipheriv as mr,createDecipheriv as gr,createHash as yr,randomBytes as hr,timingSafeEqual as kr}from"node:crypto";import{readFileSync as Sr}from"node:fs";var P=Buffer.from("TNMEME01","ascii"),Be=1,$=8,K=12,ue=16,ze=P.length+1+$+K+ue,br="toolnet-memory:remote-encryption:v1:",He="aes-256-gcm",de=32,y=class extends Error{constructor(e,n){super(n);this.code=e;this.name="RemoteEncryptionError"}code};function wr(r){return r?["1","true","yes","on","enabled"].includes(r.trim().toLowerCase()):!1}function le(r=process.env){return wr(r.TOOLNET_REMOTE_ENCRYPTION)}function Ve(r){let t=r.trim();if(!t)throw new y("REMOTE_ENCRYPTION_KEY_EMPTY","Remote encryption key is empty.");let e;if(t.startsWith("hex:")){let n=t.slice(4);if(!/^[0-9a-f]{64}$/iu.test(n))throw new y("REMOTE_ENCRYPTION_KEY_INVALID","hex: remote encryption key must contain exactly 64 hexadecimal characters.");e=Buffer.from(n,"hex")}else if(/^[0-9a-f]{64}$/iu.test(t))e=Buffer.from(t,"hex");else{let n=t.startsWith("base64:")?t.slice(7):t;if(!/^[A-Za-z0-9+/_-]+={0,2}$/u.test(n))throw new y("REMOTE_ENCRYPTION_KEY_INVALID","Remote encryption key must be 32 raw bytes encoded as hexadecimal or base64.");e=Buffer.from(n,n.includes("-")||n.includes("_")?"base64url":"base64")}if(e.length!==de)throw new y("REMOTE_ENCRYPTION_KEY_INVALID_LENGTH",`Remote encryption key must decode to exactly ${de} bytes.`);return e}function Ue(r=process.env){let t=r.TOOLNET_REMOTE_ENCRYPTION_KEY?.trim(),e=r.TOOLNET_REMOTE_ENCRYPTION_KEY_FILE?.trim();if(t&&e)throw new y("REMOTE_ENCRYPTION_KEY_AMBIGUOUS","Configure either TOOLNET_REMOTE_ENCRYPTION_KEY or TOOLNET_REMOTE_ENCRYPTION_KEY_FILE, not both.");if(t)return Ve(t);if(e){let n;try{n=Sr(e,"utf8")}catch(o){throw new y("REMOTE_ENCRYPTION_KEY_FILE_READ_FAILED",[`Unable to read remote encryption key file: ${e}.`,o instanceof Error?o.message:String(o)].join(" "))}return Ve(n)}}function Ye(r){return yr("sha256").update(r).digest().subarray(0,$)}function Je(r){return Buffer.from(`${br}${r}`,"utf8")}function pe(r){return r.byteLength<P.length?!1:Buffer.from(r).subarray(0,P.length).equals(P)}function Ge(r,t,e){if(e.byteLength!==de)throw new y("REMOTE_ENCRYPTION_KEY_INVALID_LENGTH","AES-256-GCM requires a 32-byte key.");let n=typeof t=="string"?Buffer.from(t,"utf8"):Buffer.from(t),o=hr(K),s=mr(He,e,o);s.setAAD(Je(r));let i=Buffer.concat([s.update(n),s.final()]),d=s.getAuthTag(),c=Buffer.alloc(ze),l=0;return P.copy(c,l),l+=P.length,c.writeUInt8(Be,l),l+=1,Ye(e).copy(c,l),l+=$,o.copy(c,l),l+=K,d.copy(c,l),Buffer.concat([c,i])}function qe(r,t,e){let n=Buffer.from(t);if(!pe(n))throw new y("REMOTE_ENCRYPTION_ENVELOPE_REQUIRED","Payload is not a ToolNet encrypted remote object.");if(n.length<ze)throw new y("REMOTE_ENCRYPTION_ENVELOPE_TRUNCATED","Encrypted remote payload is truncated.");let o=P.length,s=n.readUInt8(o);if(o+=1,s!==Be)throw new y("REMOTE_ENCRYPTION_VERSION_UNSUPPORTED",`Unsupported remote encryption envelope version: ${s}.`);let i=n.subarray(o,o+$);o+=$;let d=Ye(e);if(!kr(i,d))throw new y("REMOTE_ENCRYPTION_KEY_MISMATCH","Configured remote encryption key does not match this encrypted object.");let c=n.subarray(o,o+K);o+=K;let l=n.subarray(o,o+ue);o+=ue;let g=n.subarray(o),u=gr(He,e,c);u.setAAD(Je(r)),u.setAuthTag(l);try{return Buffer.concat([u.update(g),u.final()])}catch{throw new y("REMOTE_ENCRYPTION_AUTH_FAILED","Encrypted remote object failed AES-GCM authentication.")}}var fe=class{constructor(t,e){this.inner=t;this.options=e;if(this.name=t.name,e.enabled&&!e.key)throw new y("REMOTE_ENCRYPTION_KEY_REQUIRED","Remote client-side encryption is enabled but no encryption key is configured.")}inner;options;name;async put(t,e,n){if(!this.options.enabled){await this.inner.put(t,e,n);return}let o=this.options.key;if(!o)throw new y("REMOTE_ENCRYPTION_KEY_REQUIRED","Remote encryption key is unavailable.");let s=Ge(t,e,o);await this.inner.put(t,s,"application/octet-stream")}async get(t){let e=await this.inner.get(t);if(!e)return null;if(!pe(e))return e;if(!this.options.enabled)throw new y("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 y("REMOTE_ENCRYPTION_KEY_REQUIRED","Remote object is encrypted but no decryption key is configured.");return qe(t,e,n)}async getText(t){let e=await this.get(t);return e?Buffer.from(e).toString("utf8"):null}async exists(t){return this.inner.exists(t)}async delete(t){await this.inner.delete(t)}async list(t=""){return this.inner.list(t)}};function Qe(r,t=process.env){if(r.name==="local")return le(t)&&console.warn("[storage] Remote encryption requested but active storage provider is local; local data remains unchanged."),r;let e=le(t),n=e?Ue(t):void 0;return new fe(r,{enabled:e,key:n})}function L(r){return Qe(r)}function me(r,t){return console.warn(t),L(new _(r))}function Ze(r){let t=r.localRoot??vr(xr(),".toolnet-memory","storage");if(r.provider==="r2"){let e=r.r2;return e?.accountId&&e.bucket&&e.accessKeyId&&e.secretAccessKey?L(new N({name:"r2",endpoint:`https://${e.accountId}.r2.cloudflarestorage.com`,region:"auto",bucket:e.bucket,forcePathStyle:!0,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey})):me(t,"[storage] Cloudflare R2 credentials missing. Using local fallback.")}if(r.provider==="s3"){let e=r.s3;return e?.bucket&&e.accessKeyId&&e.secretAccessKey?L(new N({name:"s3",endpoint:e.endpoint,region:e.region??"us-east-1",bucket:e.bucket,forcePathStyle:e.forcePathStyle??!1,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey})):me(t,"[storage] S3 credentials missing. Using local fallback.")}if(r.provider==="huggingface"){let e=r.huggingface;return e?.namespace&&e.bucket&&e.accessKeyId&&e.secretAccessKey?L(new q({namespace:e.namespace,bucket:e.bucket,accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey})):me(t,"[storage] Hugging Face credentials missing. Using local fallback.")}return L(new _(t))}function Er(r){return new Promise(t=>setTimeout(t,r))}async function Xe(r,t={}){let e=Math.max(1,t.attempts??3),n=t.baseDelayMs??150,o=t.maxDelayMs??2e3,s;for(let i=1;i<=e;i++)try{return await r()}catch(d){if(s=d,i>=e)break;let c=Math.min(o,n*2**(i-1)),l=Math.floor(Math.random()*Math.max(1,c*.2));await Er(c+l)}throw s}var jr=new Set(["put","get","getText","delete","list"]);function et(r,t={}){return new Proxy(r,{get(e,n){let o=Reflect.get(e,n,e);return typeof o!="function"?o:jr.has(n)?(...s)=>Xe(()=>Promise.resolve(o.apply(e,s)),t):o.bind(e)}})}function tt(r){let t=r.trim().replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,"").slice(0,100);if(!t||t==="."||t==="..")throw new Error("Invalid project storage folder");return t}function rt(r){let t=r.replace(/^\/+/,"");if(t.startsWith("memories/"))return"memory/records/"+t.slice(9);if(t.startsWith("vectors/"))return"memory/vectors/"+t.slice(8);if(t.startsWith("graph/"))return"code/graph/"+t.slice(6);let e=t.match(/^(snapshots\/[^/]+\/)memories\/(.+)$/);if(e)return`${e[1]}memory/records/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)vectors\/(.+)$/),e)return`${e[1]}memory/vectors/${e[2]}`;if(e=t.match(/^(snapshots\/[^/]+\/)graph\/(.+)$/),e)return`${e[1]}code/graph/${e[2]}`;let n=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)memories\/(.+)$/);if(n)return`${n[1]}memory/records/${n[2]}`;if(n=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)vectors\/(.+)$/),n)return`${n[1]}memory/vectors/${n[2]}`;if(n=t.match(/^(projects\/[^/]+\/snapshots\/[^/]+\/)graph\/(.+)$/),n)return`${n[1]}code/graph/${n[2]}`;let o=t.match(/^(projects\/[^/]+\/)memories\/(.+)$/);return o?`${o[1]}memory/records/${o[2]}`:(o=t.match(/^(projects\/[^/]+\/)vectors\/(.+)$/),o?`${o[1]}memory/vectors/${o[2]}`:(o=t.match(/^(projects\/[^/]+\/)graph\/(.+)$/),o?`${o[1]}code/graph/${o[2]}`:t))}var Q=class{constructor(t,e,n,o){this.provider=t;this.name=t.name,this.projectId=e,this.projectName=n,this.folder=tt(o??n),this.sourcePrefix=`projects/${e}`,this.targetPrefix=`projects/${this.folder}`}provider;name;folder;sourcePrefix;targetPrefix;projectId;projectName;registryPromise;async registerProject(){let t=`${this.targetPrefix}/project.json`,e=new Date().toISOString(),n=e,o=await this.provider.getText(t);if(o){let i;try{i=JSON.parse(o)}catch(d){throw new Error(`Invalid remote ToolNet project manifest at ${t}: ${d instanceof Error?d.message:String(d)}`)}if(typeof i.id=="string"&&i.id!==this.projectId)throw new Error(["ToolNet remote project namespace collision.",`Remote folder: ${this.targetPrefix}`,`Existing project id: ${i.id}`,`Current project id: ${this.projectId}`,"Refusing to mix data from two projects."].join(" "));typeof i.createdAt=="string"&&(n=i.createdAt)}let s={version:1,id:this.projectId,name:this.projectName,remote:this.folder,createdAt:n,updatedAt:e};await this.provider.put(t,JSON.stringify(s,null,2)+`
4
+ `,"application/json")}async ensureRegistered(){return this.registryPromise||(this.registryPromise=this.registerProject()),this.registryPromise}key(t){if(t=rt(t),t===this.sourcePrefix)return this.targetPrefix;if(t.startsWith(`${this.sourcePrefix}/`))return this.targetPrefix+t.slice(this.sourcePrefix.length);if(t===this.targetPrefix||t.startsWith(`${this.targetPrefix}/`))return t;if(t.startsWith("projects/"))throw new Error(["Cross-project storage access denied.",`Current project: ${this.targetPrefix}`,`Requested key: ${t}`].join(" "));return t}async put(t,e,n){return await this.ensureRegistered(),this.provider.put(this.key(t),e,n)}async get(t){return await this.ensureRegistered(),this.provider.get(this.key(t))}async getText(t){return await this.ensureRegistered(),this.provider.getText(this.key(t))}async delete(t){return await this.ensureRegistered(),this.provider.delete(this.key(t))}async exists(t){return await this.ensureRegistered(),this.provider.exists(this.key(t))}async list(t){return await this.ensureRegistered(),this.provider.list(this.key(t))}};import{join as Tr}from"node:path";import{createHash as Ar}from"node:crypto";import{dirname as Pr}from"node:path";import{mkdirSync as Rr,readFileSync as Es,renameSync as Ir,writeFileSync as Mr}from"node:fs";function w(r){return Ar("sha256").update(r).digest("hex")}function I(r,t){Rr(Pr(r),{recursive:!0});let e=`${r}.${process.pid}.tmp`;Mr(e,JSON.stringify(t,null,2)+`
5
+ `,{encoding:"utf8",mode:384}),Ir(e,r)}function nt(r,t){let e=r.trim(),n=e.replace(/\s+/g,"_").replace(/[^A-Za-z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^\.+|\.+$/g,""),o=w(e).slice(0,12);if(!n||n==="."||n==="..")return`${t}--${o}`;let s=n.slice(0,100);return s===e&&e.length<=100?s:`${s.slice(0,85)}--${o}`}function ot(r,t,e){let n=t.trim(),o=e.trim();if(!n)throw new Error("Session agent is required");if(!o)throw new Error("Native session ID is required");let s=nt(n.toLowerCase(),"agent"),i=nt(o,"session");return{projectId:r.id,projectName:r.name,projectRoot:r.rootPath,agent:n,nativeSessionId:o,sessionKey:`${n}:${o}`,remotePrefix:["projects",r.id,"runtime","sources",s,i].join("/"),localDirectory:Tr(r.rootPath,".toolnet","runtime","sources",s,i)}}import{existsSync as at,mkdirSync as Cr,readFileSync as Or,statSync as st,writeFileSync as _r}from"node:fs";import{dirname as Nr,join as $r}from"node:path";var it=64*1024,Kr=`# ToolNet Project Operating Manual
6
6
 
7
7
  This file contains persistent instructions for AI agents working on this project.
8
8
 
@@ -45,19 +45,19 @@ Things an AI agent should always remember.
45
45
  <!--
46
46
  - [advisory] Prefer small focused files.
47
47
  -->
48
- `;function z(r){return Jt(r.rootPath,".toolnet","PROJECT.md")}function qt(r){return r.normalize("NFKC").replace(/\s+/g," ").trim()}function Gt(r){let t=[],e=new Set,n=/^\s*[-*]\s+\[(enforce|advisory)\]\s+(.+?)\s*$/gimu,s;for(;s=n.exec(r);){let o=s[1].toLowerCase(),a=qt(s[2]);if(!a)continue;let d=`${o}:${a.toLowerCase()}`;e.has(d)||(e.add(d),t.push({id:S(d).slice(0,24),mode:o,text:a,source:"manual"}))}return t}function Yt(r){let t=z(r);return Ce(t)||(Vt(Ht(t),{recursive:!0}),Bt(t,Ut,{encoding:"utf8",mode:384})),t}function B(r,t=!1){let e=t?Yt(r):z(r);if(!Ce(e))return null;if(Re(e).size>Te)throw new Error(`PROJECT.md exceeds ${Te} bytes`);let s=zt(e,"utf8");return{path:e,content:s,digest:S(s),rules:Gt(s),bytes:Buffer.byteLength(s,"utf8"),updatedAt:new Date(Re(e).mtimeMs).toISOString()}}import{existsSync as ar,mkdirSync as cr}from"node:fs";import{join as ee}from"node:path";import{join as Ee}from"node:path";import{mkdirSync as Qt}from"node:fs";function Ne(r){return r.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").replace(/\s+/g," ").trim()}function b(r,t=20){let e=[],n=new Set;for(let s of r.slice().reverse()){let o=Ne(s);if(!(!o||n.has(o))&&(n.add(o),e.push(s),e.length>=t))break}return e.reverse()}function Zt(r,t=20){let e=new Map;for(let n of r){let s=`${n.kind}|${Ne(n.command)}`;e.delete(s),e.set(s,n)}return Array.from(e.values()).slice(-t)}function Xt(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 Oe(r,t){let e=t.status??r?.status??"pending",n=e;r&&(r.status==="completed"&&e!=="completed"?n="completed":e==="pending"&&(r.status==="in_progress"||r.status==="blocked")&&(n=r.status));let s=r&&Xt(t)?r.title:t.text;return{id:r?.id??S(t.key).slice(0,24),title:s,status:n,order:t.order??r?.order,confidence:Math.max(t.confidence,r?.confidence??0),updatedAt:t.occurredAt,updatedBy:{agent:t.agent,nativeSessionId:t.nativeSessionId,eventId:t.eventId}}}async function _e(r,t){let e=`projects/${r.id}/work/observations/`,n=await t.list(e),s=[];for(let o of n.filter(a=>a.key.endsWith(".json")).sort((a,d)=>a.key.localeCompare(d.key))){let a=await t.getText(o.key);if(a)try{let d=JSON.parse(a);d.version===1&&Array.isArray(d.observations)&&s.push(d)}catch{}}return s}async function X(r,t){let n=(await _e(r,t)).flatMap(i=>i.observations).sort((i,k)=>{let ce=i.occurredAt.localeCompare(k.occurredAt);if(ce!==0)return ce;let ue=i.sequence-k.sequence;return ue!==0?ue:i.id.localeCompare(k.id)}),s=new Map,o=new Map,a,d,u,p,g,c=[],x=[],l=[],y=[],q=[],E=new Map,O=[],f=[],ne=[],se=[],oe=[],ie=[];for(let i of n)switch(i.kind){case"request":a=i.text;break;case"activity":d=i.text;break;case"goal":u=i.text;break;case"plan":p=i.text;break;case"phase":s.set(i.key,Oe(s.get(i.key),i));break;case"task":o.set(i.key,Oe(o.get(i.key),i));break;case"decision":c.push(i.text);break;case"blocker":x.push(i.text);break;case"warning":l.push(i.text);break;case"next_action":y.push(i.text);break;case"file":{q.push(i.text);let k=i.fileAction??"active";E.delete(i.text),E.set(i.text,k),k==="modified"?O.push(i.text):k==="created"?f.push(i.text):k==="deleted"&&ne.push(i.text);break}case"command":se.push(i.text);break;case"test":oe.push(i.text),i.checkKind&&ie.push({kind:i.checkKind,command:i.text,status:i.checkStatus??"unknown",updatedAt:i.occurredAt,agent:i.agent,nativeSessionId:i.nativeSessionId});break;case"session":g={agent:i.agent,nativeSessionId:i.nativeSessionId,sessionKey:i.sessionKey,updatedAt:i.occurredAt};break}let w=Array.from(s.values()).sort((i,k)=>(i.order??Number.MAX_SAFE_INTEGER)-(k.order??Number.MAX_SAFE_INTEGER)),v=Array.from(o.values()).sort((i,k)=>(i.order??Number.MAX_SAFE_INTEGER)-(k.order??Number.MAX_SAFE_INTEGER)),G=w.find(i=>i.status==="in_progress")??w.find(i=>i.status==="blocked")??w.find(i=>i.status==="pending"),N=v.find(i=>i.status==="in_progress")??v.find(i=>i.status==="blocked")??v.find(i=>i.status==="pending"),Le=b([...y,...N?[N.title]:[],...!N&&G?[G.title]:[],...v.filter(i=>i.status==="pending").slice(0,5).map(i=>i.title)],8),Fe=b([...x,...w.filter(i=>i.status==="blocked").map(i=>i.title),...v.filter(i=>i.status==="blocked").map(i=>i.title)],20),Y={version:1,projectId:r.id,projectName:r.name,currentRequest:a,currentActivity:d,goal:u,plan:p,phases:w,tasks:v,decisions:b(c,20),blockers:Fe,warnings:b(l,20),nextActions:Le,filesTouched:b(q,30),activeFiles:Array.from(E.entries()).filter(([,i])=>i!=="deleted").map(([i])=>i).slice(-5),modifiedFiles:b(O,30),createdFiles:b(f,30),deletedFiles:b(ne,30),commands:b(se,20),tests:b(oe,20),checks:Zt(ie,20),currentPhase:G,currentTask:N,progress:{phasesTotal:w.length,phasesCompleted:w.filter(i=>i.status==="completed").length,tasksTotal:v.length,tasksCompleted:v.filter(i=>i.status==="completed").length,blocked:w.filter(i=>i.status==="blocked").length+v.filter(i=>i.status==="blocked").length},lastSession:g,updatedAt:n.length?n[n.length-1].occurredAt:new Date().toISOString()},ae=Ee(r.rootPath,".toolnet","work");return Qt(ae,{recursive:!0}),A(Ee(ae,"current.json"),Y),await t.put(`projects/${r.id}/work/current.json`,JSON.stringify(Y,null,2)+`
49
- `,"application/json"),Y}async function H(r,t){if((await _e(r,t)).length>0)return X(r,t);let n=await t.getText(`projects/${r.id}/work/current.json`);if(!n)return null;try{return JSON.parse(n)}catch{return null}}var Cs=new Int32Array(new SharedArrayBuffer(4));import{closeSync as $s,existsSync as er,fsyncSync as Ws,mkdirSync as Ks,openSync as Ds,readFileSync as tr,renameSync as Ls,writeFileSync as Fs}from"node:fs";import{dirname as zs,join as rr}from"node:path";function nr(r){return rr(r.rootPath,".toolnet","work","current.json")}function $e(r){let t=nr(r);if(!er(t))return null;try{let e=JSON.parse(tr(t,"utf8"));return e.version!==1||e.projectId!==r.id?null:e}catch{return null}}function h(r,t){let e=new Set,n=[];for(let s of r){let o=s.replace(/\s+/g," ").trim();if(!o)continue;let a=o.normalize("NFKC").toLowerCase();if(!e.has(a)&&(e.add(a),n.push(o),n.length>=t))break}return n}function We(r){if(r)return{id:r.id,title:r.title,status:r.status}}function sr(r,t=[]){let e=t.slice(-10);if(e.some(s=>s.status==="failed"))return"failing";if(e.some(s=>s.status==="passed"))return"passing";let n=r.slice(-10).join(`
50
- `).toLowerCase();return/(?:failed|failing|failure|error|✗|❌)/u.test(n)?"failing":/(?:passed|passing|green|success|✓|✅)/u.test(n)?"passing":"unknown"}function or(r){return S(JSON.stringify(r))}function ir(r){let t=[];for(let e of r){if(!e)continue;let n=e.match(/https?:\/\/[^\s<>"'`)\]}]+/giu)??[];for(let s of n){let o=s.replace(/[.,;:!?]+$/gu,"").trim();o&&t.push(o)}}return h(t,30)}function Ke(r){let{project:t,identity:e,state:n}=r,s=n.activeFiles?.at(-1)??n.filesTouched.at(-1),o=n.phases.filter(f=>f.status==="completed").map(f=>f.title),a=n.tasks.filter(f=>f.status==="completed").map(f=>f.title),d=n.phases.filter(f=>f.status!=="completed"&&f.status!=="cancelled").map(f=>f.title),u=n.tasks.filter(f=>f.status!=="completed"&&f.status!=="cancelled").map(f=>f.title),p=new Set(n.tasks.filter(f=>f.status==="completed").map(f=>f.title.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim())),g=h(n.nextActions.filter(f=>!p.has(f.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim())),10),c=h([...u,...g],15),x=h(n.tests.slice().reverse(),10),l=h([...(n.activeFiles??[]).slice().reverse(),...n.filesTouched.slice().reverse()],20),y={schema:"toolnet.handoff.v2",version:2,project:{id:t.id,name:t.name},source:{agent:e.agent,nativeSessionId:e.nativeSessionId,sessionKey:e.sessionKey,sequence:r.sequence,reason:r.reason},capturedAt:r.capturedAt??new Date().toISOString(),goal:n.goal,request:n.currentRequest,activity:n.currentActivity,current:{phase:We(n.currentPhase),task:We(n.currentTask),file:s},completed:{phases:h(o,20),tasks:h(a,30)},remaining:{phases:h(d,20),tasks:h(u,30),todos:c},nextAction:g[0],blockers:h(n.blockers.slice().reverse(),10),decisions:h(n.decisions.slice().reverse(),10),files:{current:s,recent:l,active:h(n.activeFiles??[],10),modified:h(n.modifiedFiles??[],20),created:h(n.createdFiles??[],20),deleted:h(n.deletedFiles??[],20)},tests:{status:sr(n.tests,n.checks),recent:x,checks:(n.checks??[]).slice(-10).map(f=>({kind:f.kind,status:f.status,command:f.command}))},evidence:{commands:h((n.commands??[]).slice().reverse(),20),references:ir([n.currentRequest,n.currentActivity,n.goal,n.plan,...n.decisions,...n.blockers,...n.warnings,...n.nextActions,...n.filesTouched,...n.commands??[],...n.tests,...(n.checks??[]).map(f=>f.command)])},attention:h(r.attention??[],20),progress:n.progress},{capturedAt:q,source:E,...O}=y;return{...y,stateDigest:or(O)}}function ur(r){return!!(r.currentRequest||r.currentActivity||r.goal||r.plan||r.phases.length>0||r.tasks.length>0||r.nextActions.length>0||r.blockers.length>0||r.decisions.length>0||r.filesTouched.length>0)}function dr(r,t,e,n,s){if(!ur(e))return null;let o=B(r,!1),d=[...o?o.rules.filter(c=>c.mode==="enforce").map(c=>c.text):[],...e.warnings].slice(0,20),u=Ke({project:r,identity:t,state:e,reason:n,sequence:s,attention:d}),p=u.stateDigest;return{version:1,id:S([r.id,t.sessionKey,p].join("|")).slice(0,24),projectId:r.id,projectName:r.name,createdAt:new Date().toISOString(),reason:n,sourceSession:{agent:t.agent,nativeSessionId:t.nativeSessionId,sessionKey:t.sessionKey,sequence:s},currentRequest:e.currentRequest,currentActivity:e.currentActivity,goal:e.goal,plan:e.plan,progress:e.progress,currentPhase:e.currentPhase,currentTask:e.currentTask,incompletePhases:e.phases.filter(c=>c.status!=="completed"&&c.status!=="cancelled"),incompleteTasks:e.tasks.filter(c=>c.status!=="completed"&&c.status!=="cancelled"),nextActions:u.remaining.todos.slice(0,10),blockers:e.blockers.slice(-10),decisions:e.decisions.slice(-10),warnings:e.warnings.slice(-10),attention:d,filesTouched:e.filesTouched.slice(-20),activeFiles:e.activeFiles?.slice(-10),modifiedFiles:e.modifiedFiles?.slice(-20),createdFiles:e.createdFiles?.slice(-20),deletedFiles:e.deletedFiles?.slice(-20),tests:e.tests.slice(-15),checks:e.checks?.slice(-10),stateDigest:p,continuity:u}}function lr(r,t){let e=ee(r.rootPath,".toolnet","work","handoffs");cr(e,{recursive:!0});let n=ee(e,`${t.id}.json`);ar(n)||A(n,t),A(ee(r.rootPath,".toolnet","work","handoff-latest.json"),t)}var J=class{constructor(t){this.options=t}options;async capture(t,e){let n=$e(this.options.project);n||(n=await H(this.options.project,this.options.storage)),n||(n=await X(this.options.project,this.options.storage));let s=dr(this.options.project,this.options.identity,n,t,e);if(!s)return null;lr(this.options.project,s);let o=`projects/${this.options.project.id}/work/handoffs/${s.id}.json`;return await this.options.storage.exists(o)||await this.options.storage.put(o,JSON.stringify(s,null,2)+`
51
- `,"application/json"),await this.options.storage.put(`projects/${this.options.project.id}/work/handoff-latest.json`,JSON.stringify(s,null,2)+`
52
- `,"application/json"),s}};async function U(r,t){let e=await t.getText(`projects/${r.id}/work/handoff-latest.json`);if(!e)return null;try{return JSON.parse(e)}catch{return null}}async function De(r,t){let e=await t.getText(`projects/${r.id}/work/semantic/current.json`);if(!e)return null;try{return JSON.parse(e)}catch{return null}}function te(r){if(!r)return 0;let t=Array.from(r).length,e=r.trim().split(/\s+/u).filter(Boolean).length;return Math.ceil(Math.max(t/3.5,e*1.3))}function m(r,t){let e=r.replace(/\s+/g," ").trim();return e.length<=t?e:e.slice(0,Math.max(0,t-1)).trimEnd()+"\u2026"}function pr(r){let t=[],e=!1;for(let n of r.split(/\r?\n/u)){let s=n.trim();if(s.includes("<!--")&&(e=!0),e){s.includes("-->")&&(e=!1);continue}let o=s.toLowerCase();if(!(!s||s.startsWith("#")||s==="```"||o.startsWith("- [enforce]")||o.startsWith("* [enforce]")||o.startsWith("- [advisory]")||o.startsWith("* [advisory]"))&&(s=s.replace(/^[-*]\s+/u,""),s&&t.push(m(s,280)),t.length>=16))break}return t}function fr(r){let t=[],e=[];for(let n of r.split(/\\r?\\n/u)){let s=n.trim(),o=s.toLowerCase(),d=["- [enforce]","* [enforce]","- [advisory]","* [advisory]"].find(p=>o.startsWith(p));if(!d)continue;let u=s.slice(d.length).trim();u&&(d.includes("enforce")?t.push(u):e.push(u))}return{enforce:t,advisory:e}}function mr(r,t){let e=[];for(let n of r){let s=[...e,n].join(`
53
- `);if(te(s)<=t){e.push(n);continue}let o=te(e.join(`
54
- `)),a=Math.max(0,t-o);if(a>=16){let d=Math.floor(a*3.2),u=m(n,d);u&&e.push(u)}break}return e.join(`
55
- `).trim()}async function re(r){let t=Math.max(256,Math.min(2e3,r.maxTokens??1e3)),e=B(r.project,!1),n=e?.content??"";n||(n=await r.storage.getText(`projects/${r.project.id}/project/manual.md`)??"");let s=fr(n),o=e?e.rules.filter(l=>l.mode==="enforce").map(l=>l.text):s.enforce,a=e?e.rules.filter(l=>l.mode==="advisory").map(l=>l.text):s.advisory,d=n?pr(n):[],u=await H(r.project,r.storage),p=await De(r.project,r.storage),g=await U(r.project,r.storage),c=[];if(c.push("[TOOLNET PROJECT CONTEXT]"),c.push(`Project: ${r.project.name}`),c.push("Continue existing project state. Do not restart completed work unless evidence shows it is necessary."),n&&c.push(`Full operating manual: ${z(r.project)}`),o.length){c.push("","PROJECT RULES \u2014 MUST FOLLOW");for(let l of o.slice(0,24))c.push(`- [ENFORCE] ${m(l,240)}`)}if(a.length){c.push("","PROJECT PREFERENCES");for(let l of a.slice(0,10))c.push(`- ${m(l,220)}`)}if(p&&(p.mission&&c.push("","MISSION",m(p.mission.value,420)),p.activeObjective&&c.push("","CURRENT OBJECTIVE",m(p.activeObjective.value,420)),p.why&&c.push("","WHY THIS WORK MATTERS",m(p.why.value,420)),p.desiredOutcome&&c.push("","DESIRED OUTCOME",m(p.desiredOutcome.value,420)),p.planRationale&&c.push("","WHY THIS APPROACH",m(p.planRationale.value,420))),u){if(c.push("","ACTIVE WORK"),u.goal&&c.push(`Goal: ${m(u.goal,320)}`),u.plan&&c.push(`Plan: ${m(u.plan,320)}`),c.push(`Progress: phases ${u.progress.phasesCompleted}/${u.progress.phasesTotal}; tasks ${u.progress.tasksCompleted}/${u.progress.tasksTotal}; blocked ${u.progress.blocked}`),u.currentPhase&&c.push(`Current phase: ${u.currentPhase.title} [${u.currentPhase.status}]`),u.currentPhase&&p){let l=p.phases.find(y=>y.order===u.currentPhase?.order);l&&(l.objective&&c.push(`Phase objective: ${m(l.objective.value,340)}`),l.why?c.push(`Why this phase: ${m(l.why.value,340)}`):c.push("Why this phase: not explicitly recorded. Inspect existing implementation before assuming intent."),l.deliverable&&c.push(`Deliverable: ${m(l.deliverable.value,340)}`),l.dependencies.length&&c.push(`Depends on: ${l.dependencies.slice(0,4).map(y=>m(y.value,180)).join("; ")}`),l.acceptanceCriteria.length&&(c.push("","DEFINITION OF DONE"),l.acceptanceCriteria.slice(0,6).forEach(y=>{c.push(`- ${m(y.value,260)}`)})),l.openQuestions.length&&(c.push("","OPEN QUESTIONS FOR CURRENT PHASE"),l.openQuestions.slice(0,4).forEach(y=>{c.push(`- ${m(y.value,260)}`)})))}u.currentTask&&c.push(`Current task: ${u.currentTask.title} [${u.currentTask.status}]`),u.nextActions.length&&(c.push("","NEXT ACTIONS"),u.nextActions.slice(0,6).forEach((l,y)=>{c.push(`${y+1}. ${m(l,260)}`)})),u.blockers.length&&(c.push("","BLOCKERS"),u.blockers.slice(0,5).forEach(l=>{c.push(`- ${m(l,260)}`)})),u.warnings.length&&(c.push("","ATTENTION"),u.warnings.slice(-5).forEach(l=>{c.push(`- ${m(l,260)}`)})),u.decisions.length&&(c.push("","RECENT DECISIONS"),u.decisions.slice(-5).forEach(l=>{c.push(`- ${m(l,260)}`)})),u.lastSession&&c.push("",`Last work session: ${u.lastSession.agent} / ${u.lastSession.nativeSessionId}`)}if(p&&p.openQuestions.length&&(c.push("","UNRESOLVED QUESTIONS"),p.openQuestions.slice(0,5).forEach(l=>{c.push(`- ${m(l.value,260)}`)})),g&&c.push(`Latest handoff: ${g.reason} / ${g.sourceSession.agent}`),d.length){c.push("","OPERATING NOTES");for(let l of d)c.push(`- ${l}`)}c.push("","Before changing anything: verify the current repository state and continue from the active phase/task above.");let x=mr(c,t);return{version:1,projectId:r.project.id,projectName:r.project.name,text:x,estimatedTokens:te(x),maxTokens:t,hasManual:!!n,hasWorkState:!!u,hasHandoff:!!g,generatedAt:new Date().toISOString()}}function C(r,t){let e=r.indexOf(t);return e>=0?r[e+1]:void 0}function gr(r){let t=de(),e=je(we({provider:t.storage.provider,huggingface:t.storage.huggingface,localRoot:t.storage.localRoot}),{attempts:3});return new V(e,r.id,r.name,r.remote??r.name)}async function hr(){let[r="help",...t]=process.argv.slice(2),e=new K().detect(C(t,"--project")??process.cwd()),n=gr(e);if(r==="brief"){let s=Number(C(t,"--tokens")??1e3),o=await re({project:e,storage:n,maxTokens:Number.isFinite(s)?s:1e3});process.stdout.write(o.text+`
56
- `);return}if(r==="brief-json"){let s=await re({project:e,storage:n,maxTokens:Number(C(t,"--tokens")??1e3)});console.log(JSON.stringify(s,null,2));return}if(r==="handoff-latest"){let s=await U(e,n);console.log(JSON.stringify(s,null,2));return}if(r==="handoff-create"){let s=C(t,"--agent")??"manual";if(s==="manual")throw new Error("--agent must be opencode, agy, or codex");let o=C(t,"--session");if(!o)throw new Error("--session is required");let a=Ie(e,s,o),u=await new J({project:e,storage:n,identity:a}).capture("manual",0);console.log(JSON.stringify(u,null,2));return}console.log(`Work Context
48
+ `;function Z(r){return $r(r.rootPath,".toolnet","PROJECT.md")}function Lr(r){return r.normalize("NFKC").replace(/\s+/g," ").trim()}function Dr(r){let t=[],e=new Set,n=/^\s*[-*]\s+\[(enforce|advisory)\]\s+(.+?)\s*$/gimu,o;for(;o=n.exec(r);){let s=o[1].toLowerCase(),i=Lr(o[2]);if(!i)continue;let d=`${s}:${i.toLowerCase()}`;e.has(d)||(e.add(d),t.push({id:w(d).slice(0,24),mode:s,text:i,source:"manual"}))}return t}function Wr(r){let t=Z(r);return at(t)||(Cr(Nr(t),{recursive:!0}),_r(t,Kr,{encoding:"utf8",mode:384})),t}function X(r,t=!1){let e=t?Wr(r):Z(r);if(!at(e))return null;if(st(e).size>it)throw new Error(`PROJECT.md exceeds ${it} bytes`);let o=Or(e,"utf8");return{path:e,content:o,digest:w(o),rules:Dr(o),bytes:Buffer.byteLength(o,"utf8"),updatedAt:new Date(st(e).mtimeMs).toISOString()}}import{existsSync as Qr,mkdirSync as Zr}from"node:fs";import{join as ye}from"node:path";import{join as ct}from"node:path";import{mkdirSync as Fr}from"node:fs";function dt(r){return r.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").replace(/\s+/g," ").trim()}function x(r,t=20){let e=[],n=new Set;for(let o of r.slice().reverse()){let s=dt(o);if(!(!s||n.has(s))&&(n.add(s),e.push(o),e.length>=t))break}return e.reverse()}function Vr(r,t=20){let e=new Map;for(let n of r){let o=`${n.kind}|${dt(n.command)}`;e.delete(o),e.set(o,n)}return Array.from(e.values()).slice(-t)}function Br(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 ut(r,t){let e=t.status??r?.status??"pending",n=e;r&&(r.status==="completed"&&e!=="completed"?n="completed":e==="pending"&&(r.status==="in_progress"||r.status==="blocked")&&(n=r.status));let o=r&&Br(t)?r.title:t.text;return{id:r?.id??w(t.key).slice(0,24),title:o,status:n,order:t.order??r?.order,confidence:Math.max(t.confidence,r?.confidence??0),updatedAt:t.occurredAt,updatedBy:{agent:t.agent,nativeSessionId:t.nativeSessionId,eventId:t.eventId}}}async function lt(r,t){let e=`projects/${r.id}/work/observations/`,n=await t.list(e),o=[];for(let s of n.filter(i=>i.key.endsWith(".json")).sort((i,d)=>i.key.localeCompare(d.key))){let i=await t.getText(s.key);if(i)try{let d=JSON.parse(i);d.version===1&&Array.isArray(d.observations)&&o.push(d)}catch{}}return o}async function ge(r,t){let n=(await lt(r,t)).flatMap(a=>a.observations).sort((a,b)=>{let Ee=a.occurredAt.localeCompare(b.occurredAt);if(Ee!==0)return Ee;let je=a.sequence-b.sequence;return je!==0?je:a.id.localeCompare(b.id)}),o=new Map,s=new Map,i,d,c,l,g,u=[],S=[],p=[],k=[],ne=[],W=new Map,F=[],f=[],Se=[],be=[],we=[],xe=[];for(let a of n)switch(a.kind){case"request":i=a.text;break;case"activity":d=a.text;break;case"goal":c=a.text;break;case"plan":l=a.text;break;case"phase":o.set(a.key,ut(o.get(a.key),a));break;case"task":s.set(a.key,ut(s.get(a.key),a));break;case"decision":u.push(a.text);break;case"blocker":S.push(a.text);break;case"warning":p.push(a.text);break;case"next_action":k.push(a.text);break;case"file":{ne.push(a.text);let b=a.fileAction??"active";W.delete(a.text),W.set(a.text,b),b==="modified"?F.push(a.text):b==="created"?f.push(a.text):b==="deleted"&&Se.push(a.text);break}case"command":be.push(a.text);break;case"test":we.push(a.text),a.checkKind&&xe.push({kind:a.checkKind,command:a.text,status:a.checkStatus??"unknown",updatedAt:a.occurredAt,agent:a.agent,nativeSessionId:a.nativeSessionId});break;case"session":g={agent:a.agent,nativeSessionId:a.nativeSessionId,sessionKey:a.sessionKey,updatedAt:a.occurredAt};break}let E=Array.from(o.values()).sort((a,b)=>(a.order??Number.MAX_SAFE_INTEGER)-(b.order??Number.MAX_SAFE_INTEGER)),v=Array.from(s.values()).sort((a,b)=>(a.order??Number.MAX_SAFE_INTEGER)-(b.order??Number.MAX_SAFE_INTEGER)),oe=E.find(a=>a.status==="in_progress")??E.find(a=>a.status==="blocked")??E.find(a=>a.status==="pending"),V=v.find(a=>a.status==="in_progress")??v.find(a=>a.status==="blocked")??v.find(a=>a.status==="pending"),yt=x([...k,...V?[V.title]:[],...!V&&oe?[oe.title]:[],...v.filter(a=>a.status==="pending").slice(0,5).map(a=>a.title)],8),ht=x([...S,...E.filter(a=>a.status==="blocked").map(a=>a.title),...v.filter(a=>a.status==="blocked").map(a=>a.title)],20),se={version:1,projectId:r.id,projectName:r.name,currentRequest:i,currentActivity:d,goal:c,plan:l,phases:E,tasks:v,decisions:x(u,20),blockers:ht,warnings:x(p,20),nextActions:yt,filesTouched:x(ne,30),activeFiles:Array.from(W.entries()).filter(([,a])=>a!=="deleted").map(([a])=>a).slice(-5),modifiedFiles:x(F,30),createdFiles:x(f,30),deletedFiles:x(Se,30),commands:x(be,20),tests:x(we,20),checks:Vr(xe,20),currentPhase:oe,currentTask:V,progress:{phasesTotal:E.length,phasesCompleted:E.filter(a=>a.status==="completed").length,tasksTotal:v.length,tasksCompleted:v.filter(a=>a.status==="completed").length,blocked:E.filter(a=>a.status==="blocked").length+v.filter(a=>a.status==="blocked").length},lastSession:g,updatedAt:n.length?n[n.length-1].occurredAt:new Date().toISOString()},ve=ct(r.rootPath,".toolnet","work");return Fr(ve,{recursive:!0}),I(ct(ve,"current.json"),se),await t.put(`projects/${r.id}/work/current.json`,JSON.stringify(se,null,2)+`
49
+ `,"application/json"),se}async function ee(r,t){if((await lt(r,t)).length>0)return ge(r,t);let n=await t.getText(`projects/${r.id}/work/current.json`);if(!n)return null;try{return JSON.parse(n)}catch{return null}}var Ks=new Int32Array(new SharedArrayBuffer(4));import{closeSync as Vs,existsSync as zr,fsyncSync as Bs,mkdirSync as zs,openSync as Hs,readFileSync as Hr,renameSync as Us,writeFileSync as Ys}from"node:fs";import{dirname as Gs,join as Ur}from"node:path";function Yr(r){return Ur(r.rootPath,".toolnet","work","current.json")}function pt(r){let t=Yr(r);if(!zr(t))return null;try{let e=JSON.parse(Hr(t,"utf8"));return e.version!==1||e.projectId!==r.id?null:e}catch{return null}}function h(r,t){let e=new Set,n=[];for(let o of r){let s=o.replace(/\s+/g," ").trim();if(!s)continue;let i=s.normalize("NFKC").toLowerCase();if(!e.has(i)&&(e.add(i),n.push(s),n.length>=t))break}return n}function ft(r){if(r)return{id:r.id,title:r.title,status:r.status}}function Jr(r,t=[]){let e=t.slice(-10);if(e.some(o=>o.status==="failed"))return"failing";if(e.some(o=>o.status==="passed"))return"passing";let n=r.slice(-10).join(`
50
+ `).toLowerCase();return/(?:failed|failing|failure|error|✗|❌)/u.test(n)?"failing":/(?:passed|passing|green|success|✓|✅)/u.test(n)?"passing":"unknown"}function Gr(r){return w(JSON.stringify(r))}function qr(r){let t=[];for(let e of r){if(!e)continue;let n=e.match(/https?:\/\/[^\s<>"'`)\]}]+/giu)??[];for(let o of n){let s=o.replace(/[.,;:!?]+$/gu,"").trim();s&&t.push(s)}}return h(t,30)}function mt(r){let{project:t,identity:e,state:n}=r,o=n.activeFiles?.at(-1)??n.filesTouched.at(-1),s=n.phases.filter(f=>f.status==="completed").map(f=>f.title),i=n.tasks.filter(f=>f.status==="completed").map(f=>f.title),d=n.phases.filter(f=>f.status!=="completed"&&f.status!=="cancelled").map(f=>f.title),c=n.tasks.filter(f=>f.status!=="completed"&&f.status!=="cancelled").map(f=>f.title),l=new Set(n.tasks.filter(f=>f.status==="completed").map(f=>f.title.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim())),g=h(n.nextActions.filter(f=>!l.has(f.normalize("NFKC").toLowerCase().replace(/\s+/gu," ").trim())),10),u=h([...c,...g],15),S=h(n.tests.slice().reverse(),10),p=h([...(n.activeFiles??[]).slice().reverse(),...n.filesTouched.slice().reverse()],20),k={schema:"toolnet.handoff.v2",version:2,project:{id:t.id,name:t.name},source:{agent:e.agent,nativeSessionId:e.nativeSessionId,sessionKey:e.sessionKey,sequence:r.sequence,reason:r.reason},capturedAt:r.capturedAt??new Date().toISOString(),goal:n.goal,request:n.currentRequest,activity:n.currentActivity,current:{phase:ft(n.currentPhase),task:ft(n.currentTask),file:o},completed:{phases:h(s,20),tasks:h(i,30)},remaining:{phases:h(d,20),tasks:h(c,30),todos:u},nextAction:g[0],blockers:h(n.blockers.slice().reverse(),10),decisions:h(n.decisions.slice().reverse(),10),files:{current:o,recent:p,active:h(n.activeFiles??[],10),modified:h(n.modifiedFiles??[],20),created:h(n.createdFiles??[],20),deleted:h(n.deletedFiles??[],20)},tests:{status:Jr(n.tests,n.checks),recent:S,checks:(n.checks??[]).slice(-10).map(f=>({kind:f.kind,status:f.status,command:f.command}))},evidence:{commands:h((n.commands??[]).slice().reverse(),20),references:qr([n.currentRequest,n.currentActivity,n.goal,n.plan,...n.decisions,...n.blockers,...n.warnings,...n.nextActions,...n.filesTouched,...n.commands??[],...n.tests,...(n.checks??[]).map(f=>f.command)])},attention:h(r.attention??[],20),progress:n.progress},{capturedAt:ne,source:W,...F}=k;return{...k,stateDigest:Gr(F)}}function Xr(r){return!!(r.currentRequest||r.currentActivity||r.goal||r.plan||r.phases.length>0||r.tasks.length>0||r.nextActions.length>0||r.blockers.length>0||r.decisions.length>0||r.filesTouched.length>0)}function en(r,t,e,n,o){if(!Xr(e))return null;let s=X(r,!1),d=[...s?s.rules.filter(u=>u.mode==="enforce").map(u=>u.text):[],...e.warnings].slice(0,20),c=mt({project:r,identity:t,state:e,reason:n,sequence:o,attention:d}),l=c.stateDigest;return{version:1,id:w([r.id,t.sessionKey,l].join("|")).slice(0,24),projectId:r.id,projectName:r.name,createdAt:new Date().toISOString(),reason:n,sourceSession:{agent:t.agent,nativeSessionId:t.nativeSessionId,sessionKey:t.sessionKey,sequence:o},currentRequest:e.currentRequest,currentActivity:e.currentActivity,goal:e.goal,plan:e.plan,progress:e.progress,currentPhase:e.currentPhase,currentTask:e.currentTask,incompletePhases:e.phases.filter(u=>u.status!=="completed"&&u.status!=="cancelled"),incompleteTasks:e.tasks.filter(u=>u.status!=="completed"&&u.status!=="cancelled"),nextActions:c.remaining.todos.slice(0,10),blockers:e.blockers.slice(-10),decisions:e.decisions.slice(-10),warnings:e.warnings.slice(-10),attention:d,filesTouched:e.filesTouched.slice(-20),activeFiles:e.activeFiles?.slice(-10),modifiedFiles:e.modifiedFiles?.slice(-20),createdFiles:e.createdFiles?.slice(-20),deletedFiles:e.deletedFiles?.slice(-20),tests:e.tests.slice(-15),checks:e.checks?.slice(-10),stateDigest:l,continuity:c}}function tn(r,t){let e=ye(r.rootPath,".toolnet","work","handoffs");Zr(e,{recursive:!0});let n=ye(e,`${t.id}.json`);Qr(n)||I(n,t),I(ye(r.rootPath,".toolnet","work","handoff-latest.json"),t)}var te=class{constructor(t){this.options=t}options;async capture(t,e){let n=pt(this.options.project);n||(n=await ee(this.options.project,this.options.storage)),n||(n=await ge(this.options.project,this.options.storage));let o=en(this.options.project,this.options.identity,n,t,e);if(!o)return null;tn(this.options.project,o);let s=`projects/${this.options.project.id}/work/handoffs/${o.id}.json`;return await this.options.storage.exists(s)||await this.options.storage.put(s,JSON.stringify(o,null,2)+`
51
+ `,"application/json"),await this.options.storage.put(`projects/${this.options.project.id}/work/handoff-latest.json`,JSON.stringify(o,null,2)+`
52
+ `,"application/json"),o}};async function re(r,t){let e=await t.getText(`projects/${r.id}/work/handoff-latest.json`);if(!e)return null;try{return JSON.parse(e)}catch{return null}}async function gt(r,t){let e=await t.getText(`projects/${r.id}/work/semantic/current.json`);if(!e)return null;try{return JSON.parse(e)}catch{return null}}function he(r){if(!r)return 0;let t=Array.from(r).length,e=r.trim().split(/\s+/u).filter(Boolean).length;return Math.ceil(Math.max(t/3.5,e*1.3))}function m(r,t){let e=r.replace(/\s+/g," ").trim();return e.length<=t?e:e.slice(0,Math.max(0,t-1)).trimEnd()+"\u2026"}function rn(r){let t=[],e=!1;for(let n of r.split(/\r?\n/u)){let o=n.trim();if(o.includes("<!--")&&(e=!0),e){o.includes("-->")&&(e=!1);continue}let s=o.toLowerCase();if(!(!o||o.startsWith("#")||o==="```"||s.startsWith("- [enforce]")||s.startsWith("* [enforce]")||s.startsWith("- [advisory]")||s.startsWith("* [advisory]"))&&(o=o.replace(/^[-*]\s+/u,""),o&&t.push(m(o,280)),t.length>=16))break}return t}function nn(r){let t=[],e=[];for(let n of r.split(/\\r?\\n/u)){let o=n.trim(),s=o.toLowerCase(),d=["- [enforce]","* [enforce]","- [advisory]","* [advisory]"].find(l=>s.startsWith(l));if(!d)continue;let c=o.slice(d.length).trim();c&&(d.includes("enforce")?t.push(c):e.push(c))}return{enforce:t,advisory:e}}function on(r,t){let e=[];for(let n of r){let o=[...e,n].join(`
53
+ `);if(he(o)<=t){e.push(n);continue}let s=he(e.join(`
54
+ `)),i=Math.max(0,t-s);if(i>=16){let d=Math.floor(i*3.2),c=m(n,d);c&&e.push(c)}break}return e.join(`
55
+ `).trim()}async function ke(r){let t=Math.max(256,Math.min(2e3,r.maxTokens??1e3)),e=X(r.project,!1),n=e?.content??"";n||(n=await r.storage.getText(`projects/${r.project.id}/project/manual.md`)??"");let o=nn(n),s=e?e.rules.filter(p=>p.mode==="enforce").map(p=>p.text):o.enforce,i=e?e.rules.filter(p=>p.mode==="advisory").map(p=>p.text):o.advisory,d=n?rn(n):[],c=await ee(r.project,r.storage),l=await gt(r.project,r.storage),g=await re(r.project,r.storage),u=[];if(u.push("[TOOLNET PROJECT CONTEXT]"),u.push(`Project: ${r.project.name}`),u.push("Continue existing project state. Do not restart completed work unless evidence shows it is necessary."),n&&u.push(`Full operating manual: ${Z(r.project)}`),s.length){u.push("","PROJECT RULES \u2014 MUST FOLLOW");for(let p of s.slice(0,24))u.push(`- [ENFORCE] ${m(p,240)}`)}if(i.length){u.push("","PROJECT PREFERENCES");for(let p of i.slice(0,10))u.push(`- ${m(p,220)}`)}if(l&&(l.mission&&u.push("","MISSION",m(l.mission.value,420)),l.activeObjective&&u.push("","CURRENT OBJECTIVE",m(l.activeObjective.value,420)),l.why&&u.push("","WHY THIS WORK MATTERS",m(l.why.value,420)),l.desiredOutcome&&u.push("","DESIRED OUTCOME",m(l.desiredOutcome.value,420)),l.planRationale&&u.push("","WHY THIS APPROACH",m(l.planRationale.value,420))),c){if(u.push("","ACTIVE WORK"),c.goal&&u.push(`Goal: ${m(c.goal,320)}`),c.plan&&u.push(`Plan: ${m(c.plan,320)}`),u.push(`Progress: phases ${c.progress.phasesCompleted}/${c.progress.phasesTotal}; tasks ${c.progress.tasksCompleted}/${c.progress.tasksTotal}; blocked ${c.progress.blocked}`),c.currentPhase&&u.push(`Current phase: ${c.currentPhase.title} [${c.currentPhase.status}]`),c.currentPhase&&l){let p=l.phases.find(k=>k.order===c.currentPhase?.order);p&&(p.objective&&u.push(`Phase objective: ${m(p.objective.value,340)}`),p.why?u.push(`Why this phase: ${m(p.why.value,340)}`):u.push("Why this phase: not explicitly recorded. Inspect existing implementation before assuming intent."),p.deliverable&&u.push(`Deliverable: ${m(p.deliverable.value,340)}`),p.dependencies.length&&u.push(`Depends on: ${p.dependencies.slice(0,4).map(k=>m(k.value,180)).join("; ")}`),p.acceptanceCriteria.length&&(u.push("","DEFINITION OF DONE"),p.acceptanceCriteria.slice(0,6).forEach(k=>{u.push(`- ${m(k.value,260)}`)})),p.openQuestions.length&&(u.push("","OPEN QUESTIONS FOR CURRENT PHASE"),p.openQuestions.slice(0,4).forEach(k=>{u.push(`- ${m(k.value,260)}`)})))}c.currentTask&&u.push(`Current task: ${c.currentTask.title} [${c.currentTask.status}]`),c.nextActions.length&&(u.push("","NEXT ACTIONS"),c.nextActions.slice(0,6).forEach((p,k)=>{u.push(`${k+1}. ${m(p,260)}`)})),c.blockers.length&&(u.push("","BLOCKERS"),c.blockers.slice(0,5).forEach(p=>{u.push(`- ${m(p,260)}`)})),c.warnings.length&&(u.push("","ATTENTION"),c.warnings.slice(-5).forEach(p=>{u.push(`- ${m(p,260)}`)})),c.decisions.length&&(u.push("","RECENT DECISIONS"),c.decisions.slice(-5).forEach(p=>{u.push(`- ${m(p,260)}`)})),c.lastSession&&u.push("",`Last work session: ${c.lastSession.agent} / ${c.lastSession.nativeSessionId}`)}if(l&&l.openQuestions.length&&(u.push("","UNRESOLVED QUESTIONS"),l.openQuestions.slice(0,5).forEach(p=>{u.push(`- ${m(p.value,260)}`)})),g&&u.push(`Latest handoff: ${g.reason} / ${g.sourceSession.agent}`),d.length){u.push("","OPERATING NOTES");for(let p of d)u.push(`- ${p}`)}u.push("","Before changing anything: verify the current repository state and continue from the active phase/task above.");let S=on(u,t);return{version:1,projectId:r.project.id,projectName:r.project.name,text:S,estimatedTokens:he(S),maxTokens:t,hasManual:!!n,hasWorkState:!!c,hasHandoff:!!g,generatedAt:new Date().toISOString()}}function D(r,t){let e=r.indexOf(t);return e>=0?r[e+1]:void 0}function sn(r){let t=Ae(),e=et(Ze({provider:t.storage.provider,huggingface:t.storage.huggingface,localRoot:t.storage.localRoot}),{attempts:3});return new Q(e,r.id,r.name,r.remote??r.name)}async function an(){let[r="help",...t]=process.argv.slice(2),e=new Y().detect(D(t,"--project")??process.cwd()),n=sn(e);if(r==="brief"){let o=Number(D(t,"--tokens")??1e3),s=await ke({project:e,storage:n,maxTokens:Number.isFinite(o)?o:1e3});process.stdout.write(s.text+`
56
+ `);return}if(r==="brief-json"){let o=await ke({project:e,storage:n,maxTokens:Number(D(t,"--tokens")??1e3)});console.log(JSON.stringify(o,null,2));return}if(r==="handoff-latest"){let o=await re(e,n);console.log(JSON.stringify(o,null,2));return}if(r==="handoff-create"){let o=D(t,"--agent")??"manual";if(o==="manual")throw new Error("--agent must be opencode, agy, or codex");let s=D(t,"--session");if(!s)throw new Error("--session is required");let i=ot(e,o,s),c=await new te({project:e,storage:n,identity:i}).capture("manual",0);console.log(JSON.stringify(c,null,2));return}console.log(`Work Context
57
57
 
58
58
  Commands:
59
59
  brief [--project PATH] [--tokens N]
60
60
  brief-json [--project PATH] [--tokens N]
61
61
  handoff-latest [--project PATH]
62
62
  handoff-create --agent <opencode|agy|codex> --session <id>
63
- `)}hr().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});
63
+ `)}an().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});