@devflow-tools/devflow 0.12.6 → 0.13.1
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/fallback.html +106 -0
- package/dist/hooks/hook-client.js +6 -6
- package/dist/hooks/hook-daemon.js +7 -7
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/pre-tool-use.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +19 -19
- package/dist/plugin/hooks.json +52 -0
- package/dist/plugin/skills/devflow/SKILL.md +20 -0
- package/dist/plugin/skills/devflow:animation/SKILL.md +69 -0
- package/dist/plugin/skills/devflow:context/SKILL.md +63 -0
- package/dist/plugin/skills/devflow:css/SKILL.md +69 -0
- package/dist/plugin/skills/devflow:docker/SKILL.md +72 -0
- package/dist/plugin/skills/devflow:doctor/SKILL.md +17 -0
- package/dist/plugin/skills/devflow:electron/SKILL.md +72 -0
- package/dist/plugin/skills/devflow:git/SKILL.md +72 -0
- package/dist/plugin/skills/devflow:graph/SKILL.md +55 -0
- package/dist/plugin/skills/devflow:graphql/SKILL.md +72 -0
- package/dist/plugin/skills/devflow:knowledge/SKILL.md +55 -0
- package/dist/plugin/skills/devflow:memory/SKILL.md +54 -0
- package/dist/plugin/skills/devflow:nest/SKILL.md +72 -0
- package/dist/plugin/skills/devflow:nextjs/SKILL.md +70 -0
- package/dist/plugin/skills/devflow:performance/SKILL.md +72 -0
- package/dist/plugin/skills/devflow:react/SKILL.md +54 -0
- package/dist/plugin/skills/devflow:start/SKILL.md +50 -0
- package/dist/plugin/skills/devflow:tailwind/SKILL.md +69 -0
- package/dist/plugin/skills/devflow:taro/SKILL.md +71 -0
- package/dist/plugin/skills/devflow:ui-layout/SKILL.md +69 -0
- package/dist/plugin/skills/devflow:vue/SKILL.md +76 -0
- package/dist/plugin/skills/devflow:workflow/SKILL.md +55 -0
- package/package.json +13 -13
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>DevFlow</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
background: #0a0a0a;
|
|
12
|
+
color: #fafafa;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
min-height: 100vh;
|
|
17
|
+
}
|
|
18
|
+
.container {
|
|
19
|
+
text-align: center;
|
|
20
|
+
padding: 2rem;
|
|
21
|
+
}
|
|
22
|
+
.logo {
|
|
23
|
+
font-size: 3rem;
|
|
24
|
+
font-weight: 700;
|
|
25
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
26
|
+
-webkit-background-clip: text;
|
|
27
|
+
-webkit-text-fill-color: transparent;
|
|
28
|
+
margin-bottom: 1rem;
|
|
29
|
+
}
|
|
30
|
+
.status {
|
|
31
|
+
color: #a1a1aa;
|
|
32
|
+
font-size: 1.1rem;
|
|
33
|
+
margin-bottom: 2rem;
|
|
34
|
+
}
|
|
35
|
+
.spinner {
|
|
36
|
+
width: 40px;
|
|
37
|
+
height: 40px;
|
|
38
|
+
border: 3px solid #27272a;
|
|
39
|
+
border-top-color: #667eea;
|
|
40
|
+
border-radius: 50%;
|
|
41
|
+
animation: spin 0.8s linear infinite;
|
|
42
|
+
margin: 0 auto 1.5rem;
|
|
43
|
+
}
|
|
44
|
+
@keyframes spin {
|
|
45
|
+
to { transform: rotate(360deg); }
|
|
46
|
+
}
|
|
47
|
+
.info {
|
|
48
|
+
color: #52525b;
|
|
49
|
+
font-size: 0.85rem;
|
|
50
|
+
margin-top: 2rem;
|
|
51
|
+
}
|
|
52
|
+
.server-ok {
|
|
53
|
+
color: #22c55e;
|
|
54
|
+
}
|
|
55
|
+
.server-wait {
|
|
56
|
+
color: #eab308;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<div class="container">
|
|
62
|
+
<div class="logo">DevFlow</div>
|
|
63
|
+
<div class="spinner" id="spinner"></div>
|
|
64
|
+
<div class="status" id="status">Preparing dashboard...</div>
|
|
65
|
+
<div class="info" id="info">Checking server status...</div>
|
|
66
|
+
</div>
|
|
67
|
+
<script>
|
|
68
|
+
let attempts = 0;
|
|
69
|
+
const maxAttempts = 60;
|
|
70
|
+
|
|
71
|
+
async function checkStatus() {
|
|
72
|
+
attempts++;
|
|
73
|
+
const statusEl = document.getElementById('status');
|
|
74
|
+
const infoEl = document.getElementById('info');
|
|
75
|
+
const spinnerEl = document.getElementById('spinner');
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch('/api/projects/active', {
|
|
79
|
+
headers: { 'x-api-key': 'dev-key-000000' }
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (res.ok) {
|
|
83
|
+
infoEl.textContent = 'Server is running. Dashboard will appear when download completes.';
|
|
84
|
+
infoEl.className = 'info server-ok';
|
|
85
|
+
} else {
|
|
86
|
+
infoEl.textContent = 'Waiting for server...';
|
|
87
|
+
infoEl.className = 'info server-wait';
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
infoEl.textContent = 'Waiting for server...';
|
|
91
|
+
infoEl.className = 'info server-wait';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (attempts < maxAttempts) {
|
|
95
|
+
setTimeout(checkStatus, 3000);
|
|
96
|
+
} else {
|
|
97
|
+
statusEl.textContent = 'Dashboard download is taking longer than expected';
|
|
98
|
+
spinnerEl.style.display = 'none';
|
|
99
|
+
infoEl.textContent = 'The server is running. You can use the API at /api. The dashboard will appear when ready.';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
checkStatus();
|
|
104
|
+
</script>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {createConnection}from'net';import {join}from'path';import {homedir}from'os';var f=join(homedir(),".devflow","hook-daemon.sock"),
|
|
2
|
-
`);else if(
|
|
3
|
-
`);else if(
|
|
4
|
-
`);}),process.stdin.on("error",()=>t());}else t();}),
|
|
5
|
-
`)){let
|
|
6
|
-
`).filter(Boolean);for(let
|
|
1
|
+
import {createConnection}from'net';import {join}from'path';import {homedir}from'os';var f=join(homedir(),".devflow","hook-daemon.sock"),n=process.argv[2],d=process.argv[3];function t(){process.exit(1);}var o=createConnection(f,()=>{if(n==="stop")o.write(JSON.stringify({type:"stop"})+`
|
|
2
|
+
`);else if(n==="post-tool-use")o.write(JSON.stringify({type:"post-tool-use",input:d||""})+`
|
|
3
|
+
`);else if(n==="pre-tool-use"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>{e+=s;}),process.stdin.on("end",()=>{o.write(JSON.stringify({type:"pre-tool-use",input:e})+`
|
|
4
|
+
`);}),process.stdin.on("error",()=>t());}else t();}),r="";o.on("data",e=>{if(r+=e.toString(),r.includes(`
|
|
5
|
+
`)){let s=r.split(`
|
|
6
|
+
`).filter(Boolean);for(let i of s)process.stdout.write(i+`
|
|
7
7
|
`);o.end();}});o.on("error",()=>t());o.on("close",()=>process.exit(0));setTimeout(()=>t(),4e3).unref();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {createServer}from'net';import {join}from'path';import {homedir}from'os';import {existsSync,unlinkSync,readdirSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {createHash}from'crypto';import {request}from'http';import {spawn}from'child_process';var
|
|
2
|
-
`).filter(s=>s.trim())),
|
|
1
|
+
import {createServer}from'net';import {join,dirname}from'path';import {homedir}from'os';import {existsSync,unlinkSync,readdirSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {createHash}from'crypto';import {request}from'http';import {fileURLToPath}from'url';import {spawn}from'child_process';var $=1e4,lt=3e4,ut=300*1e3;function S(a,e,t){return new Promise(s=>{try{let o=new URL(a),n=request({hostname:o.hostname,port:o.port||80,path:o.pathname+o.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":t,"Content-Length":Buffer.byteLength(e)},timeout:5e3},r=>{let i=[];r.on("data",c=>i.push(c)),r.on("end",()=>{let c=Buffer.concat(i).toString();s(r.statusCode!=null&&r.statusCode>=200&&r.statusCode<300?c:null);});});n.on("error",()=>s(null)),n.on("timeout",()=>{n.destroy(),s(null);}),n.write(e),n.end();}catch{s(null);}})}var w=class{constructor(e){this.retryScheduled=false;this.retryAttempts=0;this.apiUrl=e?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=e?.apiKey??process.env.DEVFLOW_API_KEY??"dev-key-000000",this.cacheDir=e?.cacheDir??join(homedir(),".devflow","telemetry-cache");}async sendEvent(e){let t=this.truncateInput(e.input),s={...e,input:t},o=`${this.apiUrl}/api/telemetry/tool-call`,n=await S(o,JSON.stringify(s),this.apiKey);if(n)try{let r=JSON.parse(n),i=r.data?.eventId??r.eventId??null;if(i)return i}catch{}return this.cacheEvent({type:"tool_call",payload:s}),null}async sendExecutionStart(e,t,s,o){let n={executionId:e,sessionId:t,skillName:s,startedAt:o},r=`${this.apiUrl}/api/telemetry/skill-execution/start`;await S(r,JSON.stringify(n),this.apiKey)||this.cacheEvent({type:"execution_start",payload:n});}async sendSessionStart(e,t,s){let o={id:e,projectRoot:t,startedAt:s,label:t.split("/").pop()??"unknown"},n=`${this.apiUrl}/api/telemetry/sessions`;return await S(n,JSON.stringify(o),this.apiKey)!==null}async flushCache(){if(!existsSync(this.cacheDir))return;let e=readdirSync(this.cacheDir).filter(t=>t.endsWith(".json"));for(let t of e)try{let s=join(this.cacheDir,t),o=readFileSync(s,"utf8"),n=JSON.parse(o),r=n.type==="execution_start"?"/api/telemetry/skill-execution/start":"/api/telemetry/tool-call",i=`${this.apiUrl}${r}`;await S(i,JSON.stringify(n.payload),this.apiKey)&&unlinkSync(s);}catch{}}cacheEvent(e){try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let t=join(this.cacheDir,`${Date.now()}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(t,JSON.stringify(e,null,2)),this.scheduleRetry();}catch{}}scheduleRetry(){if(this.retryScheduled)return;this.retryScheduled=true;let e=Math.min(lt*Math.pow(2,this.retryAttempts),ut);setTimeout(()=>{this.retryScheduled=false,this.flushCache().then(()=>{this.retryAttempts=0;}).catch(()=>{this.retryAttempts++;});},e).unref();}truncateInput(e){let t=JSON.stringify(e);return t.length<=$?e:{_truncated:true,_original_size:t.length,_preview:t.substring(0,$)+"..."}}};var ft=1800*1e3;function J(){return process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow")}function gt(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{let a=fileURLToPath(import.meta.url);return join(dirname(a),"..","..")}catch{return process.cwd()}}function yt(){let a=gt(),e=[join(a,"dist","command-registry.json"),join(a,"command-registry.json")],t=[];try{for(let s of e){if(existsSync(s)){let o=JSON.parse(readFileSync(s,"utf8")).commands;if(o)return o}t.push(`${s}: ${existsSync(s)?"found":"missing"}`);}}catch(s){t.push(`error: ${s.message}`);}try{writeFileSync(join(J(),"hook-debug.log"),JSON.stringify({ts:Date.now(),pluginRoot:a,CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd(),paths:t},null,2));}catch{}return null}function x(a){let e=yt();return e?e[a]?.mcpTools??[]:[]}var _=class{constructor(e){this.stateDir=J();}detect(){let e=join(this.stateDir,"current-skill.json");if(!existsSync(e))return null;try{let t=readFileSync(e,"utf8"),s=JSON.parse(t);return !s.registeredAt||Date.now()-s.registeredAt>ft?(this.endExecution(),null):s}catch{return null}}startExecution(e,t){existsSync(this.stateDir)||mkdirSync(this.stateDir,{recursive:true});let s=this.getCurrentExecutionId();if(s)return s;let o=`exec_${Date.now()}_${Math.random().toString(36).slice(2,11)}`;return writeFileSync(join(this.stateDir,"current-execution-id"),o),writeFileSync(join(this.stateDir,"current-skill.json"),JSON.stringify({name:e,required_mcp_tools:t?.required_mcp_tools??[],registeredAt:Date.now()})),o}getCurrentExecutionId(){let e=join(this.stateDir,"current-execution-id");if(!existsSync(e))return null;try{return readFileSync(e,"utf8").trim()}catch{return null}}endExecution(){let e=join(this.stateDir,"current-skill.json"),t=join(this.stateDir,"current-execution-id");try{existsSync(e)&&unlinkSync(e);}catch{}try{existsSync(t)&&unlinkSync(t);}catch{}}};var O=join(homedir(),".devflow"),j=join(O,"server-refs.json"),Rt="http://127.0.0.1:13337/api/health",Et=600*1e3,It=500;function Pt(){existsSync(O)||mkdirSync(O,{recursive:true});}function T(){try{if(existsSync(j))return JSON.parse(readFileSync(j,"utf-8"))}catch{}return {sessions:[],lastActivity:0}}function B(a){Pt(),writeFileSync(j,JSON.stringify(a));}var R=class{constructor(e){this.process=null;this.idleTimer=null;this.onShutdown=null;this.projectRoot=e;}setOnShutdown(e){this.onShutdown=e;}addRef(e){let t=T();t.sessions.includes(e)||t.sessions.push(e),t.lastActivity=Date.now(),B(t),this.resetIdleTimer();}removeRef(e){let t=T();t.sessions=t.sessions.filter(s=>s!==e),t.lastActivity=Date.now(),B(t),t.sessions.length===0&&this.resetIdleTimer();}get activeSessions(){return T().sessions.length}async ensureRunning(){return await this.healthCheck()?true:(await this.startServer(),this.waitForReady())}get isRunning(){return this.process!==null&&!this.process.killed}async stop(){this.idleTimer&&clearTimeout(this.idleTimer),this.process&&(this.process.kill("SIGTERM"),await new Promise(e=>{let t=setTimeout(()=>{this.process&&!this.process.killed&&this.process.kill("SIGKILL"),e();},5e3);this.process?this.process.on("exit",()=>{clearTimeout(t),e();}):(clearTimeout(t),e());}),this.process=null);}async startServer(){let e=join(this.projectRoot,"apps","server","dist","main.js"),t=join(this.projectRoot,"node_modules","@devflow-tools","server","dist","main.js"),s=existsSync(e)?e:t;this.process=spawn("node",[s],{cwd:this.projectRoot,env:{...process.env,NODE_ENV:process.env.NODE_ENV||"development"},stdio:["ignore","pipe","pipe"]}),this.process.stdout?.on("data",o=>{}),this.process.stderr?.on("data",o=>{}),this.process.on("exit",o=>{this.process=null,this.onShutdown&&this.onShutdown();}),this.process.on("error",()=>{this.process=null;});}async waitForReady(){let e=Date.now()+3e4;for(;Date.now()<e;){if(await this.healthCheck())return true;await new Promise(t=>setTimeout(t,It));}return false}healthCheck(){return new Promise(e=>{let t=new URL(Rt),s=request({hostname:t.hostname,port:t.port,path:t.pathname,method:"GET",timeout:2e3},o=>{e(o.statusCode===200);});s.on("error",()=>e(false)),s.on("timeout",()=>{s.destroy(),e(false);}),s.end();})}resetIdleTimer(){this.idleTimer&&clearTimeout(this.idleTimer),!(T().sessions.length>0)&&(this.idleTimer=setTimeout(()=>{this.stop();},Et).unref());}};var m=join(homedir(),".devflow","telemetry-buffer.jsonl"),W=500;function Dt(){let a=join(homedir(),".devflow");existsSync(a)||mkdirSync(a,{recursive:true});}var I=class{append(e){try{Dt();let t=[];existsSync(m)&&(t=readFileSync(m,"utf-8").split(`
|
|
2
|
+
`).filter(s=>s.trim())),t.push(JSON.stringify(e)),t.length>W&&(t=t.slice(t.length-W)),writeFileSync(m,t.join(`
|
|
3
3
|
`)+`
|
|
4
|
-
`);}catch{}}readAll(){try{return existsSync(
|
|
5
|
-
`).filter(
|
|
6
|
-
`);s=
|
|
7
|
-
`);}catch(i){
|
|
8
|
-
`);}}),
|
|
4
|
+
`);}catch{}}readAll(){try{return existsSync(m)?readFileSync(m,"utf-8").split(`
|
|
5
|
+
`).filter(e=>e.trim()):[]}catch{return []}}clear(){try{existsSync(m)&&writeFileSync(m,"");}catch{}}get size(){return this.readAll().length}};var v=join(homedir(),".devflow","hook-daemon.sock");join(homedir(),".devflow");var jt=600*1e3,Y={Agent:"get_project_context",Bash:"get_project_context",Glob:"get_project_context",Grep:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge"};function At(a){switch(a){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}function Ut(a){switch(a){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"}}function tt(a){let e=createHash("sha256").update(a).digest("hex").slice(0,16);return join(process.env.TMPDIR||"/tmp",".devflow-receipts",`${e}.json`)}function Z(a){try{let e=tt(a);return existsSync(e)?JSON.parse(readFileSync(e,"utf-8")):{}}catch{return {}}}function P(a,e){try{let t=tt(a),s=join(process.env.TMPDIR||"/tmp",".devflow-receipts");existsSync(s)||mkdirSync(s,{recursive:!0}),writeFileSync(t,JSON.stringify(e));}catch{}}var U=class{constructor(e){this.sessionId=null;this.eventMap=[];this.idleTimer=null;this.projectRoot=e,this.projectName=e.split("/").pop()||"unknown",this.telemetry=new w,this.skillManager=new _,this.serverManager=new R(e),this.telemetryBuffer=new I;}async start(){try{existsSync(v)&&unlinkSync(v);}catch{}let e=createServer(t=>{this.resetIdleTimer();let s="";t.on("data",async o=>{s+=o.toString();let n=s.split(`
|
|
6
|
+
`);s=n.pop()||"";for(let r of n)if(r.trim())try{let i=JSON.parse(r),c=await this.handleRequest(i);c!==null&&t.write(JSON.stringify(c)+`
|
|
7
|
+
`);}catch(i){t.write(JSON.stringify({error:i.message})+`
|
|
8
|
+
`);}}),t.on("error",()=>{});});e.on("error",t=>{console.error("[devflow-daemon] Server error:",t.message);}),e.listen(v,()=>{console.error("[devflow-daemon] Listening on",v);}),this.flushBuffer(),this.resetIdleTimer();}resetIdleTimer(){this.idleTimer&&clearTimeout(this.idleTimer),this.idleTimer=setTimeout(()=>{console.error("[devflow-daemon] Idle timeout, exiting"),this.shutdown();},jt).unref();}async handleRequest(e){switch(e.type){case "pre-tool-use":return this.handlePreToolUse(e.input);case "post-tool-use":return this.handlePostToolUse(e.input);case "stop":return await this.handleStop(),{status:"ok"};default:return {error:"unknown request type"}}}async handlePreToolUse(e){try{if(!e||e.trim()==="")return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}};let t=JSON.parse(e);if(this.sessionId||(this.sessionId=`session:${this.projectName}:${Date.now().toString(36)}`,this.telemetry.sendSessionStart(this.sessionId,this.projectRoot,Date.now()).catch(()=>{})),t.tool_name==="Skill"){let l=t.tool_input?.skill;if(l&&l.startsWith("devflow:")){let D=x(l),y=this.skillManager.startExecution(l,{required_mcp_tools:D});this.telemetry.sendExecutionStart(y,this.sessionId,l,Date.now()).catch(()=>{});}}let s=this.skillManager.detect(),o=t.tool_name.startsWith("mcp__"),n=o?t.tool_name.replace(/^mcp__[^_]+__/,""):void 0,r=s?.required_mcp_tools??[],i=r.length>0,c=i&&!o&&t.tool_name!=="Skill",p;if(c)p={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:`\u5EFA\u8BAE\u5148\u8C03\u7528 ${r[0]||"get_project_context"} \u83B7\u53D6\u9879\u76EE\u4E0A\u4E0B\u6587\uFF0C\u7ED3\u679C\u4F1A\u66F4\u7CBE\u786E\u3002`}};else if(!o&&t.tool_name!=="Skill"){let D=Z(this.projectRoot).bypassCount??0,y=Y[t.tool_name],et=y?At(t.tool_name):1/0;y&&D>=et?p={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:`\u5DF2\u591A\u6B21\u4F7F\u7528 ${t.tool_name}\uFF0C\u5EFA\u8BAE\u5207\u6362\u5230 ${y} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`}}:p={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}};}else p={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}};let g=this.skillManager.getCurrentExecutionId()??this.sessionId,f={eventId:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`,executionId:g,sessionId:this.sessionId,timestamp:Date.now(),toolName:t.tool_name,toolType:o?"mcp":t.tool_name==="Agent"?"subagent":"direct",isMcpTool:o,mcpToolName:n,mcpEnforced:o&&i,mcpFallback:!o&&i,kind:Ut(t.tool_name),input:t.tool_input,duration:0,blocked:p&&p.hookSpecificOutput?.permissionDecision==="deny"};return this.telemetry.sendEvent(f).then(l=>{l&&this.eventMap.push({toolName:t.tool_name,eventId:l,timestamp:f.timestamp});}).catch(()=>{}),p}catch{return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}}}}popEventFromMap(e){let t=this.eventMap.filter(o=>o.toolName===e);if(t.length===0)return null;t.sort((o,n)=>Math.abs(o.timestamp-Date.now())-Math.abs(n.timestamp-Date.now()));let s=t[0];return this.eventMap=this.eventMap.filter(o=>o.eventId!==s.eventId),s}enforcePostToolUse(e){if(!Y[e])return null;let t=Z(this.projectRoot),s=t.lastMcpCall??0;if(Math.floor(Date.now()/1e3)-s<30)return null;let n=t.bypassCount??0;if(n===0)return P(this.projectRoot,{...t,bypassCount:1}),null;let r=n+1;P(this.projectRoot,{...t,bypassCount:r});let i;switch(e){case "Agent":i=`\u7B2C ${r} \u6B21\u8FDD\u89C4\uFF1A\u4F60\u521A\u624D\u4F7F\u7528\u4E86 Agent \u5B50\u4EE3\u7406\u6267\u884C\u641C\u7D22\u3002\u4E0B\u6B21\u8BF7\u7528 get_project_context MCP \u5DE5\u5177\u66FF\u4EE3\u3002`;break;case "Bash":i=`\u7B2C ${r} \u6B21\u8FDD\u89C4\uFF1A\u4F60\u521A\u624D\u7528\u4E86 Bash \u547D\u4EE4\u641C\u7D22\u4EE3\u7801\u3002\u4E0B\u6B21\u8BF7\u7528 get_project_context MCP \u5DE5\u5177\u66FF\u4EE3\u3002`;break;case "Glob":i=`\u7B2C ${r} \u6B21\u8FDD\u89C4\uFF1A\u4F60\u521A\u624D\u7528\u4E86 Glob \u5339\u914D\u6587\u4EF6\u3002\u4E0B\u6B21\u8BF7\u7528 get_project_context MCP \u5DE5\u5177\u66FF\u4EE3\u3002`;break;case "Grep":i=`\u7B2C ${r} \u6B21\u8FDD\u89C4\uFF1A\u4F60\u521A\u624D\u7528\u4E86 Grep \u641C\u7D22\u4EE3\u7801\u3002\u4E0B\u6B21\u8BF7\u7528 get_project_context MCP \u5DE5\u5177\u66FF\u4EE3\u3002`;break;case "WebSearch":case "WebFetch":i=`\u7B2C ${r} \u6B21\u8FDD\u89C4\uFF1A\u4F60\u521A\u624D\u7528\u4E86\u7F51\u7EDC\u641C\u7D22\u3002\u4E0B\u6B21\u8BF7\u7528 get_knowledge MCP \u5DE5\u5177\u66FF\u4EE3\u3002`;break;default:i=`\u7B2C ${r} \u6B21\u8FDD\u89C4\uFF1A\u8BF7\u4F7F\u7528 DevFlow MCP \u5DE5\u5177\u66FF\u4EE3\u76F4\u63A5\u8C03\u7528\u3002`;}return r>=3?i=`${i} \u5DF2\u8FBE\u5230\u6700\u5927\u8FDD\u89C4\u6B21\u6570\uFF0C\u540E\u7EED\u7ED5\u8FC7\u5C06\u88AB\u786C\u963B\u6B62\u3002`:r>=2?i=`${i} \u4E0B\u6B21\u8FDD\u89C4\u5C06\u88AB\u963B\u6B62\u3002`:i=`${i} \u8BF7\u7ACB\u5373\u7EA0\u6B63\u3002`,i}async handlePostToolUse(e){if(!e)return null;let t;try{t=JSON.parse(e);}catch{return null}let s=t.tool_name||"",o=t.tool_input||{},n=t.tool_response;if(s==="Skill"){let h=o?.skill;if(h&&h.startsWith("devflow:")){let f=x(h);this.skillManager.startExecution(h,{required_mcp_tools:f}),P(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});}}s.startsWith("mcp__")&&P(this.projectRoot,{lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});let r=this.popEventFromMap(s);if(r&&this.sessionId){let h=typeof n=="string"?n:JSON.stringify(n),f=h.length>5e3?{_truncated:true,_originalSize:h.length,text:h.slice(0,5e3)}:n,l=Date.now()-r.timestamp;this.sendHttp("/api/telemetry/tool-call/output",{eventId:r.eventId,sessionId:this.sessionId,output:f,duration:l,completedAt:Date.now()});}let i=typeof n=="object"&&n!==null?n:null,c=i?.exitCode,p=i?.stderr;s==="Bash"&&c!==void 0&&c!==0&&this.sendHttp(`/api/memory/events?rootPath=${encodeURIComponent(this.projectRoot)}`,{id:`evt:${Date.now()}:${Math.random().toString(36).slice(2,7)}`,sessionId:this.sessionId??`session:${this.projectName}`,tool:s,command:o.command||s,exitCode:c,stderr:p??null,durationMs:0,createdAt:Date.now()});let g=this.enforcePostToolUse(s);return g?(console.error("[devflow-daemon] Enforcement:",g),{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:JSON.stringify(g)}}):null}sendHttp(e,t){let s=JSON.stringify(t),o=new URL(e,"http://127.0.0.1:13337"),n=request({hostname:o.hostname,port:o.port,path:o.pathname+o.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":"dev-key-000000","Content-Length":Buffer.byteLength(s)},timeout:5e3},r=>{r.statusCode===200&&this.flushBuffer();});n.on("error",()=>{this.telemetryBuffer.append({path:e,data:t,timestamp:Date.now()});}),n.write(s),n.end();}flushBuffer(){let e=this.telemetryBuffer.readAll();if(e.length!==0){for(let t of e)try{let s=JSON.parse(t),o=JSON.stringify(s.data),n=new URL(s.path,"http://127.0.0.1:13337"),r=request({hostname:n.hostname,port:n.port,path:n.pathname+n.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":"dev-key-000000","Content-Length":Buffer.byteLength(o)},timeout:5e3},()=>{});r.on("error",()=>{}),r.write(o),r.end();}catch{}this.telemetryBuffer.clear();}}async handleStop(){this.sessionId&&(this.sendHttp(`/api/telemetry/sessions/${this.sessionId}/close`,{finishedAt:Date.now()}),this.serverManager.removeRef(this.sessionId)),this.sendHttp(`/api/memory/process?rootPath=${encodeURIComponent(this.projectRoot)}`,{}),await this.telemetry.flushCache().catch(()=>{}),this.shutdown();}shutdown(){this.idleTimer&&clearTimeout(this.idleTimer);try{unlinkSync(v);}catch{}this.serverManager.stop(),process.exit(0);}},Lt=process.argv[2]||process.env.CLAUDE_PROJECT_DIR||process.cwd(),Ft=new U(Lt);Ft.start().catch(a=>{console.error("[devflow-daemon] Failed to start:",a),process.exit(1);});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {request}from'http';import {existsSync,readFileSync,writeFileSync,mkdirSync}from'fs';import {join}from'path';import {homedir}from'os';import {createHash}from'crypto';var
|
|
2
|
-
export{
|
|
1
|
+
import {request}from'http';import {existsSync,readFileSync,writeFileSync,mkdirSync}from'fs';import {join,dirname}from'path';import {homedir}from'os';import {createHash}from'crypto';import {fileURLToPath}from'url';import {getLogWriter}from'@devflow-tools/telemetry';var W=process.env.DEVFLOW_SERVER_URL||"http://127.0.0.1:13337",$=process.env.DEVFLOW_API_KEY||"dev-key-000000",y=process.env.CLAUDE_PROJECT_DIR||process.cwd(),k=join(homedir(),".devflow","session-id"),S=join(homedir(),".devflow","event-map.json"),F=new Set(["Agent","Bash","Glob","Grep","WebSearch","WebFetch"]);function R(e,n){return new Promise(t=>{let r=JSON.stringify(n),o=new URL(e,W),s=request({hostname:o.hostname,port:o.port,path:o.pathname,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":$,"Content-Length":Buffer.byteLength(r)},timeout:5e3},()=>t());s.on("error",()=>t()),s.write(r),s.end();})}function q(){try{if(existsSync(k))return readFileSync(k,"utf-8").trim()}catch{}return null}function B(e,n){try{if(!existsSync(S))return null;let t=JSON.parse(readFileSync(S,"utf-8")),r=t.filter(i=>i.toolName===e);if(r.length===0)return null;let o=n??Date.now();r.sort((i,g)=>Math.abs(i.timestamp-o)-Math.abs(g.timestamp-o));let s=r[0],u=t.findIndex(i=>i.eventId===s.eventId);return u!==-1&&t.splice(u,1),writeFileSync(S,JSON.stringify(t)),{eventId:s.eventId,timestamp:s.timestamp}}catch{return null}}function E(e){if(!e)return 0;let n=e;if(typeof e=="string")try{n=JSON.parse(e);}catch{return 1}let t=n,r=["files","results","data","result","memories","nodes","chunks","findings","symbols"],o=t.data??t.structuredContent??t;if(Array.isArray(o))return o.length;if(typeof o=="object"&&o!==null){let s=o,u=0;for(let i of r)Array.isArray(s[i])&&(u+=s[i].length);for(let[,i]of Object.entries(s))i&&typeof i=="object"&&!Array.isArray(i)&&(u+=E(i));return u>0?u:Object.keys(s).length>0?1:0}return 0}var m=join(homedir(),".devflow");function K(){existsSync(m)||mkdirSync(m,{recursive:true});}function V(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{let e=fileURLToPath(import.meta.url);return join(dirname(e),"..","..")}catch{return process.cwd()}}function G(){let e=V(),n=[join(e,"dist","command-registry.json"),join(e,"command-registry.json")];for(let t of n)try{if(existsSync(t)){let r=JSON.parse(readFileSync(t,"utf8")).commands;if(r)return r}}catch{}return null}function Y(e){let n=G();return n?n[e]?.mcpTools??[]:[]}function z(e){K();let n=join(m,"current-execution-id");if(existsSync(n))return;let t=`exec_${Date.now()}_${Math.random().toString(36).slice(2,11)}`;writeFileSync(n,t),writeFileSync(join(m,"current-skill.json"),JSON.stringify({name:e,required_mcp_tools:Y(e),registeredAt:Date.now()}));}function N(){let e=createHash("sha256").update(y).digest("hex").slice(0,16);return join(process.env.TMPDIR||"/tmp",".devflow-receipts",`${e}.json`)}function H(){try{let e=N();return existsSync(e)?JSON.parse(readFileSync(e,"utf-8")):{}}catch{return {}}}function I(e){try{let n=N(),t=join(process.env.TMPDIR||"/tmp",".devflow-receipts");existsSync(t)||mkdirSync(t,{recursive:!0}),writeFileSync(n,JSON.stringify(e));}catch{}}function X(e){if(!F.has(e))return null;let n=H(),t=(n.bypassCount??0)+1;return I({...n,bypassCount:t}),t>20&&t%10===0?`MCP \u5DE5\u5177\u4F7F\u7528\u7387\u4F4E\uFF1A${t} \u6B21\u76F4\u63A5\u5DE5\u5177\u8C03\u7528\u3002\u5EFA\u8BAE\u4F7F\u7528 get_project_context \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`:null}async function Q(e){if(!e)return null;let n;try{n=JSON.parse(e);}catch{return null}let t=n.tool_name||"",r=n.tool_input||{},o=n.tool_response||{},s=q();if(t==="Skill"){let c=typeof r=="object"&&r.skill?r.skill:"";c&&c.startsWith("devflow:")&&(z(c),I({lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0}));}t.startsWith("mcp__")&&I({lastMcpCall:Math.floor(Date.now()/1e3),bypassCount:0});let u=getLogWriter(y),i=join(m,"current-execution-id"),g=existsSync(i)?readFileSync(i,"utf-8").trim():`auto-${Date.now()}`,l=B(t,Date.now()),p=typeof o=="object"&&o!==null?o:null,C=JSON.stringify(o),O=Buffer.byteLength(C,"utf8"),w=E(o);if(u.toolCall({tool:t,runId:g,stepId:`step-${g}`,input:r,output:o,duration:l?Date.now()-l.timestamp:0,success:!p?.error,resultCount:w,resultSizeBytes:O,error:p?.error}),l&&s){let c=typeof o=="string"?o:JSON.stringify(o),j=c.length>5e3?{_truncated:true,_originalSize:c.length,text:c.slice(0,5e3)}:o,x=Date.now()-l.timestamp;R("/api/telemetry/tool-call/output",{eventId:l.eventId,sessionId:s,output:j,duration:x,completedAt:Date.now()}).catch(()=>{});}if(t.startsWith("mcp__devflow__")){let c=t.replace("mcp__devflow__","");R("/api/telemetry/tool-metrics",{id:`metric:${Date.now()}:${Math.random().toString(36).slice(2,11)}`,toolCallEventId:l?.eventId??`unknown:${Date.now()}`,sessionId:s??"",toolName:c,query:typeof r=="object"&&r.query?r.query:void 0,status:p?.error?"error":w===0?"empty":"success",resultCount:w,resultSizeBytes:O,latencyMs:l?Date.now()-l.timestamp:0,engine:c.startsWith("get_project_context")||c==="search_symbol"||c==="get_dependency_graph"?"codegraph":c==="get_knowledge"?"knowledge":c==="get_memory"?"memory":void 0}).catch(()=>{});}let P=typeof r=="object"&&r.command?r.command:"",_=p?.exitCode,T=p?.stderr;if(t==="Bash"&&_!==void 0&&_!==0){let c={id:`evt:${Date.now()}:${Math.random().toString(36).slice(2,7)}`,sessionId:s??`session:${y.split("/").pop()||"default"}`,tool:t,command:P||t,exitCode:_,stderr:T??null,durationMs:0,createdAt:Date.now()};R(`/api/memory/events?rootPath=${encodeURIComponent(y)}`,c).catch(()=>{});}let v=X(t);return v&&console.error("[devflow] Enforcement:",v),v}if(process.argv[1]?.endsWith("post-tool-use")||process.argv[1]?.endsWith("post-tool-use.js")){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",n=>{e+=n;}),process.stdin.on("end",async()=>{let n=await Q(e.trim()||process.argv[2]||"");if(n){let t=JSON.stringify(n);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:t}}));}}),process.stdin.on("error",()=>process.exit(0)),setTimeout(()=>process.exit(0),5e3).unref();}
|
|
2
|
+
export{Q as handlePostToolUse};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync}from'fs';import {join}from'path';import {homedir}from'os';import {request}from'http';var
|
|
2
|
-
export{
|
|
1
|
+
import {appendFileSync,existsSync,readdirSync,readFileSync,unlinkSync,mkdirSync,writeFileSync}from'fs';import {join,dirname}from'path';import {homedir}from'os';import {createHash}from'crypto';import {request}from'http';import {fileURLToPath}from'url';var N=1e4,Y=3e4,z=300*1e3;function y(e,t,o){return new Promise(n=>{try{let r=new URL(e),i=request({hostname:r.hostname,port:r.port||80,path:r.pathname+r.search,method:"POST",headers:{"Content-Type":"application/json","X-API-Key":o,"Content-Length":Buffer.byteLength(t)},timeout:5e3},s=>{let a=[];s.on("data",p=>a.push(p)),s.on("end",()=>{let p=Buffer.concat(a).toString();n(s.statusCode!=null&&s.statusCode>=200&&s.statusCode<300?p:null);});});i.on("error",()=>n(null)),i.on("timeout",()=>{i.destroy(),n(null);}),i.write(t),i.end();}catch{n(null);}})}var S=class{constructor(t){this.retryScheduled=false;this.retryAttempts=0;this.apiUrl=t?.apiUrl??process.env.DEVFLOW_API_URL??"http://127.0.0.1:13337",this.apiKey=t?.apiKey??process.env.DEVFLOW_API_KEY??"dev-key-000000",this.cacheDir=t?.cacheDir??join(homedir(),".devflow","telemetry-cache");}async sendEvent(t){let o=this.truncateInput(t.input),n={...t,input:o},r=`${this.apiUrl}/api/telemetry/tool-call`,i=await y(r,JSON.stringify(n),this.apiKey);if(i)try{let s=JSON.parse(i),a=s.data?.eventId??s.eventId??null;if(a)return a}catch{}return this.cacheEvent({type:"tool_call",payload:n}),null}async sendExecutionStart(t,o,n,r){let i={executionId:t,sessionId:o,skillName:n,startedAt:r},s=`${this.apiUrl}/api/telemetry/skill-execution/start`;await y(s,JSON.stringify(i),this.apiKey)||this.cacheEvent({type:"execution_start",payload:i});}async sendSessionStart(t,o,n){let r={id:t,projectRoot:o,startedAt:n,label:o.split("/").pop()??"unknown"},i=`${this.apiUrl}/api/telemetry/sessions`;return await y(i,JSON.stringify(r),this.apiKey)!==null}async flushCache(){if(!existsSync(this.cacheDir))return;let t=readdirSync(this.cacheDir).filter(o=>o.endsWith(".json"));for(let o of t)try{let n=join(this.cacheDir,o),r=readFileSync(n,"utf8"),i=JSON.parse(r),s=i.type==="execution_start"?"/api/telemetry/skill-execution/start":"/api/telemetry/tool-call",a=`${this.apiUrl}${s}`;await y(a,JSON.stringify(i.payload),this.apiKey)&&unlinkSync(n);}catch{}}cacheEvent(t){try{existsSync(this.cacheDir)||mkdirSync(this.cacheDir,{recursive:!0});let o=join(this.cacheDir,`${Date.now()}_${Math.random().toString(36).slice(2,11)}.json`);writeFileSync(o,JSON.stringify(t,null,2)),this.scheduleRetry();}catch{}}scheduleRetry(){if(this.retryScheduled)return;this.retryScheduled=true;let t=Math.min(Y*Math.pow(2,this.retryAttempts),z);setTimeout(()=>{this.retryScheduled=false,this.flushCache().then(()=>{this.retryAttempts=0;}).catch(()=>{this.retryAttempts++;});},t).unref();}truncateInput(t){let o=JSON.stringify(t);return o.length<=N?t:{_truncated:true,_original_size:o.length,_preview:o.substring(0,N)+"..."}}};var nt=1800*1e3;function P(){return process.env.DEVFLOW_STATE_DIR??join(homedir(),".devflow")}function ot(){if(process.env.CLAUDE_PLUGIN_ROOT)return process.env.CLAUDE_PLUGIN_ROOT;try{let e=fileURLToPath(import.meta.url);return join(dirname(e),"..","..")}catch{return process.cwd()}}function rt(){let e=ot(),t=[join(e,"dist","command-registry.json"),join(e,"command-registry.json")],o=[];try{for(let n of t){if(existsSync(n)){let r=JSON.parse(readFileSync(n,"utf8")).commands;if(r)return r}o.push(`${n}: ${existsSync(n)?"found":"missing"}`);}}catch(n){o.push(`error: ${n.message}`);}try{writeFileSync(join(P(),"hook-debug.log"),JSON.stringify({ts:Date.now(),pluginRoot:e,CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd(),paths:o},null,2));}catch{}return null}function A(e){let t=rt();return t?t[e]?.mcpTools??[]:[]}var k=class{constructor(t){this.stateDir=P();}detect(){let t=join(this.stateDir,"current-skill.json");if(!existsSync(t))return null;try{let o=readFileSync(t,"utf8"),n=JSON.parse(o);return !n.registeredAt||Date.now()-n.registeredAt>nt?(this.endExecution(),null):n}catch{return null}}startExecution(t,o){existsSync(this.stateDir)||mkdirSync(this.stateDir,{recursive:true});let n=this.getCurrentExecutionId();if(n)return n;let r=`exec_${Date.now()}_${Math.random().toString(36).slice(2,11)}`;return writeFileSync(join(this.stateDir,"current-execution-id"),r),writeFileSync(join(this.stateDir,"current-skill.json"),JSON.stringify({name:t,required_mcp_tools:o?.required_mcp_tools??[],registeredAt:Date.now()})),r}getCurrentExecutionId(){let t=join(this.stateDir,"current-execution-id");if(!existsSync(t))return null;try{return readFileSync(t,"utf8").trim()}catch{return null}}endExecution(){let t=join(this.stateDir,"current-skill.json"),o=join(this.stateDir,"current-execution-id");try{existsSync(t)&&unlinkSync(t);}catch{}try{existsSync(o)&&unlinkSync(o);}catch{}}};function m(){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}}}function U(e){return {hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:e}}}var g=join(homedir(),".devflow");function M(){existsSync(g)||mkdirSync(g,{recursive:true});}function lt(){try{let e=join(g,"session-id");if(existsSync(e))return readFileSync(e,"utf-8").trim()}catch{}return null}function ut(e){M(),writeFileSync(join(g,"session-id"),e);}function pt(e,t,o){M();let n=join(g,"event-map.json"),r=[];try{existsSync(n)&&(r=JSON.parse(readFileSync(n,"utf-8")));}catch{}r.push({toolName:e,eventId:t,timestamp:o}),writeFileSync(n,JSON.stringify(r));}function dt(e){switch(e){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"}}var j={Grep:"get_project_context",Glob:"get_project_context",Agent:"get_project_context",WebSearch:"get_knowledge",WebFetch:"get_knowledge",Bash:"get_project_context"};function ht(e){switch(e){case "WebSearch":case "WebFetch":return 1;case "Agent":return 3;case "Grep":case "Glob":return 3;case "Bash":return 5;default:return 2}}function mt(){let e=process.env.CLAUDE_PROJECT_DIR||process.cwd(),t=createHash("sha256").update(e).digest("hex").slice(0,16);return join(process.env.TMPDIR||"/tmp",".devflow-receipts",`${t}.json`)}function $(){try{let e=mt();return existsSync(e)?JSON.parse(readFileSync(e,"utf-8")):{}}catch{return {}}}function gt(e){let t=j[e];if(!t)return null;let n=$().bypassCount??0,r=ht(e);return n<r?null:`Direct tool "${e}" blocked (${n} prior violations). Use mcp__devflow__${t} instead.`}async function ft(e){try{if(!e||e.trim()==="")return m();let t=JSON.parse(e),o=Date.now(),n=process.env.CLAUDE_PROJECT_DIR||process.cwd(),r=n.split("/").pop()||"unknown",i=new S,s=lt();s||(s=`session:${r}:${Date.now().toString(36)}`,ut(s),i.sendSessionStart(s,n,Date.now()).catch(()=>{}));let a=new k;if(t.tool_name==="Skill"){let c=t.tool_input?.skill;if(c&&c.startsWith("devflow:")){let _=a.startExecution(c,{required_mcp_tools:A(c)});i.sendExecutionStart(_,s,c,Date.now()).catch(()=>{});}}let p=a.detect(),u=t.tool_name.startsWith("mcp__"),F=u?t.tool_name.replace(/^mcp__[^_]+__/,""):void 0,w=p?.required_mcp_tools??[],D=w.length>0,E=D&&!u&&t.tool_name!=="Skill",d;if(E){let c=w[0]||"get_project_context";d=U(`\u5EFA\u8BAE\u5148\u8C03\u7528 ${c} \u83B7\u53D6\u9879\u76EE\u4E0A\u4E0B\u6587\uFF0C\u7ED3\u679C\u4F1A\u66F4\u7CBE\u786E\u3002`);}else if(!u&&t.tool_name!=="Skill")if(gt(t.tool_name)){let _=j[t.tool_name]||"get_project_context";d=U(`\u5DF2\u591A\u6B21\u4F7F\u7528 ${t.tool_name}\uFF0C\u5EFA\u8BAE\u5207\u6362\u5230 ${_} \u83B7\u53D6\u66F4\u7CBE\u786E\u7684\u4E0A\u4E0B\u6587\u3002`);}else d=m();else d=m();let J=a.getCurrentExecutionId()??s,I={eventId:`evt_${Date.now()}_${Math.random().toString(36).slice(2,11)}`,executionId:J,sessionId:s,timestamp:Date.now(),toolName:t.tool_name,toolType:u?"mcp":t.tool_name==="Agent"?"subagent":"direct",isMcpTool:u,mcpToolName:F,mcpEnforced:u&&D,mcpFallback:!u&&D,kind:dt(t.tool_name),input:t.tool_input,duration:0,blocked:d.hookSpecificOutput.permissionDecision==="deny"};i.sendEvent(I).then(c=>{c&&pt(t.tool_name,c,I.timestamp);}).catch(()=>{});try{let c=$(),_={ts:o,tool:t.tool_name,skill:p?.name??"(none)",requiredTools:w,isMcp:u,enforce:E,stateless:!E&&!u&&t.tool_name!=="Skill",bypassCount:c.bypassCount??0,decision:d.hookSpecificOutput.permissionDecision,env_CLAUDE_PLUGIN_ROOT:process.env.CLAUDE_PLUGIN_ROOT??"(unset)",cwd:process.cwd()},W=join(g,"hook-debug.jsonl");appendFileSync(W,JSON.stringify(_)+`
|
|
2
|
+
`);}catch{}return d}catch{return m()}}if(process.argv[1]?.endsWith("pre-tool-use")||process.argv[1]?.endsWith("pre-tool-use.js")){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t;}),process.stdin.on("end",async()=>{let t=await ft(e);console.log(JSON.stringify(t));}),process.stdin.on("error",()=>{console.log(JSON.stringify(m())),process.exit(0);}),setTimeout(()=>{console.log(JSON.stringify(m())),process.exit(0);},4e3).unref();}export{ft as handlePreToolUse};
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
2
|
+
"name": "devflow",
|
|
3
|
+
"description": "DevFlow \u2014 Developer Intelligence Platform: project context, code graph, knowledge base, memory engine, and workflow automation with 14 domain plugins",
|
|
4
|
+
"version": "0.12.6",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "DevFlow"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/shilongfeicool/dev-flow",
|
|
9
|
+
"repository": "https://github.com/shilongfeicool/dev-flow",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"devflow",
|
|
13
|
+
"context",
|
|
14
|
+
"knowledge-base",
|
|
15
|
+
"code-graph",
|
|
16
|
+
"memory",
|
|
17
|
+
"workflow",
|
|
18
|
+
"development"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start\"",
|
|
10
|
+
"async": false
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"PreToolUse": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "*",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use\"",
|
|
22
|
+
"async": false
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"PostToolUse": [
|
|
28
|
+
{
|
|
29
|
+
"matcher": "*",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use\"",
|
|
34
|
+
"async": false
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"Stop": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "",
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/stop\"",
|
|
46
|
+
"async": false
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devflow
|
|
3
|
+
description: DevFlow — 项目开发智能平台。代码检索、图谱、记忆、工作流一站式工具集。
|
|
4
|
+
required_mcp_tools: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DevFlow
|
|
8
|
+
|
|
9
|
+
你已接入 DevFlow 开发智能平台。收到 /devflow: 前缀命令时,只做一件事:调用对应的 MCP 工具。
|
|
10
|
+
|
|
11
|
+
| 命令 | 只调用这个工具 |
|
|
12
|
+
|------|-------------|
|
|
13
|
+
| /devflow:context | get_project_context |
|
|
14
|
+
| /devflow:graph | get_dependency_graph |
|
|
15
|
+
| /devflow:memory | get_memory |
|
|
16
|
+
| /devflow:knowledge | get_knowledge |
|
|
17
|
+
| /devflow:workflow | run_workflow |
|
|
18
|
+
| /devflow:doctor | get_project_context |
|
|
19
|
+
|
|
20
|
+
领域插件(/devflow:react, /devflow:vue, /devflow:nextjs, /devflow:nest, /devflow:tailwind, /devflow:docker, /devflow:electron, /devflow:graphql, /devflow:git, /devflow:performance, /devflow:animation, /devflow:css, /devflow:taro, /devflow:ui-layout)— 每个都有对应的 MCP 工具。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devflow:animation
|
|
3
|
+
description: Animation 动画专家 — CSS 动画、Framer Motion、GSAP、Lottie、性能优化
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 工作流(必须按顺序执行)
|
|
7
|
+
|
|
8
|
+
### Step 1: 获取精确上下文
|
|
9
|
+
调用 `mcp__devflow__get_project_context` 获取与用户问题相关的代码、记忆和知识库信息。
|
|
10
|
+
这一步确保你拿到的是项目真实状态,而非凭空猜测。
|
|
11
|
+
|
|
12
|
+
### Step 2: 分析
|
|
13
|
+
基于 Step 1 返回的上下文,分析问题根因或设计方案。
|
|
14
|
+
此时可以自由使用 Read 查看具体文件。
|
|
15
|
+
|
|
16
|
+
### Step 3: 执行
|
|
17
|
+
根据分析结果修改代码,逐文件编辑。
|
|
18
|
+
|
|
19
|
+
### Step 4: 验证
|
|
20
|
+
运行类型检查和测试,确保改动正确。
|
|
21
|
+
|
|
22
|
+
## 可用工具
|
|
23
|
+
|
|
24
|
+
| 工具 | 何时调用 |
|
|
25
|
+
|------|----------|
|
|
26
|
+
| `mcp__devflow__get_project_context` | 每次任务第一步(必须) |
|
|
27
|
+
| `mcp__devflow__animation_suggest` | 按需调用 |
|
|
28
|
+
|
|
29
|
+
## 规则
|
|
30
|
+
- 永远从 `mcp__devflow__get_project_context` 开始——你不知道项目里有什么
|
|
31
|
+
- 上下文返回后,用 Read 确认关键文件
|
|
32
|
+
- 改完代码后跑类型检查
|
|
33
|
+
|
|
34
|
+
## 规则
|
|
35
|
+
- 永远从 `get_project_context` 开始——你不知道项目里有什么
|
|
36
|
+
- 上下文返回后,用 Read 确认关键文件
|
|
37
|
+
- 改完代码后跑类型检查
|
|
38
|
+
|
|
39
|
+
## 规则
|
|
40
|
+
- 永远从 `animation_suggest` 开始——你不知道项目里有什么
|
|
41
|
+
- 上下文返回后,用 Read 确认关键文件
|
|
42
|
+
- 改完代码后跑类型检查
|
|
43
|
+
|
|
44
|
+
# ✨ ANIMATION
|
|
45
|
+
|
|
46
|
+
你是一个动画专家。精通 CSS Animation、Framer Motion、GSAP。动画必须 60fps 且支持 prefers-reduced-motion。
|
|
47
|
+
|
|
48
|
+
## 适用场景
|
|
49
|
+
|
|
50
|
+
**动画审计:性能检查 → 可访问性 → 实现质量**
|
|
51
|
+
|
|
52
|
+
**新建动画:需求分析 → 选型建议 → 实现 → 性能验证**
|
|
53
|
+
|
|
54
|
+
**动画 GPU 迁移:扫描 → 替换为 GPU 属性 → 验证**
|
|
55
|
+
|
|
56
|
+
## 核心规则
|
|
57
|
+
|
|
58
|
+
- 动画属性只使用 transform 和 opacity(GPU 加速)
|
|
59
|
+
- 过渡动画 200-300ms,入场 300-500ms
|
|
60
|
+
- 使用 ease-out 入场、ease-in 退场
|
|
61
|
+
- 尊重 prefers-reduced-motion,提供减弱方案
|
|
62
|
+
- 避免同时动画过多元素(批量编排使用 staggerChildren)
|
|
63
|
+
- 使用 AnimatePresence 处理 React 组件退出动画
|
|
64
|
+
- Lottie 动画控制文件大小 < 50KB
|
|
65
|
+
|
|
66
|
+
## 当前版本
|
|
67
|
+
|
|
68
|
+
- animation-docs: 2024 — https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations
|
|
69
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devflow:context
|
|
3
|
+
description: 检索项目上下文 — 自然语言查询相关代码、符号、依赖关系
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 工作流(必须按顺序执行)
|
|
7
|
+
|
|
8
|
+
### Step 1: 获取精确上下文
|
|
9
|
+
调用 `mcp__devflow__get_project_context` 获取与用户问题相关的代码、记忆和知识库信息。
|
|
10
|
+
这一步确保你拿到的是项目真实状态,而非凭空猜测。
|
|
11
|
+
|
|
12
|
+
### Step 2: 分析
|
|
13
|
+
基于 Step 1 返回的上下文,分析问题根因或设计方案。
|
|
14
|
+
此时可以自由使用 Read 查看具体文件。
|
|
15
|
+
|
|
16
|
+
### Step 3: 执行
|
|
17
|
+
根据分析结果修改代码,逐文件编辑。
|
|
18
|
+
|
|
19
|
+
### Step 4: 验证
|
|
20
|
+
运行类型检查和测试,确保改动正确。
|
|
21
|
+
|
|
22
|
+
## 可用工具
|
|
23
|
+
|
|
24
|
+
| 工具 | 何时调用 |
|
|
25
|
+
|------|----------|
|
|
26
|
+
| `mcp__devflow__get_project_context` | 每次任务第一步(必须) |
|
|
27
|
+
| `mcp__devflow__get_project_context` | 按需调用 |
|
|
28
|
+
| `mcp__devflow__search_symbol` | 按需调用 |
|
|
29
|
+
|
|
30
|
+
## 规则
|
|
31
|
+
- 永远从 `mcp__devflow__get_project_context` 开始——你不知道项目里有什么
|
|
32
|
+
- 上下文返回后,用 Read 确认关键文件
|
|
33
|
+
- 改完代码后跑类型检查
|
|
34
|
+
|
|
35
|
+
## 规则
|
|
36
|
+
- 永远从 `get_project_context` 开始——你不知道项目里有什么
|
|
37
|
+
- 上下文返回后,用 Read 确认关键文件
|
|
38
|
+
- 改完代码后跑类型检查
|
|
39
|
+
|
|
40
|
+
## 规则
|
|
41
|
+
- 永远从 `get_project_context` 开始——你不知道项目里有什么
|
|
42
|
+
- 上下文返回后,用 Read 确认关键文件
|
|
43
|
+
- 改完代码后跑类型检查
|
|
44
|
+
|
|
45
|
+
# /devflow:context
|
|
46
|
+
|
|
47
|
+
## 执行
|
|
48
|
+
|
|
49
|
+
1. 如果用户的查询包含中文,先将其中的中文关键词翻译为英文代码术语(空格分隔)
|
|
50
|
+
2. 调用 `mcp__devflow__get_project_context` MCP 工具:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
query = 用户的原始查询内容(包含中文)
|
|
54
|
+
expandedTerms = 你翻译的英文关键词(如 "code review audit module")
|
|
55
|
+
mode = "fast" (首次调用;如需更深入结果,再改用 "deep")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
也可以使用 `mcp__devflow__search_symbol` 按符号名搜索代码。
|
|
59
|
+
|
|
60
|
+
示例:
|
|
61
|
+
- 用户查询 "代码审核模块的流程" → expandedTerms = "code review audit module workflow pipeline process"
|
|
62
|
+
- 用户查询 "用户登录认证逻辑" → expandedTerms = "user login authentication auth logic service handler"
|
|
63
|
+
- 用户查询 "数据库缓存策略" → expandedTerms = "database db cache memo redis storage strategy"
|