@devflow-tools/devflow 0.16.4 → 0.16.6
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.
- package/dist/dashboard/404.html +1 -0
- package/dist/dashboard/_next/static/chunks/1dd3208c-f3e84932bdc53383.js +1 -0
- package/dist/dashboard/_next/static/chunks/221-893fab1a7d7d9317.js +1 -0
- package/dist/dashboard/_next/static/chunks/249-e04c1de6240fd78a.js +1 -0
- package/dist/dashboard/_next/static/chunks/343-09a7e255220705c5.js +1 -0
- package/dist/dashboard/_next/static/chunks/402-a2e4eb98bb6a524c.js +1 -0
- package/dist/dashboard/_next/static/chunks/429-eac85fdc49353ed8.js +1 -0
- package/dist/dashboard/_next/static/chunks/458-75822399a849befa.js +1 -0
- package/dist/dashboard/_next/static/chunks/528-7d4fc14903647f1e.js +2 -0
- package/dist/dashboard/_next/static/chunks/568-50487ea5e93fe11f.js +1 -0
- package/dist/dashboard/_next/static/chunks/697-05cf55b6c667773f.js +1 -0
- package/dist/dashboard/_next/static/chunks/6b3bb038-34d5cf36bd0210d6.js +1 -0
- package/dist/dashboard/_next/static/chunks/736-db1a009357c222d2.js +1 -0
- package/dist/dashboard/_next/static/chunks/806-195d4d5cc6c28f6c.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/_not-found/page-5252c260804ee1ce.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/accuracy/page-ca6a419d8b29eb4f.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/benchmark/page-6c6d0acbcde4eb85.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/context/page-1874ae1050524cb6.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/doctor/page-d542f2ab8d6a1c59.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/executions/page-b98ec834dcda8d93.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/graph/page-f0e7e15288791e9c.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/knowledge/page-86808abd0d47c026.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/layout-4b78776412bd4fee.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/memory/page-e169762426ac7a23.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/page-c3e980fb2621470c.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/replay/page-a8a465ed885df0cf.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/runs/[id]/page-cc1d5182707fca5b.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/runs/page-bbf0a58abd70430c.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/settings/page-9602ff45892e7910.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/timeline/page-a8eb3caf23be8a86.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/tool-metrics/page-1f86f62b187f2b5e.js +1 -0
- package/dist/dashboard/_next/static/chunks/app/workflows/page-551939c9e86b26c6.js +1 -0
- package/dist/dashboard/_next/static/chunks/framework-58c3457e28c4602f.js +1 -0
- package/dist/dashboard/_next/static/chunks/main-3175faffdbfc3051.js +1 -0
- package/dist/dashboard/_next/static/chunks/main-app-202a5f91ead17fa7.js +1 -0
- package/dist/dashboard/_next/static/chunks/pages/_app-58c3f1a8a070bd35.js +1 -0
- package/dist/dashboard/_next/static/chunks/pages/_error-312a564ec7e2663f.js +1 -0
- package/dist/dashboard/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/dashboard/_next/static/chunks/webpack-d6388c5dcd85b0c5.js +1 -0
- package/dist/dashboard/_next/static/css/322e14d06135256b.css +31 -0
- package/dist/dashboard/_next/static/css/7d4d1db5aa137b5a.css +3 -0
- package/dist/dashboard/_next/static/xNSXWGxbQQNrvH84gKSP1/_buildManifest.js +1 -0
- package/dist/dashboard/_next/static/xNSXWGxbQQNrvH84gKSP1/_ssgManifest.js +1 -0
- package/dist/dashboard/accuracy.html +1 -0
- package/dist/dashboard/accuracy.txt +7 -0
- package/dist/dashboard/benchmark.html +1 -0
- package/dist/dashboard/benchmark.txt +7 -0
- package/dist/dashboard/context.html +1 -0
- package/dist/dashboard/context.txt +7 -0
- package/dist/dashboard/doctor.html +1 -0
- package/dist/dashboard/doctor.txt +7 -0
- package/dist/dashboard/executions.html +1 -0
- package/dist/dashboard/executions.txt +7 -0
- package/dist/dashboard/graph.html +1 -0
- package/dist/dashboard/graph.txt +7 -0
- package/dist/dashboard/index.html +1 -0
- package/dist/dashboard/index.txt +7 -0
- package/dist/dashboard/knowledge.html +1 -0
- package/dist/dashboard/knowledge.txt +7 -0
- package/dist/dashboard/locales/en.json +335 -0
- package/dist/dashboard/locales/zh.json +335 -0
- package/dist/dashboard/memory.html +1 -0
- package/dist/dashboard/memory.txt +7 -0
- package/dist/dashboard/replay.html +1 -0
- package/dist/dashboard/replay.txt +7 -0
- package/dist/dashboard/runs/_.html +1 -0
- package/dist/dashboard/runs/_.txt +8 -0
- package/dist/dashboard/runs.html +1 -0
- package/dist/dashboard/runs.txt +7 -0
- package/dist/dashboard/settings.html +1 -0
- package/dist/dashboard/settings.txt +7 -0
- package/dist/dashboard/timeline.html +1 -0
- package/dist/dashboard/timeline.txt +7 -0
- package/dist/dashboard/tool-metrics.html +1 -0
- package/dist/dashboard/tool-metrics.txt +7 -0
- package/dist/dashboard/workflows.html +1 -0
- package/dist/dashboard/workflows.txt +7 -0
- package/dist/hooks/hook-client.js +6 -6
- package/dist/hooks/hook-daemon.js +11 -11
- package/dist/hooks/post-tool-use-failure.js +1 -1
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/pre-compact.js +2 -2
- package/dist/hooks/pre-tool-use.js +2 -2
- package/dist/hooks/session-end.js +2 -2
- package/dist/hooks/user-prompt-submit.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/dist/hooks/hook-client.js +6 -6
- package/dist/plugin/dist/hooks/hook-daemon.js +11 -11
- package/dist/plugin/dist/hooks/post-tool-use-failure.js +1 -1
- package/dist/plugin/dist/hooks/post-tool-use.js +2 -2
- package/dist/plugin/dist/hooks/pre-compact.js +2 -2
- package/dist/plugin/dist/hooks/pre-tool-use.js +2 -2
- package/dist/plugin/dist/hooks/session-end.js +2 -2
- package/dist/plugin/dist/hooks/user-prompt-submit.js +2 -2
- package/dist/plugin/dist/skills/devflow:react/SKILL.md +6 -16
- package/dist/plugin/package.json +9 -8
- package/dist/plugin/skills/react/SKILL.md +6 -16
- package/package.json +13 -13
- package/plugin-registry.json +29 -29
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync,rmSync,renameSync}from'fs';import {join,dirname as dirname$1}from'path';import {request}from'http';import {homedir}from'os';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {fileURLToPath}from'url';import {mkdirSync as mkdirSync$1,existsSync as existsSync$1,statSync,appendFileSync,chmodSync,rmSync as rmSync$1,renameSync as renameSync$1}from'node:fs';import {createHash as createHash$1}from'node:crypto';import {homedir as homedir$1}from'node:os';import {dirname,join as join$1}from'node:path';import {randomUUID,createHash}from'crypto';var U=1e4,he=3e4,b=500;function ye(n,t,e){return new Promise(o=>{try{let r=new URL(n),i=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(t)},timeout:5e3},s=>{let c=[];s.on("data",l=>c.push(l)),s.on("end",()=>{let l=Buffer.concat(c).toString();o(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?l:null);});});i.on("error",()=>o(null)),i.on("timeout",()=>{i.destroy(),o(null);}),i.write(t),i.end();}catch{o(null);}})}var R=class{constructor(t){this.retryScheduled=false;this.apiUrl=t?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=t?.apiKey??getLocalApiKey(),this.cacheDir=t?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=t?.legacyCacheDir??(t?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=t?.database??null,this.ownsDatabase=!t?.database;}async sendEvent(t){let e={...t,input:this.truncateInput(t.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),t.eventId):null}async sendExecutionStart(t,e,o,r,i=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let s={executionId:t,sessionId:e,skillName:o,startedAt:r,projectRoot:i};this.commitOrCache("execution_start",s)&&this.postHttp("/api/telemetry/skill-execution/start",s);}async sendExecutionComplete(t,e="completed",o=Date.now(),r){let i={executionId:t,status:e,finishedAt:o,metadata:r},s=this.commitOrCache("execution_complete",i);return s&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",i),s}async sendSessionStart(t,e,o){let r={id:t,projectRoot:e,startedAt:o,label:e.split("/").pop()??"unknown"},i=this.commitOrCache("session_start",r);return i&&this.postHttp("/api/telemetry/sessions",r),i}async completeEvent(t){let e=this.commitOrCache("complete_event",t);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",t)),e}async endSession(t,e=Date.now()){let o={sessionId:t,finishedAt:e},r=this.commitOrCache("session_end",o);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),r}async flushCache(){try{let o=this.getDatabase(),r=o.listTelemetryFailures({unresolvedOnly:!0,limit:b}).reverse();for(let i of r)try{this.applyOperation(i.operation,i.payload),o.resolveTelemetryFailure(i.id);}catch{}o.trimTelemetryFailures(b);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(o=>!!o))].filter(o=>existsSync(o)).flatMap(o=>readdirSync(o).filter(r=>r.endsWith(".json")).map(r=>{let i=join(o,r);try{return {cacheFile:i,envelope:_e(JSON.parse(readFileSync(i,"utf8")),r)}}catch{return null}})).filter(o=>o!==null).sort((o,r)=>o.envelope.timestamp-r.envelope.timestamp||L(o.envelope.operation)-L(r.envelope.operation));for(let{cacheFile:o,envelope:r}of e)try{let i=this.getDatabase();i.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),i.resolveTelemetryFailure(r.id),unlinkSync(o);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(t,e){try{return this.applyOperation(t,e),!0}catch(o){return this.cacheOperation(t,e,o),false}}applyOperation(t,e){let o=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(o,e),o.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(o,e),o.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":o.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":o.ensureSession(e),o.insertRun({id:H(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!o.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":o.closeSession(e.sessionId,e.finishedAt),o.updateRun(H(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let o=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!o)throw new Error("Canonical session ID is required for telemetry");let r=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";t.ensureSession({id:o,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,o){let r=Date.now(),i={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:o instanceof Error?o.message:String(o),timestamp:r};try{let s=this.getDatabase();s.insertTelemetryFailure({id:i.id,operation:t,payload:e,error:i.failure,createdAt:r}),s.trimTelemetryFailures(b),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let s=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(s,JSON.stringify(i,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let t=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of t.slice(0,Math.max(0,t.length-b)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},he).unref());}postHttp(t,e){ye(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=U?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,U)}...`}}};function L(n){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(n)}function H(n){return `hook-run:${n}`}function _e(n,t){if(!n||typeof n!="object")throw new Error("Invalid telemetry cache envelope");let e=n;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let o=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!o)throw new Error("Unknown legacy telemetry cache operation");let r=e.payload??{},i=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:o,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:i}}function k(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function p(n){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(k(n))}var Se=1;function xe(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{return join(dirname$1(fileURLToPath(import.meta.url)),"..","..")}catch{return process.cwd()}}function W(n=xe()){let t=[join(n,"dist","command-registry.json"),join(n,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let o=JSON.parse(readFileSync(e,"utf8"));if(o.version!==Se||!ke(o.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return o.commands}catch(o){console.error(`[devflow] command registry load failed: ${o.message}`);}return null}function ke(n){return !n||typeof n!="object"||Array.isArray(n)?false:Object.values(n).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return q(e.mcpTools)&&q(e.blockedNative)})}function q(n){return Array.isArray(n)&&n.every(t=>typeof t=="string"&&t.length>0)}function G(n){let t=W();return t?t[n]?.mcpTools??[]:[]}function h(n){if(!n||typeof n!="object"||Array.isArray(n))return {};let t=n,e={};for(let o of ["lastMcpCall","bypassCount"])if(o in t){let r=t[o];if(r==null)continue;e[o]=typeof r=="number"&&Number.isFinite(r)&&r>=0?r:0;}return e}function Te(n,t){return n.lastMcpCall===t.lastMcpCall&&n.bypassCount===t.bypassCount}function Ce(n){try{return existsSync(n)?h(JSON.parse(readFileSync(n,"utf-8"))):{}}catch{return {}}}function K(n,t){let e=t instanceof Error?t.message:String(t);console.error(`[devflow] Receipt ${n} skipped: ${e}`);}function J(n){let t=p(n);for(let e of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(t,e))&&unlinkSync(join(t,e));}catch{}}function z(n,t){if(n.getHookReceipt(t)){J(t);return}let e=join(p(t),"receipt.json");if(!existsSync(e))return;let o=Ce(e);n.updateHookReceipt(t,()=>o),J(t);}function V(n,t,e){let o;try{o=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(o,n);let r={},i=o.updateHookReceipt(n,c=>(r=h(c),h(t(r)))),s=h(i);return {receipt:s,applied:!0,changed:!Te(r,s)}}catch(r){return K(e,r),{receipt:{},applied:false,changed:false}}finally{try{o?.close();}catch{}}}function X(n){let t;try{return t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(t,n),h(t.getHookReceipt(n))}catch(e){return K("update",e),{}}finally{try{t?.close();}catch{}}}function Ie(n,t){return V(n,()=>t,"write").applied}function Ae(n,t){return V(n,t,"update")}var B={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function je(n){switch(n){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}var S=class{constructor(t,e,o){this.projectRoot=t;this.sessionId=e;this.executionId=o;}getPhase(){if(!this.sessionId||!this.executionId)return "idle";let t;try{t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250});let e=t.getContextReceipt(this.projectRoot,this.sessionId,this.executionId);if(e&&e.expiresAt>Date.now())return "context_ready";e&&t.deleteContextReceipt(this.projectRoot,this.sessionId,this.executionId);}catch{}finally{try{t?.close();}catch{}}return "context_gathering"}evaluate(t,e){let o=this.getPhase();if(e||t==="Skill")return {permissionDecision:"allow"};if(o==="context_gathering"&&B[t])return {permissionDecision:"deny",reason:`DevFlow context required. Call mcp__devflow__get_project_context, then retry ${t}.`};if(o==="context_ready")return {permissionDecision:"allow"};let r=B[t];if(!r)return {permissionDecision:"allow"};let i=Ae(this.projectRoot,l=>({...l,bypassCount:(l.bypassCount??0)+1}));if(!i.applied)return {permissionDecision:"allow"};let s=i.receipt.bypassCount??0,c=je(t);return s>=c?{permissionDecision:"allow",additionalContext:`\u5DF2 ${s} \u6B21\u76F4\u63A5\u4F7F\u7528 ${t}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${r} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){Ie(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var qe=10*1024*1024,We=5;function Ge(n){return createHash$1("sha256").update(n).digest("hex").slice(0,16)}function Z(n){return join$1(homedir$1(),".devflow","logs","hook-debug",`${Ge(n)}.jsonl`)}function ee(n,t,e={}){let o=e.maxBytes??qe,r=e.maxFiles??We;mkdirSync$1(dirname(n),{recursive:true,mode:448});let i=Buffer.byteLength(t),s=existsSync$1(n)?statSync(n).size:0;s>0&&s+i>o&&Je(n,r),appendFileSync(n,t,{mode:384}),chmodSync(n,384);}function Je(n,t){if(t<=1){rmSync$1(n,{force:true});return}rmSync$1(`${n}.${t-1}`,{force:true});for(let e=t-2;e>=1;e-=1){let o=`${n}.${e}`;existsSync$1(o)&&renameSync$1(o,`${n}.${e+1}`);}existsSync$1(n)&&renameSync$1(n,`${n}.1`);}var A="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function te(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}function Ze(n){return Buffer.from(n,"utf8").toString("base64url")}var x=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(p(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let o=this.get(e);if(o)return o.lastActivityAt=Date.now(),this.persist(o),o;let r=Date.now(),i={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:r,lastActivityAt:r};return this.sessions.set(e,i),this.persist(i),i}startExecution(t,e,o){let r=this.registerSession(t);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(o)],r.lastActivityAt=Date.now(),this.persist(r),r}get(t){let e=t.trim();if(!e)return null;let o=this.sessions.get(e);if(o)return o;let r=this.snapshotPath(e);if(!existsSync(r))return null;try{let i=JSON.parse(readFileSync(r,"utf8"));return i.sessionId!==e||i.projectRoot!==this.projectRoot?null:(i.requiredMcpTools=Array.isArray(i.requiredMcpTools)?i.requiredMcpTools:[],this.sessions.set(e,i),i)}catch{return null}}completeExecution(t){let e=this.get(t);if(!e)return null;let o={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),o}removeSession(t){let e=t.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(t){return join(this.sessionsDir,`${Ze(t)}.json`)}persist(t){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),o=`${e}.${process.pid}.tmp`;writeFileSync(o,JSON.stringify(t),{mode:384}),renameSync(o,e);}};function oe(n){let t=n.trim().replace(/^\//,"");if(!t.startsWith("devflow:"))return null;let e=t.slice(8).replace(/^devflow-/,"");return /^[a-z0-9][a-z0-9-]*$/i.test(e)?`devflow:${e.toLowerCase()}`:null}function re(n,t){return t?`evt_tool_${createHash("sha256").update(`${n}\0${t}`).digest("hex").slice(0,24)}`:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function y(){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}}}function ie(n){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:n}}}function it(n){let t=p(n);return existsSync(t)||mkdirSync(t,{recursive:true}),t}function st(n,t,e,o,r,i){let s=join(it(n),"event-map.json"),c=[];try{existsSync(s)&&(c=JSON.parse(readFileSync(s,"utf-8")));}catch{}c.push({sessionId:t,toolName:e,toolUseId:o,eventId:r,timestamp:i}),writeFileSync(s,JSON.stringify(c));}function ct(n){switch(n){case "Read":case "Glob":return "file_read";case "Write":case "Edit":case "NotebookEdit":return "file_write";case "Bash":return "bash_command";case "Agent":return "subagent";case "Skill":return "skill_invoke";default:return "tool_use"}}async function at(n){let t=te();try{if(!n||n.trim()==="")return y();let e=JSON.parse(n),o=Date.now(),r=k(),i=new R;e.tool_name.startsWith("mcp__devflow__")&&(typeof e.tool_input.projectRoot!="string"||!e.tool_input.projectRoot.trim())&&(e.tool_input.projectRoot=r);let s=e.session_id?.trim();if(!s)return ie(A);let c=new x(r),l=c.get(s)!==null;if(c.registerSession(s),l||await i.sendSessionStart(s,r,Date.now()),e.tool_name==="Skill"){let D=e.tool_input?.skill,m=D?oe(D):null;if(m){let le=c.get(s)?.executionId,w=c.startExecution(s,m,G(m));w.executionId&&w.executionId!==le&&await i.sendExecutionStart(w.executionId,s,m,w.lastActivityAt,r);}}let d=c.get(s);e.tool_name.startsWith("mcp__devflow__")&&(e.tool_input._devflow_session_id=s,e.tool_input._devflow_execution_id=d?.executionId??s,e.tool_use_id&&(e.tool_input._devflow_tool_use_id=e.tool_use_id));let u=e.tool_name.startsWith("mcp__"),ce=u?e.tool_name.replace(/^mcp__[^_]+__/,""):void 0,j=new S(r,s,d?.executionId),F=j.getPhase(),a,_=j.evaluate(e.tool_name,u);_.permissionDecision==="deny"?a={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:_.reason??"MCP context required"}}:_.additionalContext?a=ie(_.additionalContext):a=y(),e.tool_name.startsWith("mcp__devflow__")&&(a.hookSpecificOutput.updatedInput=e.tool_input),t&&a.hookSpecificOutput.permissionDecision==="allow"&&(a.hookSpecificOutput.additionalContext=A);let ae=d?.executionId??s,g={eventId:re(s,e.tool_use_id),executionId:ae,sessionId:s,projectRoot:r,toolUseId:e.tool_use_id,timestamp:Date.now(),toolName:e.tool_name,toolType:u?"mcp":e.tool_name==="Agent"?"subagent":"direct",isMcpTool:u,mcpToolName:ce,mcpEnforced:u&&F==="context_gathering",mcpFallback:!u&&F==="context_gathering",kind:ct(e.tool_name),input:e.tool_input,duration:0,blocked:a.hookSpecificOutput.permissionDecision==="deny",blockReason:a.hookSpecificOutput.permissionDecision==="deny"?"DEVFLOW_CONTEXT_REQUIRED":void 0,error:a.hookSpecificOutput.permissionDecision==="deny"?"DEVFLOW_CONTEXT_REQUIRED":void 0},v=await i.sendEvent(g);v&&!g.blocked&&st(r,s,e.tool_name,e.tool_use_id,v,g.timestamp),v&&g.blocked&&await i.completeEvent({eventId:v,sessionId:s,error:"DEVFLOW_CONTEXT_REQUIRED",duration:Date.now()-g.timestamp,completedAt:Date.now()});try{let D=X(r),m={ts:o,tool:e.tool_name,sessionId:s,skill:d?.skillName??"(none)",requiredTools:d?.requiredMcpTools??[],isMcp:u,enforce:d?.executionId!==void 0,stateless:d?.executionId===void 0&&!u&&e.tool_name!=="Skill",bypassCount:D.bypassCount??0,decision:a.hookSpecificOutput.permissionDecision,env_CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd()};ee(Z(r),`${JSON.stringify(m)}
|
|
2
|
-
`);}catch{}return a}catch{return
|
|
1
|
+
import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync,rmSync,renameSync}from'fs';import {join,dirname as dirname$1}from'path';import {request}from'http';import {homedir}from'os';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {fileURLToPath}from'url';import {mkdirSync as mkdirSync$1,existsSync as existsSync$1,statSync,appendFileSync,chmodSync,rmSync as rmSync$1,renameSync as renameSync$1}from'node:fs';import {createHash as createHash$1}from'node:crypto';import {homedir as homedir$1}from'node:os';import {dirname,join as join$1}from'node:path';import {randomUUID,createHash}from'crypto';var U=1e4,he=3e4,b=500;function ye(n,t,e){return new Promise(o=>{try{let r=new URL(n),i=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(t)},timeout:5e3},s=>{let c=[];s.on("data",l=>c.push(l)),s.on("end",()=>{let l=Buffer.concat(c).toString();o(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?l:null);});});i.on("error",()=>o(null)),i.on("timeout",()=>{i.destroy(),o(null);}),i.write(t),i.end();}catch{o(null);}})}var R=class{constructor(t){this.retryScheduled=false;this.metricAggregationScheduled=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(),s=[]){let c={executionId:t,sessionId:e,skillName:o,startedAt:r,projectRoot:i,requiredMcpTools:s};this.commitOrCache("execution_start",c)&&this.postHttp("/api/telemetry/skill-execution/start",c);}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.postHttp("/api/telemetry/tool-call/output",t),this.scheduleMetricAggregation()),e}async endSession(t,e=Date.now()){let o={sessionId:t,finishedAt:e},r=this.commitOrCache("session_end",o);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(t)}/close`,{finishedAt:e})),r}async flushCache(){try{let o=this.getDatabase(),r=o.listTelemetryFailures({unresolvedOnly:!0,limit:b}).reverse();for(let i of r)try{this.applyOperation(i.operation,i.payload),o.resolveTelemetryFailure(i.id);}catch{}o.trimTelemetryFailures(b);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(o=>!!o))].filter(o=>existsSync(o)).flatMap(o=>readdirSync(o).filter(r=>r.endsWith(".json")).map(r=>{let i=join(o,r);try{return {cacheFile:i,envelope:_e(JSON.parse(readFileSync(i,"utf8")),r)}}catch{return null}})).filter(o=>o!==null).sort((o,r)=>o.envelope.timestamp-r.envelope.timestamp||L(o.envelope.operation)-L(r.envelope.operation));for(let{cacheFile:o,envelope:r}of e)try{let i=this.getDatabase();i.insertTelemetryFailure({id:r.id,operation:r.operation,payload:r.payload,error:r.failure,createdAt:r.timestamp}),this.applyOperation(r.operation,r.payload),i.resolveTelemetryFailure(r.id),unlinkSync(o);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(t,e){try{return this.applyOperation(t,e),!0}catch(o){return this.cacheOperation(t,e,o),false}}applyOperation(t,e){let o=this.getDatabase();switch(t){case "tool_call":this.ensureParentSession(o,e),o.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(o,e),o.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running",requiredMcpTools:e.requiredMcpTools});return;case "execution_complete":o.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":o.ensureSession(e),o.insertRun({id:H(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!o.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":o.closeSession(e.sessionId,e.finishedAt),o.updateRun(H(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(t,e){let o=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!o)throw new Error("Canonical session ID is required for telemetry");let r=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";t.ensureSession({id:o,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(t,e,o){let r=Date.now(),i={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:t,payload:e,failure:o instanceof Error?o.message:String(o),timestamp:r};try{let s=this.getDatabase();s.insertTelemetryFailure({id:i.id,operation:t,payload:e,error:i.failure,createdAt:r}),s.trimTelemetryFailures(b),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let s=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(s,JSON.stringify(i,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let t=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of t.slice(0,Math.max(0,t.length-b)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},he).unref());}scheduleMetricAggregation(){if(this.metricAggregationScheduled||process.env.DEVFLOW_HOOK_DEGRADED==="1")return;this.metricAggregationScheduled=true,setTimeout(()=>{this.metricAggregationScheduled=false;try{this.getDatabase().aggregatePendingToolMetrics(50)===50&&this.scheduleMetricAggregation();}catch{}},25).unref();}postHttp(t,e){ye(`${this.apiUrl}${t}`,JSON.stringify(e),this.apiKey);}truncateInput(t){let e=JSON.stringify(t);return e===void 0||e.length<=U?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,U)}...`}}};function L(n){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(n)}function H(n){return `hook-run:${n}`}function _e(n,t){if(!n||typeof n!="object")throw new Error("Invalid telemetry cache envelope");let e=n;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let o=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!o)throw new Error("Unknown legacy telemetry cache operation");let r=e.payload??{},i=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${t}`,operation:o,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:i}}function k(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function p(n){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(k(n))}var Se=1;function xe(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{return join(dirname$1(fileURLToPath(import.meta.url)),"..","..")}catch{return process.cwd()}}function W(n=xe()){let t=[join(n,"dist","command-registry.json"),join(n,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let o=JSON.parse(readFileSync(e,"utf8"));if(o.version!==Se||!ke(o.commands)){console.error(`[devflow] command registry contract mismatch: ${e}`);continue}return o.commands}catch(o){console.error(`[devflow] command registry load failed: ${o.message}`);}return null}function ke(n){return !n||typeof n!="object"||Array.isArray(n)?false:Object.values(n).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return q(e.mcpTools)&&q(e.blockedNative)})}function q(n){return Array.isArray(n)&&n.every(t=>typeof t=="string"&&t.length>0)}function G(n){let t=W();return t?t[n]?.mcpTools??[]:[]}function y(n){if(!n||typeof n!="object"||Array.isArray(n))return {};let t=n,e={};for(let o of ["lastMcpCall","bypassCount"])if(o in t){let r=t[o];if(r==null)continue;e[o]=typeof r=="number"&&Number.isFinite(r)&&r>=0?r:0;}return e}function Te(n,t){return n.lastMcpCall===t.lastMcpCall&&n.bypassCount===t.bypassCount}function Ce(n){try{return existsSync(n)?y(JSON.parse(readFileSync(n,"utf-8"))):{}}catch{return {}}}function K(n,t){let e=t instanceof Error?t.message:String(t);console.error(`[devflow] Receipt ${n} skipped: ${e}`);}function J(n){let t=p(n);for(let e of ["receipt.json","receipt-lock.sqlite","receipt-lock.sqlite-shm","receipt-lock.sqlite-wal"])try{existsSync(join(t,e))&&unlinkSync(join(t,e));}catch{}}function z(n,t){if(n.getHookReceipt(t)){J(t);return}let e=join(p(t),"receipt.json");if(!existsSync(e))return;let o=Ce(e);n.updateHookReceipt(t,()=>o),J(t);}function V(n,t,e){let o;try{o=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250}),z(o,n);let r={},i=o.updateHookReceipt(n,c=>(r=y(c),y(t(r)))),s=y(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),y(t.getHookReceipt(n))}catch(e){return K("update",e),{}}finally{try{t?.close();}catch{}}}function Ie(n,t){return V(n,()=>t,"write").applied}function Ae(n,t){return V(n,t,"update")}var B={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",Bash:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function je(n){switch(n){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}var S=class{constructor(t,e,o){this.projectRoot=t;this.sessionId=e;this.executionId=o;}getPhase(){if(!this.sessionId||!this.executionId)return "idle";let t;try{t=openGlobalDevFlowDatabase(void 0,{busyTimeoutMs:250});let e=t.getContextReceipt(this.projectRoot,this.sessionId,this.executionId);if(e&&e.expiresAt>Date.now())return "context_ready";e&&t.deleteContextReceipt(this.projectRoot,this.sessionId,this.executionId);}catch{}finally{try{t?.close();}catch{}}return "context_gathering"}evaluate(t,e){let o=this.getPhase();if(e||t==="Skill")return {permissionDecision:"allow"};if(o==="context_gathering"&&B[t])return {permissionDecision:"deny",reason:`DevFlow context required. Call mcp__devflow__get_project_context, then retry ${t}.`};if(o==="context_ready")return {permissionDecision:"allow"};let r=B[t];if(!r)return {permissionDecision:"allow"};let i=Ae(this.projectRoot,l=>({...l,bypassCount:(l.bypassCount??0)+1}));if(!i.applied)return {permissionDecision:"allow"};let s=i.receipt.bypassCount??0,c=je(t);return s>=c?{permissionDecision:"allow",additionalContext:`\u5DF2 ${s} \u6B21\u76F4\u63A5\u4F7F\u7528 ${t}\uFF0C\u5EFA\u8BAE\u7528 mcp__devflow__${r} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}:{permissionDecision:"allow"}}recordMcpCall(){Ie(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}};var qe=10*1024*1024,We=5;function Ge(n){return createHash$1("sha256").update(n).digest("hex").slice(0,16)}function Z(n){return join$1(homedir$1(),".devflow","logs","hook-debug",`${Ge(n)}.jsonl`)}function ee(n,t,e={}){let o=e.maxBytes??qe,r=e.maxFiles??We;mkdirSync$1(dirname(n),{recursive:true,mode:448});let i=Buffer.byteLength(t),s=existsSync$1(n)?statSync(n).size:0;s>0&&s+i>o&&Je(n,r),appendFileSync(n,t,{mode:384}),chmodSync(n,384);}function Je(n,t){if(t<=1){rmSync$1(n,{force:true});return}rmSync$1(`${n}.${t-1}`,{force:true});for(let e=t-2;e>=1;e-=1){let o=`${n}.${e}`;existsSync$1(o)&&renameSync$1(o,`${n}.${e+1}`);}existsSync$1(n)&&renameSync$1(n,`${n}.1`);}var A="WARNING: DevFlow is in degraded mode (daemon unreachable): 4-Gate enforcement and memory prefetch cache are unavailable. Run `devflow doctor` for details.";function te(){return process.env.DEVFLOW_HOOK_DEGRADED==="1"}function Ze(n){return Buffer.from(n,"utf8").toString("base64url")}var x=class{constructor(t){this.projectRoot=t;this.sessions=new Map;this.sessionsDir=join(p(t),"hook-sessions");}registerSession(t){let e=t.trim();if(!e)throw new Error("session_id_required");let o=this.get(e);if(o)return o.lastActivityAt=Date.now(),this.persist(o),o;let r=Date.now(),i={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:r,lastActivityAt:r};return this.sessions.set(e,i),this.persist(i),i}startExecution(t,e,o){let r=this.registerSession(t);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(o)],r.lastActivityAt=Date.now(),this.persist(r),r}get(t){let e=t.trim();if(!e)return null;let o=this.sessions.get(e);if(o)return o;let r=this.snapshotPath(e);if(!existsSync(r))return null;try{let i=JSON.parse(readFileSync(r,"utf8"));return i.sessionId!==e||i.projectRoot!==this.projectRoot?null:(i.requiredMcpTools=Array.isArray(i.requiredMcpTools)?i.requiredMcpTools:[],this.sessions.set(e,i),i)}catch{return null}}completeExecution(t){let e=this.get(t);if(!e)return null;let o={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),o}removeSession(t){let e=t.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(t){return join(this.sessionsDir,`${Ze(t)}.json`)}persist(t){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(t.sessionId),o=`${e}.${process.pid}.tmp`;writeFileSync(o,JSON.stringify(t),{mode:384}),renameSync(o,e);}};function oe(n){let t=n.trim().replace(/^\//,"");if(!t.startsWith("devflow:"))return null;let e=t.slice(8).replace(/^devflow-/,"");return /^[a-z0-9][a-z0-9-]*$/i.test(e)?`devflow:${e.toLowerCase()}`:null}function re(n,t){return t?`evt_tool_${createHash("sha256").update(`${n}\0${t}`).digest("hex").slice(0,24)}`:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function _(){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 _();let e=JSON.parse(n),o=Date.now(),r=k(),i=new R;e.tool_name.startsWith("mcp__devflow__")&&(typeof e.tool_input.projectRoot!="string"||!e.tool_input.projectRoot.trim())&&(e.tool_input.projectRoot=r);let s=e.session_id?.trim();if(!s)return ie(A);let c=new x(r),l=c.get(s)!==null;if(c.registerSession(s),l||await i.sendSessionStart(s,r,Date.now()),e.tool_name==="Skill"){let w=e.tool_input?.skill,m=w?oe(w):null;if(m){let le=c.get(s)?.executionId,h=c.startExecution(s,m,G(m));h.executionId&&h.executionId!==le&&await i.sendExecutionStart(h.executionId,s,m,h.lastActivityAt,r,h.requiredMcpTools);}}let d=c.get(s);e.tool_name.startsWith("mcp__devflow__")&&(e.tool_input._devflow_session_id=s,e.tool_input._devflow_execution_id=d?.executionId??s,e.tool_use_id&&(e.tool_input._devflow_tool_use_id=e.tool_use_id));let u=e.tool_name.startsWith("mcp__"),ce=u?e.tool_name.replace(/^mcp__[^_]+__/,""):void 0,j=new S(r,s,d?.executionId),F=j.getPhase(),a,v=j.evaluate(e.tool_name,u);v.permissionDecision==="deny"?a={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:v.reason??"MCP context required"}}:v.additionalContext?a=ie(v.additionalContext):a=_(),e.tool_name.startsWith("mcp__devflow__")&&(a.hookSpecificOutput.updatedInput=e.tool_input),t&&a.hookSpecificOutput.permissionDecision==="allow"&&(a.hookSpecificOutput.additionalContext=A);let ae=d?.executionId??s,f={eventId:re(s,e.tool_use_id),executionId:ae,sessionId:s,projectRoot:r,toolUseId:e.tool_use_id,timestamp:Date.now(),toolName:e.tool_name,toolType:u?"mcp":e.tool_name==="Agent"?"subagent":"direct",isMcpTool:u,mcpToolName:ce,mcpEnforced:u&&F==="context_gathering",mcpFallback:!u&&F==="context_gathering",kind:ct(e.tool_name),input:e.tool_input,duration:0,blocked:a.hookSpecificOutput.permissionDecision==="deny",blockReason:a.hookSpecificOutput.permissionDecision==="deny"?"DEVFLOW_CONTEXT_REQUIRED":void 0,error:a.hookSpecificOutput.permissionDecision==="deny"?"DEVFLOW_CONTEXT_REQUIRED":void 0},D=await i.sendEvent(f);D&&!f.blocked&&st(r,s,e.tool_name,e.tool_use_id,D,f.timestamp),D&&f.blocked&&await i.completeEvent({eventId:D,sessionId:s,error:"DEVFLOW_CONTEXT_REQUIRED",duration:Date.now()-f.timestamp,completedAt:Date.now()});try{let w=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:w.bypassCount??0,decision:a.hookSpecificOutput.permissionDecision,env_CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd()};ee(Z(r),`${JSON.stringify(m)}
|
|
2
|
+
`);}catch{}return a}catch{return _()}}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(_())),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify(_())),process.exit(0);},4e3).unref();}export{at as handlePreToolUse};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {request}from'http';import {getLocalApiKey,getProjectStateDir}from'@devflow-tools/sdk';import {openGlobalDevFlowDatabase}from'@devflow-tools/database';import {existsSync,readFileSync,rmSync,mkdirSync,writeFileSync,renameSync,readdirSync,unlinkSync}from'fs';import {join}from'path';import {homedir}from'os';import {MemoryGate}from'@devflow-tools/memory-engine';import {randomUUID}from'crypto';function w(i){return i??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function R(i){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(w(i))}var A=1e4,K=3e4,g=500;function B(i,s,e){return new Promise(t=>{try{let n=new URL(i),r=request({hostname:n.hostname,port:n.port||80,path:n.pathname+n.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":e,"Content-Length":Buffer.byteLength(s)},timeout:5e3},o=>{let c=[];o.on("data",a=>c.push(a)),o.on("end",()=>{let a=Buffer.concat(c).toString();t(o.statusCode!=null&&o.statusCode>=200&&o.statusCode<300?a:null);});});r.on("error",()=>t(null)),r.on("timeout",()=>{r.destroy(),t(null);}),r.write(s),r.end();}catch{t(null);}})}var y=class{constructor(s){this.retryScheduled=false;this.apiUrl=s?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=s?.apiKey??getLocalApiKey(),this.cacheDir=s?.cacheDir??join(process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow","global"),"telemetry-cache"),this.legacyCacheDir=s?.legacyCacheDir??(s?.cacheDir?null:join(homedir(),".devflow","telemetry-cache")),this.database=s?.database??null,this.ownsDatabase=!s?.database;}async sendEvent(s){let e={...s,input:this.truncateInput(s.input)};return this.commitOrCache("tool_call",e)?(this.postHttp("/api/telemetry/tool-call",e),s.eventId):null}async sendExecutionStart(s,e,t,n,r=process.env.CLAUDE_PROJECT_DIR??process.cwd()){let o={executionId:s,sessionId:e,skillName:t,startedAt:n,projectRoot:r};this.commitOrCache("execution_start",o)&&this.postHttp("/api/telemetry/skill-execution/start",o);}async sendExecutionComplete(s,e="completed",t=Date.now(),n){let r={executionId:s,status:e,finishedAt:t,metadata:n},o=this.commitOrCache("execution_complete",r);return o&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",r),o}async sendSessionStart(s,e,t){let n={id:s,projectRoot:e,startedAt:t,label:e.split("/").pop()??"unknown"},r=this.commitOrCache("session_start",n);return r&&this.postHttp("/api/telemetry/sessions",n),r}async completeEvent(s){let e=this.commitOrCache("complete_event",s);return e&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp("/api/telemetry/tool-call/output",s)),e}async endSession(s,e=Date.now()){let t={sessionId:s,finishedAt:e},n=this.commitOrCache("session_end",t);return n&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(s)}/close`,{finishedAt:e})),n}async flushCache(){try{let t=this.getDatabase(),n=t.listTelemetryFailures({unresolvedOnly:!0,limit:g}).reverse();for(let r of n)try{this.applyOperation(r.operation,r.payload),t.resolveTelemetryFailure(r.id);}catch{}t.trimTelemetryFailures(g);}catch{}let e=[...new Set([this.cacheDir,this.legacyCacheDir].filter(t=>!!t))].filter(t=>existsSync(t)).flatMap(t=>readdirSync(t).filter(n=>n.endsWith(".json")).map(n=>{let r=join(t,n);try{return {cacheFile:r,envelope:G(JSON.parse(readFileSync(r,"utf8")),n)}}catch{return null}})).filter(t=>t!==null).sort((t,n)=>t.envelope.timestamp-n.envelope.timestamp||P(t.envelope.operation)-P(n.envelope.operation));for(let{cacheFile:t,envelope:n}of e)try{let r=this.getDatabase();r.insertTelemetryFailure({id:n.id,operation:n.operation,payload:n.payload,error:n.failure,createdAt:n.timestamp}),this.applyOperation(n.operation,n.payload),r.resolveTelemetryFailure(n.id),unlinkSync(t);}catch{}this.database?.aggregatePendingToolMetrics();}aggregateMetrics(){return this.getDatabase().aggregatePendingToolMetrics()}async flushAndAggregate(){await this.flushCache(),this.aggregateMetrics();}close(){this.ownsDatabase&&this.database?.close(),this.database=null;}getDatabase(){return this.database??=openGlobalDevFlowDatabase(),this.database}commitOrCache(s,e){try{return this.applyOperation(s,e),!0}catch(t){return this.cacheOperation(s,e,t),false}}applyOperation(s,e){let t=this.getDatabase();switch(s){case "tool_call":this.ensureParentSession(t,e),t.insertToolCallEvent(e);return;case "execution_start":this.ensureParentSession(t,e),t.insertSkillExecution({executionId:e.executionId,sessionId:e.sessionId,skillName:e.skillName,startedAt:e.startedAt,status:"running"});return;case "execution_complete":t.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":t.ensureSession(e),t.insertRun({id:M(e.id),source:"hook",tool:"session",input:{projectRoot:e.projectRoot},status:"active",startedAt:e.startedAt,tokenUsed:0,metadata:{sessionId:e.id,projectRoot:e.projectRoot}});return;case "complete_event":if(!t.updateToolCallEvent(e.eventId,{output:e.output===void 0?void 0:JSON.stringify(e.output),error:e.error,duration:e.duration}))throw new Error(`Tool call event ${e.eventId} is not available for completion`);return;case "session_end":t.closeSession(e.sessionId,e.finishedAt),t.updateRun(M(e.sessionId),{status:"completed",finishedAt:e.finishedAt});return}}ensureParentSession(s,e){let t=typeof e.sessionId=="string"?e.sessionId.trim():"";if(!t)throw new Error("Canonical session ID is required for telemetry");let n=typeof e.projectRoot=="string"&&e.projectRoot.trim()?e.projectRoot:typeof e.input?.projectRoot=="string"&&e.input.projectRoot.trim()?e.input.projectRoot:process.env.CLAUDE_PROJECT_DIR??"unknown";s.ensureSession({id:t,projectRoot:n,label:n==="unknown"?void 0:n.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(s,e,t){let n=Date.now(),r={id:`failure:${n}:${Math.random().toString(36).slice(2,11)}`,operation:s,payload:e,failure:t instanceof Error?t.message:String(t),timestamp:n};try{let o=this.getDatabase();o.insertTelemetryFailure({id:r.id,operation:s,payload:e,error:r.failure,createdAt:n}),o.trimTelemetryFailures(g),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let o=join(this.cacheDir,`${n}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(o,JSON.stringify(r,null,2),{mode:384}),this.trimCompatibilityCache(),this.scheduleRetry();}catch{}}trimCompatibilityCache(){try{let s=readdirSync(this.cacheDir).filter(e=>e.endsWith(".json")).sort();for(let e of s.slice(0,Math.max(0,s.length-g)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},K).unref());}postHttp(s,e){B(`${this.apiUrl}${s}`,JSON.stringify(e),this.apiKey);}truncateInput(s){let e=JSON.stringify(s);return e===void 0||e.length<=A?s:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,A)}...`}}};function P(i){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(i)}function M(i){return `hook-run:${i}`}function G(i,s){if(!i||typeof i!="object")throw new Error("Invalid telemetry cache envelope");let e=i;if(typeof e.operation=="string"&&e.payload!==void 0)return e;let t=e.type==="tool_call"?"tool_call":e.type==="execution_start"?"execution_start":null;if(!t)throw new Error("Unknown legacy telemetry cache operation");let n=e.payload??{},r=Number(n.timestamp??n.startedAt??Date.now());return {id:`legacy-cache:${s}`,operation:t,payload:n,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:r}}async function O(i){let s=!i.memory,e=i.memory??new MemoryGate(i.projectRoot),t=0,n=0;try{await e.forceWarmUp(),i.trigger==="session_end"&&(t=await e.releasePendingDistillLeases(i.sessionId)),n=e.getPendingEventCount();}finally{s&&e.close();}let r={pendingEvents:n,releasedLeases:t,trigger:i.trigger,sessionId:i.sessionId};try{let o=openGlobalDevFlowDatabase();try{let c=Date.now(),a=`distill:${c}:${Math.random().toString(36).slice(2,10)}`;o.recordMemoryDistillCheckpoint({id:a,projectRoot:i.projectRoot,sessionId:i.sessionId,trigger:i.trigger,pendingEvents:n,releasedLeases:t,createdAt:c}),o.insertEvent({kind:"memory_distill_requested",timestamp:c,success:!0,metadata:{...r,projectRoot:i.projectRoot}});}finally{o.close();}}catch{}return r}function se(i){return Buffer.from(i,"utf8").toString("base64url")}var f=class{constructor(s){this.projectRoot=s;this.sessions=new Map;this.sessionsDir=join(R(s),"hook-sessions");}registerSession(s){let e=s.trim();if(!e)throw new Error("session_id_required");let t=this.get(e);if(t)return t.lastActivityAt=Date.now(),this.persist(t),t;let n=Date.now(),r={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:n,lastActivityAt:n};return this.sessions.set(e,r),this.persist(r),r}startExecution(s,e,t){let n=this.registerSession(s);return (!n.executionId||n.skillName!==e)&&(n.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),n.skillName=e,n.requiredMcpTools=[...new Set(t)],n.lastActivityAt=Date.now(),this.persist(n),n}get(s){let e=s.trim();if(!e)return null;let t=this.sessions.get(e);if(t)return t;let n=this.snapshotPath(e);if(!existsSync(n))return null;try{let r=JSON.parse(readFileSync(n,"utf8"));return r.sessionId!==e||r.projectRoot!==this.projectRoot?null:(r.requiredMcpTools=Array.isArray(r.requiredMcpTools)?r.requiredMcpTools:[],this.sessions.set(e,r),r)}catch{return null}}completeExecution(s){let e=this.get(s);if(!e)return null;let t={...e,requiredMcpTools:[...e.requiredMcpTools]};return delete e.executionId,delete e.skillName,e.requiredMcpTools=[],e.lastActivityAt=Date.now(),this.persist(e),t}removeSession(s){let e=s.trim();e&&(this.sessions.delete(e),rmSync(this.snapshotPath(e),{force:true}));}list(){return [...this.sessions.values()]}snapshotPath(s){return join(this.sessionsDir,`${se(s)}.json`)}persist(s){mkdirSync(this.sessionsDir,{recursive:true,mode:448});let e=this.snapshotPath(s.sessionId),t=`${e}.${process.pid}.tmp`;writeFileSync(t,JSON.stringify(s),{mode:384}),renameSync(t,e);}};var oe=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",ae=getLocalApiKey();function ce(i,s){return new Promise(e=>{let t=JSON.stringify(s),n=new URL(i,oe),r=request({hostname:n.hostname,port:n.port,path:n.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":ae,"Content-Length":Buffer.byteLength(t)},timeout:5e3},()=>e());r.on("error",()=>e()),r.write(t),r.end();})}async function le(i,s=w(),e=true,t){let n;try{n=JSON.parse(i);}catch{return}let r=n.session_id?.trim();if(!r)return;let o=new f(s),c=o.get(r),a=t?.telemetry??new y,F=!t?.telemetry;try{try{await a.flushAndAggregate();}catch{}let l;try{l=openGlobalDevFlowDatabase();let d=l.listToolCallEventsBySession(r),p=c?.executionId;if(p){let h=d.filter(m=>m.executionId===p),v=h.length,D=h.filter(m=>m.isMcpTool).length,_=v-D,S=h.filter(m=>m.toolType==="subagent").length,b=h.map(m=>m.timestamp).filter(Boolean),C=b.length>=2?Math.max(...b)-Math.min(...b):0,T=l.getExecutionObligationCompliance(p);try{l.updateSkillExecution(p,{status:"completed",finishedAt:Date.now(),totalToolCalls:v,mcpToolCalls:D,directToolCalls:_,subagentCount:S,totalDuration:C,mcpComplianceRate:T.rate}),await ce("/api/telemetry/skill-execution/complete",{executionId:p,finishedAt:Date.now(),status:"completed",summary:{totalToolCalls:v,mcpToolCalls:D,directToolCalls:_,subagentCount:S,totalDuration:C,mcpComplianceRate:T.rate}});}catch{}}e&&l.deleteHookReceipt(s),l.deleteContextReceipt(s,r);}catch{}finally{l?.close();}try{if(await O({projectRoot:s,sessionId:r,trigger:"session_end",memory:t?.memory}),t?.memory)await t.memory.closeSession(r);else {let d=new(await import('@devflow-tools/memory-engine')).MemoryGate(s);try{await d.closeSession(r);}finally{d.close();}}}catch{}try{await a.endSession(r),await a.flushAndAggregate();}catch{}}finally{F&&a.close(),o.removeSession(r);}}if(process.argv[1]?.endsWith("session-end")||process.argv[1]?.endsWith("session-end.js")){let i="";process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{i+=s;}),process.stdin.on("end",async()=>{await le(i.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),1e4).unref();}
|
|
2
|
-
export{
|
|
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(n){return n??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function E(n){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(w(n))}var M=1e4,G=3e4,h=500;function W(n,s,e){return new Promise(t=>{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(s)},timeout:5e3},o=>{let a=[];o.on("data",c=>a.push(c)),o.on("end",()=>{let c=Buffer.concat(a).toString();t(o.statusCode!=null&&o.statusCode>=200&&o.statusCode<300?c:null);});});i.on("error",()=>t(null)),i.on("timeout",()=>{i.destroy(),t(null);}),i.write(s),i.end();}catch{t(null);}})}var g=class{constructor(s){this.retryScheduled=false;this.metricAggregationScheduled=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,r,i=process.env.CLAUDE_PROJECT_DIR??process.cwd(),o=[]){let a={executionId:s,sessionId:e,skillName:t,startedAt:r,projectRoot:i,requiredMcpTools:o};this.commitOrCache("execution_start",a)&&this.postHttp("/api/telemetry/skill-execution/start",a);}async sendExecutionComplete(s,e="completed",t=Date.now(),r){let i={executionId:s,status:e,finishedAt:t,metadata:r},o=this.commitOrCache("execution_complete",i);return o&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",i),o}async sendSessionStart(s,e,t){let r={id:s,projectRoot:e,startedAt:t,label:e.split("/").pop()??"unknown"},i=this.commitOrCache("session_start",r);return i&&this.postHttp("/api/telemetry/sessions",r),i}async completeEvent(s){let e=this.commitOrCache("complete_event",s);return e&&(this.postHttp("/api/telemetry/tool-call/output",s),this.scheduleMetricAggregation()),e}async endSession(s,e=Date.now()){let t={sessionId:s,finishedAt:e},r=this.commitOrCache("session_end",t);return r&&(this.getDatabase().aggregatePendingToolMetrics(),this.postHttp(`/api/telemetry/sessions/${encodeURIComponent(s)}/close`,{finishedAt:e})),r}async flushCache(){try{let t=this.getDatabase(),r=t.listTelemetryFailures({unresolvedOnly:!0,limit:h}).reverse();for(let i of r)try{this.applyOperation(i.operation,i.payload),t.resolveTelemetryFailure(i.id);}catch{}t.trimTelemetryFailures(h);}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 i=join(t,r);try{return {cacheFile:i,envelope:B(JSON.parse(readFileSync(i,"utf8")),r)}}catch{return null}})).filter(t=>t!==null).sort((t,r)=>t.envelope.timestamp-r.envelope.timestamp||P(t.envelope.operation)-P(r.envelope.operation));for(let{cacheFile:t,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(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",requiredMcpTools:e.requiredMcpTools});return;case "execution_complete":t.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":t.ensureSession(e),t.insertRun({id:O(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(O(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 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";s.ensureSession({id:t,projectRoot:r,label:r==="unknown"?void 0:r.split("/").pop(),startedAt:Number(e.startedAt??e.timestamp??Date.now())});}cacheOperation(s,e,t){let r=Date.now(),i={id:`failure:${r}:${Math.random().toString(36).slice(2,11)}`,operation:s,payload:e,failure:t instanceof Error?t.message:String(t),timestamp:r};try{let o=this.getDatabase();o.insertTelemetryFailure({id:i.id,operation:s,payload:e,error:i.failure,createdAt:r}),o.trimTelemetryFailures(h),this.scheduleRetry();return}catch{}try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let o=join(this.cacheDir,`${r}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(o,JSON.stringify(i,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-h)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},G).unref());}scheduleMetricAggregation(){if(this.metricAggregationScheduled||process.env.DEVFLOW_HOOK_DEGRADED==="1")return;this.metricAggregationScheduled=true,setTimeout(()=>{this.metricAggregationScheduled=false;try{this.getDatabase().aggregatePendingToolMetrics(50)===50&&this.scheduleMetricAggregation();}catch{}},25).unref();}postHttp(s,e){W(`${this.apiUrl}${s}`,JSON.stringify(e),this.apiKey);}truncateInput(s){let e=JSON.stringify(s);return e===void 0||e.length<=M?s:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,M)}...`}}};function P(n){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(n)}function O(n){return `hook-run:${n}`}function B(n,s){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 r=e.payload??{},i=Number(r.timestamp??r.startedAt??Date.now());return {id:`legacy-cache:${s}`,operation:t,payload:r,failure:"Replayed legacy HTTP-first telemetry cache entry",timestamp:i}}async function j(n){let s=!n.memory,e=n.memory??new MemoryGate(n.projectRoot),t=0,r=0;try{await e.forceWarmUp(),n.trigger==="session_end"&&(t=await e.releasePendingDistillLeases(n.sessionId)),r=e.getPendingEventCount();}finally{s&&e.close();}let i={pendingEvents:r,releasedLeases:t,trigger:n.trigger,sessionId:n.sessionId};try{let o=openGlobalDevFlowDatabase();try{let a=Date.now(),c=`distill:${a}:${Math.random().toString(36).slice(2,10)}`;o.recordMemoryDistillCheckpoint({id:c,projectRoot:n.projectRoot,sessionId:n.sessionId,trigger:n.trigger,pendingEvents:r,releasedLeases:t,createdAt:a}),o.insertEvent({kind:"memory_distill_requested",timestamp:a,success:!0,metadata:{...i,projectRoot:n.projectRoot}});}finally{o.close();}}catch{}return i}function re(n){return Buffer.from(n,"utf8").toString("base64url")}var y=class{constructor(s){this.projectRoot=s;this.sessions=new Map;this.sessionsDir=join(E(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 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(s,e,t){let r=this.registerSession(s);return (!r.executionId||r.skillName!==e)&&(r.executionId=`exec_${Date.now()}_${randomUUID().slice(0,8)}`),r.skillName=e,r.requiredMcpTools=[...new Set(t)],r.lastActivityAt=Date.now(),this.persist(r),r}get(s){let e=s.trim();if(!e)return null;let t=this.sessions.get(e);if(t)return t;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(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,`${re(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 ae=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",ce=getLocalApiKey();function le(n,s){return new Promise(e=>{let t=JSON.stringify(s),r=new URL(n,ae),i=request({hostname:r.hostname,port:r.port,path:r.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":ce,"Content-Length":Buffer.byteLength(t)},timeout:5e3},()=>e());i.on("error",()=>e()),i.write(t),i.end();})}async function ue(n,s=w(),e=true,t){let r;try{r=JSON.parse(n);}catch{return}let i=r.session_id?.trim();if(!i)return;let o=new y(s),a=o.get(i),c=t?.telemetry??new g,L=!t?.telemetry;try{try{await c.flushAndAggregate();}catch{}let l;try{l=openGlobalDevFlowDatabase();let d=l.listToolCallEventsBySession(i),u=a?.executionId;if(u){let f=d.filter(p=>p.executionId===u),v=f.length,D=f.filter(p=>p.isMcpTool).length,S=v-D,_=f.filter(p=>p.toolType==="subagent").length,b=Date.now(),T=l.getSkillExecution(u),C=T?.startedAt!=null?Math.max(0,b-T.startedAt):0,R=l.getExecutionObligationCompliance(u);try{l.updateSkillExecution(u,{status:"completed",finishedAt:b,totalToolCalls:v,mcpToolCalls:D,directToolCalls:S,subagentCount:_,totalDuration:C,mcpComplianceRate:R.rate}),await le("/api/telemetry/skill-execution/complete",{executionId:u,finishedAt:b,status:"completed",summary:{totalToolCalls:v,mcpToolCalls:D,directToolCalls:S,subagentCount:_,totalDuration:C,mcpComplianceRate:R.rate}});}catch{}}e&&l.deleteHookReceipt(s),l.deleteContextReceipt(s,i);}catch{}finally{l?.close();}try{if(await j({projectRoot:s,sessionId:i,trigger:"session_end",memory:t?.memory}),t?.memory)await t.memory.closeSession(i);else {let d=new(await import('@devflow-tools/memory-engine')).MemoryGate(s);try{await d.closeSession(i);}finally{d.close();}}}catch{}try{await c.endSession(i),await c.flushAndAggregate();}catch{}}finally{L&&c.close(),o.removeSession(i);}}if(process.argv[1]?.endsWith("session-end")||process.argv[1]?.endsWith("session-end.js")){let n="";process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{n+=s;}),process.stdin.on("end",async()=>{await ue(n.trim()||process.argv[2]||""),process.exit(0);}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),1e4).unref();}
|
|
2
|
+
export{ue as handleSessionEnd};
|
|
@@ -1,3 +1,3 @@
|
|
|
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(
|
|
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();}
|
|
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(s){let t=s.trim();return t&&L.test(t)&&!J.test(t)?t:null}function H(s){let t=createHash("sha256").update(s).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(s,t){let e=H(s);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(s){return s??process.env.CLAUDE_PROJECT_DIR??process.cwd()}function f(s){return process.env.DEVFLOW_STATE_DIR??getProjectStateDir(W(s))}function Q(s){return Buffer.from(s,"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(),o={sessionId:e,projectRoot:this.projectRoot,requiredMcpTools:[],startedAt:n,lastActivityAt:n};return this.sessions.set(e,o),this.persist(o),o}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 o=JSON.parse(readFileSync(n,"utf8"));return o.sessionId!==e||o.projectRoot!==this.projectRoot?null:(o.requiredMcpTools=Array.isArray(o.requiredMcpTools)?o.requiredMcpTools:[],this.sessions.set(e,o),o)}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(s){let t=s.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 R(s){let t=s.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 se=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(s=ie()){let t=[join(s,"dist","command-registry.json"),join(s,"command-registry.json")];for(let e of t)try{if(!existsSync(e))continue;let r=JSON.parse(readFileSync(e,"utf8"));if(r.version!==se||!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(s){return !s||typeof s!="object"||Array.isArray(s)?false:Object.values(s).every(t=>{if(!t||typeof t!="object"||Array.isArray(t))return false;let e=t;return I(e.mcpTools)&&I(e.blockedNative)})}function I(s){return Array.isArray(s)&&s.every(t=>typeof t=="string"&&t.length>0)}function E(s){let t=x();return t?t[s]?.mcpTools??[]:[]}var C=1e4,he=3e4,d=500;function ge(s,t,e){return new Promise(r=>{try{let n=new URL(s),o=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);});});o.on("error",()=>r(null)),o.on("timeout",()=>{o.destroy(),r(null);}),o.write(t),o.end();}catch{r(null);}})}var h=class{constructor(t){this.retryScheduled=false;this.metricAggregationScheduled=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,o=process.env.CLAUDE_PROJECT_DIR??process.cwd(),i=[]){let a={executionId:t,sessionId:e,skillName:r,startedAt:n,projectRoot:o,requiredMcpTools:i};this.commitOrCache("execution_start",a)&&this.postHttp("/api/telemetry/skill-execution/start",a);}async sendExecutionComplete(t,e="completed",r=Date.now(),n){let o={executionId:t,status:e,finishedAt:r,metadata:n},i=this.commitOrCache("execution_complete",o);return i&&this.postHttp("/api/telemetry/skill-execution/complete-reconciled",o),i}async sendSessionStart(t,e,r){let n={id:t,projectRoot:e,startedAt:r,label:e.split("/").pop()??"unknown"},o=this.commitOrCache("session_start",n);return o&&this.postHttp("/api/telemetry/sessions",n),o}async completeEvent(t){let e=this.commitOrCache("complete_event",t);return e&&(this.postHttp("/api/telemetry/tool-call/output",t),this.scheduleMetricAggregation()),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 o of n)try{this.applyOperation(o.operation,o.payload),r.resolveTelemetryFailure(o.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 o=join(r,n);try{return {cacheFile:o,envelope:fe(JSON.parse(readFileSync(o,"utf8")),n)}}catch{return null}})).filter(r=>r!==null).sort((r,n)=>r.envelope.timestamp-n.envelope.timestamp||M(r.envelope.operation)-M(n.envelope.operation));for(let{cacheFile:r,envelope:n}of e)try{let o=this.getDatabase();o.insertTelemetryFailure({id:n.id,operation:n.operation,payload:n.payload,error:n.failure,createdAt:n.timestamp}),this.applyOperation(n.operation,n.payload),o.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",requiredMcpTools:e.requiredMcpTools});return;case "execution_complete":r.reconcileSkillExecution(e.executionId,e.status,e.finishedAt,e.metadata);return;case "session_start":r.ensureSession(e),r.insertRun({id:N(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(N(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(),o={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:o.id,operation:t,payload:e,error:o.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(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-d)))unlinkSync(join(this.cacheDir,e));}catch{}}scheduleRetry(){this.retryScheduled||(this.retryScheduled=true,setTimeout(()=>{this.retryScheduled=false,this.flushCache();},he).unref());}scheduleMetricAggregation(){if(this.metricAggregationScheduled||process.env.DEVFLOW_HOOK_DEGRADED==="1")return;this.metricAggregationScheduled=true,setTimeout(()=>{this.metricAggregationScheduled=false;try{this.getDatabase().aggregatePendingToolMetrics(50)===50&&this.scheduleMetricAggregation();}catch{}},25).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<=C?t:{_truncated:true,_original_size:e.length,_preview:`${e.substring(0,C)}...`}}};function M(s){return ["session_start","execution_start","tool_call","complete_event","execution_complete","session_end"].indexOf(s)}function N(s){return `hook-run:${s}`}function fe(s,t){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 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??{},o=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:o}}var m=process.env.CLAUDE_PROJECT_DIR||process.cwd();async function ve(s){let t=b()?{additionalContext:w}:{},e;try{e=JSON.parse(s);}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 o=e.session_id?.trim(),i=R(r);if(o&&i){let c=new p(m),P=c.get(o)?.executionId,l=c.startExecution(o,i.skillName,E(i.skillName));if(l.executionId&&l.executionId!==P){let g=new h;try{await g.sendSessionStart(o,m,l.startedAt),await g.sendExecutionStart(l.executionId,o,i.skillName,l.lastActivityAt,m,l.requiredMcpTools);}finally{g.close();}}}let a=new MemoryGate(m);try{await a.forceWarmUp(),o&&await a.recordUserMessage(r.slice(0,2e3),o,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 s="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{s+=t;}),process.stdin.on("end",async()=>{let t=await ve(s.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
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
|
-
**性能卡顿:** 列表页滚动掉帧、输入响应慢 →
|
|
49
|
+
**性能卡顿:** 列表页滚动掉帧、输入响应慢 → Profiler 分析渲染次数和耗时,检查 memo/useMemo/useCallback 使用情况,检测不必要重渲染,生成优化方案并对比前后基准。
|
|
50
50
|
|
|
51
51
|
**Hooks 审查:** lint 警告、useEffect 循环触发 → 全量扫描所有 Hooks 调用位置,检查顶层调用规则、依赖数组完整性、清理函数缺失、反模式(渲染中创建组件、过大 useEffect),自动修复安全项。
|
|
52
52
|
|
|
@@ -59,10 +59,12 @@ rm -f ~/.devflow/current-skill.json ~/.devflow/current-execution-id
|
|
|
59
59
|
- 使用函数组件 + Hooks,不使用 Class 组件
|
|
60
60
|
- 每个文件只导出一个组件
|
|
61
61
|
- Props 使用 TypeScript interface 定义
|
|
62
|
-
- 仅在父组件重复渲染、props
|
|
62
|
+
- 仅在父组件重复渲染、props 稳定且渲染成本可观时评估 React.memo;它不会阻止组件自身 state 或 Context 更新
|
|
63
63
|
- 自定义 Hooks 命名以 use 开头
|
|
64
|
-
-
|
|
65
|
-
-
|
|
64
|
+
- 用运行时测量决定性能优化;内联 DOM style 默认低影响,requestAnimationFrame 没有采样策略时不是通用节流
|
|
65
|
+
- runtimeProfilingOccurred=false 时所有性能发现只能称为静态候选,不得标记 P0、声称卡顿/掉帧/CPU 热点或给出具体收益比例
|
|
66
|
+
- 性能报告必须分为工具发现、模型假设、需要运行时验证;工具返回 0 条发现不得表述为静态分析通过
|
|
67
|
+
- scannedFiles=0 表示目标定位失败,必须修正 filePath/componentName 后重试,不能基于空结果继续推断
|
|
66
68
|
- 遇到不确定的 API 时先查 React 官方文档再回答
|
|
67
69
|
- 修复 Bug 前先查 git log 找最近变更
|
|
68
70
|
- 生成代码后必须通过 TypeScript 类型检查
|
|
@@ -82,15 +84,3 @@ rm -f ~/.devflow/current-skill.json ~/.devflow/current-execution-id
|
|
|
82
84
|
| Hooks 审查: | 调用 MCP 工具 `react_review_hooks` |
|
|
83
85
|
| 组件拆分: | 调用 MCP 工具 `react_refactor_component` |
|
|
84
86
|
| 新建组件: | 调用 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 结果,才能支持这些结论。
|
package/dist/plugin/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devflow-tools/claude-code-plugin",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.6",
|
|
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.
|
|
12
|
-
"@devflow-tools/memory-engine": "0.16.
|
|
13
|
-
"@devflow-tools/sdk": "0.16.
|
|
14
|
-
"@devflow-tools/telemetry": "0.16.
|
|
15
|
-
"@devflow-tools/workflow-engine": "0.16.
|
|
11
|
+
"@devflow-tools/database": "0.16.6",
|
|
12
|
+
"@devflow-tools/memory-engine": "0.16.6",
|
|
13
|
+
"@devflow-tools/sdk": "0.16.6",
|
|
14
|
+
"@devflow-tools/telemetry": "0.16.6",
|
|
15
|
+
"@devflow-tools/workflow-engine": "0.16.6"
|
|
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.
|
|
22
|
+
"@devflow-tools/mcp-server": "0.16.6"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"pi-package",
|
|
@@ -39,5 +39,6 @@
|
|
|
39
39
|
"dist",
|
|
40
40
|
"hooks",
|
|
41
41
|
"skills"
|
|
42
|
-
]
|
|
42
|
+
],
|
|
43
|
+
"gitHead": "18fe151812fbde380896494d933994ff80ce59f1"
|
|
43
44
|
}
|
|
@@ -13,7 +13,7 @@ description: React 18 专家能力 — Bug 诊断、组件重构、性能优化
|
|
|
13
13
|
|
|
14
14
|
**新功能开发:** 需要加组件或页面 → 先确认现有实现模式,再做影响范围评估,然后生成符合项目约定的组件/Hook/类型代码,最后通过类型检查和测试。
|
|
15
15
|
|
|
16
|
-
**性能卡顿:** 列表页滚动掉帧、输入响应慢 →
|
|
16
|
+
**性能卡顿:** 列表页滚动掉帧、输入响应慢 → Profiler 分析渲染次数和耗时,检查 memo/useMemo/useCallback 使用情况,检测不必要重渲染,生成优化方案并对比前后基准。
|
|
17
17
|
|
|
18
18
|
**Hooks 审查:** lint 警告、useEffect 循环触发 → 全量扫描所有 Hooks 调用位置,检查顶层调用规则、依赖数组完整性、清理函数缺失、反模式(渲染中创建组件、过大 useEffect),自动修复安全项。
|
|
19
19
|
|
|
@@ -26,10 +26,12 @@ description: React 18 专家能力 — Bug 诊断、组件重构、性能优化
|
|
|
26
26
|
- 使用函数组件 + Hooks,不使用 Class 组件
|
|
27
27
|
- 每个文件只导出一个组件
|
|
28
28
|
- Props 使用 TypeScript interface 定义
|
|
29
|
-
- 仅在父组件重复渲染、props
|
|
29
|
+
- 仅在父组件重复渲染、props 稳定且渲染成本可观时评估 React.memo;它不会阻止组件自身 state 或 Context 更新
|
|
30
30
|
- 自定义 Hooks 命名以 use 开头
|
|
31
|
-
-
|
|
32
|
-
-
|
|
31
|
+
- 用运行时测量决定性能优化;内联 DOM style 默认低影响,requestAnimationFrame 没有采样策略时不是通用节流
|
|
32
|
+
- runtimeProfilingOccurred=false 时所有性能发现只能称为静态候选,不得标记 P0、声称卡顿/掉帧/CPU 热点或给出具体收益比例
|
|
33
|
+
- 性能报告必须分为工具发现、模型假设、需要运行时验证;工具返回 0 条发现不得表述为静态分析通过
|
|
34
|
+
- scannedFiles=0 表示目标定位失败,必须修正 filePath/componentName 后重试,不能基于空结果继续推断
|
|
33
35
|
- 遇到不确定的 API 时先查 React 官方文档再回答
|
|
34
36
|
- 修复 Bug 前先查 git log 找最近变更
|
|
35
37
|
- 生成代码后必须通过 TypeScript 类型检查
|
|
@@ -49,15 +51,3 @@ description: React 18 专家能力 — Bug 诊断、组件重构、性能优化
|
|
|
49
51
|
| Hooks 审查: | 调用 MCP 工具 `react_review_hooks` |
|
|
50
52
|
| 组件拆分: | 调用 MCP 工具 `react_refactor_component` |
|
|
51
53
|
| 新建组件: | 调用 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/devflow",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DevFlow - Developer Intelligence Platform (Distribution Package)",
|
|
6
6
|
"bin": {
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@colbymchenry/codegraph": "^1.1.1",
|
|
19
|
-
"@devflow-tools/adapters": "0.16.
|
|
20
|
-
"@devflow-tools/benchmark": "0.16.
|
|
21
|
-
"@devflow-tools/context-engine": "0.16.
|
|
22
|
-
"@devflow-tools/database": "0.16.
|
|
23
|
-
"@devflow-tools/knowledge-engine": "0.16.
|
|
24
|
-
"@devflow-tools/mcp-server": "0.16.
|
|
25
|
-
"@devflow-tools/memory-engine": "0.16.
|
|
26
|
-
"@devflow-tools/sdk": "0.16.
|
|
27
|
-
"@devflow-tools/server": "0.16.
|
|
28
|
-
"@devflow-tools/telemetry": "0.16.
|
|
29
|
-
"@devflow-tools/workflow-engine": "0.16.
|
|
19
|
+
"@devflow-tools/adapters": "0.16.6",
|
|
20
|
+
"@devflow-tools/benchmark": "0.16.6",
|
|
21
|
+
"@devflow-tools/context-engine": "0.16.6",
|
|
22
|
+
"@devflow-tools/database": "0.16.6",
|
|
23
|
+
"@devflow-tools/knowledge-engine": "0.16.6",
|
|
24
|
+
"@devflow-tools/mcp-server": "0.16.6",
|
|
25
|
+
"@devflow-tools/memory-engine": "0.16.6",
|
|
26
|
+
"@devflow-tools/sdk": "0.16.6",
|
|
27
|
+
"@devflow-tools/server": "0.16.6",
|
|
28
|
+
"@devflow-tools/telemetry": "0.16.6",
|
|
29
|
+
"@devflow-tools/workflow-engine": "0.16.6",
|
|
30
30
|
"@inquirer/prompts": "^7.10.1",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
32
32
|
"@nestjs/common": "^10.4.0",
|
|
@@ -54,5 +54,5 @@
|
|
|
54
54
|
"plugin-registry.json",
|
|
55
55
|
"scripts/postinstall.js"
|
|
56
56
|
],
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "b5628c42b4f6795103838c11e65601f6a59cb559"
|
|
58
58
|
}
|
package/plugin-registry.json
CHANGED
|
@@ -1,172 +1,172 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"lastUpdated": "2026-07-
|
|
3
|
+
"lastUpdated": "2026-07-23T03:27:11.960Z",
|
|
4
4
|
"plugins": [
|
|
5
5
|
{
|
|
6
6
|
"name": "@devflow-tools/plugin-animation",
|
|
7
|
-
"version": "0.16.
|
|
7
|
+
"version": "0.16.6",
|
|
8
8
|
"description": "",
|
|
9
9
|
"tags": [],
|
|
10
10
|
"compatibleStacks": [
|
|
11
11
|
"animation"
|
|
12
12
|
],
|
|
13
|
-
"size": "
|
|
13
|
+
"size": "121269 B",
|
|
14
14
|
"author": "DevFlow Team",
|
|
15
15
|
"homepage": ""
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"name": "@devflow-tools/plugin-css",
|
|
19
|
-
"version": "0.16.
|
|
19
|
+
"version": "0.16.6",
|
|
20
20
|
"description": "",
|
|
21
21
|
"tags": [],
|
|
22
22
|
"compatibleStacks": [
|
|
23
23
|
"css"
|
|
24
24
|
],
|
|
25
|
-
"size": "
|
|
25
|
+
"size": "108760 B",
|
|
26
26
|
"author": "DevFlow Team",
|
|
27
27
|
"homepage": ""
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"name": "@devflow-tools/plugin-docker",
|
|
31
|
-
"version": "0.16.
|
|
31
|
+
"version": "0.16.6",
|
|
32
32
|
"description": "",
|
|
33
33
|
"tags": [],
|
|
34
34
|
"compatibleStacks": [
|
|
35
35
|
"docker"
|
|
36
36
|
],
|
|
37
|
-
"size": "
|
|
37
|
+
"size": "137572 B",
|
|
38
38
|
"author": "DevFlow Team",
|
|
39
39
|
"homepage": ""
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"name": "@devflow-tools/plugin-electron",
|
|
43
|
-
"version": "0.16.
|
|
43
|
+
"version": "0.16.6",
|
|
44
44
|
"description": "",
|
|
45
45
|
"tags": [],
|
|
46
46
|
"compatibleStacks": [
|
|
47
47
|
"electron"
|
|
48
48
|
],
|
|
49
|
-
"size": "
|
|
49
|
+
"size": "153251 B",
|
|
50
50
|
"author": "DevFlow Team",
|
|
51
51
|
"homepage": ""
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"name": "@devflow-tools/plugin-git",
|
|
55
|
-
"version": "0.16.
|
|
55
|
+
"version": "0.16.6",
|
|
56
56
|
"description": "",
|
|
57
57
|
"tags": [],
|
|
58
58
|
"compatibleStacks": [
|
|
59
59
|
"git"
|
|
60
60
|
],
|
|
61
|
-
"size": "
|
|
61
|
+
"size": "119108 B",
|
|
62
62
|
"author": "DevFlow Team",
|
|
63
63
|
"homepage": ""
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"name": "@devflow-tools/plugin-graphql",
|
|
67
|
-
"version": "0.16.
|
|
67
|
+
"version": "0.16.6",
|
|
68
68
|
"description": "",
|
|
69
69
|
"tags": [],
|
|
70
70
|
"compatibleStacks": [
|
|
71
71
|
"graphql"
|
|
72
72
|
],
|
|
73
|
-
"size": "
|
|
73
|
+
"size": "121257 B",
|
|
74
74
|
"author": "DevFlow Team",
|
|
75
75
|
"homepage": ""
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"name": "@devflow-tools/plugin-nest",
|
|
79
|
-
"version": "0.16.
|
|
79
|
+
"version": "0.16.6",
|
|
80
80
|
"description": "",
|
|
81
81
|
"tags": [],
|
|
82
82
|
"compatibleStacks": [
|
|
83
83
|
"nest"
|
|
84
84
|
],
|
|
85
|
-
"size": "
|
|
85
|
+
"size": "158350 B",
|
|
86
86
|
"author": "DevFlow Team",
|
|
87
87
|
"homepage": ""
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"name": "@devflow-tools/plugin-nextjs",
|
|
91
|
-
"version": "0.16.
|
|
91
|
+
"version": "0.16.6",
|
|
92
92
|
"description": "",
|
|
93
93
|
"tags": [],
|
|
94
94
|
"compatibleStacks": [
|
|
95
95
|
"nextjs"
|
|
96
96
|
],
|
|
97
|
-
"size": "
|
|
97
|
+
"size": "119905 B",
|
|
98
98
|
"author": "DevFlow Team",
|
|
99
99
|
"homepage": ""
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
"name": "@devflow-tools/plugin-performance",
|
|
103
|
-
"version": "0.16.
|
|
103
|
+
"version": "0.16.6",
|
|
104
104
|
"description": "",
|
|
105
105
|
"tags": [],
|
|
106
106
|
"compatibleStacks": [
|
|
107
107
|
"performance"
|
|
108
108
|
],
|
|
109
|
-
"size": "
|
|
109
|
+
"size": "169751 B",
|
|
110
110
|
"author": "DevFlow Team",
|
|
111
111
|
"homepage": ""
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
114
|
"name": "@devflow-tools/plugin-react",
|
|
115
|
-
"version": "0.16.
|
|
115
|
+
"version": "0.16.6",
|
|
116
116
|
"description": "",
|
|
117
117
|
"tags": [],
|
|
118
118
|
"compatibleStacks": [
|
|
119
119
|
"react"
|
|
120
120
|
],
|
|
121
|
-
"size": "
|
|
121
|
+
"size": "281286 B",
|
|
122
122
|
"author": "DevFlow Team",
|
|
123
123
|
"homepage": ""
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
"name": "@devflow-tools/plugin-tailwind",
|
|
127
|
-
"version": "0.16.
|
|
127
|
+
"version": "0.16.6",
|
|
128
128
|
"description": "",
|
|
129
129
|
"tags": [],
|
|
130
130
|
"compatibleStacks": [
|
|
131
131
|
"tailwind"
|
|
132
132
|
],
|
|
133
|
-
"size": "
|
|
133
|
+
"size": "125918 B",
|
|
134
134
|
"author": "DevFlow Team",
|
|
135
135
|
"homepage": ""
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"name": "@devflow-tools/plugin-taro",
|
|
139
|
-
"version": "0.16.
|
|
139
|
+
"version": "0.16.6",
|
|
140
140
|
"description": "",
|
|
141
141
|
"tags": [],
|
|
142
142
|
"compatibleStacks": [
|
|
143
143
|
"taro"
|
|
144
144
|
],
|
|
145
|
-
"size": "
|
|
145
|
+
"size": "129537 B",
|
|
146
146
|
"author": "DevFlow Team",
|
|
147
147
|
"homepage": ""
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "@devflow-tools/plugin-ui-layout",
|
|
151
|
-
"version": "0.16.
|
|
151
|
+
"version": "0.16.6",
|
|
152
152
|
"description": "",
|
|
153
153
|
"tags": [],
|
|
154
154
|
"compatibleStacks": [
|
|
155
155
|
"ui-layout"
|
|
156
156
|
],
|
|
157
|
-
"size": "
|
|
157
|
+
"size": "106638 B",
|
|
158
158
|
"author": "DevFlow Team",
|
|
159
159
|
"homepage": ""
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
"name": "@devflow-tools/plugin-vue",
|
|
163
|
-
"version": "0.16.
|
|
163
|
+
"version": "0.16.6",
|
|
164
164
|
"description": "",
|
|
165
165
|
"tags": [],
|
|
166
166
|
"compatibleStacks": [
|
|
167
167
|
"vue"
|
|
168
168
|
],
|
|
169
|
-
"size": "
|
|
169
|
+
"size": "138184 B",
|
|
170
170
|
"author": "DevFlow Team",
|
|
171
171
|
"homepage": ""
|
|
172
172
|
}
|