@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
package/README.md
CHANGED
|
@@ -303,6 +303,20 @@ Hooks let scaffold-mcp proactively suggest templates when your AI agent creates
|
|
|
303
303
|
|
|
304
304
|
When Claude tries to write a new file, the hook shows available scaffolding methods that match, so Claude can use templates instead of writing from scratch.
|
|
305
305
|
|
|
306
|
+
Hooks are also available for **Gemini CLI** and **OpenAI Codex CLI** (`--type codex.preToolUse`, configured in `.codex/config.toml`); see the hooks docs for setup.
|
|
307
|
+
|
|
308
|
+
**Relaxing enforcement:** to let some files (docs, content, generated code) be written directly, add exclude globs — workspace-wide via `scaffold-mcp.hook.excludeGlobs` in `.toolkit/settings.yaml`, or per-template via a top-level `exclude` in `scaffold.yaml`:
|
|
309
|
+
|
|
310
|
+
```yaml
|
|
311
|
+
# .toolkit/settings.yaml
|
|
312
|
+
scaffold-mcp:
|
|
313
|
+
hook:
|
|
314
|
+
excludeGlobs:
|
|
315
|
+
- '**/*.md'
|
|
316
|
+
- '**/*.mdx'
|
|
317
|
+
- '**/src/content/**'
|
|
318
|
+
```
|
|
319
|
+
|
|
306
320
|
See [Hooks Documentation](./docs/hooks.md) for details.
|
|
307
321
|
|
|
308
322
|
---
|
package/dist/{ListScaffoldingMethodsTool-DxdrPlcb.mjs → ListScaffoldingMethodsTool-dHaM4QXT.mjs}
RENAMED
|
@@ -174,6 +174,7 @@ const ScaffoldConfigEntrySchema = z.object({
|
|
|
174
174
|
patterns: z.array(z.string()).optional()
|
|
175
175
|
});
|
|
176
176
|
const ScaffoldYamlSchema = z.object({
|
|
177
|
+
exclude: z.array(z.string()).optional(),
|
|
177
178
|
boilerplate: z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]).optional(),
|
|
178
179
|
features: z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]).optional()
|
|
179
180
|
}).catchall(z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]));
|
|
@@ -761,6 +762,7 @@ var ScaffoldingMethodsService = class {
|
|
|
761
762
|
if (!await this.fileSystem.pathExists(scaffoldYamlPath)) throw new Error(`scaffold.yaml not found at ${scaffoldYamlPath}`);
|
|
762
763
|
const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
|
|
763
764
|
const architectConfig = yaml.load(scaffoldContent);
|
|
765
|
+
const excludeGlobs = Array.isArray(architectConfig.exclude) ? architectConfig.exclude : [];
|
|
764
766
|
const methods = [];
|
|
765
767
|
if (architectConfig.features && Array.isArray(architectConfig.features)) architectConfig.features.forEach((feature) => {
|
|
766
768
|
const featureName = feature.name || `scaffold-${templateName}`;
|
|
@@ -779,16 +781,18 @@ var ScaffoldingMethodsService = class {
|
|
|
779
781
|
});
|
|
780
782
|
return {
|
|
781
783
|
templatePath,
|
|
782
|
-
methods
|
|
784
|
+
methods,
|
|
785
|
+
excludeGlobs
|
|
783
786
|
};
|
|
784
787
|
}
|
|
785
788
|
async listScaffoldingMethodsByTemplate(templateName, cursor) {
|
|
786
|
-
const { templatePath, methods } = await this.collectAllMethodsByTemplate(templateName);
|
|
789
|
+
const { templatePath, methods, excludeGlobs } = await this.collectAllMethodsByTemplate(templateName);
|
|
787
790
|
const paginatedResult = PaginationHelper.paginate(methods, cursor);
|
|
788
791
|
return {
|
|
789
792
|
sourceTemplate: templateName,
|
|
790
793
|
templatePath,
|
|
791
794
|
methods: paginatedResult.items,
|
|
795
|
+
excludeGlobs,
|
|
792
796
|
nextCursor: paginatedResult.nextCursor,
|
|
793
797
|
_meta: paginatedResult._meta
|
|
794
798
|
};
|
package/dist/{ListScaffoldingMethodsTool-CYZtpU-W.cjs → ListScaffoldingMethodsTool-rX9Hxn2d.cjs}
RENAMED
|
@@ -198,6 +198,7 @@ const ScaffoldConfigEntrySchema = zod.z.object({
|
|
|
198
198
|
patterns: zod.z.array(zod.z.string()).optional()
|
|
199
199
|
});
|
|
200
200
|
const ScaffoldYamlSchema = zod.z.object({
|
|
201
|
+
exclude: zod.z.array(zod.z.string()).optional(),
|
|
201
202
|
boilerplate: zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]).optional(),
|
|
202
203
|
features: zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]).optional()
|
|
203
204
|
}).catchall(zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]));
|
|
@@ -785,6 +786,7 @@ var ScaffoldingMethodsService = class {
|
|
|
785
786
|
if (!await this.fileSystem.pathExists(scaffoldYamlPath)) throw new Error(`scaffold.yaml not found at ${scaffoldYamlPath}`);
|
|
786
787
|
const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
|
|
787
788
|
const architectConfig = js_yaml.default.load(scaffoldContent);
|
|
789
|
+
const excludeGlobs = Array.isArray(architectConfig.exclude) ? architectConfig.exclude : [];
|
|
788
790
|
const methods = [];
|
|
789
791
|
if (architectConfig.features && Array.isArray(architectConfig.features)) architectConfig.features.forEach((feature) => {
|
|
790
792
|
const featureName = feature.name || `scaffold-${templateName}`;
|
|
@@ -803,16 +805,18 @@ var ScaffoldingMethodsService = class {
|
|
|
803
805
|
});
|
|
804
806
|
return {
|
|
805
807
|
templatePath,
|
|
806
|
-
methods
|
|
808
|
+
methods,
|
|
809
|
+
excludeGlobs
|
|
807
810
|
};
|
|
808
811
|
}
|
|
809
812
|
async listScaffoldingMethodsByTemplate(templateName, cursor) {
|
|
810
|
-
const { templatePath, methods } = await this.collectAllMethodsByTemplate(templateName);
|
|
813
|
+
const { templatePath, methods, excludeGlobs } = await this.collectAllMethodsByTemplate(templateName);
|
|
811
814
|
const paginatedResult = PaginationHelper.paginate(methods, cursor);
|
|
812
815
|
return {
|
|
813
816
|
sourceTemplate: templateName,
|
|
814
817
|
templatePath,
|
|
815
818
|
methods: paginatedResult.items,
|
|
819
|
+
excludeGlobs,
|
|
816
820
|
nextCursor: paginatedResult.nextCursor,
|
|
817
821
|
_meta: paginatedResult._meta
|
|
818
822
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-
|
|
2
|
-
import "./tools-
|
|
3
|
-
import { n as resolveNewFileWriteTarget, t as formatScaffoldMethodsHookMessage } from "./shared-
|
|
1
|
+
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
2
|
+
import "./tools-BR1vQd_Y.mjs";
|
|
3
|
+
import { n as getGlobalExcludeGlobs, o as resolveNewFileWriteTarget, r as matchesExcludeGlob, t as formatScaffoldMethodsHookMessage } from "./shared-BJjclypj.mjs";
|
|
4
4
|
import { ProjectFinderService, TemplatesManagerService } from "@agiflowai/aicode-utils";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
@@ -152,6 +152,7 @@ var PhantomCodeCheckHook = class {
|
|
|
152
152
|
function isScaffoldMethodsResponse(value) {
|
|
153
153
|
if (typeof value !== "object" || value === null) return false;
|
|
154
154
|
if ("methods" in value && !Array.isArray(value.methods)) return false;
|
|
155
|
+
if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
|
|
155
156
|
if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
|
|
156
157
|
return true;
|
|
157
158
|
}
|
|
@@ -189,6 +190,10 @@ var UseScaffoldMethodHook = class {
|
|
|
189
190
|
decision: DECISION_SKIP,
|
|
190
191
|
message: "Not a new file write operation"
|
|
191
192
|
};
|
|
193
|
+
if (matchesExcludeGlob(absoluteFilePath, await getGlobalExcludeGlobs(context.cwd))) return {
|
|
194
|
+
decision: DECISION_ALLOW,
|
|
195
|
+
message: "File matches configured excludeGlobs - writing directly."
|
|
196
|
+
};
|
|
192
197
|
const executionLog = new ExecutionLogService(context.session_id);
|
|
193
198
|
if (await executionLog.hasExecuted({
|
|
194
199
|
filePath,
|
|
@@ -225,6 +230,10 @@ var UseScaffoldMethodHook = class {
|
|
|
225
230
|
message: "⚠️ Unexpected response shape from scaffolding methods tool"
|
|
226
231
|
};
|
|
227
232
|
const data = parsed;
|
|
233
|
+
if (matchesExcludeGlob(absoluteFilePath, data.excludeGlobs)) return {
|
|
234
|
+
decision: DECISION_ALLOW,
|
|
235
|
+
message: "File matches template exclude globs - writing directly."
|
|
236
|
+
};
|
|
228
237
|
if (!data.methods || data.methods.length === 0) {
|
|
229
238
|
await executionLog.logExecution({
|
|
230
239
|
filePath,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-
|
|
2
|
-
require("./tools-
|
|
3
|
-
const require_shared = require("./shared-
|
|
1
|
+
const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
|
|
2
|
+
require("./tools-CV6GWs6f.cjs");
|
|
3
|
+
const require_shared = require("./shared-B0H-6AUG.cjs");
|
|
4
4
|
let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
|
|
5
5
|
let node_path = require("node:path");
|
|
6
6
|
node_path = require_ListScaffoldingMethodsTool.__toESM(node_path, 1);
|
|
@@ -155,6 +155,7 @@ var PhantomCodeCheckHook = class {
|
|
|
155
155
|
function isScaffoldMethodsResponse(value) {
|
|
156
156
|
if (typeof value !== "object" || value === null) return false;
|
|
157
157
|
if ("methods" in value && !Array.isArray(value.methods)) return false;
|
|
158
|
+
if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
|
|
158
159
|
if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
|
|
159
160
|
return true;
|
|
160
161
|
}
|
|
@@ -192,6 +193,10 @@ var UseScaffoldMethodHook = class {
|
|
|
192
193
|
decision: _agiflowai_hooks_adapter.DECISION_SKIP,
|
|
193
194
|
message: "Not a new file write operation"
|
|
194
195
|
};
|
|
196
|
+
if (require_shared.matchesExcludeGlob(absoluteFilePath, await require_shared.getGlobalExcludeGlobs(context.cwd))) return {
|
|
197
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
198
|
+
message: "File matches configured excludeGlobs - writing directly."
|
|
199
|
+
};
|
|
195
200
|
const executionLog = new _agiflowai_hooks_adapter.ExecutionLogService(context.session_id);
|
|
196
201
|
if (await executionLog.hasExecuted({
|
|
197
202
|
filePath,
|
|
@@ -228,6 +233,10 @@ var UseScaffoldMethodHook = class {
|
|
|
228
233
|
message: "⚠️ Unexpected response shape from scaffolding methods tool"
|
|
229
234
|
};
|
|
230
235
|
const data = parsed;
|
|
236
|
+
if (require_shared.matchesExcludeGlob(absoluteFilePath, data.excludeGlobs)) return {
|
|
237
|
+
decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
|
|
238
|
+
message: "File matches template exclude globs - writing directly."
|
|
239
|
+
};
|
|
231
240
|
if (!data.methods || data.methods.length === 0) {
|
|
232
241
|
await executionLog.logExecution({
|
|
233
242
|
filePath,
|
package/dist/cli.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
|
|
6
6
|
let node_path = require("node:path");
|
|
7
7
|
node_path = require_ListScaffoldingMethodsTool.__toESM(node_path, 1);
|
|
@@ -712,7 +712,7 @@ const hookCommand = new commander.Command("hook").description("Execute scaffold
|
|
|
712
712
|
const resolvedAdapterConfig = Object.keys(adapterConfig).length > 0 ? adapterConfig : void 0;
|
|
713
713
|
if (!isHookMethod(hookMethod)) process.exit(0);
|
|
714
714
|
if (agent === _agiflowai_coding_agent_bridge.CLAUDE_CODE) {
|
|
715
|
-
const hookModule = await Promise.resolve().then(() => require("./claudeCode-
|
|
715
|
+
const hookModule = await Promise.resolve().then(() => require("./claudeCode-DZnJyFQt.cjs"));
|
|
716
716
|
const claudeCallbacks = [];
|
|
717
717
|
if (hookModule.UseScaffoldMethodHook) {
|
|
718
718
|
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
@@ -727,7 +727,7 @@ const hookCommand = new commander.Command("hook").description("Execute scaffold
|
|
|
727
727
|
if (claudeCallbacks.length === 0) process.exit(0);
|
|
728
728
|
await new _agiflowai_hooks_adapter.ClaudeCodeAdapter().executeMultiple(claudeCallbacks, resolvedAdapterConfig);
|
|
729
729
|
} else if (agent === _agiflowai_coding_agent_bridge.GEMINI_CLI) {
|
|
730
|
-
const hookModule = await Promise.resolve().then(() => require("./geminiCli
|
|
730
|
+
const hookModule = await Promise.resolve().then(() => require("./geminiCli-wTHUG-A6.cjs"));
|
|
731
731
|
const geminiCallbacks = [];
|
|
732
732
|
if (hookModule.UseScaffoldMethodHook) {
|
|
733
733
|
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
@@ -736,7 +736,17 @@ const hookCommand = new commander.Command("hook").description("Execute scaffold
|
|
|
736
736
|
}
|
|
737
737
|
if (geminiCallbacks.length === 0) process.exit(0);
|
|
738
738
|
await new _agiflowai_hooks_adapter.GeminiCliAdapter().executeMultiple(geminiCallbacks, resolvedAdapterConfig);
|
|
739
|
-
} else
|
|
739
|
+
} else if (agent === _agiflowai_coding_agent_bridge.CODEX) {
|
|
740
|
+
const hookModule = await Promise.resolve().then(() => require("./codex-CfOJFx29.cjs"));
|
|
741
|
+
const codexCallbacks = [];
|
|
742
|
+
if (hookModule.UseScaffoldMethodHook) {
|
|
743
|
+
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
744
|
+
const hookFn = hookInstance[hookMethod];
|
|
745
|
+
if (hookFn) codexCallbacks.push(hookFn.bind(hookInstance));
|
|
746
|
+
}
|
|
747
|
+
if (codexCallbacks.length === 0) process.exit(0);
|
|
748
|
+
await new _agiflowai_hooks_adapter.CodexAdapter().executeMultiple(codexCallbacks, resolvedAdapterConfig);
|
|
749
|
+
} else throw new Error(`Unsupported agent: ${agent}. Supported: ${_agiflowai_coding_agent_bridge.CLAUDE_CODE}, ${_agiflowai_coding_agent_bridge.GEMINI_CLI}, ${_agiflowai_coding_agent_bridge.CODEX}`);
|
|
740
750
|
} catch (error) {
|
|
741
751
|
_agiflowai_aicode_utils.print.error(`Hook error: ${error instanceof Error ? error.message : String(error)}`);
|
|
742
752
|
process.exit(1);
|
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as createServer, i as StdioTransportHandler, n as HttpTransportHandler, o as version, r as SseTransportHandler, t as TransportMode } from "./src-
|
|
3
|
-
import { n as ScaffoldingMethodsService, s as FileSystemService } from "./ListScaffoldingMethodsTool-
|
|
4
|
-
import { c as GenerateBoilerplateFileTool, o as GenerateFeatureScaffoldTool, r as writePendingScaffoldLog, s as GenerateBoilerplateTool, t as WriteToFileTool, u as BoilerplateService } from "./tools-
|
|
2
|
+
import { a as createServer, i as StdioTransportHandler, n as HttpTransportHandler, o as version, r as SseTransportHandler, t as TransportMode } from "./src-CUQR93cQ.mjs";
|
|
3
|
+
import { n as ScaffoldingMethodsService, s as FileSystemService } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
4
|
+
import { c as GenerateBoilerplateFileTool, o as GenerateFeatureScaffoldTool, r as writePendingScaffoldLog, s as GenerateBoilerplateTool, t as WriteToFileTool, u as BoilerplateService } from "./tools-BR1vQd_Y.mjs";
|
|
5
5
|
import { ProjectConfigResolver, TemplatesManagerService, generateStableId, icons, messages, print, readFile, sections } from "@agiflowai/aicode-utils";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { CLAUDE_CODE, GEMINI_CLI, SUPPORTED_LLM_TOOLS, isValidLlmTool } from "@agiflowai/coding-agent-bridge";
|
|
9
|
-
import { ClaudeCodeAdapter, GeminiCliAdapter, parseHookType } from "@agiflowai/hooks-adapter";
|
|
8
|
+
import { CLAUDE_CODE, CODEX, GEMINI_CLI, SUPPORTED_LLM_TOOLS, isValidLlmTool } from "@agiflowai/coding-agent-bridge";
|
|
9
|
+
import { ClaudeCodeAdapter, CodexAdapter, GeminiCliAdapter, parseHookType } from "@agiflowai/hooks-adapter";
|
|
10
10
|
//#region src/commands/utils.ts
|
|
11
11
|
function parseJsonOption(value, flagName) {
|
|
12
12
|
if (!value) throw new Error(`${flagName} is required`);
|
|
@@ -711,7 +711,7 @@ const hookCommand = new Command("hook").description("Execute scaffold hooks for
|
|
|
711
711
|
const resolvedAdapterConfig = Object.keys(adapterConfig).length > 0 ? adapterConfig : void 0;
|
|
712
712
|
if (!isHookMethod(hookMethod)) process.exit(0);
|
|
713
713
|
if (agent === CLAUDE_CODE) {
|
|
714
|
-
const hookModule = await import("./claudeCode-
|
|
714
|
+
const hookModule = await import("./claudeCode-CyHppxA_.mjs");
|
|
715
715
|
const claudeCallbacks = [];
|
|
716
716
|
if (hookModule.UseScaffoldMethodHook) {
|
|
717
717
|
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
@@ -726,7 +726,7 @@ const hookCommand = new Command("hook").description("Execute scaffold hooks for
|
|
|
726
726
|
if (claudeCallbacks.length === 0) process.exit(0);
|
|
727
727
|
await new ClaudeCodeAdapter().executeMultiple(claudeCallbacks, resolvedAdapterConfig);
|
|
728
728
|
} else if (agent === GEMINI_CLI) {
|
|
729
|
-
const hookModule = await import("./geminiCli-
|
|
729
|
+
const hookModule = await import("./geminiCli-CgajucCd.mjs");
|
|
730
730
|
const geminiCallbacks = [];
|
|
731
731
|
if (hookModule.UseScaffoldMethodHook) {
|
|
732
732
|
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
@@ -735,7 +735,17 @@ const hookCommand = new Command("hook").description("Execute scaffold hooks for
|
|
|
735
735
|
}
|
|
736
736
|
if (geminiCallbacks.length === 0) process.exit(0);
|
|
737
737
|
await new GeminiCliAdapter().executeMultiple(geminiCallbacks, resolvedAdapterConfig);
|
|
738
|
-
} else
|
|
738
|
+
} else if (agent === CODEX) {
|
|
739
|
+
const hookModule = await import("./codex-7ry557jE.mjs");
|
|
740
|
+
const codexCallbacks = [];
|
|
741
|
+
if (hookModule.UseScaffoldMethodHook) {
|
|
742
|
+
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
743
|
+
const hookFn = hookInstance[hookMethod];
|
|
744
|
+
if (hookFn) codexCallbacks.push(hookFn.bind(hookInstance));
|
|
745
|
+
}
|
|
746
|
+
if (codexCallbacks.length === 0) process.exit(0);
|
|
747
|
+
await new CodexAdapter().executeMultiple(codexCallbacks, resolvedAdapterConfig);
|
|
748
|
+
} else throw new Error(`Unsupported agent: ${agent}. Supported: ${CLAUDE_CODE}, ${GEMINI_CLI}, ${CODEX}`);
|
|
739
749
|
} catch (error) {
|
|
740
750
|
print.error(`Hook error: ${error instanceof Error ? error.message : String(error)}`);
|
|
741
751
|
process.exit(1);
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
2
|
+
import { a as resolveApplyPatchNewFileTargets, i as resolveApplyPatchEditedPaths, n as getGlobalExcludeGlobs, r as matchesExcludeGlob, t as formatScaffoldMethodsHookMessage } from "./shared-BJjclypj.mjs";
|
|
3
|
+
import { ProjectFinderService, TemplatesManagerService } from "@agiflowai/aicode-utils";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { DECISION_ALLOW, DECISION_DENY, DECISION_SKIP, ExecutionLogService } from "@agiflowai/hooks-adapter";
|
|
6
|
+
//#region src/hooks/codex/useScaffoldMethod.ts
|
|
7
|
+
/**
|
|
8
|
+
* Type guard for ScaffoldMethodsResponse
|
|
9
|
+
*/
|
|
10
|
+
function isScaffoldMethodsResponse(value) {
|
|
11
|
+
if (typeof value !== "object" || value === null) return false;
|
|
12
|
+
if ("methods" in value && !Array.isArray(value.methods)) return false;
|
|
13
|
+
if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
|
|
14
|
+
if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* UseScaffoldMethod Hook class for Codex CLI
|
|
19
|
+
*
|
|
20
|
+
* Provides lifecycle hooks for tool execution:
|
|
21
|
+
* - preToolUse: Shows available scaffolding methods before an apply_patch creates new files
|
|
22
|
+
* - postToolUse: Tracks scaffold completion progress after file edits
|
|
23
|
+
*/
|
|
24
|
+
var UseScaffoldMethodHook = class {
|
|
25
|
+
/**
|
|
26
|
+
* PreToolUse hook for Codex CLI
|
|
27
|
+
* Proactively shows available scaffolding methods and guides the agent to use them
|
|
28
|
+
* when an apply_patch is about to create a new file.
|
|
29
|
+
*
|
|
30
|
+
* @param context - Codex CLI hook input
|
|
31
|
+
* @returns Hook response with scaffolding methods guidance
|
|
32
|
+
*/
|
|
33
|
+
async preToolUse(context) {
|
|
34
|
+
try {
|
|
35
|
+
if (!("tool_name" in context)) return {
|
|
36
|
+
decision: DECISION_SKIP,
|
|
37
|
+
message: "Not a tool use event"
|
|
38
|
+
};
|
|
39
|
+
const command = typeof context.tool_input?.command === "string" ? context.tool_input.command : void 0;
|
|
40
|
+
const newFileTargets = resolveApplyPatchNewFileTargets(context.cwd, context.tool_name, command);
|
|
41
|
+
if (newFileTargets.length === 0) return {
|
|
42
|
+
decision: DECISION_SKIP,
|
|
43
|
+
message: "Not a new-file apply_patch operation"
|
|
44
|
+
};
|
|
45
|
+
const globalExcludeGlobs = await getGlobalExcludeGlobs(context.cwd);
|
|
46
|
+
const target = newFileTargets.find((p) => !matchesExcludeGlob(p, globalExcludeGlobs));
|
|
47
|
+
if (!target) return {
|
|
48
|
+
decision: DECISION_ALLOW,
|
|
49
|
+
message: "New file(s) match configured excludeGlobs - writing directly."
|
|
50
|
+
};
|
|
51
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
52
|
+
if (await executionLog.hasExecuted({
|
|
53
|
+
filePath: target,
|
|
54
|
+
decision: DECISION_DENY
|
|
55
|
+
})) return {
|
|
56
|
+
decision: DECISION_SKIP,
|
|
57
|
+
message: "Scaffolding methods already provided for this file"
|
|
58
|
+
};
|
|
59
|
+
const templatesPath = await TemplatesManagerService.findTemplatesPath();
|
|
60
|
+
if (!templatesPath) return {
|
|
61
|
+
decision: DECISION_SKIP,
|
|
62
|
+
message: "Templates folder not found - skipping scaffold method check"
|
|
63
|
+
};
|
|
64
|
+
const tool = new ListScaffoldingMethodsTool(templatesPath, false);
|
|
65
|
+
const projectPath = (await new ProjectFinderService(await TemplatesManagerService.getWorkspaceRoot(context.cwd)).findProjectForFile(target))?.root || context.cwd;
|
|
66
|
+
const result = await tool.execute({ projectPath });
|
|
67
|
+
const firstContent = result.content[0];
|
|
68
|
+
if (firstContent?.type !== "text") return {
|
|
69
|
+
decision: DECISION_SKIP,
|
|
70
|
+
message: "⚠️ Unexpected response type from scaffolding methods tool"
|
|
71
|
+
};
|
|
72
|
+
if (result.isError) return {
|
|
73
|
+
decision: DECISION_SKIP,
|
|
74
|
+
message: `⚠️ Could not load scaffolding methods: ${firstContent.text}`
|
|
75
|
+
};
|
|
76
|
+
const resultText = firstContent.text;
|
|
77
|
+
if (typeof resultText !== "string") return {
|
|
78
|
+
decision: DECISION_SKIP,
|
|
79
|
+
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
80
|
+
};
|
|
81
|
+
const parsed = JSON.parse(resultText);
|
|
82
|
+
if (!isScaffoldMethodsResponse(parsed)) return {
|
|
83
|
+
decision: DECISION_SKIP,
|
|
84
|
+
message: "⚠️ Unexpected response shape from scaffolding methods tool"
|
|
85
|
+
};
|
|
86
|
+
const data = parsed;
|
|
87
|
+
if (matchesExcludeGlob(target, data.excludeGlobs)) return {
|
|
88
|
+
decision: DECISION_ALLOW,
|
|
89
|
+
message: "File matches template exclude globs - writing directly."
|
|
90
|
+
};
|
|
91
|
+
if (!data.methods || data.methods.length === 0) {
|
|
92
|
+
await executionLog.logExecution({
|
|
93
|
+
filePath: target,
|
|
94
|
+
operation: "list-scaffold-methods",
|
|
95
|
+
decision: DECISION_ALLOW
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
decision: DECISION_ALLOW,
|
|
99
|
+
message: "No scaffolding methods are available for this project template. You should write new files directly."
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const message = formatScaffoldMethodsHookMessage(data.methods);
|
|
103
|
+
await executionLog.logExecution({
|
|
104
|
+
filePath: target,
|
|
105
|
+
operation: "list-scaffold-methods",
|
|
106
|
+
decision: DECISION_DENY
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
decision: DECISION_DENY,
|
|
110
|
+
message
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
decision: DECISION_SKIP,
|
|
115
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* PostToolUse hook for Codex CLI
|
|
121
|
+
* Tracks file edits after scaffold generation and reminds the agent to complete
|
|
122
|
+
* the implementation of all scaffold-generated files.
|
|
123
|
+
*
|
|
124
|
+
* @param context - Codex CLI hook input
|
|
125
|
+
* @returns Hook response with scaffold completion tracking
|
|
126
|
+
*/
|
|
127
|
+
async postToolUse(context) {
|
|
128
|
+
try {
|
|
129
|
+
if (!("tool_name" in context)) return {
|
|
130
|
+
decision: DECISION_SKIP,
|
|
131
|
+
message: "Not a tool use event"
|
|
132
|
+
};
|
|
133
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
134
|
+
if (context.tool_name === "use-scaffold-method" || context.tool_input?.toolName === "use-scaffold-method") return {
|
|
135
|
+
decision: DECISION_ALLOW,
|
|
136
|
+
message: "Scaffold execution logged for progress tracking"
|
|
137
|
+
};
|
|
138
|
+
const command = typeof context.tool_input?.command === "string" ? context.tool_input.command : void 0;
|
|
139
|
+
const editedPaths = resolveApplyPatchEditedPaths(context.tool_name, command);
|
|
140
|
+
if (editedPaths.length === 0) return {
|
|
141
|
+
decision: DECISION_SKIP,
|
|
142
|
+
message: "Not a file edit/write operation"
|
|
143
|
+
};
|
|
144
|
+
const lastScaffoldExecution = await getLastScaffoldExecution(executionLog);
|
|
145
|
+
if (!lastScaffoldExecution) return {
|
|
146
|
+
decision: DECISION_SKIP,
|
|
147
|
+
message: "No scaffold execution found"
|
|
148
|
+
};
|
|
149
|
+
const { scaffoldId, generatedFiles, featureName } = lastScaffoldExecution;
|
|
150
|
+
const fulfilledKey = `scaffold-fulfilled-${scaffoldId}`;
|
|
151
|
+
if (await executionLog.hasExecuted({
|
|
152
|
+
filePath: fulfilledKey,
|
|
153
|
+
decision: DECISION_ALLOW
|
|
154
|
+
})) return {
|
|
155
|
+
decision: DECISION_SKIP,
|
|
156
|
+
message: "Scaffold already fulfilled"
|
|
157
|
+
};
|
|
158
|
+
let touchedScaffoldFile = false;
|
|
159
|
+
for (const editedPath of editedPaths) {
|
|
160
|
+
const absoluteEditedPath = path.isAbsolute(editedPath) ? editedPath : path.join(context.cwd, editedPath);
|
|
161
|
+
const generatedMatch = generatedFiles.find((f) => f === editedPath || f === absoluteEditedPath);
|
|
162
|
+
if (!generatedMatch) continue;
|
|
163
|
+
touchedScaffoldFile = true;
|
|
164
|
+
const editKey = `scaffold-edit-${scaffoldId}-${generatedMatch}`;
|
|
165
|
+
if (!await executionLog.hasExecuted({
|
|
166
|
+
filePath: editKey,
|
|
167
|
+
decision: DECISION_ALLOW
|
|
168
|
+
})) await executionLog.logExecution({
|
|
169
|
+
filePath: editKey,
|
|
170
|
+
operation: "scaffold-file-edit",
|
|
171
|
+
decision: DECISION_ALLOW
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const editedFiles = await getEditedScaffoldFiles(executionLog, scaffoldId);
|
|
175
|
+
const totalFiles = generatedFiles.length;
|
|
176
|
+
const remainingFiles = generatedFiles.filter((f) => !editedFiles.includes(f));
|
|
177
|
+
if (remainingFiles.length === 0) {
|
|
178
|
+
await executionLog.logExecution({
|
|
179
|
+
filePath: fulfilledKey,
|
|
180
|
+
operation: "scaffold-fulfilled",
|
|
181
|
+
decision: DECISION_ALLOW
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
decision: DECISION_ALLOW,
|
|
185
|
+
message: `✅ All scaffold files${featureName ? ` for "${featureName}"` : ""} have been implemented! (${totalFiles}/${totalFiles} files completed)`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (touchedScaffoldFile) {
|
|
189
|
+
const remainingFilesList = remainingFiles.map((f) => ` - ${f}`).join("\n");
|
|
190
|
+
return {
|
|
191
|
+
decision: DECISION_ALLOW,
|
|
192
|
+
message: `
|
|
193
|
+
⚠️ **Scaffold Implementation Progress${featureName ? ` for "${featureName}"` : ""}: ${editedFiles.length}/${totalFiles} files completed**
|
|
194
|
+
|
|
195
|
+
**Remaining files to implement:**
|
|
196
|
+
${remainingFilesList}
|
|
197
|
+
|
|
198
|
+
Don't forget to complete the implementation for all scaffolded files!
|
|
199
|
+
`.trim()
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
decision: DECISION_SKIP,
|
|
204
|
+
message: "Edited file not part of last scaffold execution"
|
|
205
|
+
};
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
decision: DECISION_SKIP,
|
|
209
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Helper function to get the last scaffold execution for a session.
|
|
216
|
+
* Returns null if no scaffold execution found or on error.
|
|
217
|
+
*/
|
|
218
|
+
async function getLastScaffoldExecution(executionLog) {
|
|
219
|
+
try {
|
|
220
|
+
const entries = await executionLog.loadLog();
|
|
221
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
222
|
+
const entry = entries[i];
|
|
223
|
+
if (entry.operation === "scaffold" && entry.scaffoldId && entry.generatedFiles && entry.generatedFiles.length > 0) return {
|
|
224
|
+
scaffoldId: entry.scaffoldId,
|
|
225
|
+
generatedFiles: entry.generatedFiles,
|
|
226
|
+
featureName: entry.featureName
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error("Error getting last scaffold execution:", error);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Helper function to get the list of edited scaffold files.
|
|
237
|
+
* Returns an empty array if no files found or on error.
|
|
238
|
+
*/
|
|
239
|
+
async function getEditedScaffoldFiles(executionLog, scaffoldId) {
|
|
240
|
+
try {
|
|
241
|
+
const entries = await executionLog.loadLog();
|
|
242
|
+
const editedFiles = [];
|
|
243
|
+
for (const entry of entries) if (entry.operation === "scaffold-file-edit" && entry.filePath.startsWith(`scaffold-edit-${scaffoldId}-`)) {
|
|
244
|
+
const filePath = entry.filePath.replace(`scaffold-edit-${scaffoldId}-`, "");
|
|
245
|
+
editedFiles.push(filePath);
|
|
246
|
+
}
|
|
247
|
+
return editedFiles;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`Error getting edited scaffold files for ${scaffoldId}:`, error);
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { UseScaffoldMethodHook };
|