@agiflowai/scaffold-mcp 1.0.6 → 1.0.7
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/README.md +40 -0
- package/dist/ListScaffoldingMethodsTool-BLTCwsd1.mjs +350 -0
- package/dist/ListScaffoldingMethodsTool-BuxKRbwi.cjs +376 -0
- package/dist/{ScaffoldConfigLoader-B-NLy6VP.cjs → ScaffoldConfigLoader-BB4_YUFL.cjs} +1 -1
- package/dist/{ScaffoldConfigLoader-SHk-KEje.mjs → ScaffoldConfigLoader-DKJtnrWT.mjs} +1 -1
- package/dist/ScaffoldService-BCjJE9yK.mjs +3 -0
- package/dist/ScaffoldService-Bhzxp5-C.cjs +3 -0
- package/dist/TemplateService-B1bd6iHw.mjs +3 -0
- package/dist/TemplateService-BrJGDvQt.cjs +3 -0
- package/dist/VariableReplacementService-BO-UYgcf.mjs +3 -0
- package/dist/{VariableReplacementService-DKaF2C9l.cjs → VariableReplacementService-CNimgwaq.cjs} +1 -1
- package/dist/cli.cjs +89 -11
- package/dist/cli.mjs +84 -6
- package/dist/index.cjs +9 -8
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +6 -5
- package/dist/{stdio-BGj_FLky.cjs → stdio-BcTSxlVH.cjs} +47 -367
- package/dist/{stdio-wAlpLC6l.mjs → stdio-DovjJsGY.mjs} +42 -347
- package/dist/useScaffoldMethod-Btc_9iCj.cjs +237 -0
- package/dist/useScaffoldMethod-C1hQdBVD.cjs +267 -0
- package/dist/useScaffoldMethod-CHJAsgA2.mjs +236 -0
- package/dist/useScaffoldMethod-CsBTssSw.mjs +263 -0
- package/package.json +6 -2
- package/dist/ScaffoldService-BNOyoqSb.cjs +0 -3
- package/dist/ScaffoldService-BNdfC21Z.mjs +0 -3
- package/dist/TemplateService-BRfzfaZs.mjs +0 -3
- package/dist/TemplateService-DqieT1Tq.cjs +0 -3
- package/dist/VariableReplacementService-BWCd-z7X.mjs +0 -3
- /package/dist/{ScaffoldConfigLoader-BDMJNI1o.mjs → ScaffoldConfigLoader-8YI7v2GJ.mjs} +0 -0
- /package/dist/{ScaffoldConfigLoader-Y_SBLPg7.cjs → ScaffoldConfigLoader-CQlXVksz.cjs} +0 -0
- /package/dist/{ScaffoldService-ChzxM0Yc.cjs → ScaffoldService-BPyiY_0B.cjs} +0 -0
- /package/dist/{ScaffoldService-BNuN00Fm.mjs → ScaffoldService-CgYunbKN.mjs} +0 -0
- /package/dist/{TemplateService-D3ydJR_R.cjs → TemplateService-CAD8jkoO.cjs} +0 -0
- /package/dist/{TemplateService-Cg5QV29n.mjs → TemplateService-CVDL2uqt.mjs} +0 -0
- /package/dist/{VariableReplacementService-DHIINRnJ.mjs → VariableReplacementService-B9RA8D0a.mjs} +0 -0
- /package/dist/{VariableReplacementService-CAjesAYq.cjs → VariableReplacementService-DDG5KZpb.cjs} +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-BLTCwsd1.mjs";
|
|
2
|
+
import "./TemplateService-CVDL2uqt.mjs";
|
|
3
|
+
import { TemplatesManagerService } from "@agiflowai/aicode-utils";
|
|
4
|
+
import { DECISION_ALLOW, DECISION_DENY, DECISION_SKIP, ExecutionLogService } from "@agiflowai/hooks-adapter";
|
|
5
|
+
|
|
6
|
+
//#region src/hooks/geminiCli/useScaffoldMethod.ts
|
|
7
|
+
/**
|
|
8
|
+
* UseScaffoldMethod Hook class for Gemini CLI
|
|
9
|
+
*
|
|
10
|
+
* Provides lifecycle hooks for tool execution:
|
|
11
|
+
* - preToolUse: Shows available scaffolding methods before operations
|
|
12
|
+
* - postToolUse: Tracks scaffold completion progress after file edits
|
|
13
|
+
*/
|
|
14
|
+
var UseScaffoldMethodHook = class {
|
|
15
|
+
/**
|
|
16
|
+
* PreToolUse hook for Gemini CLI
|
|
17
|
+
* Proactively shows available scaffolding methods and guides AI to use them
|
|
18
|
+
*
|
|
19
|
+
* @param context - Gemini CLI hook input
|
|
20
|
+
* @returns Hook response with scaffolding methods guidance
|
|
21
|
+
*/
|
|
22
|
+
async preToolUse(context) {
|
|
23
|
+
try {
|
|
24
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
25
|
+
const sessionKey = `list-scaffold-methods-${context.session_id}`;
|
|
26
|
+
if (await executionLog.hasExecuted({
|
|
27
|
+
filePath: sessionKey,
|
|
28
|
+
decision: DECISION_DENY
|
|
29
|
+
})) {
|
|
30
|
+
await executionLog.logExecution({
|
|
31
|
+
filePath: sessionKey,
|
|
32
|
+
operation: "list-scaffold-methods",
|
|
33
|
+
decision: DECISION_SKIP
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
decision: DECISION_SKIP,
|
|
37
|
+
message: "Scaffolding methods already provided in this session"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const result = await new ListScaffoldingMethodsTool(await TemplatesManagerService.findTemplatesPath(), false).execute(context.tool_input || {});
|
|
41
|
+
if (result.isError) {
|
|
42
|
+
await executionLog.logExecution({
|
|
43
|
+
filePath: sessionKey,
|
|
44
|
+
operation: "list-scaffold-methods",
|
|
45
|
+
decision: DECISION_SKIP
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
decision: DECISION_SKIP,
|
|
49
|
+
message: `⚠️ Could not load scaffolding methods: ${result.content[0].text}`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const resultText = result.content[0]?.text;
|
|
53
|
+
if (typeof resultText !== "string") return {
|
|
54
|
+
decision: DECISION_SKIP,
|
|
55
|
+
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
56
|
+
};
|
|
57
|
+
const data = JSON.parse(resultText);
|
|
58
|
+
if (!data.methods || data.methods.length === 0) {
|
|
59
|
+
await executionLog.logExecution({
|
|
60
|
+
filePath: sessionKey,
|
|
61
|
+
operation: "list-scaffold-methods",
|
|
62
|
+
decision: DECISION_DENY
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
decision: DECISION_DENY,
|
|
66
|
+
message: "No scaffolding methods are available for this project template. You should write new files directly using the Write tool."
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
let message = "🎯 **Scaffolding Methods Available**\n\n";
|
|
70
|
+
message += "Before writing new files, check if any of these scaffolding methods match your needs:\n\n";
|
|
71
|
+
for (const method of data.methods) {
|
|
72
|
+
message += `**${method.name}**\n`;
|
|
73
|
+
message += `${method.instruction || method.description || "No description available"}\n`;
|
|
74
|
+
if (method.variables_schema?.required && method.variables_schema.required.length > 0) message += `Required: ${method.variables_schema.required.join(", ")}\n`;
|
|
75
|
+
message += "\n";
|
|
76
|
+
}
|
|
77
|
+
if (data.nextCursor) message += `\n_Note: More methods available. Use cursor "${data.nextCursor}" to see more._\n\n`;
|
|
78
|
+
message += "\n**Instructions:**\n";
|
|
79
|
+
message += "1. If one of these scaffold methods matches what you need to create, use the `use-scaffold-method` MCP tool instead of writing files manually\n";
|
|
80
|
+
message += "2. If none of these methods are relevant to your task, proceed to write new files directly using the Write tool\n";
|
|
81
|
+
message += "3. Using scaffold methods ensures consistency with project patterns and includes all necessary boilerplate\n";
|
|
82
|
+
await executionLog.logExecution({
|
|
83
|
+
filePath: sessionKey,
|
|
84
|
+
operation: "list-scaffold-methods",
|
|
85
|
+
decision: DECISION_DENY
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
decision: DECISION_DENY,
|
|
89
|
+
message
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
decision: DECISION_SKIP,
|
|
94
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* PostToolUse hook for Gemini CLI
|
|
100
|
+
* Tracks file edits after scaffold generation and reminds AI to complete implementation
|
|
101
|
+
*
|
|
102
|
+
* @param context - Gemini CLI hook input
|
|
103
|
+
* @returns Hook response with scaffold completion tracking
|
|
104
|
+
*/
|
|
105
|
+
async postToolUse(context) {
|
|
106
|
+
try {
|
|
107
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
108
|
+
const filePath = context.tool_input?.file_path;
|
|
109
|
+
const actualToolName = context.tool_name === "mcp__one-mcp__use_tool" ? context.tool_input?.toolName : context.tool_name;
|
|
110
|
+
if (actualToolName === "use-scaffold-method") return {
|
|
111
|
+
decision: DECISION_ALLOW,
|
|
112
|
+
message: "Scaffold execution logged for progress tracking"
|
|
113
|
+
};
|
|
114
|
+
const operation = extractOperation(actualToolName);
|
|
115
|
+
if (!filePath || operation !== "edit" && operation !== "write") return {
|
|
116
|
+
decision: DECISION_SKIP,
|
|
117
|
+
message: "Not a file edit/write operation"
|
|
118
|
+
};
|
|
119
|
+
const lastScaffoldExecution = await getLastScaffoldExecution(executionLog);
|
|
120
|
+
if (!lastScaffoldExecution) return {
|
|
121
|
+
decision: DECISION_SKIP,
|
|
122
|
+
message: "No scaffold execution found"
|
|
123
|
+
};
|
|
124
|
+
const { scaffoldId, generatedFiles, featureName } = lastScaffoldExecution;
|
|
125
|
+
const fulfilledKey = `scaffold-fulfilled-${scaffoldId}`;
|
|
126
|
+
if (await executionLog.hasExecuted({
|
|
127
|
+
filePath: fulfilledKey,
|
|
128
|
+
decision: DECISION_ALLOW
|
|
129
|
+
})) return {
|
|
130
|
+
decision: DECISION_SKIP,
|
|
131
|
+
message: "Scaffold already fulfilled"
|
|
132
|
+
};
|
|
133
|
+
const isScaffoldedFile = generatedFiles.includes(filePath);
|
|
134
|
+
if (isScaffoldedFile) {
|
|
135
|
+
const editKey = `scaffold-edit-${scaffoldId}-${filePath}`;
|
|
136
|
+
if (!await executionLog.hasExecuted({
|
|
137
|
+
filePath: editKey,
|
|
138
|
+
decision: DECISION_ALLOW
|
|
139
|
+
})) await executionLog.logExecution({
|
|
140
|
+
filePath: editKey,
|
|
141
|
+
operation: "scaffold-file-edit",
|
|
142
|
+
decision: DECISION_ALLOW
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const editedFiles = await getEditedScaffoldFiles(executionLog, scaffoldId);
|
|
146
|
+
const totalFiles = generatedFiles.length;
|
|
147
|
+
const remainingFiles = generatedFiles.filter((f) => !editedFiles.includes(f));
|
|
148
|
+
if (remainingFiles.length === 0) {
|
|
149
|
+
await executionLog.logExecution({
|
|
150
|
+
filePath: fulfilledKey,
|
|
151
|
+
operation: "scaffold-fulfilled",
|
|
152
|
+
decision: DECISION_ALLOW
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
decision: DECISION_ALLOW,
|
|
156
|
+
message: `✅ All scaffold files${featureName ? ` for "${featureName}"` : ""} have been implemented! (${totalFiles}/${totalFiles} files completed)`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (isScaffoldedFile) {
|
|
160
|
+
const remainingFilesList = remainingFiles.map((f) => ` - ${f}`).join("\n");
|
|
161
|
+
return {
|
|
162
|
+
decision: DECISION_ALLOW,
|
|
163
|
+
message: `
|
|
164
|
+
⚠️ **Scaffold Implementation Progress${featureName ? ` for "${featureName}"` : ""}: ${editedFiles.length}/${totalFiles} files completed**
|
|
165
|
+
|
|
166
|
+
**Remaining files to implement:**
|
|
167
|
+
${remainingFilesList}
|
|
168
|
+
|
|
169
|
+
Don't forget to complete the implementation for all scaffolded files!
|
|
170
|
+
`.trim()
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
decision: DECISION_SKIP,
|
|
175
|
+
message: "Edited file not part of last scaffold execution"
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
decision: DECISION_SKIP,
|
|
180
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Extract operation type from tool name
|
|
187
|
+
*/
|
|
188
|
+
function extractOperation(toolName) {
|
|
189
|
+
const lowerToolName = toolName.toLowerCase();
|
|
190
|
+
if (lowerToolName === "edit" || lowerToolName === "update") return "edit";
|
|
191
|
+
if (lowerToolName === "write") return "write";
|
|
192
|
+
if (lowerToolName === "read") return "read";
|
|
193
|
+
return "unknown";
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Helper function to get the last scaffold execution for a session
|
|
197
|
+
* Returns null if no scaffold execution found or on error
|
|
198
|
+
*/
|
|
199
|
+
async function getLastScaffoldExecution(executionLog) {
|
|
200
|
+
try {
|
|
201
|
+
const entries = await executionLog.loadLog();
|
|
202
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
203
|
+
const entry = entries[i];
|
|
204
|
+
if (entry.operation === "scaffold" && entry.scaffoldId && entry.generatedFiles && entry.generatedFiles.length > 0) return {
|
|
205
|
+
scaffoldId: entry.scaffoldId,
|
|
206
|
+
generatedFiles: entry.generatedFiles,
|
|
207
|
+
featureName: entry.featureName
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("Error getting last scaffold execution:", error);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Helper function to get list of edited scaffold files
|
|
218
|
+
* Returns empty array if no files found or on error
|
|
219
|
+
*/
|
|
220
|
+
async function getEditedScaffoldFiles(executionLog, scaffoldId) {
|
|
221
|
+
try {
|
|
222
|
+
const entries = await executionLog.loadLog();
|
|
223
|
+
const editedFiles = [];
|
|
224
|
+
for (const entry of entries) if (entry.operation === "scaffold-file-edit" && entry.filePath.startsWith(`scaffold-edit-${scaffoldId}-`)) {
|
|
225
|
+
const filePath = entry.filePath.replace(`scaffold-edit-${scaffoldId}-`, "");
|
|
226
|
+
editedFiles.push(filePath);
|
|
227
|
+
}
|
|
228
|
+
return editedFiles;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error(`Error getting edited scaffold files for ${scaffoldId}:`, error);
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
236
|
+
export { UseScaffoldMethodHook };
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-BLTCwsd1.mjs";
|
|
2
|
+
import "./TemplateService-CVDL2uqt.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ProjectFinderService, TemplatesManagerService } from "@agiflowai/aicode-utils";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import { DECISION_ALLOW, DECISION_DENY, DECISION_SKIP, ExecutionLogService } from "@agiflowai/hooks-adapter";
|
|
8
|
+
|
|
9
|
+
//#region src/hooks/claudeCode/useScaffoldMethod.ts
|
|
10
|
+
/**
|
|
11
|
+
* UseScaffoldMethod Hook class for Claude Code
|
|
12
|
+
*
|
|
13
|
+
* Provides lifecycle hooks for tool execution:
|
|
14
|
+
* - preToolUse: Shows available scaffolding methods before Write operations
|
|
15
|
+
* - postToolUse: Tracks scaffold completion progress after file edits
|
|
16
|
+
*/
|
|
17
|
+
var UseScaffoldMethodHook = class {
|
|
18
|
+
/**
|
|
19
|
+
* PreToolUse hook for Claude Code
|
|
20
|
+
* Proactively shows available scaffolding methods and guides AI to use them
|
|
21
|
+
*
|
|
22
|
+
* @param context - Claude Code hook input
|
|
23
|
+
* @returns Hook response with scaffolding methods guidance
|
|
24
|
+
*/
|
|
25
|
+
async preToolUse(context) {
|
|
26
|
+
try {
|
|
27
|
+
const filePath = context.tool_input?.file_path;
|
|
28
|
+
if (!filePath || context.tool_name !== "Write") return {
|
|
29
|
+
decision: DECISION_SKIP,
|
|
30
|
+
message: "Not a file write operation"
|
|
31
|
+
};
|
|
32
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
33
|
+
if (await executionLog.hasExecuted({
|
|
34
|
+
filePath,
|
|
35
|
+
decision: DECISION_DENY
|
|
36
|
+
})) return {
|
|
37
|
+
decision: DECISION_SKIP,
|
|
38
|
+
message: "Scaffolding methods already provided for this file"
|
|
39
|
+
};
|
|
40
|
+
const tool = new ListScaffoldingMethodsTool(await TemplatesManagerService.findTemplatesPath(), false);
|
|
41
|
+
const projectFinder = new ProjectFinderService(await TemplatesManagerService.getWorkspaceRoot(context.cwd));
|
|
42
|
+
const absoluteFilePath = path.isAbsolute(filePath) ? filePath : path.join(context.cwd, filePath);
|
|
43
|
+
const projectPath = (await projectFinder.findProjectForFile(absoluteFilePath))?.root || context.cwd;
|
|
44
|
+
const result = await tool.execute({ projectPath });
|
|
45
|
+
if (result.isError) return {
|
|
46
|
+
decision: DECISION_SKIP,
|
|
47
|
+
message: `⚠️ Could not load scaffolding methods: ${result.content[0].text}`
|
|
48
|
+
};
|
|
49
|
+
const resultText = result.content[0]?.text;
|
|
50
|
+
if (typeof resultText !== "string") return {
|
|
51
|
+
decision: DECISION_SKIP,
|
|
52
|
+
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
53
|
+
};
|
|
54
|
+
const data = JSON.parse(resultText);
|
|
55
|
+
if (!data.methods || data.methods.length === 0) {
|
|
56
|
+
await executionLog.logExecution({
|
|
57
|
+
filePath,
|
|
58
|
+
operation: "list-scaffold-methods",
|
|
59
|
+
decision: DECISION_DENY
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
decision: DECISION_DENY,
|
|
63
|
+
message: "No scaffolding methods are available for this project template. You should write new files directly using the Write tool."
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
let message = "🎯 **Scaffolding Methods Available**\\n\\n";
|
|
67
|
+
message += "Before writing new files, check if any of these scaffolding methods match your needs:\\n\\n";
|
|
68
|
+
for (const method of data.methods) {
|
|
69
|
+
message += `**${method.name}**\\n`;
|
|
70
|
+
message += `${method.instruction || method.description || "No description available"}\\n`;
|
|
71
|
+
if (method.variables_schema?.required && method.variables_schema.required.length > 0) message += `Required: ${method.variables_schema.required.join(", ")}\\n`;
|
|
72
|
+
message += "\\n";
|
|
73
|
+
}
|
|
74
|
+
if (data.nextCursor) message += `\\n_Note: More methods available. Use cursor "${data.nextCursor}" to see more._\\n\\n`;
|
|
75
|
+
message += "\\n**Instructions:**\\n";
|
|
76
|
+
message += "1. If one of these scaffold methods matches what you need to create, use the `use-scaffold-method` MCP tool instead of writing files manually\\n";
|
|
77
|
+
message += "2. If none of these methods are relevant to your task, proceed to write new files directly using the Write tool\\n";
|
|
78
|
+
message += "3. Using scaffold methods ensures consistency with project patterns and includes all necessary boilerplate\\n";
|
|
79
|
+
await executionLog.logExecution({
|
|
80
|
+
filePath,
|
|
81
|
+
operation: "list-scaffold-methods",
|
|
82
|
+
decision: DECISION_DENY
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
decision: DECISION_DENY,
|
|
86
|
+
message
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
decision: DECISION_SKIP,
|
|
91
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* PostToolUse hook for Claude Code
|
|
97
|
+
* Tracks file edits after scaffold generation and reminds AI to complete implementation
|
|
98
|
+
*
|
|
99
|
+
* @param context - Claude Code hook input
|
|
100
|
+
* @returns Hook response with scaffold completion tracking
|
|
101
|
+
*/
|
|
102
|
+
async postToolUse(context) {
|
|
103
|
+
try {
|
|
104
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
105
|
+
const filePath = context.tool_input?.file_path;
|
|
106
|
+
if ((context.tool_name === "mcp__one-mcp__use_tool" ? context.tool_input?.toolName : context.tool_name) === "use-scaffold-method") {
|
|
107
|
+
if (context.hook_event_name === "PostToolUse") {
|
|
108
|
+
const scaffoldId$1 = extractScaffoldId(context.tool_response);
|
|
109
|
+
if (scaffoldId$1) await processPendingScaffoldLogs(context.session_id, scaffoldId$1);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
decision: DECISION_ALLOW,
|
|
113
|
+
message: "Scaffold execution logged for progress tracking"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (!filePath || context.tool_name !== "Edit" && context.tool_name !== "Write" && context.tool_name !== "Update") return {
|
|
117
|
+
decision: DECISION_SKIP,
|
|
118
|
+
message: "Not a file edit/write operation"
|
|
119
|
+
};
|
|
120
|
+
const lastScaffoldExecution = await getLastScaffoldExecution(executionLog);
|
|
121
|
+
if (!lastScaffoldExecution) return {
|
|
122
|
+
decision: DECISION_SKIP,
|
|
123
|
+
message: "No scaffold execution found"
|
|
124
|
+
};
|
|
125
|
+
const { scaffoldId, generatedFiles, featureName } = lastScaffoldExecution;
|
|
126
|
+
const fulfilledKey = `scaffold-fulfilled-${scaffoldId}`;
|
|
127
|
+
if (await executionLog.hasExecuted({
|
|
128
|
+
filePath: fulfilledKey,
|
|
129
|
+
decision: DECISION_ALLOW
|
|
130
|
+
})) return {
|
|
131
|
+
decision: DECISION_SKIP,
|
|
132
|
+
message: "Scaffold already fulfilled"
|
|
133
|
+
};
|
|
134
|
+
const isScaffoldedFile = generatedFiles.includes(filePath);
|
|
135
|
+
if (isScaffoldedFile) {
|
|
136
|
+
const editKey = `scaffold-edit-${scaffoldId}-${filePath}`;
|
|
137
|
+
if (!await executionLog.hasExecuted({
|
|
138
|
+
filePath: editKey,
|
|
139
|
+
decision: DECISION_ALLOW
|
|
140
|
+
})) await executionLog.logExecution({
|
|
141
|
+
filePath: editKey,
|
|
142
|
+
operation: "scaffold-file-edit",
|
|
143
|
+
decision: DECISION_ALLOW
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const editedFiles = await getEditedScaffoldFiles(executionLog, scaffoldId);
|
|
147
|
+
const totalFiles = generatedFiles.length;
|
|
148
|
+
const remainingFiles = generatedFiles.filter((f) => !editedFiles.includes(f));
|
|
149
|
+
if (remainingFiles.length === 0) {
|
|
150
|
+
await executionLog.logExecution({
|
|
151
|
+
filePath: fulfilledKey,
|
|
152
|
+
operation: "scaffold-fulfilled",
|
|
153
|
+
decision: DECISION_ALLOW
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
decision: DECISION_ALLOW,
|
|
157
|
+
message: `✅ All scaffold files${featureName ? ` for "${featureName}"` : ""} have been implemented! (${totalFiles}/${totalFiles} files completed)`
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (isScaffoldedFile) {
|
|
161
|
+
const remainingFilesList = remainingFiles.map((f) => ` - ${f}`).join("\\n");
|
|
162
|
+
return {
|
|
163
|
+
decision: DECISION_ALLOW,
|
|
164
|
+
message: `
|
|
165
|
+
⚠️ **Scaffold Implementation Progress${featureName ? ` for "${featureName}"` : ""}: ${editedFiles.length}/${totalFiles} files completed**
|
|
166
|
+
|
|
167
|
+
**Remaining files to implement:**
|
|
168
|
+
${remainingFilesList}
|
|
169
|
+
|
|
170
|
+
Don't forget to complete the implementation for all scaffolded files!
|
|
171
|
+
`.trim()
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
decision: DECISION_SKIP,
|
|
176
|
+
message: "Edited file not part of last scaffold execution"
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return {
|
|
180
|
+
decision: DECISION_SKIP,
|
|
181
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Extract scaffold ID from tool result
|
|
188
|
+
*/
|
|
189
|
+
function extractScaffoldId(toolResult) {
|
|
190
|
+
try {
|
|
191
|
+
if (!toolResult || !toolResult.content) return null;
|
|
192
|
+
for (const item of toolResult.content) if (item.type === "text" && typeof item.text === "string") {
|
|
193
|
+
const match = item.text.match(/^SCAFFOLD_ID:([a-z0-9]+)$/);
|
|
194
|
+
if (match) return match[1];
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Helper function to get the last scaffold execution for a session
|
|
203
|
+
*/
|
|
204
|
+
async function getLastScaffoldExecution(executionLog) {
|
|
205
|
+
const entries = await executionLog.loadLog();
|
|
206
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
207
|
+
const entry = entries[i];
|
|
208
|
+
if (entry.operation === "scaffold" && entry.scaffoldId && entry.generatedFiles && entry.generatedFiles.length > 0) return {
|
|
209
|
+
scaffoldId: entry.scaffoldId,
|
|
210
|
+
generatedFiles: entry.generatedFiles,
|
|
211
|
+
featureName: entry.featureName
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Helper function to get list of edited scaffold files
|
|
218
|
+
*/
|
|
219
|
+
async function getEditedScaffoldFiles(executionLog, scaffoldId) {
|
|
220
|
+
const entries = await executionLog.loadLog();
|
|
221
|
+
const editedFiles = [];
|
|
222
|
+
for (const entry of entries) if (entry.operation === "scaffold-file-edit" && entry.filePath.startsWith(`scaffold-edit-${scaffoldId}-`)) {
|
|
223
|
+
const filePath = entry.filePath.replace(`scaffold-edit-${scaffoldId}-`, "");
|
|
224
|
+
editedFiles.push(filePath);
|
|
225
|
+
}
|
|
226
|
+
return editedFiles;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Process pending scaffold logs from temp file and copy to ExecutionLogService
|
|
230
|
+
* Called when use-scaffold-method tool is executed
|
|
231
|
+
*/
|
|
232
|
+
async function processPendingScaffoldLogs(sessionId, scaffoldId) {
|
|
233
|
+
const tempLogFile = path.join(os.tmpdir(), `scaffold-mcp-pending-${scaffoldId}.jsonl`);
|
|
234
|
+
try {
|
|
235
|
+
const lines = (await fs.readFile(tempLogFile, "utf-8")).trim().split("\\n").filter(Boolean);
|
|
236
|
+
const executionLog = new ExecutionLogService(sessionId);
|
|
237
|
+
try {
|
|
238
|
+
for (const line of lines) try {
|
|
239
|
+
const entry = JSON.parse(line);
|
|
240
|
+
await executionLog.logExecution({
|
|
241
|
+
filePath: `scaffold-${entry.scaffoldId}`,
|
|
242
|
+
operation: "scaffold",
|
|
243
|
+
decision: DECISION_ALLOW,
|
|
244
|
+
generatedFiles: entry.generatedFiles,
|
|
245
|
+
scaffoldId: entry.scaffoldId,
|
|
246
|
+
projectPath: entry.projectPath,
|
|
247
|
+
featureName: entry.featureName
|
|
248
|
+
});
|
|
249
|
+
} catch (parseError) {
|
|
250
|
+
console.error("Failed to parse pending scaffold log entry:", parseError);
|
|
251
|
+
}
|
|
252
|
+
} finally {
|
|
253
|
+
try {
|
|
254
|
+
await fs.unlink(tempLogFile);
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") console.error("Error processing pending scaffold logs:", error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
export { UseScaffoldMethodHook };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agiflowai/scaffold-mcp",
|
|
3
3
|
"description": "MCP server for scaffolding applications with boilerplate templates",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.7",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"author": "AgiflowIO",
|
|
7
7
|
"repository": {
|
|
@@ -44,10 +44,14 @@
|
|
|
44
44
|
"express": "^4.21.2",
|
|
45
45
|
"js-yaml": "4.1.0",
|
|
46
46
|
"liquidjs": "10.21.1",
|
|
47
|
+
"minimatch": "^10.0.1",
|
|
47
48
|
"pino": "^10.0.0",
|
|
48
49
|
"pino-pretty": "^13.1.1",
|
|
49
50
|
"zod": "3.25.76",
|
|
50
|
-
"@agiflowai/aicode-utils": "1.0.
|
|
51
|
+
"@agiflowai/aicode-utils": "1.0.6",
|
|
52
|
+
"@agiflowai/architect-mcp": "1.0.7",
|
|
53
|
+
"@agiflowai/hooks-adapter": "0.0.2",
|
|
54
|
+
"@agiflowai/coding-agent-bridge": "1.0.6"
|
|
51
55
|
},
|
|
52
56
|
"devDependencies": {
|
|
53
57
|
"@types/express": "^5.0.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/{VariableReplacementService-DHIINRnJ.mjs → VariableReplacementService-B9RA8D0a.mjs}
RENAMED
|
File without changes
|
/package/dist/{VariableReplacementService-CAjesAYq.cjs → VariableReplacementService-DDG5KZpb.cjs}
RENAMED
|
File without changes
|