@agiflowai/scaffold-mcp 1.2.0 → 1.3.0
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 +14 -0
- package/dist/{ListScaffoldingMethodsTool-DxdrPlcb.mjs → ListScaffoldingMethodsTool-dHaM4QXT.mjs} +6 -2
- package/dist/{ListScaffoldingMethodsTool-CYZtpU-W.cjs → ListScaffoldingMethodsTool-rX9Hxn2d.cjs} +6 -2
- package/dist/{claudeCode-DbKOy8W8.mjs → claudeCode-CyHppxA_.mjs} +12 -3
- package/dist/{claudeCode-g1eRidPR.cjs → claudeCode-DZnJyFQt.cjs} +12 -3
- package/dist/cli.cjs +16 -6
- package/dist/cli.mjs +18 -8
- package/dist/codex-7ry557jE.mjs +254 -0
- package/dist/codex-CfOJFx29.cjs +255 -0
- package/dist/{geminiCli-CcrZEqsz.mjs → geminiCli-CgajucCd.mjs} +10 -2
- package/dist/{geminiCli--s1Qy7ZP.cjs → geminiCli-wTHUG-A6.cjs} +10 -2
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +3 -3
- package/dist/shared-B0H-6AUG.cjs +187 -0
- package/dist/shared-BJjclypj.mjs +149 -0
- package/dist/{src-BG_X82dw.cjs → src-CRT1Er93.cjs} +3 -3
- package/dist/{src-DnVktdhD.mjs → src-CUQR93cQ.mjs} +3 -3
- package/dist/{tools-Dn5KgBUI.mjs → tools-BR1vQd_Y.mjs} +1 -1
- package/dist/{tools-COb9iBtt.cjs → tools-CV6GWs6f.cjs} +1 -1
- package/package.json +5 -5
- package/dist/shared-BVYIN3Is.mjs +0 -38
- package/dist/shared-QxPXh-L-.cjs +0 -52
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
|
|
2
|
+
const require_shared = require("./shared-B0H-6AUG.cjs");
|
|
3
|
+
let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_ListScaffoldingMethodsTool.__toESM(node_path, 1);
|
|
6
|
+
let _agiflowai_hooks_adapter = require("@agiflowai/hooks-adapter");
|
|
7
|
+
//#region src/hooks/codex/useScaffoldMethod.ts
|
|
8
|
+
/**
|
|
9
|
+
* Type guard for ScaffoldMethodsResponse
|
|
10
|
+
*/
|
|
11
|
+
function isScaffoldMethodsResponse(value) {
|
|
12
|
+
if (typeof value !== "object" || value === null) return false;
|
|
13
|
+
if ("methods" in value && !Array.isArray(value.methods)) return false;
|
|
14
|
+
if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
|
|
15
|
+
if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* UseScaffoldMethod Hook class for Codex CLI
|
|
20
|
+
*
|
|
21
|
+
* Provides lifecycle hooks for tool execution:
|
|
22
|
+
* - preToolUse: Shows available scaffolding methods before an apply_patch creates new files
|
|
23
|
+
* - postToolUse: Tracks scaffold completion progress after file edits
|
|
24
|
+
*/
|
|
25
|
+
var UseScaffoldMethodHook = class {
|
|
26
|
+
/**
|
|
27
|
+
* PreToolUse hook for Codex CLI
|
|
28
|
+
* Proactively shows available scaffolding methods and guides the agent to use them
|
|
29
|
+
* when an apply_patch is about to create a new file.
|
|
30
|
+
*
|
|
31
|
+
* @param context - Codex CLI hook input
|
|
32
|
+
* @returns Hook response with scaffolding methods guidance
|
|
33
|
+
*/
|
|
34
|
+
async preToolUse(context) {
|
|
35
|
+
try {
|
|
36
|
+
if (!("tool_name" in context)) return {
|
|
37
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
38
|
+
message: "Not a tool use event"
|
|
39
|
+
};
|
|
40
|
+
const command = typeof context.tool_input?.command === "string" ? context.tool_input.command : void 0;
|
|
41
|
+
const newFileTargets = require_shared.resolveApplyPatchNewFileTargets(context.cwd, context.tool_name, command);
|
|
42
|
+
if (newFileTargets.length === 0) return {
|
|
43
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
44
|
+
message: "Not a new-file apply_patch operation"
|
|
45
|
+
};
|
|
46
|
+
const globalExcludeGlobs = await require_shared.getGlobalExcludeGlobs(context.cwd);
|
|
47
|
+
const target = newFileTargets.find((p) => !require_shared.matchesExcludeGlob(p, globalExcludeGlobs));
|
|
48
|
+
if (!target) return {
|
|
49
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
50
|
+
message: "New file(s) match configured excludeGlobs - writing directly."
|
|
51
|
+
};
|
|
52
|
+
const executionLog = new _agiflowai_hooks_adapter.ExecutionLogService(context.session_id);
|
|
53
|
+
if (await executionLog.hasExecuted({
|
|
54
|
+
filePath: target,
|
|
55
|
+
decision: _agiflowai_hooks_adapter.DECISION_DENY
|
|
56
|
+
})) return {
|
|
57
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
58
|
+
message: "Scaffolding methods already provided for this file"
|
|
59
|
+
};
|
|
60
|
+
const templatesPath = await _agiflowai_aicode_utils.TemplatesManagerService.findTemplatesPath();
|
|
61
|
+
if (!templatesPath) return {
|
|
62
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
63
|
+
message: "Templates folder not found - skipping scaffold method check"
|
|
64
|
+
};
|
|
65
|
+
const tool = new require_ListScaffoldingMethodsTool.ListScaffoldingMethodsTool(templatesPath, false);
|
|
66
|
+
const projectPath = (await new _agiflowai_aicode_utils.ProjectFinderService(await _agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRoot(context.cwd)).findProjectForFile(target))?.root || context.cwd;
|
|
67
|
+
const result = await tool.execute({ projectPath });
|
|
68
|
+
const firstContent = result.content[0];
|
|
69
|
+
if (firstContent?.type !== "text") return {
|
|
70
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
71
|
+
message: "⚠️ Unexpected response type from scaffolding methods tool"
|
|
72
|
+
};
|
|
73
|
+
if (result.isError) return {
|
|
74
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
75
|
+
message: `⚠️ Could not load scaffolding methods: ${firstContent.text}`
|
|
76
|
+
};
|
|
77
|
+
const resultText = firstContent.text;
|
|
78
|
+
if (typeof resultText !== "string") return {
|
|
79
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
80
|
+
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
81
|
+
};
|
|
82
|
+
const parsed = JSON.parse(resultText);
|
|
83
|
+
if (!isScaffoldMethodsResponse(parsed)) return {
|
|
84
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
85
|
+
message: "⚠️ Unexpected response shape from scaffolding methods tool"
|
|
86
|
+
};
|
|
87
|
+
const data = parsed;
|
|
88
|
+
if (require_shared.matchesExcludeGlob(target, data.excludeGlobs)) return {
|
|
89
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
90
|
+
message: "File matches template exclude globs - writing directly."
|
|
91
|
+
};
|
|
92
|
+
if (!data.methods || data.methods.length === 0) {
|
|
93
|
+
await executionLog.logExecution({
|
|
94
|
+
filePath: target,
|
|
95
|
+
operation: "list-scaffold-methods",
|
|
96
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
100
|
+
message: "No scaffolding methods are available for this project template. You should write new files directly."
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const message = require_shared.formatScaffoldMethodsHookMessage(data.methods);
|
|
104
|
+
await executionLog.logExecution({
|
|
105
|
+
filePath: target,
|
|
106
|
+
operation: "list-scaffold-methods",
|
|
107
|
+
decision: _agiflowai_hooks_adapter.DECISION_DENY
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
decision: _agiflowai_hooks_adapter.DECISION_DENY,
|
|
111
|
+
message
|
|
112
|
+
};
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
116
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* PostToolUse hook for Codex CLI
|
|
122
|
+
* Tracks file edits after scaffold generation and reminds the agent to complete
|
|
123
|
+
* the implementation of all scaffold-generated files.
|
|
124
|
+
*
|
|
125
|
+
* @param context - Codex CLI hook input
|
|
126
|
+
* @returns Hook response with scaffold completion tracking
|
|
127
|
+
*/
|
|
128
|
+
async postToolUse(context) {
|
|
129
|
+
try {
|
|
130
|
+
if (!("tool_name" in context)) return {
|
|
131
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
132
|
+
message: "Not a tool use event"
|
|
133
|
+
};
|
|
134
|
+
const executionLog = new _agiflowai_hooks_adapter.ExecutionLogService(context.session_id);
|
|
135
|
+
if (context.tool_name === "use-scaffold-method" || context.tool_input?.toolName === "use-scaffold-method") return {
|
|
136
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
137
|
+
message: "Scaffold execution logged for progress tracking"
|
|
138
|
+
};
|
|
139
|
+
const command = typeof context.tool_input?.command === "string" ? context.tool_input.command : void 0;
|
|
140
|
+
const editedPaths = require_shared.resolveApplyPatchEditedPaths(context.tool_name, command);
|
|
141
|
+
if (editedPaths.length === 0) return {
|
|
142
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
143
|
+
message: "Not a file edit/write operation"
|
|
144
|
+
};
|
|
145
|
+
const lastScaffoldExecution = await getLastScaffoldExecution(executionLog);
|
|
146
|
+
if (!lastScaffoldExecution) return {
|
|
147
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
148
|
+
message: "No scaffold execution found"
|
|
149
|
+
};
|
|
150
|
+
const { scaffoldId, generatedFiles, featureName } = lastScaffoldExecution;
|
|
151
|
+
const fulfilledKey = `scaffold-fulfilled-${scaffoldId}`;
|
|
152
|
+
if (await executionLog.hasExecuted({
|
|
153
|
+
filePath: fulfilledKey,
|
|
154
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW
|
|
155
|
+
})) return {
|
|
156
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
157
|
+
message: "Scaffold already fulfilled"
|
|
158
|
+
};
|
|
159
|
+
let touchedScaffoldFile = false;
|
|
160
|
+
for (const editedPath of editedPaths) {
|
|
161
|
+
const absoluteEditedPath = node_path.default.isAbsolute(editedPath) ? editedPath : node_path.default.join(context.cwd, editedPath);
|
|
162
|
+
const generatedMatch = generatedFiles.find((f) => f === editedPath || f === absoluteEditedPath);
|
|
163
|
+
if (!generatedMatch) continue;
|
|
164
|
+
touchedScaffoldFile = true;
|
|
165
|
+
const editKey = `scaffold-edit-${scaffoldId}-${generatedMatch}`;
|
|
166
|
+
if (!await executionLog.hasExecuted({
|
|
167
|
+
filePath: editKey,
|
|
168
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW
|
|
169
|
+
})) await executionLog.logExecution({
|
|
170
|
+
filePath: editKey,
|
|
171
|
+
operation: "scaffold-file-edit",
|
|
172
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const editedFiles = await getEditedScaffoldFiles(executionLog, scaffoldId);
|
|
176
|
+
const totalFiles = generatedFiles.length;
|
|
177
|
+
const remainingFiles = generatedFiles.filter((f) => !editedFiles.includes(f));
|
|
178
|
+
if (remainingFiles.length === 0) {
|
|
179
|
+
await executionLog.logExecution({
|
|
180
|
+
filePath: fulfilledKey,
|
|
181
|
+
operation: "scaffold-fulfilled",
|
|
182
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
186
|
+
message: `✅ All scaffold files${featureName ? ` for "${featureName}"` : ""} have been implemented! (${totalFiles}/${totalFiles} files completed)`
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (touchedScaffoldFile) {
|
|
190
|
+
const remainingFilesList = remainingFiles.map((f) => ` - ${f}`).join("\n");
|
|
191
|
+
return {
|
|
192
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
193
|
+
message: `
|
|
194
|
+
⚠️ **Scaffold Implementation Progress${featureName ? ` for "${featureName}"` : ""}: ${editedFiles.length}/${totalFiles} files completed**
|
|
195
|
+
|
|
196
|
+
**Remaining files to implement:**
|
|
197
|
+
${remainingFilesList}
|
|
198
|
+
|
|
199
|
+
Don't forget to complete the implementation for all scaffolded files!
|
|
200
|
+
`.trim()
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
205
|
+
message: "Edited file not part of last scaffold execution"
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return {
|
|
209
|
+
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
210
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Helper function to get the last scaffold execution for a session.
|
|
217
|
+
* Returns null if no scaffold execution found or on error.
|
|
218
|
+
*/
|
|
219
|
+
async function getLastScaffoldExecution(executionLog) {
|
|
220
|
+
try {
|
|
221
|
+
const entries = await executionLog.loadLog();
|
|
222
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
223
|
+
const entry = entries[i];
|
|
224
|
+
if (entry.operation === "scaffold" && entry.scaffoldId && entry.generatedFiles && entry.generatedFiles.length > 0) return {
|
|
225
|
+
scaffoldId: entry.scaffoldId,
|
|
226
|
+
generatedFiles: entry.generatedFiles,
|
|
227
|
+
featureName: entry.featureName
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error("Error getting last scaffold execution:", error);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Helper function to get the list of edited scaffold files.
|
|
238
|
+
* Returns an empty array if no files found or on error.
|
|
239
|
+
*/
|
|
240
|
+
async function getEditedScaffoldFiles(executionLog, scaffoldId) {
|
|
241
|
+
try {
|
|
242
|
+
const entries = await executionLog.loadLog();
|
|
243
|
+
const editedFiles = [];
|
|
244
|
+
for (const entry of entries) if (entry.operation === "scaffold-file-edit" && entry.filePath.startsWith(`scaffold-edit-${scaffoldId}-`)) {
|
|
245
|
+
const filePath = entry.filePath.replace(`scaffold-edit-${scaffoldId}-`, "");
|
|
246
|
+
editedFiles.push(filePath);
|
|
247
|
+
}
|
|
248
|
+
return editedFiles;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(`Error getting edited scaffold files for ${scaffoldId}:`, error);
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
exports.UseScaffoldMethodHook = UseScaffoldMethodHook;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-
|
|
2
|
-
import { n as resolveNewFileWriteTarget, t as formatScaffoldMethodsHookMessage } from "./shared-
|
|
1
|
+
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
2
|
+
import { n as getGlobalExcludeGlobs, o as resolveNewFileWriteTarget, r as matchesExcludeGlob, t as formatScaffoldMethodsHookMessage } from "./shared-BJjclypj.mjs";
|
|
3
3
|
import { ProjectFinderService, TemplatesManagerService } from "@agiflowai/aicode-utils";
|
|
4
4
|
import { DECISION_ALLOW, DECISION_DENY, DECISION_SKIP, ExecutionLogService } from "@agiflowai/hooks-adapter";
|
|
5
5
|
//#region src/hooks/geminiCli/useScaffoldMethod.ts
|
|
@@ -26,6 +26,10 @@ var UseScaffoldMethodHook = class {
|
|
|
26
26
|
decision: DECISION_SKIP,
|
|
27
27
|
message: "Not a new file write operation"
|
|
28
28
|
};
|
|
29
|
+
if (matchesExcludeGlob(absoluteFilePath, await getGlobalExcludeGlobs(context.cwd))) return {
|
|
30
|
+
decision: DECISION_ALLOW,
|
|
31
|
+
message: "File matches configured excludeGlobs - writing directly."
|
|
32
|
+
};
|
|
29
33
|
const executionLog = new ExecutionLogService(context.session_id);
|
|
30
34
|
const sessionKey = filePath;
|
|
31
35
|
if (await executionLog.hasExecuted({
|
|
@@ -72,6 +76,10 @@ var UseScaffoldMethodHook = class {
|
|
|
72
76
|
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
73
77
|
};
|
|
74
78
|
const data = JSON.parse(resultText);
|
|
79
|
+
if (matchesExcludeGlob(absoluteFilePath, data.excludeGlobs)) return {
|
|
80
|
+
decision: DECISION_ALLOW,
|
|
81
|
+
message: "File matches template exclude globs - writing directly."
|
|
82
|
+
};
|
|
75
83
|
if (!data.methods || data.methods.length === 0) {
|
|
76
84
|
await executionLog.logExecution({
|
|
77
85
|
filePath: sessionKey,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-
|
|
2
|
-
const require_shared = require("./shared-
|
|
1
|
+
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
|
|
2
|
+
const require_shared = require("./shared-B0H-6AUG.cjs");
|
|
3
3
|
let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
|
|
4
4
|
let _agiflowai_hooks_adapter = require("@agiflowai/hooks-adapter");
|
|
5
5
|
//#region src/hooks/geminiCli/useScaffoldMethod.ts
|
|
@@ -26,6 +26,10 @@ var UseScaffoldMethodHook = class {
|
|
|
26
26
|
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
27
27
|
message: "Not a new file write operation"
|
|
28
28
|
};
|
|
29
|
+
if (require_shared.matchesExcludeGlob(absoluteFilePath, await require_shared.getGlobalExcludeGlobs(context.cwd))) return {
|
|
30
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
31
|
+
message: "File matches configured excludeGlobs - writing directly."
|
|
32
|
+
};
|
|
29
33
|
const executionLog = new _agiflowai_hooks_adapter.ExecutionLogService(context.session_id);
|
|
30
34
|
const sessionKey = filePath;
|
|
31
35
|
if (await executionLog.hasExecuted({
|
|
@@ -72,6 +76,10 @@ var UseScaffoldMethodHook = class {
|
|
|
72
76
|
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
73
77
|
};
|
|
74
78
|
const data = JSON.parse(resultText);
|
|
79
|
+
if (require_shared.matchesExcludeGlob(absoluteFilePath, data.excludeGlobs)) return {
|
|
80
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
81
|
+
message: "File matches template exclude globs - writing directly."
|
|
82
|
+
};
|
|
75
83
|
if (!data.methods || data.methods.length === 0) {
|
|
76
84
|
await executionLog.logExecution({
|
|
77
85
|
filePath: sessionKey,
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-
|
|
3
|
-
const require_src = require("./src-
|
|
4
|
-
const require_tools = require("./tools-
|
|
2
|
+
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
|
|
3
|
+
const require_src = require("./src-CRT1Er93.cjs");
|
|
4
|
+
const require_tools = require("./tools-CV6GWs6f.cjs");
|
|
5
5
|
exports.BoilerplateGeneratorService = require_tools.BoilerplateGeneratorService;
|
|
6
6
|
exports.BoilerplateService = require_tools.BoilerplateService;
|
|
7
7
|
exports.FileSystemService = require_ListScaffoldingMethodsTool.FileSystemService;
|
package/dist/index.d.cts
CHANGED
|
@@ -789,6 +789,11 @@ interface ListScaffoldingMethodsResult {
|
|
|
789
789
|
sourceTemplate: string;
|
|
790
790
|
templatePath: string;
|
|
791
791
|
methods: ScaffoldMethod[];
|
|
792
|
+
/**
|
|
793
|
+
* Template-level glob patterns (from scaffold.yaml `exclude`) whose new-file
|
|
794
|
+
* writes bypass scaffold enforcement. Template-wide, so repeated on every page.
|
|
795
|
+
*/
|
|
796
|
+
excludeGlobs?: string[];
|
|
792
797
|
nextCursor?: string;
|
|
793
798
|
_meta?: {
|
|
794
799
|
total: number;
|
package/dist/index.d.mts
CHANGED
|
@@ -788,6 +788,11 @@ interface ListScaffoldingMethodsResult {
|
|
|
788
788
|
sourceTemplate: string;
|
|
789
789
|
templatePath: string;
|
|
790
790
|
methods: ScaffoldMethod[];
|
|
791
|
+
/**
|
|
792
|
+
* Template-level glob patterns (from scaffold.yaml `exclude`) whose new-file
|
|
793
|
+
* writes bypass scaffold enforcement. Template-wide, so repeated on every page.
|
|
794
|
+
*/
|
|
795
|
+
excludeGlobs?: string[];
|
|
791
796
|
nextCursor?: string;
|
|
792
797
|
_meta?: {
|
|
793
798
|
total: number;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as createServer, i as StdioTransportHandler, n as HttpTransportHandler, r as SseTransportHandler, t as TransportMode } from "./src-
|
|
2
|
-
import { a as ScaffoldProcessingService, i as ScaffoldService, l as TemplateService, n as ScaffoldingMethodsService, o as ScaffoldConfigLoader, r as VariableReplacementService, s as FileSystemService, t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-
|
|
3
|
-
import { a as ListBoilerplatesTool, c as GenerateBoilerplateFileTool, d as BoilerplateGeneratorService, i as UseBoilerplateTool, l as ScaffoldGeneratorService, n as UseScaffoldMethodTool, o as GenerateFeatureScaffoldTool, s as GenerateBoilerplateTool, t as WriteToFileTool, u as BoilerplateService } from "./tools-
|
|
1
|
+
import { a as createServer, i as StdioTransportHandler, n as HttpTransportHandler, r as SseTransportHandler, t as TransportMode } from "./src-CUQR93cQ.mjs";
|
|
2
|
+
import { a as ScaffoldProcessingService, i as ScaffoldService, l as TemplateService, n as ScaffoldingMethodsService, o as ScaffoldConfigLoader, r as VariableReplacementService, s as FileSystemService, t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
3
|
+
import { a as ListBoilerplatesTool, c as GenerateBoilerplateFileTool, d as BoilerplateGeneratorService, i as UseBoilerplateTool, l as ScaffoldGeneratorService, n as UseScaffoldMethodTool, o as GenerateFeatureScaffoldTool, s as GenerateBoilerplateTool, t as WriteToFileTool, u as BoilerplateService } from "./tools-BR1vQd_Y.mjs";
|
|
4
4
|
export { BoilerplateGeneratorService, BoilerplateService, FileSystemService, GenerateBoilerplateFileTool, GenerateBoilerplateTool, GenerateFeatureScaffoldTool, HttpTransportHandler, ListBoilerplatesTool, ListScaffoldingMethodsTool, ScaffoldConfigLoader, ScaffoldGeneratorService, ScaffoldProcessingService, ScaffoldService, ScaffoldingMethodsService, SseTransportHandler, StdioTransportHandler, TemplateService, TransportMode, UseBoilerplateTool, UseScaffoldMethodTool, VariableReplacementService, WriteToFileTool, createServer };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
|
|
2
|
+
let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_ListScaffoldingMethodsTool.__toESM(node_path, 1);
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
node_fs_promises = require_ListScaffoldingMethodsTool.__toESM(node_fs_promises, 1);
|
|
7
|
+
let minimatch = require("minimatch");
|
|
8
|
+
/**
|
|
9
|
+
* Returns true when an absolute write target matches any of the given exclude globs.
|
|
10
|
+
*
|
|
11
|
+
* Scaffold methods carry no target glob, so the hook cannot tell whether a method
|
|
12
|
+
* applies to a given path: once any method exists it denies every new-file write,
|
|
13
|
+
* which pushes agents to bypass the hook via Bash heredocs (skipping downstream
|
|
14
|
+
* review hooks). Exclude globs are the configured escape hatch — matching writes are
|
|
15
|
+
* allowed through directly. Globs come from two sources: workspace-wide
|
|
16
|
+
* (`scaffold-mcp.hook.excludeGlobs` in .toolkit/settings.yaml, see
|
|
17
|
+
* {@link getGlobalExcludeGlobs}) and per-template (`exclude` in scaffold.yaml).
|
|
18
|
+
*
|
|
19
|
+
* `dot: true` lets the globs traverse dot-directories (e.g. worktree paths) so a
|
|
20
|
+
* file nested under one is not misclassified.
|
|
21
|
+
*
|
|
22
|
+
* @param absPath - Absolute path of the new-file write target.
|
|
23
|
+
* @param globs - Glob patterns to match against; empty/undefined matches nothing.
|
|
24
|
+
* @returns True when `absPath` matches at least one glob.
|
|
25
|
+
*/
|
|
26
|
+
function matchesExcludeGlob(absPath, globs) {
|
|
27
|
+
if (!globs || globs.length === 0) return false;
|
|
28
|
+
return globs.some((glob) => (0, minimatch.minimatch)(absPath, glob, { dot: true }));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Loads the workspace-wide scaffold-enforcement exclude globs from the toolkit
|
|
32
|
+
* config (`scaffold-mcp.hook.excludeGlobs` in .toolkit/settings.yaml).
|
|
33
|
+
*
|
|
34
|
+
* Fails open (returns an empty array) when the config is missing or unreadable so a
|
|
35
|
+
* broken config never blocks writes.
|
|
36
|
+
*
|
|
37
|
+
* @param cwd - Directory to resolve the workspace config from (walks up to the root).
|
|
38
|
+
* @returns The configured exclude globs, or an empty array when none are set.
|
|
39
|
+
*/
|
|
40
|
+
async function getGlobalExcludeGlobs(cwd) {
|
|
41
|
+
try {
|
|
42
|
+
const globs = (await _agiflowai_aicode_utils.TemplatesManagerService.readToolkitConfig(cwd))?.["scaffold-mcp"]?.hook?.excludeGlobs;
|
|
43
|
+
return Array.isArray(globs) ? globs : [];
|
|
44
|
+
} catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function resolveNewFileWriteTarget(cwd, toolName, filePath) {
|
|
49
|
+
if (!filePath || toolName.toLowerCase() !== "write") return null;
|
|
50
|
+
const absoluteFilePath = node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath);
|
|
51
|
+
if (!absoluteFilePath.startsWith(cwd + node_path.default.sep) && absoluteFilePath !== cwd) return null;
|
|
52
|
+
try {
|
|
53
|
+
await node_fs_promises.default.access(absoluteFilePath);
|
|
54
|
+
return null;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return absoluteFilePath;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parses a Codex `apply_patch` command body into the file paths it adds, updates,
|
|
62
|
+
* and deletes. Paths are returned verbatim (as written in the patch, i.e. relative
|
|
63
|
+
* to cwd). Pure and synchronous — performs no filesystem access.
|
|
64
|
+
*
|
|
65
|
+
* @param command - The `apply_patch` patch body (the tool_input.command string).
|
|
66
|
+
* @returns The added/updated/deleted path lists.
|
|
67
|
+
*/
|
|
68
|
+
function parseApplyPatchTargets(command) {
|
|
69
|
+
const added = [];
|
|
70
|
+
const updated = [];
|
|
71
|
+
const deleted = [];
|
|
72
|
+
for (const line of command.split("\n")) {
|
|
73
|
+
const add = line.match(/^\*\*\* Add File: (.+)$/);
|
|
74
|
+
if (add) {
|
|
75
|
+
added.push(add[1].trim());
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const update = line.match(/^\*\*\* Update File: (.+)$/);
|
|
79
|
+
if (update) {
|
|
80
|
+
updated.push(update[1].trim());
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const del = line.match(/^\*\*\* Delete File: (.+)$/);
|
|
84
|
+
if (del) deleted.push(del[1].trim());
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
added,
|
|
88
|
+
updated,
|
|
89
|
+
deleted
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolves the absolute, cwd-scoped paths of NEW files a Codex `apply_patch` tool
|
|
94
|
+
* call creates (its `*** Add File:` targets). Returns an empty array for any other
|
|
95
|
+
* tool or a missing command.
|
|
96
|
+
*
|
|
97
|
+
* Unlike {@link resolveNewFileWriteTarget} (the Claude/Gemini `Write` resolver), this
|
|
98
|
+
* performs no on-disk existence check: an `Add File` block is new-file creation by
|
|
99
|
+
* definition, and the patch fails downstream if the path already exists.
|
|
100
|
+
*
|
|
101
|
+
* @param cwd - Working directory the patch paths are relative to.
|
|
102
|
+
* @param toolName - The Codex tool name (only `apply_patch` is handled).
|
|
103
|
+
* @param command - The `apply_patch` patch body (tool_input.command).
|
|
104
|
+
* @returns Absolute paths of new files created under cwd.
|
|
105
|
+
*/
|
|
106
|
+
function resolveApplyPatchNewFileTargets(cwd, toolName, command) {
|
|
107
|
+
if (!command || toolName !== "apply_patch") return [];
|
|
108
|
+
const targets = [];
|
|
109
|
+
for (const filePath of parseApplyPatchTargets(command).added) {
|
|
110
|
+
const absoluteFilePath = node_path.default.isAbsolute(filePath) ? filePath : node_path.default.join(cwd, filePath);
|
|
111
|
+
if (absoluteFilePath.startsWith(cwd + node_path.default.sep) || absoluteFilePath === cwd) targets.push(absoluteFilePath);
|
|
112
|
+
}
|
|
113
|
+
return targets;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Returns the file paths a Codex `apply_patch` call writes (adds or updates), as
|
|
117
|
+
* written in the patch. Used to correlate post-write edits with a scaffold's
|
|
118
|
+
* generated files. Returns an empty array for any other tool or a missing command.
|
|
119
|
+
*
|
|
120
|
+
* @param toolName - The Codex tool name (only `apply_patch` is handled).
|
|
121
|
+
* @param command - The `apply_patch` patch body (tool_input.command).
|
|
122
|
+
* @returns The added and updated paths (verbatim).
|
|
123
|
+
*/
|
|
124
|
+
function resolveApplyPatchEditedPaths(toolName, command) {
|
|
125
|
+
if (!command || toolName !== "apply_patch") return [];
|
|
126
|
+
const { added, updated } = parseApplyPatchTargets(command);
|
|
127
|
+
return [...added, ...updated];
|
|
128
|
+
}
|
|
129
|
+
function formatScaffoldMethodsHookMessage(methods, options) {
|
|
130
|
+
const maxMethods = options?.maxMethods ?? 5;
|
|
131
|
+
const requiredVarsMethodLimit = options?.requiredVarsMethodLimit ?? 2;
|
|
132
|
+
const maxRequiredVars = options?.maxRequiredVars ?? 3;
|
|
133
|
+
const visibleMethods = methods.slice(0, maxMethods);
|
|
134
|
+
const hiddenCount = Math.max(methods.length - visibleMethods.length, 0);
|
|
135
|
+
let message = "Before writing this new file, use `use-scaffold-method` if any of these fit:\n\n";
|
|
136
|
+
for (const [index, method] of visibleMethods.entries()) {
|
|
137
|
+
message += `- **${method.name}**: ${method.description || "No description available"}\n`;
|
|
138
|
+
if (options?.includeRequiredVars && index < requiredVarsMethodLimit) {
|
|
139
|
+
const requiredVars = method.variables_schema?.required ?? [];
|
|
140
|
+
if (requiredVars.length > 0) {
|
|
141
|
+
const visibleVars = requiredVars.slice(0, maxRequiredVars);
|
|
142
|
+
const moreCount = Math.max(requiredVars.length - visibleVars.length, 0);
|
|
143
|
+
const suffix = moreCount > 0 ? `, +${moreCount} more` : "";
|
|
144
|
+
message += ` Required: ${visibleVars.join(", ")}${suffix}\n`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (hiddenCount > 0) message += `\n...and ${hiddenCount} more methods. Call \`list-scaffolding-methods\` for the full list.\n`;
|
|
149
|
+
return message.trimEnd();
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
Object.defineProperty(exports, "formatScaffoldMethodsHookMessage", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
get: function() {
|
|
155
|
+
return formatScaffoldMethodsHookMessage;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(exports, "getGlobalExcludeGlobs", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get: function() {
|
|
161
|
+
return getGlobalExcludeGlobs;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, "matchesExcludeGlob", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function() {
|
|
167
|
+
return matchesExcludeGlob;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
Object.defineProperty(exports, "resolveApplyPatchEditedPaths", {
|
|
171
|
+
enumerable: true,
|
|
172
|
+
get: function() {
|
|
173
|
+
return resolveApplyPatchEditedPaths;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
Object.defineProperty(exports, "resolveApplyPatchNewFileTargets", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
get: function() {
|
|
179
|
+
return resolveApplyPatchNewFileTargets;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
Object.defineProperty(exports, "resolveNewFileWriteTarget", {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
get: function() {
|
|
185
|
+
return resolveNewFileWriteTarget;
|
|
186
|
+
}
|
|
187
|
+
});
|