@devflow-tools/cli 0.16.2 → 0.16.4

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.
@@ -1,3 +1,3 @@
1
- import {existsSync,readFileSync,rmSync,mkdirSync,writeFileSync,renameSync,readdirSync,unlinkSync,appendFileSync}from'fs';import {join,dirname}from'path';import {getLogWriter}from'@devflow-tools/telemetry';import {request}from'http';import {homedir}from'os';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {MemoryGate}from'@devflow-tools/memory-engine';import {randomUUID}from'crypto';import {fileURLToPath}from'url';function q(o,t,e){try{let n=openGlobalDevFlowDatabase();try{n.insertEvent({kind:o,timestamp:Date.now(),duration:e,success:t.success!==!1,metadata:t});}finally{n.close();}}catch{}}var O=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",we=getLocalApiKey(),ve=8,be=6e4,W=new Map;function _e(o){let t=new URL(o,O).pathname,e=W.get(t);return e||(e={failures:0,lastFailure:0,openUntil:0,status:"closed"},W.set(t,e)),e}function j(o,t,e){if(t.status===e)return;let n=t.status;t.status=e,q("http_circuit_transition",{path:new URL(o,O).pathname,previous:n,status:e,failures:t.failures,openUntil:t.openUntil});}function T(o,t){t.failures++,t.lastFailure=Date.now(),t.failures>=ve&&(t.openUntil=Date.now()+be,j(o,t,"open"));}function Se(o,t){let e=t.status!=="closed";t.failures=0,t.openUntil=0,e&&j(o,t,"closed");}var A=join(homedir(),".devflow","errors");function H(o,t){try{existsSync(A)||mkdirSync(A,{recursive:!0});let e=`${new Date().toISOString()} | ${o} | ${t?.message??String(t)}
2
- `;appendFileSync(join(A,"http-errors.log"),e);}catch{}}function G(o,t,e={}){let n=e.timeout??5e3,r=e.maxRetries??2,s=e.circuitBreaker??true,i=_e(o);if(s&&i.openUntil>Date.now())return Promise.resolve();s&&i.status==="open"&&j(o,i,"half_open");let l=c=>new Promise(a=>{let u=JSON.stringify(t),g=new URL(o,O),m=request({hostname:g.hostname,port:g.port,path:g.pathname+g.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":we,"Content-Length":Buffer.byteLength(u)},timeout:n},f=>{if(f.resume(),f.statusCode&&f.statusCode>=400){let _=new Error(`HTTP ${f.statusCode}`);if(H(o,_),c>0){let b=Math.min(1e3*Math.pow(2,r-c),8e3);setTimeout(()=>{l(c-1).then(a);},b);}else T(o,i),a();return}Se(o,i),a();});m.on("error",f=>{if(H(o,f),c>0){let _=Math.min(1e3*Math.pow(2,r-c),8e3);setTimeout(()=>{l(c-1).then(a);},_);}else T(o,i),a();}),m.on("timeout",()=>{m.destroy(),c>0?l(c-1).then(a):(T(o,i),a());}),m.write(u),m.end();});return l(r)}function F(o){return o??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function h(o){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(F(o))}function R(o){if(!o||typeof o!="object"||Array.isArray(o))return {};let t=o,e={};for(let n of ["lastMcpCall","bypassCount"])if(n in t){let r=t[n];if(r==null)continue;e[n]=typeof r=="number"&&Number.isFinite(r)&&r>=0?r:0;}return e}function xe(o,t){return o.lastMcpCall===t.lastMcpCall&&o.bypassCount===t.bypassCount}function ke(o){try{return existsSync(o)?R(JSON.parse(readFileSync(o,"utf-8"))):{}}catch{return {}}}function Ee(o,t){let e=t instanceof Error?t.message:String(t);console.error(`[devflow] Receipt ${o} skipped: ${e}`);}function z(o){let t=h(o);for(let e of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(t,e))&&unlinkSync(join(t,e));}catch{}}function Ie(o,t){if(o.getHookReceipt(t)){z(t);return}let e=join(h(t),"receipt.json");if(!existsSync(e))return;let n=ke(e);o.updateHookReceipt(t,()=>n),z(t);}function V(o,t,e){let n;try{n=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),Ie(n,o);let r={},s=n.updateHookReceipt(o,l=>(r=R(l),R(t(r)))),i=R(s);return {receipt:i,applied:!0,changed:!xe(r,i)}}catch(r){return Ee(e,r),{receipt:{},applied:false,changed:false}}finally{try{n?.close();}catch{}}}function P(o,t){return V(o,()=>t,"write").applied}function U(o,t){return V(o,t,"update")}var K={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function Te(o){switch(o){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}var D=class{constructor(t,e,n){this.projectRoot=t;this.sessionId=e;this.executionId=n;}getPhase(){if(!this.sessionId||!this.executionId)return "idle";let t;try{t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250});let e=t.getContextReceipt(this.projectRoot,this.sessionId,this.executionId);if(e&&e.expiresAt>Date.now())return "context_ready";e&&t.deleteContextReceipt(this.projectRoot,this.sessionId,this.executionId);}catch{}finally{try{t?.close();}catch{}}return "context_gathering"}evaluate(t,e){let n=this.getPhase();if(e||t==="Skill")return {permissionDecision:"allow"};if(n==="context_gathering"&&K[t])return {permissionDecision:"deny",reason:`DevFlow context required. Call mcp__devflow__get_project_context, then retry ${t}.`};if(n==="context_ready")return {permissionDecision:"allow"};let r=K[t];if(!r)return {permissionDecision:"allow"};let s=U(this.projectRoot,c=>({...c,bypassCount:(c.bypassCount??0)+1}));if(!s.applied)return {permissionDecision:"allow"};let i=s.receipt.bypassCount??0,l=Te(t);return i>=l?{permissionDecision:"allow",additionalContext:`\u5DF2 ${i} \u6B21\u76F4\u63A5\u4F7F\u7528 ${t}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${r} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){P(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var ee=1e4,Pe=3e4,C=500;function Ue(o,t,e){return new Promise(n=>{try{let r=new URL(o),s=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(t)},timeout:5e3},i=>{let l=[];i.on("data",c=>l.push(c)),i.on("end",()=>{let c=Buffer.concat(l).toString();n(i.statusCode!=null&&i.statusCode>=200&&i.statusCode<300?c:null);});});s.on("error",()=>n(null)),s.on("timeout",()=>{s.destroy(),n(null);}),s.write(t),s.end();}catch{n(null);}})}var x=class{constructor(t){this.retryScheduled=false;this.apiUrl=t?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=t?.apiKey??getLocalApiKey(),this.cacheDir=t?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=t?.legacyCacheDir??(t?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=t?.database??null,this.ownsDatabase=!t?.database;}async sendEvent(t){let e={...t,input:this.truncateInput(t.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),t.eventId):null}async sendExecutionStart(t,e,n,r,s=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let i={executionId:t,sessionId:e,skillName:n,startedAt:r,projectRoot:s};this.commitOrCache("execution_start",i)&&this.postHttp("/api/telemetry/skill-execution/start",i);}async sendExecutionComplete(t,e="completed",n=Date.now(),r){let s={executionId:t,status:e,finishedAt:n,metadata:r},i=this.commitOrCache("execution_complete",s);return i&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",s),i}async sendSessionStart(t,e,n){let r={id:t,projectRoot:e,startedAt:n,label:e.split("/").pop()??"unknown"},s=this.commitOrCache("session_start",r);return s&&this.postHttp("/api/telemetry/sessions",r),s}async completeEvent(t){let e=this.commitOrCache("complete_event",t);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",t)),e}async endSession(t,e=Date.now()){let n={sessionId:t,finishedAt:e},r=this.commitOrCache("session_end",n);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),r}async flushCache(){try{let n=this.getDatabase(),r=n.listTelemetryFailures({unresolvedOnly:!0,limit:C}).reverse();for(let s of r)try{this.applyOperation(s.operation,s.payload),n.resolveTelemetryFailure(s.id);}catch{}n.trimTelemetryFailures(C);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(n=>!!n))].filter(n=>existsSync(n)).flatMap(n=>readdirSync(n).filter(r=>r.endsWith(".json")).map(r=>{let s=join(n,r);try{return {cacheFile:s,envelope:Le(JSON.parse(readFileSync(s,"utf8")),r)}}catch{return null}})).filter(n=>n!==null).sort((n,r)=>n.envelope.timestamp-r.envelope.timestamp||te(n.envelope.operation)-te(r.envelope.operation));for(let{cacheFile:n,envelope:r}of e)try{let s=this.getDatabase();s.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),s.resolveTelemetryFailure(r.id),unlinkSync(n);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(t,e){try{return this.applyOperation(t,e),!0}catch(n){return this.cacheOperation(t,e,n),false}}applyOperation(t,e){let n=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(n,e),n.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(n,e),n.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":n.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":n.ensureSession(e),n.insertRun({id:ne(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!n.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":n.closeSession(e.sessionId,e.finishedAt),n.updateRun(ne(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let n=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!n)throw new Error("Canonical session ID is required for telemetry");let r=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";t.ensureSession({id:n,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,n){let r=Date.now(),s={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:n instanceof Error?n.message:String(n),timestamp:r};try{let i=this.getDatabase();i.insertTelemetryFailure({id:s.id,operation:t,payload:e,error:s.failure,createdAt:r}),i.trimTelemetryFailures(C),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let i=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(i,JSON.stringify(s,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let t=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of t.slice(0,Math.max(0,t.length-C)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},Pe).unref());}postHttp(t,e){Ue(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=ee?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,ee)}...`}}};function te(o){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(o)}function ne(o){return `hook-run:${o}`}function Le(o,t){if(!o||typeof o!="object")throw new Error("Invalid telemetry cache envelope");let e=o;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let n=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!n)throw new Error("Unknown legacy telemetry cache operation");let r=e.payload??{},s=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:n,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:s}}function Ke(o){return Buffer.from(o,"utf8").toString("base64url")}var k=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(h(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let n=this.get(e);if(n)return n.lastActivityAt=Date.now(),this.persist(n),n;let r=Date.now(),s={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:r,lastActivityAt:r};return this.sessions.set(e,s),this.persist(s),s}startExecution(t,e,n){let r=this.registerSession(t);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(n)],r.lastActivityAt=Date.now(),this.persist(r),r}get(t){let e=t.trim();if(!e)return null;let n=this.sessions.get(e);if(n)return n;let r=this.snapshotPath(e);if(!existsSync(r))return null;try{let s=JSON.parse(readFileSync(r,"utf8"));return s.sessionId!==e||s.projectRoot!==this.projectRoot?null:(s.requiredMcpTools=Array.isArray(s.requiredMcpTools)?s.requiredMcpTools:[],this.sessions.set(e,s),s)}catch{return null}}completeExecution(t){let e=this.get(t);if(!e)return null;let n={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),n}removeSession(t){let e=t.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(t){return join(this.sessionsDir,`${Ke(t)}.json`)}persist(t){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),n=`${e}.${process.pid}.tmp`;writeFileSync(n,JSON.stringify(t),{mode:384}),renameSync(n,e);}};var Ze=1;function Qe(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{return join(dirname(fileURLToPath(import.meta.url)),"..","..")}catch{return process.cwd()}}function se(o=Qe()){let t=[join(o,"dist","command-registry.json"),join(o,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let n=JSON.parse(readFileSync(e,"utf8"));if(n.version!==Ze||!et(n.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return n.commands}catch(n){console.error(`[devflow] command registry load failed: ${n.message}`);}return null}function et(o){return !o||typeof o!="object"||Array.isArray(o)?false:Object.values(o).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return oe(e.mcpTools)&&oe(e.blockedNative)})}function oe(o){return Array.isArray(o)&&o.every(t=>typeof t=="string"&&t.length>0)}function ie(o){let t=se();return t?t[o]?.mcpTools??[]:[]}function ae(o){let t=o.trim().replace(/^\//,"");if(!t.startsWith("devflow:"))return null;let e=t.slice(8).replace(/^devflow-/,"");return /^[a-z0-9][a-z0-9-]*$/i.test(e)?`devflow:${e.toLowerCase()}`:null}var at=new Set(["Agent","Bash","Glob","Grep","WebSearch","WebFetch"]);function ct(o,t,e,n){try{let r=join(h(o),"event-map.json");if(!existsSync(r))return null;let s=JSON.parse(readFileSync(r,"utf-8")),i=s.filter(u=>u.toolName===e&&(!u.sessionId||u.sessionId===t));if(i.length===0)return null;let c=(n?i.find(u=>u.toolUseId===n):void 0)??i.sort((u,g)=>u.timestamp-g.timestamp)[0],a=s.findIndex(u=>u.eventId===c.eventId);return a!==-1&&s.splice(a,1),writeFileSync(r,JSON.stringify(s)),{eventId:c.eventId,timestamp:c.timestamp}}catch{return null}}function ce(o){if(!o)return 0;let t=o;if(typeof o=="string")try{t=JSON.parse(o);}catch{return 1}let e=t,n=["files","results","data","result","memories","nodes","chunks","findings","symbols","keySymbols"],r=["reasoning","riskHints","nextActions"],s=e.data??e.structuredContent??e;if(Array.isArray(s))return s.length;if(typeof s=="object"&&s!==null){let i=s,l=0;for(let a of n)Array.isArray(i[a])&&(l+=i[a].length);for(let a of r)Array.isArray(i[a])&&(l+=i[a].length);for(let[,a]of Object.entries(i))a&&typeof a=="object"&&!Array.isArray(a)&&(l+=ce(a));let c=!!(i._devflow||i._devflow_unique||i.taskType);return l===0&&c?1:l>0?l:Object.keys(i).length>0?1:0}return 0}function lt(o,t){if(!at.has(t))return null;let e=U(o,r=>({...r,bypassCount:(r.bypassCount??0)+1}));if(!e.applied)return null;let n=e.receipt.bypassCount??0;return n>20&&n%10===0?`MCP \u5DE5\u5177\u4F7F\u7528\u7387\u4F4E\uFF1A${n} \u6B21\u76F4\u63A5\u5DE5\u5177\u8C03\u7528\u3002\u5EFA\u8BAE\u4F7F\u7528 get_project_context \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`:null}async function ut(o){if(!o)return null;let t;try{t=JSON.parse(o);}catch{return null}let e=t.tool_name||"",n=F(),r=t.tool_input||{},s=t.tool_response||{},i=t.session_id?.trim()||null,l=new k(n);if(i&&l.registerSession(i),e==="Skill"){let d=typeof r=="object"&&r.skill?r.skill:"",p=ae(d);i&&p&&(l.startExecution(i,p,ie(p)),P(n,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0}));}e.startsWith("mcp__")&&new D(n).recordMcpCall();let c=i?l.get(i):null,a=i?ct(n,i,e,t.tool_use_id):null,u=typeof s=="object"&&s!==null?s:null,g=JSON.stringify(s),m=Buffer.byteLength(g,"utf8"),f=ce(s);if(c?.executionId!==void 0||e.startsWith("mcp__devflow__")||e==="Skill"){let d=getLogWriter(n),p=c?.executionId??`auto-${i??Date.now()}`;d.toolCall({tool:e,runId:p,stepId:`step-${p}`,input:r,output:s,duration:a?Date.now()-a.timestamp:0,success:!u?.error,resultCount:f,resultSizeBytes:m,error:u?.error});}if(a&&i){let d=typeof s=="string"?s:JSON.stringify(s),p=d.length>5e3?{_truncated:true,_originalSize:d.length,text:d.slice(0,5e3)}:s,ue=Date.now()-a.timestamp;await new x().completeEvent({eventId:a.eventId,sessionId:i,output:p,duration:ue,completedAt:Date.now()});}let b=typeof r=="object"&&r.command?r.command:"",S=u?.exitCode,$=u?.stderr,le=/^\s*(ls|cat|pwd|cd|echo|head|tail|wc|which|whoami|date|env|printenv|id|hostname|uname)\b/,y,w,E=true;if(e.startsWith("mcp__"))y="mcp_call",w={mcpTool:e.replace(/^mcp__[^_]+__/,""),query:typeof r=="object"&&r.query?r.query:null,resultCount:f,resultSizeBytes:m};else if(e==="Read"||e==="Write"||e==="Edit")y=e==="Read"?"file_read":"file_write",w={filePath:typeof r=="object"&&r.file_path?r.file_path:null,fileContentSize:m};else if(e==="Bash"){let d=le.test(b),p=S!==void 0&&S!==0;E=!d||p,y=p?"bash_error":"bash_command",w={command:b||e,exitCode:S??null,stderr:$??null};}else e==="Agent"?(y="subagent",w={subagentType:typeof r=="object"&&r.subagent_type?r.subagent_type:null,description:typeof r=="object"&&r.description?String(r.description).slice(0,200):null}):e==="WebSearch"||e==="WebFetch"?(y=e==="WebSearch"?"web_search":"web_fetch",w={query:typeof r=="object"&&r.query?String(r.query).slice(0,200):null}):(E=false,y="tool_use",w={});if(E){let d={id:`evt:${Date.now()}:${Math.random().toString(36).slice(2,7)}`,sessionId:i??"",tool:e,kind:y,payload:w,command:b||void 0,exitCode:S,stderr:$??void 0,durationMs:a?Date.now()-a.timestamp:0,createdAt:Date.now()};if(i){let p=new MemoryGate(n);try{await p.forceWarmUp(),await p.recordEvent(d);}catch{}finally{p.close();}G(`/api/memory/session-events?rootPath=${encodeURIComponent(n)}`,d);}}let I=lt(n,e);return I&&console.error("[devflow] Enforcement:",I),I}if(process.argv[1]?.endsWith("post-tool-use")||process.argv[1]?.endsWith("post-tool-use.js")){let o="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{o+=t;}),process.stdin.on("end",async()=>{let t=await ut(o.trim()||process.argv[2]||"");if(t){let e=JSON.stringify(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:e}}));}}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),5e3).unref();}
3
- export{ut as handlePostToolUse};
1
+ import {existsSync,readFileSync,rmSync,mkdirSync,writeFileSync,renameSync,readdirSync,unlinkSync,appendFileSync}from'fs';import {join,dirname}from'path';import {getLogWriter}from'@devflow-tools/telemetry';import {request}from'http';import {homedir}from'os';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {MemoryGate}from'@devflow-tools/memory-engine';import {randomUUID,createHash}from'crypto';import {fileURLToPath}from'url';function q(o,t,e){try{let n=openGlobalDevFlowDatabase();try{n.insertEvent({kind:o,timestamp:Date.now(),duration:e,success:t.success!==!1,metadata:t});}finally{n.close();}}catch{}}var O=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",we=getLocalApiKey(),_e=8,be=6e4,W=new Map;function Se(o){let t=new URL(o,O).pathname,e=W.get(t);return e||(e={failures:0,lastFailure:0,openUntil:0,status:"closed"},W.set(t,e)),e}function j(o,t,e){if(t.status===e)return;let n=t.status;t.status=e,q("http_circuit_transition",{path:new URL(o,O).pathname,previous:n,status:e,failures:t.failures,openUntil:t.openUntil});}function T(o,t){t.failures++,t.lastFailure=Date.now(),t.failures>=_e&&(t.openUntil=Date.now()+be,j(o,t,"open"));}function Re(o,t){let e=t.status!=="closed";t.failures=0,t.openUntil=0,e&&j(o,t,"closed");}var A=join(homedir(),".devflow","errors");function H(o,t){try{existsSync(A)||mkdirSync(A,{recursive:!0});let e=`${new Date().toISOString()} | ${o} | ${t?.message??String(t)}
2
+ `;appendFileSync(join(A,"http-errors.log"),e);}catch{}}function G(o,t,e={}){let n=e.timeout??5e3,r=e.maxRetries??2,s=e.circuitBreaker??true,i=Se(o);if(s&&i.openUntil>Date.now())return Promise.resolve();s&&i.status==="open"&&j(o,i,"half_open");let c=l=>new Promise(a=>{let d=JSON.stringify(t),v=new URL(o,O),m=request({hostname:v.hostname,port:v.port,path:v.pathname+v.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":we,"Content-Length":Buffer.byteLength(d)},timeout:n},f=>{if(f.resume(),f.statusCode&&f.statusCode>=400){let b=new Error(`HTTP ${f.statusCode}`);if(H(o,b),l>0){let _=Math.min(1e3*Math.pow(2,r-l),8e3);setTimeout(()=>{c(l-1).then(a);},_);}else T(o,i),a();return}Re(o,i),a();});m.on("error",f=>{if(H(o,f),l>0){let b=Math.min(1e3*Math.pow(2,r-l),8e3);setTimeout(()=>{c(l-1).then(a);},b);}else T(o,i),a();}),m.on("timeout",()=>{m.destroy(),l>0?c(l-1).then(a):(T(o,i),a());}),m.write(d),m.end();});return c(r)}function F(o){return o??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function g(o){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(F(o))}function R(o){if(!o||typeof o!="object"||Array.isArray(o))return {};let t=o,e={};for(let n of ["lastMcpCall","bypassCount"])if(n in t){let r=t[n];if(r==null)continue;e[n]=typeof r=="number"&&Number.isFinite(r)&&r>=0?r:0;}return e}function ke(o,t){return o.lastMcpCall===t.lastMcpCall&&o.bypassCount===t.bypassCount}function Ee(o){try{return existsSync(o)?R(JSON.parse(readFileSync(o,"utf-8"))):{}}catch{return {}}}function Ie(o,t){let e=t instanceof Error?t.message:String(t);console.error(`[devflow] Receipt ${o} skipped: ${e}`);}function z(o){let t=g(o);for(let e of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(t,e))&&unlinkSync(join(t,e));}catch{}}function Te(o,t){if(o.getHookReceipt(t)){z(t);return}let e=join(g(t),"receipt.json");if(!existsSync(e))return;let n=Ee(e);o.updateHookReceipt(t,()=>n),z(t);}function V(o,t,e){let n;try{n=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),Te(n,o);let r={},s=n.updateHookReceipt(o,c=>(r=R(c),R(t(r)))),i=R(s);return {receipt:i,applied:!0,changed:!ke(r,i)}}catch(r){return Ie(e,r),{receipt:{},applied:false,changed:false}}finally{try{n?.close();}catch{}}}function P(o,t){return V(o,()=>t,"write").applied}function U(o,t){return V(o,t,"update")}var K={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function Ae(o){switch(o){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}var D=class{constructor(t,e,n){this.projectRoot=t;this.sessionId=e;this.executionId=n;}getPhase(){if(!this.sessionId||!this.executionId)return "idle";let t;try{t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250});let e=t.getContextReceipt(this.projectRoot,this.sessionId,this.executionId);if(e&&e.expiresAt>Date.now())return "context_ready";e&&t.deleteContextReceipt(this.projectRoot,this.sessionId,this.executionId);}catch{}finally{try{t?.close();}catch{}}return "context_gathering"}evaluate(t,e){let n=this.getPhase();if(e||t==="Skill")return {permissionDecision:"allow"};if(n==="context_gathering"&&K[t])return {permissionDecision:"deny",reason:`DevFlow context required. Call mcp__devflow__get_project_context, then retry ${t}.`};if(n==="context_ready")return {permissionDecision:"allow"};let r=K[t];if(!r)return {permissionDecision:"allow"};let s=U(this.projectRoot,l=>({...l,bypassCount:(l.bypassCount??0)+1}));if(!s.applied)return {permissionDecision:"allow"};let i=s.receipt.bypassCount??0,c=Ae(t);return i>=c?{permissionDecision:"allow",additionalContext:`\u5DF2 ${i} \u6B21\u76F4\u63A5\u4F7F\u7528 ${t}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${r} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){P(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var ee=1e4,Ue=3e4,C=500;function Le(o,t,e){return new Promise(n=>{try{let r=new URL(o),s=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(t)},timeout:5e3},i=>{let c=[];i.on("data",l=>c.push(l)),i.on("end",()=>{let l=Buffer.concat(c).toString();n(i.statusCode!=null&&i.statusCode>=200&&i.statusCode<300?l:null);});});s.on("error",()=>n(null)),s.on("timeout",()=>{s.destroy(),n(null);}),s.write(t),s.end();}catch{n(null);}})}var x=class{constructor(t){this.retryScheduled=false;this.apiUrl=t?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=t?.apiKey??getLocalApiKey(),this.cacheDir=t?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=t?.legacyCacheDir??(t?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=t?.database??null,this.ownsDatabase=!t?.database;}async sendEvent(t){let e={...t,input:this.truncateInput(t.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),t.eventId):null}async sendExecutionStart(t,e,n,r,s=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let i={executionId:t,sessionId:e,skillName:n,startedAt:r,projectRoot:s};this.commitOrCache("execution_start",i)&&this.postHttp("/api/telemetry/skill-execution/start",i);}async sendExecutionComplete(t,e="completed",n=Date.now(),r){let s={executionId:t,status:e,finishedAt:n,metadata:r},i=this.commitOrCache("execution_complete",s);return i&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",s),i}async sendSessionStart(t,e,n){let r={id:t,projectRoot:e,startedAt:n,label:e.split("/").pop()??"unknown"},s=this.commitOrCache("session_start",r);return s&&this.postHttp("/api/telemetry/sessions",r),s}async completeEvent(t){let e=this.commitOrCache("complete_event",t);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",t)),e}async endSession(t,e=Date.now()){let n={sessionId:t,finishedAt:e},r=this.commitOrCache("session_end",n);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),r}async flushCache(){try{let n=this.getDatabase(),r=n.listTelemetryFailures({unresolvedOnly:!0,limit:C}).reverse();for(let s of r)try{this.applyOperation(s.operation,s.payload),n.resolveTelemetryFailure(s.id);}catch{}n.trimTelemetryFailures(C);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(n=>!!n))].filter(n=>existsSync(n)).flatMap(n=>readdirSync(n).filter(r=>r.endsWith(".json")).map(r=>{let s=join(n,r);try{return {cacheFile:s,envelope:$e(JSON.parse(readFileSync(s,"utf8")),r)}}catch{return null}})).filter(n=>n!==null).sort((n,r)=>n.envelope.timestamp-r.envelope.timestamp||te(n.envelope.operation)-te(r.envelope.operation));for(let{cacheFile:n,envelope:r}of e)try{let s=this.getDatabase();s.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),s.resolveTelemetryFailure(r.id),unlinkSync(n);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(t,e){try{return this.applyOperation(t,e),!0}catch(n){return this.cacheOperation(t,e,n),false}}applyOperation(t,e){let n=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(n,e),n.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(n,e),n.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":n.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":n.ensureSession(e),n.insertRun({id:ne(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!n.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":n.closeSession(e.sessionId,e.finishedAt),n.updateRun(ne(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let n=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!n)throw new Error("Canonical session ID is required for telemetry");let r=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";t.ensureSession({id:n,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,n){let r=Date.now(),s={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:n instanceof Error?n.message:String(n),timestamp:r};try{let i=this.getDatabase();i.insertTelemetryFailure({id:s.id,operation:t,payload:e,error:s.failure,createdAt:r}),i.trimTelemetryFailures(C),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let i=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(i,JSON.stringify(s,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let t=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of t.slice(0,Math.max(0,t.length-C)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},Ue).unref());}postHttp(t,e){Le(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=ee?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,ee)}...`}}};function te(o){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(o)}function ne(o){return `hook-run:${o}`}function $e(o,t){if(!o||typeof o!="object")throw new Error("Invalid telemetry cache envelope");let e=o;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let n=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!n)throw new Error("Unknown legacy telemetry cache operation");let r=e.payload??{},s=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:n,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:s}}function Be(o){return Buffer.from(o,"utf8").toString("base64url")}var k=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(g(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let n=this.get(e);if(n)return n.lastActivityAt=Date.now(),this.persist(n),n;let r=Date.now(),s={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:r,lastActivityAt:r};return this.sessions.set(e,s),this.persist(s),s}startExecution(t,e,n){let r=this.registerSession(t);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(n)],r.lastActivityAt=Date.now(),this.persist(r),r}get(t){let e=t.trim();if(!e)return null;let n=this.sessions.get(e);if(n)return n;let r=this.snapshotPath(e);if(!existsSync(r))return null;try{let s=JSON.parse(readFileSync(r,"utf8"));return s.sessionId!==e||s.projectRoot!==this.projectRoot?null:(s.requiredMcpTools=Array.isArray(s.requiredMcpTools)?s.requiredMcpTools:[],this.sessions.set(e,s),s)}catch{return null}}completeExecution(t){let e=this.get(t);if(!e)return null;let n={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),n}removeSession(t){let e=t.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(t){return join(this.sessionsDir,`${Be(t)}.json`)}persist(t){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),n=`${e}.${process.pid}.tmp`;writeFileSync(n,JSON.stringify(t),{mode:384}),renameSync(n,e);}};var Qe=1;function et(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{return join(dirname(fileURLToPath(import.meta.url)),"..","..")}catch{return process.cwd()}}function se(o=et()){let t=[join(o,"dist","command-registry.json"),join(o,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let n=JSON.parse(readFileSync(e,"utf8"));if(n.version!==Qe||!tt(n.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return n.commands}catch(n){console.error(`[devflow] command registry load failed: ${n.message}`);}return null}function tt(o){return !o||typeof o!="object"||Array.isArray(o)?false:Object.values(o).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return oe(e.mcpTools)&&oe(e.blockedNative)})}function oe(o){return Array.isArray(o)&&o.every(t=>typeof t=="string"&&t.length>0)}function ie(o){let t=se();return t?t[o]?.mcpTools??[]:[]}function ae(o){let t=o.trim().replace(/^\//,"");if(!t.startsWith("devflow:"))return null;let e=t.slice(8).replace(/^devflow-/,"");return /^[a-z0-9][a-z0-9-]*$/i.test(e)?`devflow:${e.toLowerCase()}`:null}function ce(o,t){return t?`evt_tool_${createHash("sha256").update(`${o}\0${t}`).digest("hex").slice(0,24)}`:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}var lt=new Set(["Agent","Bash","Glob","Grep","WebSearch","WebFetch"]);function ut(o,t,e,n){try{let r=join(g(o),"event-map.json");if(!existsSync(r))return null;let s=JSON.parse(readFileSync(r,"utf-8")),i=s.filter(a=>a.toolName===e&&(!a.sessionId||a.sessionId===t));if(i.length===0)return null;let c=n?i.find(a=>a.toolUseId===n):i.sort((a,d)=>a.timestamp-d.timestamp)[0];if(!c)return null;let l=s.findIndex(a=>a.eventId===c.eventId);return l!==-1&&s.splice(l,1),writeFileSync(r,JSON.stringify(s)),{eventId:c.eventId,timestamp:c.timestamp}}catch{return null}}function le(o){if(!o)return 0;let t=o;if(typeof o=="string")try{t=JSON.parse(o);}catch{return 1}let e=t,n=["files","results","data","result","memories","nodes","chunks","findings","symbols","keySymbols"],r=["reasoning","riskHints","nextActions"],s=e.data??e.structuredContent??e;if(Array.isArray(s))return s.length;if(typeof s=="object"&&s!==null){let i=s,c=0;for(let a of n)Array.isArray(i[a])&&(c+=i[a].length);for(let a of r)Array.isArray(i[a])&&(c+=i[a].length);for(let[,a]of Object.entries(i))a&&typeof a=="object"&&!Array.isArray(a)&&(c+=le(a));let l=!!(i._devflow||i._devflow_unique||i.taskType);return c===0&&l?1:c>0?c:Object.keys(i).length>0?1:0}return 0}function pt(o,t){if(!lt.has(t))return null;let e=U(o,r=>({...r,bypassCount:(r.bypassCount??0)+1}));if(!e.applied)return null;let n=e.receipt.bypassCount??0;return n>20&&n%10===0?`MCP \u5DE5\u5177\u4F7F\u7528\u7387\u4F4E\uFF1A${n} \u6B21\u76F4\u63A5\u5DE5\u5177\u8C03\u7528\u3002\u5EFA\u8BAE\u4F7F\u7528 get_project_context \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`:null}async function dt(o){if(!o)return null;let t;try{t=JSON.parse(o);}catch{return null}let e=t.tool_name||"",n=F(),r=t.tool_input||{},s=t.tool_response||{},i=t.session_id?.trim()||null,c=new k(n);if(i&&c.registerSession(i),e==="Skill"){let p=typeof r=="object"&&r.skill?r.skill:"",u=ae(p);i&&u&&(c.startExecution(i,u,ie(u)),P(n,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0}));}e.startsWith("mcp__")&&new D(n).recordMcpCall();let l=i?c.get(i):null,a=i?ut(n,i,e,t.tool_use_id):null,d=typeof s=="object"&&s!==null?s:null,v=JSON.stringify(s),m=Buffer.byteLength(v,"utf8"),f=le(s);if(l?.executionId!==void 0||e.startsWith("mcp__devflow__")||e==="Skill"){let p=getLogWriter(n),u=l?.executionId??`auto-${i??Date.now()}`;p.toolCall({tool:e,runId:u,stepId:`step-${u}`,input:r,output:s,duration:a?Date.now()-a.timestamp:0,success:!d?.error,resultCount:f,resultSizeBytes:m,error:d?.error});}if(a&&i){let p=typeof s=="string"?s:JSON.stringify(s),u=p.length>5e3?{_truncated:true,_originalSize:p.length,text:p.slice(0,5e3)}:s,pe=Date.now()-a.timestamp;await new x().completeEvent({eventId:a.eventId,sessionId:i,output:u,duration:pe,completedAt:Date.now()});}let _=typeof r=="object"&&r.command?r.command:"",S=d?.exitCode,$=d?.stderr,ue=/^\s*(ls|cat|pwd|cd|echo|head|tail|wc|which|whoami|date|env|printenv|id|hostname|uname)\b/,h,y,E=true;if(e.startsWith("mcp__"))h="mcp_call",y={mcpTool:e.replace(/^mcp__[^_]+__/,""),query:typeof r=="object"&&r.query?r.query:null,resultCount:f,resultSizeBytes:m};else if(e==="Read"||e==="Write"||e==="Edit")h=e==="Read"?"file_read":"file_write",y={filePath:typeof r=="object"&&r.file_path?r.file_path:null,fileContentSize:m};else if(e==="Bash"){let p=ue.test(_),u=S!==void 0&&S!==0;E=!p||u,h=u?"bash_error":"bash_command",y={command:_||e,exitCode:S??null,stderr:$??null};}else e==="Agent"?(h="subagent",y={subagentType:typeof r=="object"&&r.subagent_type?r.subagent_type:null,description:typeof r=="object"&&r.description?String(r.description).slice(0,200):null}):e==="WebSearch"||e==="WebFetch"?(h=e==="WebSearch"?"web_search":"web_fetch",y={query:typeof r=="object"&&r.query?String(r.query).slice(0,200):null}):(E=false,h="tool_use",y={});if(E){let p={id:t.tool_use_id?`memory:${ce(i??"",t.tool_use_id)}`:`evt:${Date.now()}:${Math.random().toString(36).slice(2,7)}`,sessionId:i??"",tool:e,kind:h,payload:y,command:_||void 0,exitCode:S,stderr:$??void 0,durationMs:a?Date.now()-a.timestamp:0,createdAt:Date.now()};i&&(G(`/api/memory/session-events?rootPath=${encodeURIComponent(n)}`,p),(async()=>{let u=new MemoryGate(n);try{await u.forceWarmUp(),await u.recordEvent(p);}catch{}finally{u.close();}})());}let I=pt(n,e);return I&&console.error("[devflow] Enforcement:",I),I}if(process.argv[1]?.endsWith("post-tool-use")||process.argv[1]?.endsWith("post-tool-use.js")){let o="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{o+=t;}),process.stdin.on("end",async()=>{let t=await dt(o.trim()||process.argv[2]||"");if(t){let e=JSON.stringify(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:e}}));}}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),5e3).unref();}
3
+ export{dt as handlePostToolUse};
@@ -1,2 +1,2 @@
1
- import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync,rmSync,renameSync}from'fs';import {join,dirname as dirname$1}from'path';import {request}from'http';import {homedir}from'os';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {fileURLToPath}from'url';import {mkdirSync as mkdirSync$1,existsSync as existsSync$1,statSync,appendFileSync,chmodSync,rmSync as rmSync$1,renameSync as renameSync$1}from'node:fs';import {createHash as createHash$1}from'node:crypto';import {homedir as homedir$1}from'node:os';import {dirname,join as join$1}from'node:path';import {randomUUID,createHash}from'crypto';var U=1e4,he=3e4,D=500;function ye(n,t,e){return new Promise(o=>{try{let r=new URL(n),i=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(t)},timeout:5e3},s=>{let c=[];s.on("data",l=>c.push(l)),s.on("end",()=>{let l=Buffer.concat(c).toString();o(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?l:null);});});i.on("error",()=>o(null)),i.on("timeout",()=>{i.destroy(),o(null);}),i.write(t),i.end();}catch{o(null);}})}var w=class{constructor(t){this.retryScheduled=false;this.apiUrl=t?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=t?.apiKey??getLocalApiKey(),this.cacheDir=t?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=t?.legacyCacheDir??(t?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=t?.database??null,this.ownsDatabase=!t?.database;}async sendEvent(t){let e={...t,input:this.truncateInput(t.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),t.eventId):null}async sendExecutionStart(t,e,o,r,i=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let s={executionId:t,sessionId:e,skillName:o,startedAt:r,projectRoot:i};this.commitOrCache("execution_start",s)&&this.postHttp("/api/telemetry/skill-execution/start",s);}async sendExecutionComplete(t,e="completed",o=Date.now(),r){let i={executionId:t,status:e,finishedAt:o,metadata:r},s=this.commitOrCache("execution_complete",i);return s&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",i),s}async sendSessionStart(t,e,o){let r={id:t,projectRoot:e,startedAt:o,label:e.split("/").pop()??"unknown"},i=this.commitOrCache("session_start",r);return i&&this.postHttp("/api/telemetry/sessions",r),i}async completeEvent(t){let e=this.commitOrCache("complete_event",t);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",t)),e}async endSession(t,e=Date.now()){let o={sessionId:t,finishedAt:e},r=this.commitOrCache("session_end",o);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),r}async flushCache(){try{let o=this.getDatabase(),r=o.listTelemetryFailures({unresolvedOnly:!0,limit:D}).reverse();for(let i of r)try{this.applyOperation(i.operation,i.payload),o.resolveTelemetryFailure(i.id);}catch{}o.trimTelemetryFailures(D);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(o=>!!o))].filter(o=>existsSync(o)).flatMap(o=>readdirSync(o).filter(r=>r.endsWith(".json")).map(r=>{let i=join(o,r);try{return {cacheFile:i,envelope:_e(JSON.parse(readFileSync(i,"utf8")),r)}}catch{return null}})).filter(o=>o!==null).sort((o,r)=>o.envelope.timestamp-r.envelope.timestamp||H(o.envelope.operation)-H(r.envelope.operation));for(let{cacheFile:o,envelope:r}of e)try{let i=this.getDatabase();i.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),i.resolveTelemetryFailure(r.id),unlinkSync(o);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(t,e){try{return this.applyOperation(t,e),!0}catch(o){return this.cacheOperation(t,e,o),false}}applyOperation(t,e){let o=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(o,e),o.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(o,e),o.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":o.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":o.ensureSession(e),o.insertRun({id:L(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!o.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":o.closeSession(e.sessionId,e.finishedAt),o.updateRun(L(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let o=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!o)throw new Error("Canonical session ID is required for telemetry");let r=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";t.ensureSession({id:o,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,o){let r=Date.now(),i={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:o instanceof Error?o.message:String(o),timestamp:r};try{let s=this.getDatabase();s.insertTelemetryFailure({id:i.id,operation:t,payload:e,error:i.failure,createdAt:r}),s.trimTelemetryFailures(D),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let s=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(s,JSON.stringify(i,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let t=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of t.slice(0,Math.max(0,t.length-D)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},he).unref());}postHttp(t,e){ye(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=U?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,U)}...`}}};function H(n){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(n)}function L(n){return `hook-run:${n}`}function _e(n,t){if(!n||typeof n!="object")throw new Error("Invalid telemetry cache envelope");let e=n;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let o=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!o)throw new Error("Unknown legacy telemetry cache operation");let r=e.payload??{},i=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:o,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:i}}function S(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function p(n){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(S(n))}var Se=1;function xe(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{return join(dirname$1(fileURLToPath(import.meta.url)),"..","..")}catch{return process.cwd()}}function G(n=xe()){let t=[join(n,"dist","command-registry.json"),join(n,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let o=JSON.parse(readFileSync(e,"utf8"));if(o.version!==Se||!ke(o.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return o.commands}catch(o){console.error(`[devflow] command registry load failed: ${o.message}`);}return null}function ke(n){return !n||typeof n!="object"||Array.isArray(n)?false:Object.values(n).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return q(e.mcpTools)&&q(e.blockedNative)})}function q(n){return Array.isArray(n)&&n.every(t=>typeof t=="string"&&t.length>0)}function W(n){let t=G();return t?t[n]?.mcpTools??[]:[]}function f(n){if(!n||typeof n!="object"||Array.isArray(n))return {};let t=n,e={};for(let o of ["lastMcpCall","bypassCount"])if(o in t){let r=t[o];if(r==null)continue;e[o]=typeof r=="number"&&Number.isFinite(r)&&r>=0?r:0;}return e}function Te(n,t){return n.lastMcpCall===t.lastMcpCall&&n.bypassCount===t.bypassCount}function Oe(n){try{return existsSync(n)?f(JSON.parse(readFileSync(n,"utf-8"))):{}}catch{return {}}}function K(n,t){let e=t instanceof Error?t.message:String(t);console.error(`[devflow] Receipt ${n} skipped: ${e}`);}function J(n){let t=p(n);for(let e of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(t,e))&&unlinkSync(join(t,e));}catch{}}function z(n,t){if(n.getHookReceipt(t)){J(t);return}let e=join(p(t),"receipt.json");if(!existsSync(e))return;let o=Oe(e);n.updateHookReceipt(t,()=>o),J(t);}function V(n,t,e){let o;try{o=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(o,n);let r={},i=o.updateHookReceipt(n,c=>(r=f(c),f(t(r)))),s=f(i);return {receipt:s,applied:!0,changed:!Te(r,s)}}catch(r){return K(e,r),{receipt:{},applied:false,changed:false}}finally{try{o?.close();}catch{}}}function X(n){let t;try{return t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(t,n),f(t.getHookReceipt(n))}catch(e){return K("update",e),{}}finally{try{t?.close();}catch{}}}function Ie(n,t){return V(n,()=>t,"write").applied}function Ae(n,t){return V(n,t,"update")}var B={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function je(n){switch(n){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}var b=class{constructor(t,e,o){this.projectRoot=t;this.sessionId=e;this.executionId=o;}getPhase(){if(!this.sessionId||!this.executionId)return "idle";let t;try{t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250});let e=t.getContextReceipt(this.projectRoot,this.sessionId,this.executionId);if(e&&e.expiresAt>Date.now())return "context_ready";e&&t.deleteContextReceipt(this.projectRoot,this.sessionId,this.executionId);}catch{}finally{try{t?.close();}catch{}}return "context_gathering"}evaluate(t,e){let o=this.getPhase();if(e||t==="Skill")return {permissionDecision:"allow"};if(o==="context_gathering"&&B[t])return {permissionDecision:"deny",reason:`DevFlow context required. Call mcp__devflow__get_project_context, then retry ${t}.`};if(o==="context_ready")return {permissionDecision:"allow"};let r=B[t];if(!r)return {permissionDecision:"allow"};let i=Ae(this.projectRoot,l=>({...l,bypassCount:(l.bypassCount??0)+1}));if(!i.applied)return {permissionDecision:"allow"};let s=i.receipt.bypassCount??0,c=je(t);return s>=c?{permissionDecision:"allow",additionalContext:`\u5DF2 ${s} \u6B21\u76F4\u63A5\u4F7F\u7528 ${t}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${r} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){Ie(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var qe=10*1024*1024,Ge=5;function We(n){return createHash$1("sha256").update(n).digest("hex").slice(0,16)}function Q(n){return join$1(homedir$1(),".devflow","logs","hook-debug",`${We(n)}.jsonl`)}function ee(n,t,e={}){let o=e.maxBytes??qe,r=e.maxFiles??Ge;mkdirSync$1(dirname(n),{recursive:true,mode:448});let i=Buffer.byteLength(t),s=existsSync$1(n)?statSync(n).size:0;s>0&&s+i>o&&Je(n,r),appendFileSync(n,t,{mode:384}),chmodSync(n,384);}function Je(n,t){if(t<=1){rmSync$1(n,{force:true});return}rmSync$1(`${n}.${t-1}`,{force:true});for(let e=t-2;e>=1;e-=1){let o=`${n}.${e}`;existsSync$1(o)&&renameSync$1(o,`${n}.${e+1}`);}existsSync$1(n)&&renameSync$1(n,`${n}.1`);}var O="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function te(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}function Qe(n){return Buffer.from(n,"utf8").toString("base64url")}var R=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(p(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let o=this.get(e);if(o)return o.lastActivityAt=Date.now(),this.persist(o),o;let r=Date.now(),i={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:r,lastActivityAt:r};return this.sessions.set(e,i),this.persist(i),i}startExecution(t,e,o){let r=this.registerSession(t);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(o)],r.lastActivityAt=Date.now(),this.persist(r),r}get(t){let e=t.trim();if(!e)return null;let o=this.sessions.get(e);if(o)return o;let r=this.snapshotPath(e);if(!existsSync(r))return null;try{let i=JSON.parse(readFileSync(r,"utf8"));return i.sessionId!==e||i.projectRoot!==this.projectRoot?null:(i.requiredMcpTools=Array.isArray(i.requiredMcpTools)?i.requiredMcpTools:[],this.sessions.set(e,i),i)}catch{return null}}completeExecution(t){let e=this.get(t);if(!e)return null;let o={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),o}removeSession(t){let e=t.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(t){return join(this.sessionsDir,`${Qe(t)}.json`)}persist(t){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),o=`${e}.${process.pid}.tmp`;writeFileSync(o,JSON.stringify(t),{mode:384}),renameSync(o,e);}};function oe(n){let t=n.trim().replace(/^\//,"");if(!t.startsWith("devflow:"))return null;let e=t.slice(8).replace(/^devflow-/,"");return /^[a-z0-9][a-z0-9-]*$/i.test(e)?`devflow:${e.toLowerCase()}`:null}function re(n,t){return t?`evt_tool_${createHash("sha256").update(`${n}\0${t}`).digest("hex").slice(0,24)}`:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function h(){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}}}function ie(n){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:n}}}function it(n){let t=p(n);return existsSync(t)||mkdirSync(t,{recursive:true}),t}function st(n,t,e,o,r,i){let s=join(it(n),"event-map.json"),c=[];try{existsSync(s)&&(c=JSON.parse(readFileSync(s,"utf-8")));}catch{}c.push({sessionId:t,toolName:e,toolUseId:o,eventId:r,timestamp:i}),writeFileSync(s,JSON.stringify(c));}function ct(n){switch(n){case "Read":case "Glob":return "file_read";case "Write":case "Edit":case "NotebookEdit":return "file_write";case "Bash":return "bash_command";case "Agent":return "subagent";case "Skill":return "skill_invoke";default:return "tool_use"}}async function at(n){let t=te();try{if(!n||n.trim()==="")return h();let e=JSON.parse(n),o=Date.now(),r=S(),i=new w;e.tool_name.startsWith("mcp__devflow__")&&(typeof e.tool_input.projectRoot!="string"||!e.tool_input.projectRoot.trim())&&(e.tool_input.projectRoot=r);let s=e.session_id?.trim();if(!s)return ie(O);let c=new R(r),l=c.get(s)!==null;if(c.registerSession(s),l||await i.sendSessionStart(s,r,Date.now()),e.tool_name==="Skill"){let _=e.tool_input?.skill,m=_?oe(_):null;if(m){let le=c.get(s)?.executionId,v=c.startExecution(s,m,W(m));v.executionId&&v.executionId!==le&&await i.sendExecutionStart(v.executionId,s,m,v.lastActivityAt,r);}}let d=c.get(s);e.tool_name.startsWith("mcp__devflow__")&&(e.tool_input._devflow_session_id=s,e.tool_input._devflow_execution_id=d?.executionId??s,e.tool_use_id&&(e.tool_input._devflow_tool_use_id=e.tool_use_id));let u=e.tool_name.startsWith("mcp__"),ce=u?e.tool_name.replace(/^mcp__[^_]+__/,""):void 0,I=new b(r,s,d?.executionId),A=I.getPhase(),a,y=I.evaluate(e.tool_name,u);y.permissionDecision==="deny"?a={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:y.reason??"MCP context required"}}:y.additionalContext?a=ie(y.additionalContext):a=h(),e.tool_name.startsWith("mcp__devflow__")&&(a.hookSpecificOutput.updatedInput=e.tool_input),t&&a.hookSpecificOutput.permissionDecision==="allow"&&(a.hookSpecificOutput.additionalContext=O);let ae=d?.executionId??s,j={eventId:re(s,e.tool_use_id),executionId:ae,sessionId:s,projectRoot:r,toolUseId:e.tool_use_id,timestamp:Date.now(),toolName:e.tool_name,toolType:u?"mcp":e.tool_name==="Agent"?"subagent":"direct",isMcpTool:u,mcpToolName:ce,mcpEnforced:u&&A==="context_gathering",mcpFallback:!u&&A==="context_gathering",kind:ct(e.tool_name),input:e.tool_input,duration:0,blocked:a.hookSpecificOutput.permissionDecision==="deny"},F=await i.sendEvent(j);F&&st(r,s,e.tool_name,e.tool_use_id,F,j.timestamp);try{let _=X(r),m={ts:o,tool:e.tool_name,sessionId:s,skill:d?.skillName??"(none)",requiredTools:d?.requiredMcpTools??[],isMcp:u,enforce:d?.executionId!==void 0,stateless:d?.executionId===void 0&&!u&&e.tool_name!=="Skill",bypassCount:_.bypassCount??0,decision:a.hookSpecificOutput.permissionDecision,env_CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd()};ee(Q(r),`${JSON.stringify(m)}
2
- `);}catch{}return a}catch{return h()}}if(process.argv[1]?.endsWith("pre-tool-use")||process.argv[1]?.endsWith("pre-tool-use.js")){let n="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{n+=t;}),process.stdin.on("end",async()=>{let t=await at(n);console.log(JSON.stringify(t));}),process.stdin.on("error",()=>{console.log(JSON.stringify(h())),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify(h())),process.exit(0);},4e3).unref();}export{at as handlePreToolUse};
1
+ import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync,rmSync,renameSync}from'fs';import {join,dirname as dirname$1}from'path';import {request}from'http';import {homedir}from'os';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {fileURLToPath}from'url';import {mkdirSync as mkdirSync$1,existsSync as existsSync$1,statSync,appendFileSync,chmodSync,rmSync as rmSync$1,renameSync as renameSync$1}from'node:fs';import {createHash as createHash$1}from'node:crypto';import {homedir as homedir$1}from'node:os';import {dirname,join as join$1}from'node:path';import {randomUUID,createHash}from'crypto';var U=1e4,he=3e4,b=500;function ye(n,t,e){return new Promise(o=>{try{let r=new URL(n),i=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(t)},timeout:5e3},s=>{let c=[];s.on("data",l=>c.push(l)),s.on("end",()=>{let l=Buffer.concat(c).toString();o(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?l:null);});});i.on("error",()=>o(null)),i.on("timeout",()=>{i.destroy(),o(null);}),i.write(t),i.end();}catch{o(null);}})}var R=class{constructor(t){this.retryScheduled=false;this.apiUrl=t?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=t?.apiKey??getLocalApiKey(),this.cacheDir=t?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=t?.legacyCacheDir??(t?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=t?.database??null,this.ownsDatabase=!t?.database;}async sendEvent(t){let e={...t,input:this.truncateInput(t.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),t.eventId):null}async sendExecutionStart(t,e,o,r,i=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let s={executionId:t,sessionId:e,skillName:o,startedAt:r,projectRoot:i};this.commitOrCache("execution_start",s)&&this.postHttp("/api/telemetry/skill-execution/start",s);}async sendExecutionComplete(t,e="completed",o=Date.now(),r){let i={executionId:t,status:e,finishedAt:o,metadata:r},s=this.commitOrCache("execution_complete",i);return s&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",i),s}async sendSessionStart(t,e,o){let r={id:t,projectRoot:e,startedAt:o,label:e.split("/").pop()??"unknown"},i=this.commitOrCache("session_start",r);return i&&this.postHttp("/api/telemetry/sessions",r),i}async completeEvent(t){let e=this.commitOrCache("complete_event",t);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",t)),e}async endSession(t,e=Date.now()){let o={sessionId:t,finishedAt:e},r=this.commitOrCache("session_end",o);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),r}async flushCache(){try{let o=this.getDatabase(),r=o.listTelemetryFailures({unresolvedOnly:!0,limit:b}).reverse();for(let i of r)try{this.applyOperation(i.operation,i.payload),o.resolveTelemetryFailure(i.id);}catch{}o.trimTelemetryFailures(b);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(o=>!!o))].filter(o=>existsSync(o)).flatMap(o=>readdirSync(o).filter(r=>r.endsWith(".json")).map(r=>{let i=join(o,r);try{return {cacheFile:i,envelope:_e(JSON.parse(readFileSync(i,"utf8")),r)}}catch{return null}})).filter(o=>o!==null).sort((o,r)=>o.envelope.timestamp-r.envelope.timestamp||L(o.envelope.operation)-L(r.envelope.operation));for(let{cacheFile:o,envelope:r}of e)try{let i=this.getDatabase();i.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),i.resolveTelemetryFailure(r.id),unlinkSync(o);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(t,e){try{return this.applyOperation(t,e),!0}catch(o){return this.cacheOperation(t,e,o),false}}applyOperation(t,e){let o=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(o,e),o.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(o,e),o.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":o.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":o.ensureSession(e),o.insertRun({id:H(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!o.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":o.closeSession(e.sessionId,e.finishedAt),o.updateRun(H(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let o=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!o)throw new Error("Canonical session ID is required for telemetry");let r=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";t.ensureSession({id:o,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,o){let r=Date.now(),i={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:o instanceof Error?o.message:String(o),timestamp:r};try{let s=this.getDatabase();s.insertTelemetryFailure({id:i.id,operation:t,payload:e,error:i.failure,createdAt:r}),s.trimTelemetryFailures(b),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let s=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(s,JSON.stringify(i,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let t=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of t.slice(0,Math.max(0,t.length-b)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},he).unref());}postHttp(t,e){ye(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=U?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,U)}...`}}};function L(n){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(n)}function H(n){return `hook-run:${n}`}function _e(n,t){if(!n||typeof n!="object")throw new Error("Invalid telemetry cache envelope");let e=n;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let o=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!o)throw new Error("Unknown legacy telemetry cache operation");let r=e.payload??{},i=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:o,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:i}}function k(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function p(n){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(k(n))}var Se=1;function xe(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{return join(dirname$1(fileURLToPath(import.meta.url)),"..","..")}catch{return process.cwd()}}function W(n=xe()){let t=[join(n,"dist","command-registry.json"),join(n,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let o=JSON.parse(readFileSync(e,"utf8"));if(o.version!==Se||!ke(o.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return o.commands}catch(o){console.error(`[devflow] command registry load failed: ${o.message}`);}return null}function ke(n){return !n||typeof n!="object"||Array.isArray(n)?false:Object.values(n).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return q(e.mcpTools)&&q(e.blockedNative)})}function q(n){return Array.isArray(n)&&n.every(t=>typeof t=="string"&&t.length>0)}function G(n){let t=W();return t?t[n]?.mcpTools??[]:[]}function h(n){if(!n||typeof n!="object"||Array.isArray(n))return {};let t=n,e={};for(let o of ["lastMcpCall","bypassCount"])if(o in t){let r=t[o];if(r==null)continue;e[o]=typeof r=="number"&&Number.isFinite(r)&&r>=0?r:0;}return e}function Te(n,t){return n.lastMcpCall===t.lastMcpCall&&n.bypassCount===t.bypassCount}function Ce(n){try{return existsSync(n)?h(JSON.parse(readFileSync(n,"utf-8"))):{}}catch{return {}}}function K(n,t){let e=t instanceof Error?t.message:String(t);console.error(`[devflow] Receipt ${n} skipped: ${e}`);}function J(n){let t=p(n);for(let e of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(t,e))&&unlinkSync(join(t,e));}catch{}}function z(n,t){if(n.getHookReceipt(t)){J(t);return}let e=join(p(t),"receipt.json");if(!existsSync(e))return;let o=Ce(e);n.updateHookReceipt(t,()=>o),J(t);}function V(n,t,e){let o;try{o=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(o,n);let r={},i=o.updateHookReceipt(n,c=>(r=h(c),h(t(r)))),s=h(i);return {receipt:s,applied:!0,changed:!Te(r,s)}}catch(r){return K(e,r),{receipt:{},applied:false,changed:false}}finally{try{o?.close();}catch{}}}function X(n){let t;try{return t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(t,n),h(t.getHookReceipt(n))}catch(e){return K("update",e),{}}finally{try{t?.close();}catch{}}}function Ie(n,t){return V(n,()=>t,"write").applied}function Ae(n,t){return V(n,t,"update")}var B={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function je(n){switch(n){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}var S=class{constructor(t,e,o){this.projectRoot=t;this.sessionId=e;this.executionId=o;}getPhase(){if(!this.sessionId||!this.executionId)return "idle";let t;try{t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250});let e=t.getContextReceipt(this.projectRoot,this.sessionId,this.executionId);if(e&&e.expiresAt>Date.now())return "context_ready";e&&t.deleteContextReceipt(this.projectRoot,this.sessionId,this.executionId);}catch{}finally{try{t?.close();}catch{}}return "context_gathering"}evaluate(t,e){let o=this.getPhase();if(e||t==="Skill")return {permissionDecision:"allow"};if(o==="context_gathering"&&B[t])return {permissionDecision:"deny",reason:`DevFlow context required. Call mcp__devflow__get_project_context, then retry ${t}.`};if(o==="context_ready")return {permissionDecision:"allow"};let r=B[t];if(!r)return {permissionDecision:"allow"};let i=Ae(this.projectRoot,l=>({...l,bypassCount:(l.bypassCount??0)+1}));if(!i.applied)return {permissionDecision:"allow"};let s=i.receipt.bypassCount??0,c=je(t);return s>=c?{permissionDecision:"allow",additionalContext:`\u5DF2 ${s} \u6B21\u76F4\u63A5\u4F7F\u7528 ${t}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${r} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){Ie(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var qe=10*1024*1024,We=5;function Ge(n){return createHash$1("sha256").update(n).digest("hex").slice(0,16)}function Z(n){return join$1(homedir$1(),".devflow","logs","hook-debug",`${Ge(n)}.jsonl`)}function ee(n,t,e={}){let o=e.maxBytes??qe,r=e.maxFiles??We;mkdirSync$1(dirname(n),{recursive:true,mode:448});let i=Buffer.byteLength(t),s=existsSync$1(n)?statSync(n).size:0;s>0&&s+i>o&&Je(n,r),appendFileSync(n,t,{mode:384}),chmodSync(n,384);}function Je(n,t){if(t<=1){rmSync$1(n,{force:true});return}rmSync$1(`${n}.${t-1}`,{force:true});for(let e=t-2;e>=1;e-=1){let o=`${n}.${e}`;existsSync$1(o)&&renameSync$1(o,`${n}.${e+1}`);}existsSync$1(n)&&renameSync$1(n,`${n}.1`);}var A="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function te(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}function Ze(n){return Buffer.from(n,"utf8").toString("base64url")}var x=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(p(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let o=this.get(e);if(o)return o.lastActivityAt=Date.now(),this.persist(o),o;let r=Date.now(),i={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:r,lastActivityAt:r};return this.sessions.set(e,i),this.persist(i),i}startExecution(t,e,o){let r=this.registerSession(t);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(o)],r.lastActivityAt=Date.now(),this.persist(r),r}get(t){let e=t.trim();if(!e)return null;let o=this.sessions.get(e);if(o)return o;let r=this.snapshotPath(e);if(!existsSync(r))return null;try{let i=JSON.parse(readFileSync(r,"utf8"));return i.sessionId!==e||i.projectRoot!==this.projectRoot?null:(i.requiredMcpTools=Array.isArray(i.requiredMcpTools)?i.requiredMcpTools:[],this.sessions.set(e,i),i)}catch{return null}}completeExecution(t){let e=this.get(t);if(!e)return null;let o={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),o}removeSession(t){let e=t.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(t){return join(this.sessionsDir,`${Ze(t)}.json`)}persist(t){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),o=`${e}.${process.pid}.tmp`;writeFileSync(o,JSON.stringify(t),{mode:384}),renameSync(o,e);}};function oe(n){let t=n.trim().replace(/^\//,"");if(!t.startsWith("devflow:"))return null;let e=t.slice(8).replace(/^devflow-/,"");return /^[a-z0-9][a-z0-9-]*$/i.test(e)?`devflow:${e.toLowerCase()}`:null}function re(n,t){return t?`evt_tool_${createHash("sha256").update(`${n}\0${t}`).digest("hex").slice(0,24)}`:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function y(){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}}}function ie(n){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:n}}}function it(n){let t=p(n);return existsSync(t)||mkdirSync(t,{recursive:true}),t}function st(n,t,e,o,r,i){let s=join(it(n),"event-map.json"),c=[];try{existsSync(s)&&(c=JSON.parse(readFileSync(s,"utf-8")));}catch{}c.push({sessionId:t,toolName:e,toolUseId:o,eventId:r,timestamp:i}),writeFileSync(s,JSON.stringify(c));}function ct(n){switch(n){case "Read":case "Glob":return "file_read";case "Write":case "Edit":case "NotebookEdit":return "file_write";case "Bash":return "bash_command";case "Agent":return "subagent";case "Skill":return "skill_invoke";default:return "tool_use"}}async function at(n){let t=te();try{if(!n||n.trim()==="")return y();let e=JSON.parse(n),o=Date.now(),r=k(),i=new R;e.tool_name.startsWith("mcp__devflow__")&&(typeof e.tool_input.projectRoot!="string"||!e.tool_input.projectRoot.trim())&&(e.tool_input.projectRoot=r);let s=e.session_id?.trim();if(!s)return ie(A);let c=new x(r),l=c.get(s)!==null;if(c.registerSession(s),l||await i.sendSessionStart(s,r,Date.now()),e.tool_name==="Skill"){let D=e.tool_input?.skill,m=D?oe(D):null;if(m){let le=c.get(s)?.executionId,w=c.startExecution(s,m,G(m));w.executionId&&w.executionId!==le&&await i.sendExecutionStart(w.executionId,s,m,w.lastActivityAt,r);}}let d=c.get(s);e.tool_name.startsWith("mcp__devflow__")&&(e.tool_input._devflow_session_id=s,e.tool_input._devflow_execution_id=d?.executionId??s,e.tool_use_id&&(e.tool_input._devflow_tool_use_id=e.tool_use_id));let u=e.tool_name.startsWith("mcp__"),ce=u?e.tool_name.replace(/^mcp__[^_]+__/,""):void 0,j=new S(r,s,d?.executionId),F=j.getPhase(),a,_=j.evaluate(e.tool_name,u);_.permissionDecision==="deny"?a={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:_.reason??"MCP context required"}}:_.additionalContext?a=ie(_.additionalContext):a=y(),e.tool_name.startsWith("mcp__devflow__")&&(a.hookSpecificOutput.updatedInput=e.tool_input),t&&a.hookSpecificOutput.permissionDecision==="allow"&&(a.hookSpecificOutput.additionalContext=A);let ae=d?.executionId??s,g={eventId:re(s,e.tool_use_id),executionId:ae,sessionId:s,projectRoot:r,toolUseId:e.tool_use_id,timestamp:Date.now(),toolName:e.tool_name,toolType:u?"mcp":e.tool_name==="Agent"?"subagent":"direct",isMcpTool:u,mcpToolName:ce,mcpEnforced:u&&F==="context_gathering",mcpFallback:!u&&F==="context_gathering",kind:ct(e.tool_name),input:e.tool_input,duration:0,blocked:a.hookSpecificOutput.permissionDecision==="deny",blockReason:a.hookSpecificOutput.permissionDecision==="deny"?"DEVFLOW_CONTEXT_REQUIRED":void 0,error:a.hookSpecificOutput.permissionDecision==="deny"?"DEVFLOW_CONTEXT_REQUIRED":void 0},v=await i.sendEvent(g);v&&!g.blocked&&st(r,s,e.tool_name,e.tool_use_id,v,g.timestamp),v&&g.blocked&&await i.completeEvent({eventId:v,sessionId:s,error:"DEVFLOW_CONTEXT_REQUIRED",duration:Date.now()-g.timestamp,completedAt:Date.now()});try{let D=X(r),m={ts:o,tool:e.tool_name,sessionId:s,skill:d?.skillName??"(none)",requiredTools:d?.requiredMcpTools??[],isMcp:u,enforce:d?.executionId!==void 0,stateless:d?.executionId===void 0&&!u&&e.tool_name!=="Skill",bypassCount:D.bypassCount??0,decision:a.hookSpecificOutput.permissionDecision,env_CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd()};ee(Z(r),`${JSON.stringify(m)}
2
+ `);}catch{}return a}catch{return y()}}if(process.argv[1]?.endsWith("pre-tool-use")||process.argv[1]?.endsWith("pre-tool-use.js")){let n="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{n+=t;}),process.stdin.on("end",async()=>{let t=await at(n);console.log(JSON.stringify(t));}),process.stdin.on("error",()=>{console.log(JSON.stringify(y())),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify(y())),process.exit(0);},4e3).unref();}export{at as handlePreToolUse};
@@ -1,2 +1,2 @@
1
- import {request}from'http';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {existsSync,readFileSync,rmSync,mkdirSync,writeFileSync,renameSync,readdirSync,unlinkSync}from'fs';import {join}from'path';import {homedir}from'os';import {MemoryGate}from'@devflow-tools/memory-engine';import {randomUUID}from'crypto';function w(o){return o??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function C(o){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(w(o))}var k=1e4,H=3e4,f=500;function K(o,s,e){return new Promise(t=>{try{let n=new URL(o),r=request({hostname:n.hostname,port:n.port||80,path:n.pathname+n.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(s)},timeout:5e3},i=>{let c=[];i.on("data",a=>c.push(a)),i.on("end",()=>{let a=Buffer.concat(c).toString();t(i.statusCode!=null&&i.statusCode>=200&&i.statusCode<300?a:null);});});r.on("error",()=>t(null)),r.on("timeout",()=>{r.destroy(),t(null);}),r.write(s),r.end();}catch{t(null);}})}var v=class{constructor(s){this.retryScheduled=false;this.apiUrl=s?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=s?.apiKey??getLocalApiKey(),this.cacheDir=s?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=s?.legacyCacheDir??(s?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=s?.database??null,this.ownsDatabase=!s?.database;}async sendEvent(s){let e={...s,input:this.truncateInput(s.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),s.eventId):null}async sendExecutionStart(s,e,t,n,r=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let i={executionId:s,sessionId:e,skillName:t,startedAt:n,projectRoot:r};this.commitOrCache("execution_start",i)&&this.postHttp("/api/telemetry/skill-execution/start",i);}async sendExecutionComplete(s,e="completed",t=Date.now(),n){let r={executionId:s,status:e,finishedAt:t,metadata:n},i=this.commitOrCache("execution_complete",r);return i&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",r),i}async sendSessionStart(s,e,t){let n={id:s,projectRoot:e,startedAt:t,label:e.split("/").pop()??"unknown"},r=this.commitOrCache("session_start",n);return r&&this.postHttp("/api/telemetry/sessions",n),r}async completeEvent(s){let e=this.commitOrCache("complete_event",s);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",s)),e}async endSession(s,e=Date.now()){let t={sessionId:s,finishedAt:e},n=this.commitOrCache("session_end",t);return n&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(s)}/close`,{finishedAt:e})),n}async flushCache(){try{let t=this.getDatabase(),n=t.listTelemetryFailures({unresolvedOnly:!0,limit:f}).reverse();for(let r of n)try{this.applyOperation(r.operation,r.payload),t.resolveTelemetryFailure(r.id);}catch{}t.trimTelemetryFailures(f);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(t=>!!t))].filter(t=>existsSync(t)).flatMap(t=>readdirSync(t).filter(n=>n.endsWith(".json")).map(n=>{let r=join(t,n);try{return {cacheFile:r,envelope:B(JSON.parse(readFileSync(r,"utf8")),n)}}catch{return null}})).filter(t=>t!==null).sort((t,n)=>t.envelope.timestamp-n.envelope.timestamp||A(t.envelope.operation)-A(n.envelope.operation));for(let{cacheFile:t,envelope:n}of e)try{let r=this.getDatabase();r.insertTelemetryFailure({id:n.id,operation:n.operation,payload:n.payload,error:n.failure,createdAt:n.timestamp}),this.applyOperation(n.operation,n.payload),r.resolveTelemetryFailure(n.id),unlinkSync(t);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(s,e){try{return this.applyOperation(s,e),!0}catch(t){return this.cacheOperation(s,e,t),false}}applyOperation(s,e){let t=this.getDatabase();switch(s){case "tool_call":this.ensureParentSession(t,e),t.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(t,e),t.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":t.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":t.ensureSession(e),t.insertRun({id:P(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!t.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":t.closeSession(e.sessionId,e.finishedAt),t.updateRun(P(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(s,e){let t=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!t)throw new Error("Canonical session ID is required for telemetry");let n=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";s.ensureSession({id:t,projectRoot:n,label:n==="unknown"?void 0:n.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(s,e,t){let n=Date.now(),r={id:`failure:${n}:${Math.random().toString(36).slice(2,11)}`,operation:s,payload:e,failure:t instanceof Error?t.message:String(t),timestamp:n};try{let i=this.getDatabase();i.insertTelemetryFailure({id:r.id,operation:s,payload:e,error:r.failure,createdAt:n}),i.trimTelemetryFailures(f),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let i=join(this.cacheDir,`${n}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(i,JSON.stringify(r,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let s=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of s.slice(0,Math.max(0,s.length-f)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},H).unref());}postHttp(s,e){K(`${this.apiUrl}${s}`,JSON.stringify(e),this.apiKey);}truncateInput(s){let e=JSON.stringify(s);return e===void 0||e.length<=k?s:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,k)}...`}}};function A(o){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(o)}function P(o){return `hook-run:${o}`}function B(o,s){if(!o||typeof o!="object")throw new Error("Invalid telemetry cache envelope");let e=o;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let t=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!t)throw new Error("Unknown legacy telemetry cache operation");let n=e.payload??{},r=Number(n.timestamp??n.startedAt??Date.now());return {id:`legacy-cache:${s}`,operation:t,payload:n,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:r}}async function M(o){let s=!o.memory,e=o.memory??new MemoryGate(o.projectRoot),t=0,n=0;try{await e.forceWarmUp(),o.trigger==="session_end"&&(t=await e.releasePendingDistillLeases(o.sessionId)),n=e.getPendingEventCount();}finally{s&&e.close();}let r={pendingEvents:n,releasedLeases:t,trigger:o.trigger,sessionId:o.sessionId};try{let i=openGlobalDevFlowDatabase();try{let c=Date.now(),a=`distill:${c}:${Math.random().toString(36).slice(2,10)}`;i.recordMemoryDistillCheckpoint({id:a,projectRoot:o.projectRoot,sessionId:o.sessionId,trigger:o.trigger,pendingEvents:n,releasedLeases:t,createdAt:c}),i.insertEvent({kind:"memory_distill_requested",timestamp:c,success:!0,metadata:{...r,projectRoot:o.projectRoot}});}finally{i.close();}}catch{}return r}function te(o){return Buffer.from(o,"utf8").toString("base64url")}var D=class{constructor(s){this.projectRoot=s;this.sessions=new Map;this.sessionsDir=join(C(s),"hook-sessions");}registerSession(s){let e=s.trim();if(!e)throw new Error("session_id_required");let t=this.get(e);if(t)return t.lastActivityAt=Date.now(),this.persist(t),t;let n=Date.now(),r={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:n,lastActivityAt:n};return this.sessions.set(e,r),this.persist(r),r}startExecution(s,e,t){let n=this.registerSession(s);return (!n.executionId||n.skillName!==e)&&(n.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),n.skillName=e,n.requiredMcpTools=[...new Set(t)],n.lastActivityAt=Date.now(),this.persist(n),n}get(s){let e=s.trim();if(!e)return null;let t=this.sessions.get(e);if(t)return t;let n=this.snapshotPath(e);if(!existsSync(n))return null;try{let r=JSON.parse(readFileSync(n,"utf8"));return r.sessionId!==e||r.projectRoot!==this.projectRoot?null:(r.requiredMcpTools=Array.isArray(r.requiredMcpTools)?r.requiredMcpTools:[],this.sessions.set(e,r),r)}catch{return null}}completeExecution(s){let e=this.get(s);if(!e)return null;let t={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),t}removeSession(s){let e=s.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(s){return join(this.sessionsDir,`${te(s)}.json`)}persist(s){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(s.sessionId),t=`${e}.${process.pid}.tmp`;writeFileSync(t,JSON.stringify(s),{mode:384}),renameSync(t,e);}};var oe=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",ie=getLocalApiKey();function ae(o,s){return new Promise(e=>{let t=JSON.stringify(s),n=new URL(o,oe),r=request({hostname:n.hostname,port:n.port,path:n.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":ie,"Content-Length":Buffer.byteLength(t)},timeout:5e3},()=>e());r.on("error",()=>e()),r.write(t),r.end();})}async function ce(o,s=w(),e=true,t){let n;try{n=JSON.parse(o);}catch{return}let r=n.session_id?.trim();if(!r)return;let i=new D(s),c=i.get(r),a=t?.telemetry??new v,j=!t?.telemetry;try{try{await a.flushAndAggregate();}catch{}let u;try{u=openGlobalDevFlowDatabase();let h=u.listToolCallEventsBySession(r),g=c?.executionId;if(g){let y=h.filter(m=>m.executionId===g),l=y.length,d=y.filter(m=>m.isMcpTool).length,_=l-d,S=y.filter(m=>m.toolType==="subagent").length,b=y.map(m=>m.timestamp).filter(Boolean),T=b.length>=2?Math.max(...b)-Math.min(...b):0;try{u.updateSkillExecution(g,{status:"completed",finishedAt:Date.now(),totalToolCalls:l,mcpToolCalls:d,directToolCalls:_,subagentCount:S,totalDuration:T,mcpComplianceRate:l>0?Math.round(d/l*1e4)/100:0}),await ae("/api/telemetry/skill-execution/complete",{executionId:g,finishedAt:Date.now(),status:"completed",summary:{totalToolCalls:l,mcpToolCalls:d,directToolCalls:_,subagentCount:S,totalDuration:T,mcpComplianceRate:l>0?Math.round(d/l*1e4)/100:0}});}catch{}}e&&u.deleteHookReceipt(s),u.deleteContextReceipt(s,r);}catch{}finally{u?.close();}try{if(await M({projectRoot:s,sessionId:r,trigger:"session_end",memory:t?.memory}),t?.memory)await t.memory.closeSession(r);else {let h=new(await import('@devflow-tools/memory-engine')).MemoryGate(s);try{await h.closeSession(r);}finally{h.close();}}}catch{}try{await a.endSession(r),await a.flushAndAggregate();}catch{}}finally{j&&a.close(),i.removeSession(r);}}if(process.argv[1]?.endsWith("session-end")||process.argv[1]?.endsWith("session-end.js")){let o="";process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{o+=s;}),process.stdin.on("end",async()=>{await ce(o.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),1e4).unref();}
2
- export{ce as handleSessionEnd};
1
+ import {request}from'http';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {existsSync,readFileSync,rmSync,mkdirSync,writeFileSync,renameSync,readdirSync,unlinkSync}from'fs';import {join}from'path';import {homedir}from'os';import {MemoryGate}from'@devflow-tools/memory-engine';import {randomUUID}from'crypto';function w(i){return i??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function R(i){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(w(i))}var A=1e4,K=3e4,g=500;function B(i,s,e){return new Promise(t=>{try{let n=new URL(i),r=request({hostname:n.hostname,port:n.port||80,path:n.pathname+n.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(s)},timeout:5e3},o=>{let c=[];o.on("data",a=>c.push(a)),o.on("end",()=>{let a=Buffer.concat(c).toString();t(o.statusCode!=null&&o.statusCode>=200&&o.statusCode<300?a:null);});});r.on("error",()=>t(null)),r.on("timeout",()=>{r.destroy(),t(null);}),r.write(s),r.end();}catch{t(null);}})}var y=class{constructor(s){this.retryScheduled=false;this.apiUrl=s?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=s?.apiKey??getLocalApiKey(),this.cacheDir=s?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=s?.legacyCacheDir??(s?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=s?.database??null,this.ownsDatabase=!s?.database;}async sendEvent(s){let e={...s,input:this.truncateInput(s.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),s.eventId):null}async sendExecutionStart(s,e,t,n,r=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let o={executionId:s,sessionId:e,skillName:t,startedAt:n,projectRoot:r};this.commitOrCache("execution_start",o)&&this.postHttp("/api/telemetry/skill-execution/start",o);}async sendExecutionComplete(s,e="completed",t=Date.now(),n){let r={executionId:s,status:e,finishedAt:t,metadata:n},o=this.commitOrCache("execution_complete",r);return o&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",r),o}async sendSessionStart(s,e,t){let n={id:s,projectRoot:e,startedAt:t,label:e.split("/").pop()??"unknown"},r=this.commitOrCache("session_start",n);return r&&this.postHttp("/api/telemetry/sessions",n),r}async completeEvent(s){let e=this.commitOrCache("complete_event",s);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",s)),e}async endSession(s,e=Date.now()){let t={sessionId:s,finishedAt:e},n=this.commitOrCache("session_end",t);return n&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(s)}/close`,{finishedAt:e})),n}async flushCache(){try{let t=this.getDatabase(),n=t.listTelemetryFailures({unresolvedOnly:!0,limit:g}).reverse();for(let r of n)try{this.applyOperation(r.operation,r.payload),t.resolveTelemetryFailure(r.id);}catch{}t.trimTelemetryFailures(g);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(t=>!!t))].filter(t=>existsSync(t)).flatMap(t=>readdirSync(t).filter(n=>n.endsWith(".json")).map(n=>{let r=join(t,n);try{return {cacheFile:r,envelope:G(JSON.parse(readFileSync(r,"utf8")),n)}}catch{return null}})).filter(t=>t!==null).sort((t,n)=>t.envelope.timestamp-n.envelope.timestamp||P(t.envelope.operation)-P(n.envelope.operation));for(let{cacheFile:t,envelope:n}of e)try{let r=this.getDatabase();r.insertTelemetryFailure({id:n.id,operation:n.operation,payload:n.payload,error:n.failure,createdAt:n.timestamp}),this.applyOperation(n.operation,n.payload),r.resolveTelemetryFailure(n.id),unlinkSync(t);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(s,e){try{return this.applyOperation(s,e),!0}catch(t){return this.cacheOperation(s,e,t),false}}applyOperation(s,e){let t=this.getDatabase();switch(s){case "tool_call":this.ensureParentSession(t,e),t.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(t,e),t.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":t.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":t.ensureSession(e),t.insertRun({id:M(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!t.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":t.closeSession(e.sessionId,e.finishedAt),t.updateRun(M(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(s,e){let t=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!t)throw new Error("Canonical session ID is required for telemetry");let n=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";s.ensureSession({id:t,projectRoot:n,label:n==="unknown"?void 0:n.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(s,e,t){let n=Date.now(),r={id:`failure:${n}:${Math.random().toString(36).slice(2,11)}`,operation:s,payload:e,failure:t instanceof Error?t.message:String(t),timestamp:n};try{let o=this.getDatabase();o.insertTelemetryFailure({id:r.id,operation:s,payload:e,error:r.failure,createdAt:n}),o.trimTelemetryFailures(g),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let o=join(this.cacheDir,`${n}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(o,JSON.stringify(r,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let s=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of s.slice(0,Math.max(0,s.length-g)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},K).unref());}postHttp(s,e){B(`${this.apiUrl}${s}`,JSON.stringify(e),this.apiKey);}truncateInput(s){let e=JSON.stringify(s);return e===void 0||e.length<=A?s:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,A)}...`}}};function P(i){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(i)}function M(i){return `hook-run:${i}`}function G(i,s){if(!i||typeof i!="object")throw new Error("Invalid telemetry cache envelope");let e=i;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let t=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!t)throw new Error("Unknown legacy telemetry cache operation");let n=e.payload??{},r=Number(n.timestamp??n.startedAt??Date.now());return {id:`legacy-cache:${s}`,operation:t,payload:n,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:r}}async function O(i){let s=!i.memory,e=i.memory??new MemoryGate(i.projectRoot),t=0,n=0;try{await e.forceWarmUp(),i.trigger==="session_end"&&(t=await e.releasePendingDistillLeases(i.sessionId)),n=e.getPendingEventCount();}finally{s&&e.close();}let r={pendingEvents:n,releasedLeases:t,trigger:i.trigger,sessionId:i.sessionId};try{let o=openGlobalDevFlowDatabase();try{let c=Date.now(),a=`distill:${c}:${Math.random().toString(36).slice(2,10)}`;o.recordMemoryDistillCheckpoint({id:a,projectRoot:i.projectRoot,sessionId:i.sessionId,trigger:i.trigger,pendingEvents:n,releasedLeases:t,createdAt:c}),o.insertEvent({kind:"memory_distill_requested",timestamp:c,success:!0,metadata:{...r,projectRoot:i.projectRoot}});}finally{o.close();}}catch{}return r}function se(i){return Buffer.from(i,"utf8").toString("base64url")}var f=class{constructor(s){this.projectRoot=s;this.sessions=new Map;this.sessionsDir=join(R(s),"hook-sessions");}registerSession(s){let e=s.trim();if(!e)throw new Error("session_id_required");let t=this.get(e);if(t)return t.lastActivityAt=Date.now(),this.persist(t),t;let n=Date.now(),r={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:n,lastActivityAt:n};return this.sessions.set(e,r),this.persist(r),r}startExecution(s,e,t){let n=this.registerSession(s);return (!n.executionId||n.skillName!==e)&&(n.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),n.skillName=e,n.requiredMcpTools=[...new Set(t)],n.lastActivityAt=Date.now(),this.persist(n),n}get(s){let e=s.trim();if(!e)return null;let t=this.sessions.get(e);if(t)return t;let n=this.snapshotPath(e);if(!existsSync(n))return null;try{let r=JSON.parse(readFileSync(n,"utf8"));return r.sessionId!==e||r.projectRoot!==this.projectRoot?null:(r.requiredMcpTools=Array.isArray(r.requiredMcpTools)?r.requiredMcpTools:[],this.sessions.set(e,r),r)}catch{return null}}completeExecution(s){let e=this.get(s);if(!e)return null;let t={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),t}removeSession(s){let e=s.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(s){return join(this.sessionsDir,`${se(s)}.json`)}persist(s){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(s.sessionId),t=`${e}.${process.pid}.tmp`;writeFileSync(t,JSON.stringify(s),{mode:384}),renameSync(t,e);}};var oe=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",ae=getLocalApiKey();function ce(i,s){return new Promise(e=>{let t=JSON.stringify(s),n=new URL(i,oe),r=request({hostname:n.hostname,port:n.port,path:n.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":ae,"Content-Length":Buffer.byteLength(t)},timeout:5e3},()=>e());r.on("error",()=>e()),r.write(t),r.end();})}async function le(i,s=w(),e=true,t){let n;try{n=JSON.parse(i);}catch{return}let r=n.session_id?.trim();if(!r)return;let o=new f(s),c=o.get(r),a=t?.telemetry??new y,F=!t?.telemetry;try{try{await a.flushAndAggregate();}catch{}let l;try{l=openGlobalDevFlowDatabase();let d=l.listToolCallEventsBySession(r),p=c?.executionId;if(p){let h=d.filter(m=>m.executionId===p),v=h.length,D=h.filter(m=>m.isMcpTool).length,_=v-D,S=h.filter(m=>m.toolType==="subagent").length,b=h.map(m=>m.timestamp).filter(Boolean),C=b.length>=2?Math.max(...b)-Math.min(...b):0,T=l.getExecutionObligationCompliance(p);try{l.updateSkillExecution(p,{status:"completed",finishedAt:Date.now(),totalToolCalls:v,mcpToolCalls:D,directToolCalls:_,subagentCount:S,totalDuration:C,mcpComplianceRate:T.rate}),await ce("/api/telemetry/skill-execution/complete",{executionId:p,finishedAt:Date.now(),status:"completed",summary:{totalToolCalls:v,mcpToolCalls:D,directToolCalls:_,subagentCount:S,totalDuration:C,mcpComplianceRate:T.rate}});}catch{}}e&&l.deleteHookReceipt(s),l.deleteContextReceipt(s,r);}catch{}finally{l?.close();}try{if(await O({projectRoot:s,sessionId:r,trigger:"session_end",memory:t?.memory}),t?.memory)await t.memory.closeSession(r);else {let d=new(await import('@devflow-tools/memory-engine')).MemoryGate(s);try{await d.closeSession(r);}finally{d.close();}}}catch{}try{await a.endSession(r),await a.flushAndAggregate();}catch{}}finally{F&&a.close(),o.removeSession(r);}}if(process.argv[1]?.endsWith("session-end")||process.argv[1]?.endsWith("session-end.js")){let i="";process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{i+=s;}),process.stdin.on("end",async()=>{await le(i.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),1e4).unref();}
2
+ export{le as handleSessionEnd};
@@ -16,13 +16,43 @@ elif command -v npx &>/dev/null; then
16
16
  fi
17
17
 
18
18
  if [ -n "$DOCTOR_OUTPUT" ] && echo "$DOCTOR_OUTPUT" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
19
- PROJECT_NAME=$(echo "$DOCTOR_OUTPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('project',{}).get('name','') or d.get('projectName',''))" 2>/dev/null || echo "")
20
- FILES=$(echo "$DOCTOR_OUTPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('project',{}).get('files', d.get('indexedFiles','?')))" 2>/dev/null || echo "?")
21
- TECH=$(echo "$DOCTOR_OUTPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); s=d.get('project',{}).get('techStack',[]); print(', '.join(s) if s else 'unknown')" 2>/dev/null || echo "unknown")
22
- ENGINES=$(echo "$DOCTOR_OUTPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('engines',{}); print(' '.join([f'{k} {\"✓\" if v else \"✗\"}' for k,v in e.items()]))" 2>/dev/null || echo "")
23
- PLUGINS=$(echo "$DOCTOR_OUTPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); p=d.get('plugins',[]); print(', '.join(p))" 2>/dev/null || echo "")
24
-
25
- SUMMARY="DevFlow 已就绪。项目 ${PROJECT_NAME:-unknown} (${TECH})。${FILES} 个文件已索引。引擎:${ENGINES}。已装插件:${PLUGINS}。使用 /devflow:context 查代码,/devflow:react 获取 React 专家能力。"
19
+ SUMMARY=$(
20
+ DEVFLOW_DOCTOR_OUTPUT="$DOCTOR_OUTPUT" python3 - 2>/dev/null <<'PY'
21
+ import json, os
22
+ from pathlib import Path
23
+
24
+ d = json.loads(os.environ.get('DEVFLOW_DOCTOR_OUTPUT', '{}'))
25
+ metrics = {}
26
+ red_sections = []
27
+ for section in d.get('sections', []):
28
+ if section.get('status') == 'red':
29
+ red_sections.append(str(section.get('name', '')).strip())
30
+ for metric in section.get('metrics', []):
31
+ key = metric.get('key')
32
+ if key:
33
+ metrics[key] = metric.get('value')
34
+
35
+ # One-release compatibility with the previous top-level doctor schema.
36
+ legacy_project = d.get('project', {}) if isinstance(d.get('project'), dict) else {}
37
+ project_root = metrics.get('canonical_project') or legacy_project.get('rootPath') or ''
38
+ project_name = legacy_project.get('name') or d.get('projectName') or (Path(project_root).name if project_root else '')
39
+ parts = ['DevFlow 已就绪']
40
+ if project_name:
41
+ parts.append(f'项目 {project_name}')
42
+ nodes = metrics.get('codegraph_nodes')
43
+ if isinstance(nodes, (int, float)):
44
+ parts.append(f'代码图 {int(nodes)} 个节点')
45
+ plugin_source = metrics.get('active_skill_source')
46
+ if isinstance(plugin_source, str) and plugin_source:
47
+ parts.append(f'技能来源 {plugin_source}')
48
+ if red_sections:
49
+ parts.append('需处理 ' + ', '.join(x for x in red_sections if x))
50
+ print('。'.join(parts) + '。使用 /devflow:context 查代码,/devflow:react 获取 React 专家能力。')
51
+ PY
52
+ ) || SUMMARY=""
53
+ if [ -z "$SUMMARY" ]; then
54
+ SUMMARY="DevFlow 插件已加载。Doctor 状态暂不可用,运行 devflow doctor 查看详情。"
55
+ fi
26
56
  else
27
57
  SUMMARY="DevFlow 插件已加载。如尚未初始化项目,请运行 devflow init。使用 /devflow 查看可用命令。"
28
58
  fi
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-tools/claude-code-plugin",
3
- "version": "0.16.2",
3
+ "version": "0.16.4",
4
4
  "description": "DevFlow — Developer Intelligence Platform for Claude Code",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -8,18 +8,18 @@
8
8
  "deploy": "npx tsx scripts/inject-gates.ts --deploy"
9
9
  },
10
10
  "dependencies": {
11
- "@devflow-tools/database": "0.16.2",
12
- "@devflow-tools/memory-engine": "0.16.2",
13
- "@devflow-tools/sdk": "0.16.2",
14
- "@devflow-tools/telemetry": "0.16.2",
15
- "@devflow-tools/workflow-engine": "0.16.2"
11
+ "@devflow-tools/database": "0.16.4",
12
+ "@devflow-tools/memory-engine": "0.16.4",
13
+ "@devflow-tools/sdk": "0.16.4",
14
+ "@devflow-tools/telemetry": "0.16.4",
15
+ "@devflow-tools/workflow-engine": "0.16.4"
16
16
  },
17
17
  "allowScripts": {
18
18
  "better-sqlite3@12.11.1": true,
19
19
  "sharp@0.32.6": true
20
20
  },
21
21
  "devDependencies": {
22
- "@devflow-tools/mcp-server": "0.16.2"
22
+ "@devflow-tools/mcp-server": "0.16.4"
23
23
  },
24
24
  "keywords": [
25
25
  "pi-package",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-tools/cli",
3
- "version": "0.16.2",
3
+ "version": "0.16.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -12,31 +12,31 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@colbymchenry/codegraph": "^1.1.1",
15
- "@devflow-tools/adapters": "0.16.2",
16
- "@devflow-tools/benchmark": "0.16.2",
17
- "@devflow-tools/context-engine": "0.16.2",
18
- "@devflow-tools/database": "0.16.2",
19
- "@devflow-tools/knowledge-engine": "0.16.2",
20
- "@devflow-tools/mcp-server": "0.16.2",
21
- "@devflow-tools/memory-engine": "0.16.2",
22
- "@devflow-tools/plugin-animation": "0.16.2",
23
- "@devflow-tools/plugin-css": "0.16.2",
24
- "@devflow-tools/plugin-docker": "0.16.2",
25
- "@devflow-tools/plugin-electron": "0.16.2",
26
- "@devflow-tools/plugin-git": "0.16.2",
27
- "@devflow-tools/plugin-graphql": "0.16.2",
28
- "@devflow-tools/plugin-nest": "0.16.2",
29
- "@devflow-tools/plugin-nextjs": "0.16.2",
30
- "@devflow-tools/plugin-performance": "0.16.2",
31
- "@devflow-tools/plugin-react": "0.16.2",
32
- "@devflow-tools/plugin-tailwind": "0.16.2",
33
- "@devflow-tools/plugin-taro": "0.16.2",
34
- "@devflow-tools/plugin-ui-layout": "0.16.2",
35
- "@devflow-tools/plugin-vue": "0.16.2",
36
- "@devflow-tools/sdk": "0.16.2",
37
- "@devflow-tools/server": "0.16.2",
38
- "@devflow-tools/telemetry": "0.16.2",
39
- "@devflow-tools/workflow-engine": "0.16.2",
15
+ "@devflow-tools/adapters": "0.16.4",
16
+ "@devflow-tools/benchmark": "0.16.4",
17
+ "@devflow-tools/context-engine": "0.16.4",
18
+ "@devflow-tools/database": "0.16.4",
19
+ "@devflow-tools/knowledge-engine": "0.16.4",
20
+ "@devflow-tools/mcp-server": "0.16.4",
21
+ "@devflow-tools/memory-engine": "0.16.4",
22
+ "@devflow-tools/plugin-animation": "0.16.4",
23
+ "@devflow-tools/plugin-css": "0.16.4",
24
+ "@devflow-tools/plugin-docker": "0.16.4",
25
+ "@devflow-tools/plugin-electron": "0.16.4",
26
+ "@devflow-tools/plugin-git": "0.16.4",
27
+ "@devflow-tools/plugin-graphql": "0.16.4",
28
+ "@devflow-tools/plugin-nest": "0.16.4",
29
+ "@devflow-tools/plugin-nextjs": "0.16.4",
30
+ "@devflow-tools/plugin-performance": "0.16.4",
31
+ "@devflow-tools/plugin-react": "0.16.4",
32
+ "@devflow-tools/plugin-tailwind": "0.16.4",
33
+ "@devflow-tools/plugin-taro": "0.16.4",
34
+ "@devflow-tools/plugin-ui-layout": "0.16.4",
35
+ "@devflow-tools/plugin-vue": "0.16.4",
36
+ "@devflow-tools/sdk": "0.16.4",
37
+ "@devflow-tools/server": "0.16.4",
38
+ "@devflow-tools/telemetry": "0.16.4",
39
+ "@devflow-tools/workflow-engine": "0.16.4",
40
40
  "@inquirer/prompts": "^7.10.1",
41
41
  "chalk": "^5.3.0",
42
42
  "commander": "^12.0.0"
@@ -49,5 +49,5 @@
49
49
  "dist",
50
50
  "bin"
51
51
  ],
52
- "gitHead": "a3062daea57d62fe3adbc79ddd178a2ccb0a6f5c"
52
+ "gitHead": "84cfad781c11d0041dac8986010995ce2018acee"
53
53
  }