@agiflowai/scaffold-mcp 1.1.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.
@@ -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-DxdrPlcb.mjs";
2
- import { n as resolveNewFileWriteTarget, t as formatScaffoldMethodsHookMessage } from "./shared-BVYIN3Is.mjs";
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-CYZtpU-W.cjs");
2
- const require_shared = require("./shared-QxPXh-L-.cjs");
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-CYZtpU-W.cjs");
3
- const require_src = require("./src-oF_UdSBu.cjs");
4
- const require_tools = require("./tools-CVSZSirE.cjs");
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
@@ -267,15 +267,10 @@ declare class UseBoilerplateTool {
267
267
  //#region src/tools/UseScaffoldMethodTool.d.ts
268
268
  declare class UseScaffoldMethodTool {
269
269
  static readonly TOOL_NAME = "use-scaffold-method";
270
- private static readonly TEMP_LOG_DIR;
271
270
  private fileSystemService;
272
271
  private scaffoldingMethodsService;
273
272
  private isMonolith;
274
273
  constructor(templatesPath: string, isMonolith?: boolean);
275
- /**
276
- * Write scaffold execution info to temp log file for hook processing
277
- */
278
- private writePendingScaffoldLog;
279
274
  /**
280
275
  * Get the tool definition for MCP
281
276
  */
@@ -794,6 +789,11 @@ interface ListScaffoldingMethodsResult {
794
789
  sourceTemplate: string;
795
790
  templatePath: string;
796
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[];
797
797
  nextCursor?: string;
798
798
  _meta?: {
799
799
  total: number;
package/dist/index.d.mts CHANGED
@@ -266,15 +266,10 @@ declare class UseBoilerplateTool {
266
266
  //#region src/tools/UseScaffoldMethodTool.d.ts
267
267
  declare class UseScaffoldMethodTool {
268
268
  static readonly TOOL_NAME = "use-scaffold-method";
269
- private static readonly TEMP_LOG_DIR;
270
269
  private fileSystemService;
271
270
  private scaffoldingMethodsService;
272
271
  private isMonolith;
273
272
  constructor(templatesPath: string, isMonolith?: boolean);
274
- /**
275
- * Write scaffold execution info to temp log file for hook processing
276
- */
277
- private writePendingScaffoldLog;
278
273
  /**
279
274
  * Get the tool definition for MCP
280
275
  */
@@ -793,6 +788,11 @@ interface ListScaffoldingMethodsResult {
793
788
  sourceTemplate: string;
794
789
  templatePath: string;
795
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[];
796
796
  nextCursor?: string;
797
797
  _meta?: {
798
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-BEwgHhCT.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-DxdrPlcb.mjs";
3
- import { a as GenerateFeatureScaffoldTool, c as ScaffoldGeneratorService, i as ListBoilerplatesTool, l as BoilerplateService, n as UseScaffoldMethodTool, o as GenerateBoilerplateTool, r as UseBoilerplateTool, s as GenerateBoilerplateFileTool, t as WriteToFileTool, u as BoilerplateGeneratorService } from "./tools-Cl06aoBi.mjs";
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
+ });