@devflow-tools/cli 0.16.1 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/commands/doctor.d.ts.map +1 -1
  2. package/dist/commands/doctor.js +266 -5
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/lib/plugin-setup.d.ts +2 -2
  5. package/dist/lib/plugin-setup.d.ts.map +1 -1
  6. package/dist/lib/plugin-setup.js +4 -10
  7. package/dist/lib/plugin-setup.js.map +1 -1
  8. package/dist/plugin-files/.claude-plugin/plugin.json +1 -1
  9. package/dist/plugin-files/dist/hooks/hook-client.js +5 -5
  10. package/dist/plugin-files/dist/hooks/hook-daemon.js +11 -11
  11. package/dist/plugin-files/dist/hooks/post-tool-use-failure.js +2 -2
  12. package/dist/plugin-files/dist/hooks/post-tool-use.js +3 -3
  13. package/dist/plugin-files/dist/hooks/pre-compact.js +2 -2
  14. package/dist/plugin-files/dist/hooks/pre-tool-use.js +2 -2
  15. package/dist/plugin-files/dist/hooks/session-end.js +2 -2
  16. package/dist/plugin-files/dist/hooks/stop.js +2 -2
  17. package/dist/plugin-files/dist/hooks/user-prompt-submit.js +3 -2
  18. package/dist/plugin-files/dist/skills/devflow:react/SKILL.md +16 -3
  19. package/dist/plugin-files/hooks/session-start +0 -10
  20. package/dist/plugin-files/hooks/stop +3 -2
  21. package/dist/plugin-files/package.json +7 -8
  22. package/dist/plugin-files/skills/{devflow:react → react}/SKILL.md +16 -3
  23. package/package.json +27 -27
  24. /package/dist/plugin-files/{hooks.json → hooks/hooks.json} +0 -0
  25. /package/dist/plugin-files/skills/{devflow:animation → animation}/SKILL.md +0 -0
  26. /package/dist/plugin-files/skills/{devflow:context → context}/SKILL.md +0 -0
  27. /package/dist/plugin-files/skills/{devflow:css → css}/SKILL.md +0 -0
  28. /package/dist/plugin-files/skills/{devflow:docker → docker}/SKILL.md +0 -0
  29. /package/dist/plugin-files/skills/{devflow:doctor → doctor}/SKILL.md +0 -0
  30. /package/dist/plugin-files/skills/{devflow:electron → electron}/SKILL.md +0 -0
  31. /package/dist/plugin-files/skills/{devflow:git → git}/SKILL.md +0 -0
  32. /package/dist/plugin-files/skills/{devflow:graph → graph}/SKILL.md +0 -0
  33. /package/dist/plugin-files/skills/{devflow:graphql → graphql}/SKILL.md +0 -0
  34. /package/dist/plugin-files/skills/{devflow:knowledge → knowledge}/SKILL.md +0 -0
  35. /package/dist/plugin-files/skills/{devflow:memory → memory}/SKILL.md +0 -0
  36. /package/dist/plugin-files/skills/{devflow:nest → nest}/SKILL.md +0 -0
  37. /package/dist/plugin-files/skills/{devflow:nextjs → nextjs}/SKILL.md +0 -0
  38. /package/dist/plugin-files/skills/{devflow:performance → performance}/SKILL.md +0 -0
  39. /package/dist/plugin-files/skills/{devflow:start → start}/SKILL.md +0 -0
  40. /package/dist/plugin-files/skills/{devflow:tailwind → tailwind}/SKILL.md +0 -0
  41. /package/dist/plugin-files/skills/{devflow:taro → taro}/SKILL.md +0 -0
  42. /package/dist/plugin-files/skills/{devflow:ui-layout → ui-layout}/SKILL.md +0 -0
  43. /package/dist/plugin-files/skills/{devflow:vue → vue}/SKILL.md +0 -0
  44. /package/dist/plugin-files/skills/{devflow:workflow → workflow}/SKILL.md +0 -0
@@ -1,3 +1,3 @@
1
- import {getLocalApiKey}from'@devflow-tools/sdk';import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync}from'fs';import {request}from'http';import {join}from'path';import {homedir}from'os';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {MemoryGate}from'@devflow-tools/memory-engine';function u(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}var f=1e4,S=3e4,d=500;function x(n,r,e){return new Promise(t=>{try{let o=new URL(n),s=request({hostname:o.hostname,port:o.port||80,path:o.pathname+o.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(r)},timeout:5e3},i=>{let a=[];i.on("data",l=>a.push(l)),i.on("end",()=>{let l=Buffer.concat(a).toString();t(i.statusCode!=null&&i.statusCode>=200&&i.statusCode<300?l:null);});});s.on("error",()=>t(null)),s.on("timeout",()=>{s.destroy(),t(null);}),s.write(r),s.end();}catch{t(null);}})}var m=class{constructor(r){this.retryScheduled=false;this.apiUrl=r?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=r?.apiKey??getLocalApiKey(),this.cacheDir=r?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=r?.legacyCacheDir??(r?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=r?.database??null,this.ownsDatabase=!r?.database;}async sendEvent(r){let e={...r,input:this.truncateInput(r.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),r.eventId):null}async sendExecutionStart(r,e,t,o){let s={executionId:r,sessionId:e,skillName:t,startedAt:o};this.commitOrCache("execution_start",s)&&this.postHttp("/api/telemetry/skill-execution/start",s);}async sendSessionStart(r,e,t){let o={id:r,projectRoot:e,startedAt:t,label:e.split("/").pop()??"unknown"},s=this.commitOrCache("session_start",o);return s&&this.postHttp("/api/telemetry/sessions",o),s}async completeEvent(r){let e=this.commitOrCache("complete_event",r);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",r)),e}async endSession(r,e=Date.now()){let t={sessionId:r,finishedAt:e},o=this.commitOrCache("session_end",t);return o&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(r)}/close`,{finishedAt:e})),o}async flushCache(){try{let t=this.getDatabase(),o=t.listTelemetryFailures({unresolvedOnly:!0,limit:d}).reverse();for(let s of o)try{this.applyOperation(s.operation,s.payload),t.resolveTelemetryFailure(s.id);}catch{}t.trimTelemetryFailures(d);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(t=>!!t))].filter(t=>existsSync(t)).flatMap(t=>readdirSync(t).filter(o=>o.endsWith(".json")).map(o=>{let s=join(t,o);try{return {cacheFile:s,envelope:M(JSON.parse(readFileSync(s,"utf8")),o)}}catch{return null}})).filter(t=>t!==null).sort((t,o)=>t.envelope.timestamp-o.envelope.timestamp||v(t.envelope.operation)-v(o.envelope.operation));for(let{cacheFile:t,envelope:o}of e)try{let s=this.getDatabase();s.insertTelemetryFailure({id:o.id,operation:o.operation,payload:o.payload,error:o.failure,createdAt:o.timestamp}),this.applyOperation(o.operation,o.payload),s.resolveTelemetryFailure(o.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(r,e){try{return this.applyOperation(r,e),!0}catch(t){return this.cacheOperation(r,e,t),false}}applyOperation(r,e){let t=this.getDatabase();switch(r){case "tool_call":t.insertToolCallEvent(e);return;case "execution_start":t.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "session_start":t.insertSession(e),t.insertRun({id:D(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(D(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}cacheOperation(r,e,t){let o=Date.now(),s={id:`failure:${o}:${Math.random().toString(36).slice(2,11)}`,operation:r,payload:e,failure:t instanceof Error?t.message:String(t),timestamp:o};try{let i=this.getDatabase();i.insertTelemetryFailure({id:s.id,operation:r,payload:e,error:s.failure,createdAt:o}),i.trimTelemetryFailures(d),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let i=join(this.cacheDir,`${o}_${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 r=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of r.slice(0,Math.max(0,r.length-d)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},S).unref());}postHttp(r,e){x(`${this.apiUrl}${r}`,JSON.stringify(e),this.apiKey);}truncateInput(r){let e=JSON.stringify(r);return e===void 0||e.length<=f?r:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,f)}...`}}};function v(n){return ["session_start","execution_start","tool_call","complete_event","session_end"].indexOf(n)}function D(n){return `hook-run:${n}`}function M(n,r){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 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 o=e.payload??{},s=Number(o.timestamp??o.startedAt??Date.now());return {id:`legacy-cache:${r}`,operation:t,payload:o,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:s}}async function b(n){let r=!n.memory,e=n.memory??new MemoryGate(n.projectRoot),t=0,o=0;try{await e.forceWarmUp(),n.trigger==="session_end"&&(t=await e.releasePendingDistillLeases(n.sessionId)),o=e.getPendingEventCount();}finally{r&&e.close();}let s={pendingEvents:o,releasedLeases:t,trigger:n.trigger,sessionId:n.sessionId};try{let i=openGlobalDevFlowDatabase();try{let a=Date.now(),l=`distill:${a}:${Math.random().toString(36).slice(2,10)}`;i.recordMemoryDistillCheckpoint({id:l,projectRoot:n.projectRoot,sessionId:n.sessionId,trigger:n.trigger,pendingEvents:o,releasedLeases:t,createdAt:a}),i.insertEvent({kind:"memory_distill_requested",timestamp:a,success:!0,metadata:{...s,projectRoot:n.projectRoot}});}finally{i.close();}}catch{}return s}function _(n){if(n.pendingEvents===0)return "";let r=n.sessionId?`\uFF0CsessionId=${n.sessionId}`:"";return `Memory distill checkpoint: ${n.pendingEvents} \u4E2A\u4E8B\u4EF6\u5F85\u63D0\u70BC${r}\u3002\u4E0A\u4E0B\u6587\u538B\u7F29\u5B8C\u6210\u540E\uFF0C\u8C03\u7528 mcp__devflow__memory_request_distill\uFF1B\u6309\u8FD4\u56DE prompt \u63D0\u70BC observations\uFF0C\u518D\u8C03\u7528 mcp__devflow__memory_save_distilled\u3002`}var w="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function C(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}async function F(n="",r=u()){let e;try{let i=JSON.parse(n);typeof i.session_id=="string"&&i.session_id.trim()&&(e=i.session_id.trim());}catch{}let t=new m;try{await t.flushAndAggregate();}finally{t.close();}let o=await b({projectRoot:r,sessionId:e,trigger:"pre_compact"}),s=[C()?w:"",_(o)].filter(Boolean).join(`
1
+ import {getLocalApiKey}from'@devflow-tools/sdk';import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync}from'fs';import {request}from'http';import {join}from'path';import {homedir}from'os';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {MemoryGate}from'@devflow-tools/memory-engine';function p(s){return s??process.env.CLAUDE_PROJECT_DIR??process.cwd()}var y=1e4,S=3e4,u=500;function x(s,o,e){return new Promise(t=>{try{let r=new URL(s),n=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(o)},timeout:5e3},i=>{let a=[];i.on("data",l=>a.push(l)),i.on("end",()=>{let l=Buffer.concat(a).toString();t(i.statusCode!=null&&i.statusCode>=200&&i.statusCode<300?l:null);});});n.on("error",()=>t(null)),n.on("timeout",()=>{n.destroy(),t(null);}),n.write(o),n.end();}catch{t(null);}})}var m=class{constructor(o){this.retryScheduled=false;this.apiUrl=o?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=o?.apiKey??getLocalApiKey(),this.cacheDir=o?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=o?.legacyCacheDir??(o?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=o?.database??null,this.ownsDatabase=!o?.database;}async sendEvent(o){let e={...o,input:this.truncateInput(o.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),o.eventId):null}async sendExecutionStart(o,e,t,r,n=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let i={executionId:o,sessionId:e,skillName:t,startedAt:r,projectRoot:n};this.commitOrCache("execution_start",i)&&this.postHttp("/api/telemetry/skill-execution/start",i);}async sendExecutionComplete(o,e="completed",t=Date.now(),r){let n={executionId:o,status:e,finishedAt:t,metadata:r},i=this.commitOrCache("execution_complete",n);return i&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",n),i}async sendSessionStart(o,e,t){let r={id:o,projectRoot:e,startedAt:t,label:e.split("/").pop()??"unknown"},n=this.commitOrCache("session_start",r);return n&&this.postHttp("/api/telemetry/sessions",r),n}async completeEvent(o){let e=this.commitOrCache("complete_event",o);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",o)),e}async endSession(o,e=Date.now()){let t={sessionId:o,finishedAt:e},r=this.commitOrCache("session_end",t);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(o)}/close`,{finishedAt:e})),r}async flushCache(){try{let t=this.getDatabase(),r=t.listTelemetryFailures({unresolvedOnly:!0,limit:u}).reverse();for(let n of r)try{this.applyOperation(n.operation,n.payload),t.resolveTelemetryFailure(n.id);}catch{}t.trimTelemetryFailures(u);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(t=>!!t))].filter(t=>existsSync(t)).flatMap(t=>readdirSync(t).filter(r=>r.endsWith(".json")).map(r=>{let n=join(t,r);try{return {cacheFile:n,envelope:P(JSON.parse(readFileSync(n,"utf8")),r)}}catch{return null}})).filter(t=>t!==null).sort((t,r)=>t.envelope.timestamp-r.envelope.timestamp||v(t.envelope.operation)-v(r.envelope.operation));for(let{cacheFile:t,envelope:r}of e)try{let n=this.getDatabase();n.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),n.resolveTelemetryFailure(r.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(o,e){try{return this.applyOperation(o,e),!0}catch(t){return this.cacheOperation(o,e,t),false}}applyOperation(o,e){let t=this.getDatabase();switch(o){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:D(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(D(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(o,e){let t=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!t)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";o.ensureSession({id:t,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(o,e,t){let r=Date.now(),n={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:o,payload:e,failure:t instanceof Error?t.message:String(t),timestamp:r};try{let i=this.getDatabase();i.insertTelemetryFailure({id:n.id,operation:o,payload:e,error:n.failure,createdAt:r}),i.trimTelemetryFailures(u),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(n,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let o=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of o.slice(0,Math.max(0,o.length-u)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},S).unref());}postHttp(o,e){x(`${this.apiUrl}${o}`,JSON.stringify(e),this.apiKey);}truncateInput(o){let e=JSON.stringify(o);return e===void 0||e.length<=y?o:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,y)}...`}}};function v(s){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(s)}function D(s){return `hook-run:${s}`}function P(s,o){if(!s||typeof s!="object")throw new Error("Invalid telemetry cache envelope");let e=s;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 r=e.payload??{},n=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${o}`,operation:t,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:n}}async function b(s){let o=!s.memory,e=s.memory??new MemoryGate(s.projectRoot),t=0,r=0;try{await e.forceWarmUp(),s.trigger==="session_end"&&(t=await e.releasePendingDistillLeases(s.sessionId)),r=e.getPendingEventCount();}finally{o&&e.close();}let n={pendingEvents:r,releasedLeases:t,trigger:s.trigger,sessionId:s.sessionId};try{let i=openGlobalDevFlowDatabase();try{let a=Date.now(),l=`distill:${a}:${Math.random().toString(36).slice(2,10)}`;i.recordMemoryDistillCheckpoint({id:l,projectRoot:s.projectRoot,sessionId:s.sessionId,trigger:s.trigger,pendingEvents:r,releasedLeases:t,createdAt:a}),i.insertEvent({kind:"memory_distill_requested",timestamp:a,success:!0,metadata:{...n,projectRoot:s.projectRoot}});}finally{i.close();}}catch{}return n}function _(s){if(s.pendingEvents===0)return "";let o=s.sessionId?`\uFF0CsessionId=${s.sessionId}`:"";return `Memory distill checkpoint: ${s.pendingEvents} \u4E2A\u4E8B\u4EF6\u5F85\u63D0\u70BC${o}\u3002\u4E0A\u4E0B\u6587\u538B\u7F29\u5B8C\u6210\u540E\uFF0C\u8C03\u7528 mcp__devflow__memory_request_distill\uFF1B\u6309\u8FD4\u56DE prompt \u63D0\u70BC observations\uFF0C\u518D\u8C03\u7528 mcp__devflow__memory_save_distilled\u3002`}var w="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function C(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}async function M(s="",o=p()){let e;try{let i=JSON.parse(s);typeof i.session_id=="string"&&i.session_id.trim()&&(e=i.session_id.trim());}catch{}let t=new m;try{await t.flushAndAggregate();}finally{t.close();}let r=await b({projectRoot:o,sessionId:e,trigger:"pre_compact"}),n=[C()?w:"",_(r)].filter(Boolean).join(`
2
2
 
3
- `);return {hookSpecificOutput:{hookEventName:"PreCompact",...s?{additionalContext:s}:{}}}}if(process.argv[1]?.endsWith("pre-compact")||process.argv[1]?.endsWith("pre-compact.js")){let n=console.log,r=console.info,e=()=>{console.log=n,console.info=r;};console.log=console.error.bind(console),console.info=console.error.bind(console);let t="";process.stdin.setEncoding("utf8"),process.stdin.on("data",o=>{t+=o;}),process.stdin.on("end",()=>{F(t.trim()||process.argv[2]||"").then(o=>{e(),n(JSON.stringify(o)),process.exit(0);}).catch(()=>{e(),process.exit(0);});}),process.stdin.on("error",()=>{e(),process.exit(0);}),setTimeout(()=>process.exit(0),14e3).unref();}export{F as handlePreCompact};
3
+ `);return {hookSpecificOutput:{hookEventName:"PreCompact",...n?{additionalContext:n}:{}}}}if(process.argv[1]?.endsWith("pre-compact")||process.argv[1]?.endsWith("pre-compact.js")){let s=console.log,o=console.info,e=()=>{console.log=s,console.info=o;};console.log=console.error.bind(console),console.info=console.error.bind(console);let t="";process.stdin.setEncoding("utf8"),process.stdin.on("data",r=>{t+=r;}),process.stdin.on("end",()=>{M(t.trim()||process.argv[2]||"").then(r=>{e(),s(JSON.stringify(r)),process.exit(0);}).catch(()=>{e(),process.exit(0);});}),process.stdin.on("error",()=>{e(),process.exit(0);}),setTimeout(()=>process.exit(0),14e3).unref();}export{M as handlePreCompact};
@@ -1,2 +1,2 @@
1
- import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync}from'fs';import {join,dirname as dirname$1}from'path';import {createHash}from'crypto';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,renameSync}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';var $=1e4,be=3e4,b=500;function De(n,e,t){return new Promise(r=>{try{let o=new URL(n),i=request({hostname:o.hostname,port:o.port||80,path:o.pathname+o.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":t,"Content-Length":Buffer.byteLength(e)},timeout:5e3},s=>{let c=[];s.on("data",u=>c.push(u)),s.on("end",()=>{let u=Buffer.concat(c).toString();r(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?u:null);});});i.on("error",()=>r(null)),i.on("timeout",()=>{i.destroy(),r(null);}),i.write(e),i.end();}catch{r(null);}})}var D=class{constructor(e){this.retryScheduled=false;this.apiUrl=e?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=e?.apiKey??getLocalApiKey(),this.cacheDir=e?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=e?.legacyCacheDir??(e?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=e?.database??null,this.ownsDatabase=!e?.database;}async sendEvent(e){let t={...e,input:this.truncateInput(e.input)};return this.commitOrCache("tool_call",t)?(this.postHttp("/api/telemetry/tool-call",t),e.eventId):null}async sendExecutionStart(e,t,r,o){let i={executionId:e,sessionId:t,skillName:r,startedAt:o};this.commitOrCache("execution_start",i)&&this.postHttp("/api/telemetry/skill-execution/start",i);}async sendSessionStart(e,t,r){let o={id:e,projectRoot:t,startedAt:r,label:t.split("/").pop()??"unknown"},i=this.commitOrCache("session_start",o);return i&&this.postHttp("/api/telemetry/sessions",o),i}async completeEvent(e){let t=this.commitOrCache("complete_event",e);return t&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",e)),t}async endSession(e,t=Date.now()){let r={sessionId:e,finishedAt:t},o=this.commitOrCache("session_end",r);return o&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(e)}/close`,{finishedAt:t})),o}async flushCache(){try{let r=this.getDatabase(),o=r.listTelemetryFailures({unresolvedOnly:!0,limit:b}).reverse();for(let i of o)try{this.applyOperation(i.operation,i.payload),r.resolveTelemetryFailure(i.id);}catch{}r.trimTelemetryFailures(b);}catch{}let t=[...new Set([this.cacheDir,this.legacyCacheDir].filter(r=>!!r))].filter(r=>existsSync(r)).flatMap(r=>readdirSync(r).filter(o=>o.endsWith(".json")).map(o=>{let i=join(r,o);try{return {cacheFile:i,envelope:ve(JSON.parse(readFileSync(i,"utf8")),o)}}catch{return null}})).filter(r=>r!==null).sort((r,o)=>r.envelope.timestamp-o.envelope.timestamp||L(r.envelope.operation)-L(o.envelope.operation));for(let{cacheFile:r,envelope:o}of t)try{let i=this.getDatabase();i.insertTelemetryFailure({id:o.id,operation:o.operation,payload:o.payload,error:o.failure,createdAt:o.timestamp}),this.applyOperation(o.operation,o.payload),i.resolveTelemetryFailure(o.id),unlinkSync(r);}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(e,t){try{return this.applyOperation(e,t),!0}catch(r){return this.cacheOperation(e,t,r),false}}applyOperation(e,t){let r=this.getDatabase();switch(e){case "tool_call":r.insertToolCallEvent(t);return;case "execution_start":r.insertSkillExecution({executionId:t.executionId,sessionId:t.sessionId,skillName:t.skillName,startedAt:t.startedAt,status:"running"});return;case "session_start":r.insertSession(t),r.insertRun({id:U(t.id),source:"hook",tool:"session",input:{projectRoot:t.projectRoot},status:"active",startedAt:t.startedAt,tokenUsed:0,metadata:{sessionId:t.id,projectRoot:t.projectRoot}});return;case "complete_event":if(!r.updateToolCallEvent(t.eventId,{output:t.output===void 0?void 0:JSON.stringify(t.output),error:t.error,duration:t.duration}))throw new Error(`Tool call event ${t.eventId} is not available for completion`);return;case "session_end":r.closeSession(t.sessionId,t.finishedAt),r.updateRun(U(t.sessionId),{status:"completed",finishedAt:t.finishedAt});return}}cacheOperation(e,t,r){let o=Date.now(),i={id:`failure:${o}:${Math.random().toString(36).slice(2,11)}`,operation:e,payload:t,failure:r instanceof Error?r.message:String(r),timestamp:o};try{let s=this.getDatabase();s.insertTelemetryFailure({id:i.id,operation:e,payload:t,error:i.failure,createdAt:o}),s.trimTelemetryFailures(b),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let s=join(this.cacheDir,`${o}_${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 e=readdirSync(this.cacheDir).filter(t=>t.endsWith(".json")).sort();for(let t of e.slice(0,Math.max(0,e.length-b)))unlinkSync(join(this.cacheDir,t));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},be).unref());}postHttp(e,t){De(`${this.apiUrl}${e}`,JSON.stringify(t),this.apiKey);}truncateInput(e){let t=JSON.stringify(e);return t===void 0||t.length<=$?e:{_truncated:true,_original_size:t.length,_preview:`${t.substring(0,$)}...`}}};function L(n){return ["session_start","execution_start","tool_call","complete_event","session_end"].indexOf(n)}function U(n){return `hook-run:${n}`}function ve(n,e){if(!n||typeof n!="object")throw new Error("Invalid telemetry cache envelope");let t=n;if(typeof t.operation=="string"&&t.payload!==void 0)return t;let r=t.type==="tool_call"?"tool_call":t.type==="execution_start"?"execution_start":null;if(!r)throw new Error("Unknown legacy telemetry cache operation");let o=t.payload??{},i=Number(o.timestamp??o.startedAt??Date.now());return {id:`legacy-cache:${e}`,operation:r,payload:o,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:i}}function x(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function l(n){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(x(n))}var Ee=1;function Ce(){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 q(n=Ce()){let e=[join(n,"dist","command-registry.json"),join(n,"command-registry.json")];for(let t of e)try{if(!existsSync(t))continue;let r=JSON.parse(readFileSync(t,"utf8"));if(r.version!==Ee||!Oe(r.commands)){console.error(`[devflow] command registry contract mismatch: ${t}`);continue}return r.commands}catch(r){console.error(`[devflow] command registry load failed: ${r.message}`);}return null}function Oe(n){return !n||typeof n!="object"||Array.isArray(n)?false:Object.values(n).every(e=>{if(!e||typeof e!="object"||Array.isArray(e))return false;let t=e;return G(t.mcpTools)&&G(t.blockedNative)})}function G(n){return Array.isArray(n)&&n.every(e=>typeof e=="string"&&e.length>0)}var Ae=1800*1e3,Fe=300*1e3;function B(n){let e=q();return e?e[n]?.mcpTools??[]:[]}var v=class{constructor(e){this.stateDir=l(e);}detect(){let e=join(this.stateDir,"current-skill.json");if(!existsSync(e))return null;try{let t=readFileSync(e,"utf8"),r=JSON.parse(t);return !r.registeredAt||Date.now()-r.registeredAt>Ae?(this.endExecution(),null):(Date.now()-r.registeredAt>Fe&&(r.registeredAt=Date.now(),writeFileSync(e,JSON.stringify(r))),r)}catch{return null}}startExecution(e,t){existsSync(this.stateDir)||mkdirSync(this.stateDir,{recursive:true});let r=this.getCurrentExecutionId();if(r)return r;let o=`exec_${Date.now()}_${Math.random().toString(36).slice(2,11)}`;return writeFileSync(join(this.stateDir,"current-execution-id"),o),writeFileSync(join(this.stateDir,"current-skill.json"),JSON.stringify({name:e,required_mcp_tools:t?.required_mcp_tools??[],registeredAt:Date.now()})),o}getCurrentExecutionId(){let e=join(this.stateDir,"current-execution-id");if(!existsSync(e))return null;try{return readFileSync(e,"utf8").trim()}catch{return null}}endExecution(){let e=join(this.stateDir,"current-skill.json"),t=join(this.stateDir,"current-execution-id");try{existsSync(e)&&unlinkSync(e);}catch{}try{existsSync(t)&&unlinkSync(t);}catch{}}};function h(n){if(!n||typeof n!="object"||Array.isArray(n))return {};let e=n,t={};for(let r of ["lastMcpCall","bypassCount"])if(r in e){let o=e[r];if(o==null)continue;t[r]=typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0;}return t}function je(n,e){return n.lastMcpCall===e.lastMcpCall&&n.bypassCount===e.bypassCount}function Me(n){try{return existsSync(n)?h(JSON.parse(readFileSync(n,"utf-8"))):{}}catch{return {}}}function X(n,e){let t=e instanceof Error?e.message:String(e);console.error(`[devflow] Receipt ${n} skipped: ${t}`);}function K(n){let e=l(n);for(let t of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(e,t))&&unlinkSync(join(e,t));}catch{}}function Y(n,e){if(n.getHookReceipt(e)){K(e);return}let t=join(l(e),"receipt.json");if(!existsSync(t))return;let r=Me(t);n.updateHookReceipt(e,()=>r),K(e);}function Z(n,e,t){let r;try{r=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),Y(r,n);let o={},i=r.updateHookReceipt(n,c=>(o=h(c),h(e(o)))),s=h(i);return {receipt:s,applied:!0,changed:!je(o,s)}}catch(o){return X(t,o),{receipt:{},applied:false,changed:false}}finally{try{r?.close();}catch{}}}function O(n){let e;try{return e=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),Y(e,n),h(e.getHookReceipt(n))}catch(t){return X("update",t),{}}finally{try{e?.close();}catch{}}}function Ne(n,e){return Z(n,()=>e,"write").applied}function Pe(n,e){return Z(n,e,"update")}var He={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function $e(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 w=class{constructor(e){this.projectRoot=e;}getPhase(){let t=O(this.projectRoot).lastMcpCall??0;if(Math.floor(Date.now()/1e3)-t<30)return "context_ready";let o=join(l(this.projectRoot),"current-skill.json");if(existsSync(o))try{let c=JSON.parse(readFileSync(o,"utf-8")).registeredAt??0;if(Date.now()-c<300*1e3)return "context_gathering"}catch{}return "idle"}evaluate(e,t){let r=this.getPhase();if(t||e==="Skill")return {permissionDecision:"allow"};if(r==="context_gathering")return {permissionDecision:"deny",reason:"DevFlow skill requires MCP context first. The context is being gathered automatically \u2014 retry this tool after the MCP call completes."};if(r==="context_ready")return {permissionDecision:"allow"};let o=He[e];if(!o)return {permissionDecision:"allow"};let i=Pe(this.projectRoot,u=>({...u,bypassCount:(u.bypassCount??0)+1}));if(!i.applied)return {permissionDecision:"allow"};let s=i.receipt.bypassCount??0,c=$e(e);return s>=c?{permissionDecision:"allow",additionalContext:`\u5DF2 ${s} \u6B21\u76F4\u63A5\u4F7F\u7528 ${e}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${o} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){Ne(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var Ve=10*1024*1024,ze=5;function Xe(n){return createHash$1("sha256").update(n).digest("hex").slice(0,16)}function te(n){return join$1(homedir$1(),".devflow","logs","hook-debug",`${Xe(n)}.jsonl`)}function ne(n,e,t={}){let r=t.maxBytes??Ve,o=t.maxFiles??ze;mkdirSync$1(dirname(n),{recursive:true,mode:448});let i=Buffer.byteLength(e),s=existsSync$1(n)?statSync(n).size:0;s>0&&s+i>r&&Ye(n,o),appendFileSync(n,e,{mode:384}),chmodSync(n,384);}function Ye(n,e){if(e<=1){rmSync(n,{force:true});return}rmSync(`${n}.${e-1}`,{force:true});for(let t=e-2;t>=1;t-=1){let r=`${n}.${t}`;existsSync$1(r)&&renameSync(r,`${n}.${t+1}`);}existsSync$1(n)&&renameSync(n,`${n}.1`);}var re="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function oe(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}function y(){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}}}function ie(n){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:n}}}function le(n){let e=l(n);return existsSync(e)||mkdirSync(e,{recursive:true}),e}function ue(n){let e=createHash("sha256").update(n).digest("hex").slice(0,12);return join(l(n),`session-id-${e}`)}function et(n){try{let e=ue(n);if(existsSync(e))return readFileSync(e,"utf-8").trim()}catch{}return null}function tt(n,e){le(e),writeFileSync(ue(e),n);}function nt(n,e,t,r){let o=join(le(n),"event-map.json"),i=[];try{existsSync(o)&&(i=JSON.parse(readFileSync(o,"utf-8")));}catch{}i.push({toolName:e,eventId:t,timestamp:r}),writeFileSync(o,JSON.stringify(i));}function rt(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 ot(n){if(oe())return ie(re);try{if(!n||n.trim()==="")return y();let e=JSON.parse(n),t=Date.now(),r=x(),o=r.split("/").pop()||"unknown",i=new D,s=et(r);s||(s=`session:${o}:${Date.now().toString(36)}`,tt(s,r),i.sendSessionStart(s,r,Date.now()).catch(()=>{}));let c=new v(r);if(e.tool_name==="Skill"){let a=e.tool_input?.skill;if(a&&a.startsWith("devflow:")){let R=c.startExecution(a,{required_mcp_tools:B(a)});i.sendExecutionStart(R,s,a,Date.now()).catch(()=>{});}}let u=c.detect(),p=e.tool_name.startsWith("mcp__"),pe=p?e.tool_name.replace(/^mcp__[^_]+__/,""):void 0,F=new w(r),I=F.getPhase(),d,_=F.evaluate(e.tool_name,p);_.permissionDecision==="deny"?d={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:_.reason??"MCP context required"}}:_.additionalContext?d=ie(_.additionalContext):d=y();let de=c.getCurrentExecutionId()??s,j={eventId:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`,executionId:de,sessionId:s,timestamp:Date.now(),toolName:e.tool_name,toolType:p?"mcp":e.tool_name==="Agent"?"subagent":"direct",isMcpTool:p,mcpToolName:pe,mcpEnforced:p&&I==="context_gathering",mcpFallback:!p&&I==="context_gathering",kind:rt(e.tool_name),input:e.tool_input,duration:0,blocked:d.hookSpecificOutput.permissionDecision==="deny"};i.sendEvent(j).then(a=>{a&&nt(r,e.tool_name,a,j.timestamp);}).catch(()=>{});try{let a=O(r),R={ts:t,tool:e.tool_name,skill:u?.name??"(none)",requiredTools:requiredMcpTools,isMcp:p,enforce:shouldSkillEnforce,stateless:!shouldSkillEnforce&&!p&&e.tool_name!=="Skill",bypassCount:a.bypassCount??0,decision:d.hookSpecificOutput.permissionDecision,env_CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd()};ne(te(r),`${JSON.stringify(R)}
2
- `);}catch{}return d}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",e=>{n+=e;}),process.stdin.on("end",async()=>{let e=await ot(n);console.log(JSON.stringify(e));}),process.stdin.on("error",()=>{console.log(JSON.stringify(y())),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify(y())),process.exit(0);},4e3).unref();}export{ot 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,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,2 +1,2 @@
1
- import {request}from'http';import {existsSync,readFileSync,readdirSync,unlinkSync,mkdirSync,writeFileSync}from'fs';import {join}from'path';import {createHash}from'crypto';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {homedir}from'os';import {MemoryGate}from'@devflow-tools/memory-engine';function b(i){return i??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function I(i){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(b(i))}var O=1e4,B=3e4,g=500;function G(i,t,e){return new Promise(n=>{try{let r=new URL(i),o=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 l=[];s.on("data",a=>l.push(a)),s.on("end",()=>{let a=Buffer.concat(l).toString();n(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?a:null);});});o.on("error",()=>n(null)),o.on("timeout",()=>{o.destroy(),n(null);}),o.write(t),o.end();}catch{n(null);}})}var y=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){let o={executionId:t,sessionId:e,skillName:n,startedAt:r};this.commitOrCache("execution_start",o)&&this.postHttp("/api/telemetry/skill-execution/start",o);}async sendSessionStart(t,e,n){let r={id:t,projectRoot:e,startedAt:n,label:e.split("/").pop()??"unknown"},o=this.commitOrCache("session_start",r);return o&&this.postHttp("/api/telemetry/sessions",r),o}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:g}).reverse();for(let o of r)try{this.applyOperation(o.operation,o.payload),n.resolveTelemetryFailure(o.id);}catch{}n.trimTelemetryFailures(g);}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 o=join(n,r);try{return {cacheFile:o,envelope:J(JSON.parse(readFileSync(o,"utf8")),r)}}catch{return null}})).filter(n=>n!==null).sort((n,r)=>n.envelope.timestamp-r.envelope.timestamp||P(n.envelope.operation)-P(r.envelope.operation));for(let{cacheFile:n,envelope:r}of e)try{let o=this.getDatabase();o.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),o.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":n.insertToolCallEvent(e);return;case "execution_start":n.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "session_start":n.insertSession(e),n.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(!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(M(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}cacheOperation(t,e,n){let r=Date.now(),o={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 s=this.getDatabase();s.insertTelemetryFailure({id:o.id,operation:t,payload:e,error:o.failure,createdAt:r}),s.trimTelemetryFailures(g),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(o,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-g)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},B).unref());}postHttp(t,e){G(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=O?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,O)}...`}}};function P(i){return ["session_start","execution_start","tool_call","complete_event","session_end"].indexOf(i)}function M(i){return `hook-run:${i}`}function J(i,t){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 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??{},o=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:o}}async function F(i){let t=!i.memory,e=i.memory??new MemoryGate(i.projectRoot),n=0,r=0;try{await e.forceWarmUp(),i.trigger==="session_end"&&(n=await e.releasePendingDistillLeases(i.sessionId)),r=e.getPendingEventCount();}finally{t&&e.close();}let o={pendingEvents:r,releasedLeases:n,trigger:i.trigger,sessionId:i.sessionId};try{let s=openGlobalDevFlowDatabase();try{let l=Date.now(),a=`distill:${l}:${Math.random().toString(36).slice(2,10)}`;s.recordMemoryDistillCheckpoint({id:a,projectRoot:i.projectRoot,sessionId:i.sessionId,trigger:i.trigger,pendingEvents:r,releasedLeases:n,createdAt:l}),s.insertEvent({kind:"memory_distill_requested",timestamp:l,success:!0,metadata:{...o,projectRoot:i.projectRoot}});}finally{s.close();}}catch{}return o}var ee=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",te=getLocalApiKey();function ne(i,t){return new Promise(e=>{let n=JSON.stringify(t),r=new URL(i,ee),o=request({hostname:r.hostname,port:r.port,path:r.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":te,"Content-Length":Buffer.byteLength(n)},timeout:5e3},()=>e());o.on("error",()=>e()),o.write(n),o.end();})}function _(i){let t=I(i),e=createHash("sha256").update(i).digest("hex").slice(0,12);return [join(t,`session-id-${e}`),join(t,"current-execution-id"),join(t,"current-skill.json"),join(t,"event-map.json")]}function re(i){try{let t=_(i)[0];if(existsSync(t))return readFileSync(t,"utf-8").trim()}catch{}return null}function ie(i){for(let t of _(i))try{existsSync(t)&&unlinkSync(t);}catch{}}async function oe(i,t=b(),e=true,n){let r;try{r=JSON.parse(i);}catch{return}let o=r.session_id||re(t);if(!o)return;let s=n?.telemetry??new y,l=!n?.telemetry;try{try{await s.flushAndAggregate();}catch{}let a;try{a=openGlobalDevFlowDatabase();let L=a.listToolCallEventsBySession(o),C=_(t)[1];if(e&&existsSync(C)){let p="";try{p=readFileSync(C,"utf-8").trim();}catch{}if(p){let h=L.filter(m=>m.executionId===p),c=h.length,d=h.filter(m=>m.isMcpTool).length,T=c-d,w=h.filter(m=>m.toolType==="subagent").length,v=h.map(m=>m.timestamp).filter(Boolean),E=v.length>=2?Math.max(...v)-Math.min(...v):0;try{a.updateSkillExecution(p,{status:"completed",finishedAt:Date.now(),totalToolCalls:c,mcpToolCalls:d,directToolCalls:T,subagentCount:w,totalDuration:E,mcpComplianceRate:c>0?Math.round(d/c*1e4)/100:0}),await ne("/api/telemetry/skill-execution/complete",{executionId:p,finishedAt:Date.now(),status:"completed",summary:{totalToolCalls:c,mcpToolCalls:d,directToolCalls:T,subagentCount:w,totalDuration:E,mcpComplianceRate:c>0?Math.round(d/c*1e4)/100:0}});}catch{}}}e&&a.deleteHookReceipt(t);}catch{}finally{a?.close();}try{await F({projectRoot:t,sessionId:o,trigger:"session_end",memory:n?.memory});}catch{}try{await s.endSession(o),await s.flushAndAggregate();}catch{}}finally{l&&s.close(),e&&ie(t);}}if(process.argv[1]?.endsWith("session-end")||process.argv[1]?.endsWith("session-end.js")){let i="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{i+=t;}),process.stdin.on("end",async()=>{await oe(i.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),1e4).unref();}
2
- export{oe 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(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,2 +1,2 @@
1
- import {request}from'http';import {existsSync,readFileSync}from'fs';import {join}from'path';import {homedir}from'os';import {createHash}from'crypto';import {getLocalApiKey}from'@devflow-tools/sdk';var h=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",f=getLocalApiKey();function _(t,e){return new Promise(o=>{let r=JSON.stringify(e),n=new URL(t,h),s=request({hostname:n.hostname,port:n.port,path:n.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":f,"Content-Length":Buffer.byteLength(r)},timeout:5e3},()=>o());s.on("error",()=>o()),s.write(r),s.end();})}var S=join(homedir(),".devflow"),g=process.env.CLAUDE_PROJECT_DIR||process.cwd(),E=createHash("sha256").update(g).digest("hex").slice(0,12),i=join(S,`session-id-${E}`);function R(){try{if(existsSync(i))return readFileSync(i,"utf-8").trim()}catch{}return null}async function y(t){let e;try{e=JSON.parse(t);}catch{return null}if(e.stop_hook_active)return null;let o=R();return o&&_(`/api/telemetry/sessions/${encodeURIComponent(o)}/tick`,{timestamp:Date.now()}).catch(()=>{}),null}if(process.argv[1]?.endsWith("stop")||process.argv[1]?.endsWith("stop.js")){let t="";process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>{t+=e;}),process.stdin.on("end",async()=>{await y(t.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),4e3).unref();}
2
- export{y as handleStop};
1
+ import {request}from'http';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {existsSync,readFileSync,rmSync,mkdirSync,writeFileSync,renameSync}from'fs';import {join}from'path';import {randomUUID}from'crypto';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';function c(i){return i??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function a(i){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(c(i))}function y(i){return Buffer.from(i,"utf8").toString("base64url")}var r=class{constructor(e){this.projectRoot=e;this.sessions=new Map;this.sessionsDir=join(a(e),"hook-sessions");}registerSession(e){let t=e.trim();if(!t)throw new Error("session_id_required");let s=this.get(t);if(s)return s.lastActivityAt=Date.now(),this.persist(s),s;let o=Date.now(),n={sessionId:t,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:o,lastActivityAt:o};return this.sessions.set(t,n),this.persist(n),n}startExecution(e,t,s){let o=this.registerSession(e);return (!o.executionId||o.skillName!==t)&&(o.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),o.skillName=t,o.requiredMcpTools=[...new Set(s)],o.lastActivityAt=Date.now(),this.persist(o),o}get(e){let t=e.trim();if(!t)return null;let s=this.sessions.get(t);if(s)return s;let o=this.snapshotPath(t);if(!existsSync(o))return null;try{let n=JSON.parse(readFileSync(o,"utf8"));return n.sessionId!==t||n.projectRoot!==this.projectRoot?null:(n.requiredMcpTools=Array.isArray(n.requiredMcpTools)?n.requiredMcpTools:[],this.sessions.set(t,n),n)}catch{return null}}completeExecution(e){let t=this.get(e);if(!t)return null;let s={...t,requiredMcpTools:[...t.requiredMcpTools]};return delete t.executionId,delete t.skillName,t.requiredMcpTools=[],t.lastActivityAt=Date.now(),this.persist(t),s}removeSession(e){let t=e.trim();t&&(this.sessions.delete(t),rmSync(this.snapshotPath(t),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(e){return join(this.sessionsDir,`${y(e)}.json`)}persist(e){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let t=this.snapshotPath(e.sessionId),s=`${t}.${process.pid}.tmp`;writeFileSync(s,JSON.stringify(e),{mode:384}),renameSync(s,t);}};var D=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",I=getLocalApiKey();function w(i,e){return new Promise(t=>{let s=JSON.stringify(e),o=new URL(i,D),n=request({hostname:o.hostname,port:o.port,path:o.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":I,"Content-Length":Buffer.byteLength(s)},timeout:5e3},()=>t());n.on("error",()=>t()),n.write(s),n.end();})}async function _(i,e=c()){let t;try{t=JSON.parse(i);}catch{return null}if(t.stop_hook_active)return null;let s=t.session_id?.trim();if(!s)return null;let o=new r(e).completeExecution(s);if(o?.executionId){let n=openGlobalDevFlowDatabase();try{n.reconcileSkillExecution(o.executionId,"completed"),n.deleteContextReceipt(e,s,o.executionId);}finally{n.close();}}return w(`/api/telemetry/sessions/${encodeURIComponent(s)}/tick`,{timestamp:Date.now()}).catch(()=>{}),null}if(process.argv[1]?.endsWith("stop")||process.argv[1]?.endsWith("stop.js")){let i="";process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>{i+=e;}),process.stdin.on("end",async()=>{await _(i.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),4e3).unref();}
2
+ export{_ as handleStop};
@@ -1,2 +1,3 @@
1
- import {createHash}from'node:crypto';import {mkdirSync,appendFileSync}from'node:fs';import {homedir}from'node:os';import {dirname,join}from'node:path';import {MemoryGate}from'@devflow-tools/memory-engine';import'@devflow-tools/database';var g=/(?:^|[.!?。!?]\s*)(?:(?:please\s+)?(?:remember|memorize)\b|(?:请记|(?:请)?记住))/iu,D=/\b(?:(?:do\s+not|don't|dont|never|not)(?:\s+need\s+to)?|no\s+need\s+to)\s+(?:please\s+)?(?:remember|memorize)\b|(?:不要|别|不用|无需|不必|不需要)(?:再)?(?:记住|记|记忆)/iu;function c(e){let t=e.trim();return t&&g.test(t)&&!D.test(t)?t:null}function S(e){let t=createHash("sha256").update(e).digest("hex").slice(0,16),n=process.env.DEVFLOW_STATE_DIR||join(homedir(),".devflow","state",t);return join(n,"memory-intents.jsonl")}function a(e,t){let n=S(e);mkdirSync(dirname(n),{recursive:true}),appendFileSync(n,`${JSON.stringify(t)}
2
- `,{mode:384});}var m="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function u(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}var p=process.env.CLAUDE_PROJECT_DIR||process.cwd();async function I(e){let t=u()?{additionalContext:m}:{},n;try{n=JSON.parse(e);}catch{return {hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow",...t}}}let r=n.prompt||"";if(!r.trim())return {hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow",...t}};let s=c(r);if(s)try{a(p,{content:s,sessionId:n.session_id,createdAt:Date.now()});}catch{}let o=new MemoryGate(p);try{await o.forceWarmUp(),await o.recordUserMessage(r.slice(0,2e3));}catch{}finally{o.close();}return {hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow",...t}}}if(process.argv[1]?.endsWith("user-prompt-submit")||process.argv[1]?.endsWith("user-prompt-submit.js")){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t;}),process.stdin.on("end",async()=>{let t=await I(e.trim()||process.argv[2]||"");console.log(JSON.stringify(t)),process.exit(0);}),process.stdin.on("error",()=>{console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow"}})),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow"}})),process.exit(0);},3e4).unref();}export{I as handleUserPromptSubmit};
1
+ import {createHash}from'node:crypto';import {mkdirSync,appendFileSync}from'node:fs';import {homedir as homedir$1}from'node:os';import {dirname,join as join$1}from'node:path';import {MemoryGate}from'@devflow-tools/memory-engine';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {existsSync,readFileSync,rmSync,mkdirSync as mkdirSync$1,writeFileSync,renameSync,readdirSync,unlinkSync}from'fs';import {join,dirname as dirname$1}from'path';import {randomUUID}from'crypto';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {fileURLToPath}from'url';import {request}from'http';import {homedir}from'os';var L=/(?:^|[.!?。!?]\s*)(?:(?:please\s+)?(?:remember|memorize)\b|(?:请记|(?:请)?记住))/iu,J=/\b(?:(?:do\s+not|don't|dont|never|not)(?:\s+need\s+to)?|no\s+need\s+to)\s+(?:please\s+)?(?:remember|memorize)\b|(?:不要|别|不用|无需|不必|不需要)(?:再)?(?:记住|记|记忆)/iu;function S(o){let t=o.trim();return t&&L.test(t)&&!J.test(t)?t:null}function H(o){let t=createHash("sha256").update(o).digest("hex").slice(0,16),e=process.env.DEVFLOW_STATE_DIR||join$1(homedir$1(),".devflow","state",t);return join$1(e,"memory-intents.jsonl")}function D(o,t){let e=H(o);mkdirSync(dirname(e),{recursive:true}),appendFileSync(e,`${JSON.stringify(t)}
2
+ `,{mode:384});}var w="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function b(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}function W(o){return o??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function f(o){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(W(o))}function Q(o){return Buffer.from(o,"utf8").toString("base64url")}var p=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(f(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let r=this.get(e);if(r)return r.lastActivityAt=Date.now(),this.persist(r),r;let n=Date.now(),s={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:n,lastActivityAt:n};return this.sessions.set(e,s),this.persist(s),s}startExecution(t,e,r){let n=this.registerSession(t);return (!n.executionId||n.skillName!==e)&&(n.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),n.skillName=e,n.requiredMcpTools=[...new Set(r)],n.lastActivityAt=Date.now(),this.persist(n),n}get(t){let e=t.trim();if(!e)return null;let r=this.sessions.get(e);if(r)return r;let n=this.snapshotPath(e);if(!existsSync(n))return null;try{let s=JSON.parse(readFileSync(n,"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 r={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),r}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,`${Q(t)}.json`)}persist(t){mkdirSync$1(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),r=`${e}.${process.pid}.tmp`;writeFileSync(r,JSON.stringify(t),{mode:384}),renameSync(r,e);}};function ee(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 I(o){let t=o.trimStart().match(/^\/(devflow:[a-z0-9][a-z0-9-]*)\b/i);if(!t)return null;let e=ee(t[1]);return e?{rawName:t[1],skillName:e}:null}var oe=1;function ie(){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 x(o=ie()){let t=[join(o,"dist","command-registry.json"),join(o,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let r=JSON.parse(readFileSync(e,"utf8"));if(r.version!==oe||!ae(r.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return r.commands}catch(r){console.error(`[devflow] command registry load failed: ${r.message}`);}return null}function ae(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 R(e.mcpTools)&&R(e.blockedNative)})}function R(o){return Array.isArray(o)&&o.every(t=>typeof t=="string"&&t.length>0)}function E(o){let t=x();return t?t[o]?.mcpTools??[]:[]}var A=1e4,he=3e4,d=500;function ge(o,t,e){return new Promise(r=>{try{let n=new URL(o),s=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(t)},timeout:5e3},i=>{let a=[];i.on("data",c=>a.push(c)),i.on("end",()=>{let c=Buffer.concat(a).toString();r(i.statusCode!=null&&i.statusCode>=200&&i.statusCode<300?c:null);});});s.on("error",()=>r(null)),s.on("timeout",()=>{s.destroy(),r(null);}),s.write(t),s.end();}catch{r(null);}})}var h=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,r,n,s=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let i={executionId:t,sessionId:e,skillName:r,startedAt:n,projectRoot:s};this.commitOrCache("execution_start",i)&&this.postHttp("/api/telemetry/skill-execution/start",i);}async sendExecutionComplete(t,e="completed",r=Date.now(),n){let s={executionId:t,status:e,finishedAt:r,metadata:n},i=this.commitOrCache("execution_complete",s);return i&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",s),i}async sendSessionStart(t,e,r){let n={id:t,projectRoot:e,startedAt:r,label:e.split("/").pop()??"unknown"},s=this.commitOrCache("session_start",n);return s&&this.postHttp("/api/telemetry/sessions",n),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 r={sessionId:t,finishedAt:e},n=this.commitOrCache("session_end",r);return n&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),n}async flushCache(){try{let r=this.getDatabase(),n=r.listTelemetryFailures({unresolvedOnly:!0,limit:d}).reverse();for(let s of n)try{this.applyOperation(s.operation,s.payload),r.resolveTelemetryFailure(s.id);}catch{}r.trimTelemetryFailures(d);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(r=>!!r))].filter(r=>existsSync(r)).flatMap(r=>readdirSync(r).filter(n=>n.endsWith(".json")).map(n=>{let s=join(r,n);try{return {cacheFile:s,envelope:fe(JSON.parse(readFileSync(s,"utf8")),n)}}catch{return null}})).filter(r=>r!==null).sort((r,n)=>r.envelope.timestamp-n.envelope.timestamp||N(r.envelope.operation)-N(n.envelope.operation));for(let{cacheFile:r,envelope:n}of e)try{let s=this.getDatabase();s.insertTelemetryFailure({id:n.id,operation:n.operation,payload:n.payload,error:n.failure,createdAt:n.timestamp}),this.applyOperation(n.operation,n.payload),s.resolveTelemetryFailure(n.id),unlinkSync(r);}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(r){return this.cacheOperation(t,e,r),false}}applyOperation(t,e){let r=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(r,e),r.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(r,e),r.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":r.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":r.ensureSession(e),r.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(!r.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":r.closeSession(e.sessionId,e.finishedAt),r.updateRun(M(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let r=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!r)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";t.ensureSession({id:r,projectRoot:n,label:n==="unknown"?void 0:n.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,r){let n=Date.now(),s={id:`failure:${n}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:r instanceof Error?r.message:String(r),timestamp:n};try{let i=this.getDatabase();i.insertTelemetryFailure({id:s.id,operation:t,payload:e,error:s.failure,createdAt:n}),i.trimTelemetryFailures(d),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync$1(this.cacheDir,{recursive:!0});let i=join(this.cacheDir,`${n}_${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-d)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},he).unref());}postHttp(t,e){ge(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=A?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,A)}...`}}};function N(o){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(o)}function M(o){return `hook-run:${o}`}function fe(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 r=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!r)throw new Error("Unknown legacy telemetry cache operation");let n=e.payload??{},s=Number(n.timestamp??n.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:r,payload:n,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:s}}var m=process.env.CLAUDE_PROJECT_DIR||process.cwd();async function ve(o){let t=b()?{additionalContext:w}:{},e;try{e=JSON.parse(o);}catch{return {hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow",...t}}}let r=e.prompt||"";if(!r.trim())return {hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow",...t}};let n=S(r);if(n)try{D(m,{content:n,sessionId:e.session_id,createdAt:Date.now()});}catch{}let s=e.session_id?.trim(),i=I(r);if(s&&i){let c=new p(m),P=c.get(s)?.executionId,u=c.startExecution(s,i.skillName,E(i.skillName));if(u.executionId&&u.executionId!==P){let g=new h;try{await g.sendSessionStart(s,m,u.startedAt),await g.sendExecutionStart(u.executionId,s,i.skillName,u.lastActivityAt,m);}finally{g.close();}}}let a=new MemoryGate(m);try{await a.forceWarmUp(),s&&await a.recordUserMessage(r.slice(0,2e3),s,r.slice(0,200));}catch{}finally{a.close();}return {hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow",...t}}}if(process.argv[1]?.endsWith("user-prompt-submit")||process.argv[1]?.endsWith("user-prompt-submit.js")){let o="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{o+=t;}),process.stdin.on("end",async()=>{let t=await ve(o.trim()||process.argv[2]||"");console.log(JSON.stringify(t)),process.exit(0);}),process.stdin.on("error",()=>{console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow"}})),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",permissionDecision:"allow"}})),process.exit(0);},3e4).unref();}
3
+ export{ve as handleUserPromptSubmit};
@@ -46,7 +46,7 @@ rm -f ~/.devflow/current-skill.json ~/.devflow/current-execution-id
46
46
 
47
47
  **新功能开发:** 需要加组件或页面 → 先确认现有实现模式,再做影响范围评估,然后生成符合项目约定的组件/Hook/类型代码,最后通过类型检查和测试。
48
48
 
49
- **性能卡顿:** 列表页滚动掉帧、输入响应慢 → Profiler 分析渲染次数和耗时,检查 memo/useMemo/useCallback 使用情况,检测不必要重渲染,生成优化方案并对比前后基准。
49
+ **性能卡顿:** 列表页滚动掉帧、输入响应慢 → 先用静态工具生成候选,再用 Profiler、浏览器 Performance trace 或可复现 benchmark 测量渲染次数、耗时和 CPU;只有测量后才能确认影响和排序优化。
50
50
 
51
51
  **Hooks 审查:** lint 警告、useEffect 循环触发 → 全量扫描所有 Hooks 调用位置,检查顶层调用规则、依赖数组完整性、清理函数缺失、反模式(渲染中创建组件、过大 useEffect),自动修复安全项。
52
52
 
@@ -59,9 +59,10 @@ rm -f ~/.devflow/current-skill.json ~/.devflow/current-execution-id
59
59
  - 使用函数组件 + Hooks,不使用 Class 组件
60
60
  - 每个文件只导出一个组件
61
61
  - Props 使用 TypeScript interface 定义
62
- - 使用 React.memo 优化纯展示组件
62
+ - 仅在父组件重复渲染、props 稳定且组件渲染成本可观时评估 React.memo;React.memo 不会阻止组件自身 state 或所消费 Context 变化引发的更新
63
63
  - 自定义 Hooks 命名以 use 开头
64
- - 避免在渲染中创建新对象/函数(使用 useMemo / useCallback)
64
+ - 内联 DOM style 默认是低影响静态发现;只有测量证明位于热点路径时才升级影响等级
65
+ - requestAnimationFrame 只对齐浏览器绘制时机;没有明确采样频率、合并和丢弃策略时,不得把它描述为通用节流
65
66
  - 遇到不确定的 API 时先查 React 官方文档再回答
66
67
  - 修复 Bug 前先查 git log 找最近变更
67
68
  - 生成代码后必须通过 TypeScript 类型检查
@@ -81,3 +82,15 @@ rm -f ~/.devflow/current-skill.json ~/.devflow/current-execution-id
81
82
  | Hooks 审查: | 调用 MCP 工具 `react_review_hooks` |
82
83
  | 组件拆分: | 调用 MCP 工具 `react_refactor_component` |
83
84
  | 新建组件: | 调用 MCP 工具 `react_new_component` |
85
+
86
+ ## 性能证据契约
87
+
88
+ `react_audit_performance` 返回静态候选,并通过 `runtimeProfilingOccurred` 明确说明是否执行过运行时 profiling。每条发现必须保留 `evidenceMode`、`detectionMethod`、`confidence`、`impact` 和 `limitations`,不得省略限制条件。
89
+
90
+ 最终答复必须按以下三个标题分开陈述:
91
+
92
+ 1. **工具发现**:只复述工具实际返回的证据、位置和限制,不扩写为已测事实。
93
+ 2. **模型假设**:明确标注基于代码上下文的推断,不与工具发现混写。
94
+ 3. **需要运行时验证**:列出复现场景、Profiler/trace/benchmark 方法和待采集指标。
95
+
96
+ 没有运行时测量时,不得把任何静态发现标为 P0,不得声称已观察到 jank/卡顿或 CPU 影响,也不得称其为性能瓶颈。只有包含场景、指标、数值和单位的 Profiler、Performance trace 或 benchmark 结果,才能支持这些结论。
@@ -124,7 +124,6 @@ Next session instruction: call mcp__devflow__memory_request_distill once at sess
124
124
  DEVFLOW_DAEMON_REACHABLE=0
125
125
  run_lifecycle() {
126
126
  local PROJECT_HASH=""
127
- local state_dir=""
128
127
  local log_dir="${HOME}/.devflow/logs"
129
128
  local fallback_dir="${TMPDIR:-/tmp}"
130
129
 
@@ -133,15 +132,6 @@ run_lifecycle() {
133
132
  fi
134
133
  [[ "$PROJECT_HASH" =~ ^[0-9a-f]{16}$ ]] || PROJECT_HASH=""
135
134
 
136
- if [ -n "${DEVFLOW_STATE_DIR:-}" ]; then
137
- state_dir="$DEVFLOW_STATE_DIR"
138
- elif [ -n "$PROJECT_HASH" ]; then
139
- state_dir="${HOME}/.devflow/state/${PROJECT_HASH}"
140
- fi
141
- if [ -n "$state_dir" ]; then
142
- rm -f "${state_dir}/current-execution-id" "${state_dir}/current-skill.json" 2>/dev/null || true
143
- fi
144
-
145
135
  if mkdir -p "$log_dir" 2>/dev/null; then
146
136
  DAEMON_LOG="${log_dir}/hook-daemon-${PROJECT_HASH:-fallback}.log"
147
137
  SERVER_LOG="${HOME}/.devflow/logs/server.log"
@@ -7,14 +7,15 @@ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$SCRIPT_DIR/..}"
7
7
 
8
8
  # 1. Tell daemon to stop via hook-client
9
9
  CLIENT_JS="$PLUGIN_ROOT/dist/hooks/hook-client.js"
10
+ INPUT="$(cat)"
10
11
  if [ -f "$CLIENT_JS" ]; then
11
- node "$CLIENT_JS" stop </dev/null 2>/dev/null || true
12
+ node "$CLIENT_JS" stop "$INPUT" </dev/null 2>/dev/null || true
12
13
  fi
13
14
 
14
15
  # 2. Run existing stop.js cleanup (session close, memory extraction)
15
16
  STOP_JS="$PLUGIN_ROOT/dist/hooks/stop.js"
16
17
  if [ -f "$STOP_JS" ]; then
17
- node "$STOP_JS" 2>/dev/null || true
18
+ printf '%s' "$INPUT" | node "$STOP_JS" 2>/dev/null || true
18
19
  fi
19
20
 
20
21
  # 3. Clean up stale receipt files for this project
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-tools/claude-code-plugin",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
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.0",
12
- "@devflow-tools/memory-engine": "0.16.1",
13
- "@devflow-tools/sdk": "0.16.1",
14
- "@devflow-tools/telemetry": "0.16.1",
15
- "@devflow-tools/workflow-engine": "0.16.1"
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"
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.1"
22
+ "@devflow-tools/mcp-server": "0.16.2"
23
23
  },
24
24
  "keywords": [
25
25
  "pi-package",
@@ -38,7 +38,6 @@
38
38
  "CLAUDE.md",
39
39
  "dist",
40
40
  "hooks",
41
- "hooks.json",
42
41
  "skills"
43
42
  ]
44
43
  }
@@ -13,7 +13,7 @@ description: React 18 专家能力 — Bug 诊断、组件重构、性能优化
13
13
 
14
14
  **新功能开发:** 需要加组件或页面 → 先确认现有实现模式,再做影响范围评估,然后生成符合项目约定的组件/Hook/类型代码,最后通过类型检查和测试。
15
15
 
16
- **性能卡顿:** 列表页滚动掉帧、输入响应慢 → Profiler 分析渲染次数和耗时,检查 memo/useMemo/useCallback 使用情况,检测不必要重渲染,生成优化方案并对比前后基准。
16
+ **性能卡顿:** 列表页滚动掉帧、输入响应慢 → 先用静态工具生成候选,再用 Profiler、浏览器 Performance trace 或可复现 benchmark 测量渲染次数、耗时和 CPU;只有测量后才能确认影响和排序优化。
17
17
 
18
18
  **Hooks 审查:** lint 警告、useEffect 循环触发 → 全量扫描所有 Hooks 调用位置,检查顶层调用规则、依赖数组完整性、清理函数缺失、反模式(渲染中创建组件、过大 useEffect),自动修复安全项。
19
19
 
@@ -26,9 +26,10 @@ description: React 18 专家能力 — Bug 诊断、组件重构、性能优化
26
26
  - 使用函数组件 + Hooks,不使用 Class 组件
27
27
  - 每个文件只导出一个组件
28
28
  - Props 使用 TypeScript interface 定义
29
- - 使用 React.memo 优化纯展示组件
29
+ - 仅在父组件重复渲染、props 稳定且组件渲染成本可观时评估 React.memo;React.memo 不会阻止组件自身 state 或所消费 Context 变化引发的更新
30
30
  - 自定义 Hooks 命名以 use 开头
31
- - 避免在渲染中创建新对象/函数(使用 useMemo / useCallback)
31
+ - 内联 DOM style 默认是低影响静态发现;只有测量证明位于热点路径时才升级影响等级
32
+ - requestAnimationFrame 只对齐浏览器绘制时机;没有明确采样频率、合并和丢弃策略时,不得把它描述为通用节流
32
33
  - 遇到不确定的 API 时先查 React 官方文档再回答
33
34
  - 修复 Bug 前先查 git log 找最近变更
34
35
  - 生成代码后必须通过 TypeScript 类型检查
@@ -48,3 +49,15 @@ description: React 18 专家能力 — Bug 诊断、组件重构、性能优化
48
49
  | Hooks 审查: | 调用 MCP 工具 `react_review_hooks` |
49
50
  | 组件拆分: | 调用 MCP 工具 `react_refactor_component` |
50
51
  | 新建组件: | 调用 MCP 工具 `react_new_component` |
52
+
53
+ ## 性能证据契约
54
+
55
+ `react_audit_performance` 返回静态候选,并通过 `runtimeProfilingOccurred` 明确说明是否执行过运行时 profiling。每条发现必须保留 `evidenceMode`、`detectionMethod`、`confidence`、`impact` 和 `limitations`,不得省略限制条件。
56
+
57
+ 最终答复必须按以下三个标题分开陈述:
58
+
59
+ 1. **工具发现**:只复述工具实际返回的证据、位置和限制,不扩写为已测事实。
60
+ 2. **模型假设**:明确标注基于代码上下文的推断,不与工具发现混写。
61
+ 3. **需要运行时验证**:列出复现场景、Profiler/trace/benchmark 方法和待采集指标。
62
+
63
+ 没有运行时测量时,不得把任何静态发现标为 P0,不得声称已观察到 jank/卡顿或 CPU 影响,也不得称其为性能瓶颈。只有包含场景、指标、数值和单位的 Profiler、Performance trace 或 benchmark 结果,才能支持这些结论。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-tools/cli",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
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.1",
16
- "@devflow-tools/benchmark": "0.16.1",
17
- "@devflow-tools/context-engine": "0.16.1",
18
- "@devflow-tools/database": "0.16.0",
19
- "@devflow-tools/knowledge-engine": "0.16.1",
20
- "@devflow-tools/mcp-server": "0.16.1",
21
- "@devflow-tools/memory-engine": "0.16.1",
22
- "@devflow-tools/plugin-animation": "0.16.1",
23
- "@devflow-tools/plugin-css": "0.16.1",
24
- "@devflow-tools/plugin-docker": "0.16.1",
25
- "@devflow-tools/plugin-electron": "0.16.1",
26
- "@devflow-tools/plugin-git": "0.16.1",
27
- "@devflow-tools/plugin-graphql": "0.16.1",
28
- "@devflow-tools/plugin-nest": "0.16.1",
29
- "@devflow-tools/plugin-nextjs": "0.16.1",
30
- "@devflow-tools/plugin-performance": "0.16.1",
31
- "@devflow-tools/plugin-react": "0.16.1",
32
- "@devflow-tools/plugin-tailwind": "0.16.1",
33
- "@devflow-tools/plugin-taro": "0.16.1",
34
- "@devflow-tools/plugin-ui-layout": "0.16.1",
35
- "@devflow-tools/plugin-vue": "0.16.1",
36
- "@devflow-tools/sdk": "0.16.1",
37
- "@devflow-tools/server": "0.16.1",
38
- "@devflow-tools/telemetry": "0.16.1",
39
- "@devflow-tools/workflow-engine": "0.16.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",
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": "dfaeb66efe9c87c388fbe6f1b706e9fcf087ce6d"
52
+ "gitHead": "a3062daea57d62fe3adbc79ddd178a2ccb0a6f5c"
53
53
  }