@agiflowai/scaffold-mcp 1.0.22 → 1.0.24

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 CHANGED
@@ -149,6 +149,17 @@ npx @agiflowai/scaffold-mcp mcp-serve --type sse --port 3000
149
149
 
150
150
  # With admin tools enabled
151
151
  npx @agiflowai/scaffold-mcp mcp-serve --admin-enable
152
+
153
+ # With Claude Code skill front matter on prompts
154
+ npx @agiflowai/scaffold-mcp mcp-serve --prompt-as-skill
155
+
156
+ # With a fallback LLM tool for scaffold operations
157
+ npx @agiflowai/scaffold-mcp mcp-serve --fallback-tool claude-code
158
+
159
+ # Fallback tool with a specific model config
160
+ npx @agiflowai/scaffold-mcp mcp-serve \
161
+ --fallback-tool claude-code \
162
+ --fallback-tool-config '{"model":"claude-sonnet-4-6"}'
152
163
  ```
153
164
 
154
165
  | Option | Description | Default |
@@ -157,6 +168,9 @@ npx @agiflowai/scaffold-mcp mcp-serve --admin-enable
157
168
  | `-p, --port` | Port for HTTP/SSE | `3000` |
158
169
  | `--host` | Host for HTTP/SSE | `localhost` |
159
170
  | `--admin-enable` | Enable template creation tools | `false` |
171
+ | `--prompt-as-skill` | Render MCP prompts with Claude Code skill front matter, exposing them as `/skill` commands | `false` |
172
+ | `--fallback-tool` | LLM tool for scaffold operations (`claude-code`, `gemini-cli`, `codex`) | disabled |
173
+ | `--fallback-tool-config` | JSON config for the fallback tool (e.g., `{"model":"claude-sonnet-4-6"}`) | `{}` |
160
174
 
161
175
  ---
162
176
 
@@ -25,16 +25,91 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  }) : target, mod));
26
26
 
27
27
  //#endregion
28
+ let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
29
+ let zod = require("zod");
30
+ let liquidjs = require("liquidjs");
28
31
  let node_path = require("node:path");
29
32
  node_path = __toESM(node_path);
30
- let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
31
33
  let js_yaml = require("js-yaml");
32
34
  js_yaml = __toESM(js_yaml);
33
35
  let __composio_json_schema_to_zod = require("@composio/json-schema-to-zod");
34
- let zod = require("zod");
35
36
  let node_url = require("node:url");
36
- let liquidjs = require("liquidjs");
37
37
 
38
+ //#region src/services/TemplateService.ts
39
+ var TemplateService = class {
40
+ liquid;
41
+ constructor() {
42
+ this.liquid = new liquidjs.Liquid({
43
+ strictFilters: false,
44
+ strictVariables: false
45
+ });
46
+ this.setupCustomFilters();
47
+ __agiflowai_aicode_utils.log.info("TemplateService initialized");
48
+ }
49
+ toPascalCase(str) {
50
+ const camelCase = str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
51
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
52
+ }
53
+ setupCustomFilters() {
54
+ this.liquid.registerFilter("camelCase", (str) => {
55
+ return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
56
+ });
57
+ this.liquid.registerFilter("pascalCase", (str) => {
58
+ return this.toPascalCase(str);
59
+ });
60
+ this.liquid.registerFilter("titleCase", (str) => {
61
+ return this.toPascalCase(str);
62
+ });
63
+ this.liquid.registerFilter("kebabCase", (str) => {
64
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
65
+ });
66
+ this.liquid.registerFilter("snakeCase", (str) => {
67
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
68
+ });
69
+ this.liquid.registerFilter("upperCase", (str) => {
70
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toUpperCase();
71
+ });
72
+ this.liquid.registerFilter("lower", (str) => str.toLowerCase());
73
+ this.liquid.registerFilter("upper", (str) => str.toUpperCase());
74
+ this.liquid.registerFilter("pluralize", (str) => {
75
+ if (str.endsWith("y")) return `${str.slice(0, -1)}ies`;
76
+ else if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch") || str.endsWith("x") || str.endsWith("z")) return `${str}es`;
77
+ else return `${str}s`;
78
+ });
79
+ this.liquid.registerFilter("singularize", (str) => {
80
+ if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
81
+ else if (str.endsWith("es")) return str.slice(0, -2);
82
+ else if (str.endsWith("s") && !str.endsWith("ss")) return str.slice(0, -1);
83
+ else return str;
84
+ });
85
+ this.liquid.registerFilter("strip", (str) => {
86
+ return str.trim();
87
+ });
88
+ }
89
+ renderString(template, variables) {
90
+ try {
91
+ __agiflowai_aicode_utils.log.debug("Rendering template", {
92
+ variables,
93
+ templatePreview: template.substring(0, 100)
94
+ });
95
+ const result = this.liquid.parseAndRenderSync(template, variables);
96
+ __agiflowai_aicode_utils.log.debug("Rendered template", { resultPreview: result.substring(0, 100) });
97
+ return result;
98
+ } catch (error) {
99
+ __agiflowai_aicode_utils.log.error("LiquidJS rendering error", {
100
+ error: error instanceof Error ? error.message : String(error),
101
+ templatePreview: template.substring(0, 200),
102
+ variables
103
+ });
104
+ return template;
105
+ }
106
+ }
107
+ containsTemplateVariables(content) {
108
+ return [/\{\{.*?\}\}/, /\{%.*?%\}/].some((pattern) => pattern.test(content));
109
+ }
110
+ };
111
+
112
+ //#endregion
38
113
  //#region src/utils/pagination.ts
39
114
  var PaginationHelper = class PaginationHelper {
40
115
  /**
@@ -622,81 +697,6 @@ var ScaffoldService = class ScaffoldService {
622
697
  }
623
698
  };
624
699
 
625
- //#endregion
626
- //#region src/services/TemplateService.ts
627
- var TemplateService = class {
628
- liquid;
629
- constructor() {
630
- this.liquid = new liquidjs.Liquid({
631
- strictFilters: false,
632
- strictVariables: false
633
- });
634
- this.setupCustomFilters();
635
- __agiflowai_aicode_utils.log.info("TemplateService initialized");
636
- }
637
- toPascalCase(str) {
638
- const camelCase = str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
639
- return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
640
- }
641
- setupCustomFilters() {
642
- this.liquid.registerFilter("camelCase", (str) => {
643
- return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
644
- });
645
- this.liquid.registerFilter("pascalCase", (str) => {
646
- return this.toPascalCase(str);
647
- });
648
- this.liquid.registerFilter("titleCase", (str) => {
649
- return this.toPascalCase(str);
650
- });
651
- this.liquid.registerFilter("kebabCase", (str) => {
652
- return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
653
- });
654
- this.liquid.registerFilter("snakeCase", (str) => {
655
- return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
656
- });
657
- this.liquid.registerFilter("upperCase", (str) => {
658
- return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toUpperCase();
659
- });
660
- this.liquid.registerFilter("lower", (str) => str.toLowerCase());
661
- this.liquid.registerFilter("upper", (str) => str.toUpperCase());
662
- this.liquid.registerFilter("pluralize", (str) => {
663
- if (str.endsWith("y")) return `${str.slice(0, -1)}ies`;
664
- else if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch") || str.endsWith("x") || str.endsWith("z")) return `${str}es`;
665
- else return `${str}s`;
666
- });
667
- this.liquid.registerFilter("singularize", (str) => {
668
- if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
669
- else if (str.endsWith("es")) return str.slice(0, -2);
670
- else if (str.endsWith("s") && !str.endsWith("ss")) return str.slice(0, -1);
671
- else return str;
672
- });
673
- this.liquid.registerFilter("strip", (str) => {
674
- return str.trim();
675
- });
676
- }
677
- renderString(template, variables) {
678
- try {
679
- __agiflowai_aicode_utils.log.debug("Rendering template", {
680
- variables,
681
- templatePreview: template.substring(0, 100)
682
- });
683
- const result = this.liquid.parseAndRenderSync(template, variables);
684
- __agiflowai_aicode_utils.log.debug("Rendered template", { resultPreview: result.substring(0, 100) });
685
- return result;
686
- } catch (error) {
687
- __agiflowai_aicode_utils.log.error("LiquidJS rendering error", {
688
- error: error instanceof Error ? error.message : String(error),
689
- templatePreview: template.substring(0, 200),
690
- variables
691
- });
692
- return template;
693
- }
694
- }
695
- containsTemplateVariables(content) {
696
- return [/\{\{.*?\}\}/, /\{%.*?%\}/].some((pattern) => pattern.test(content));
697
- }
698
- };
699
-
700
700
  //#endregion
701
701
  //#region src/services/VariableReplacementService.ts
702
702
  var VariableReplacementService = class {
@@ -1,11 +1,86 @@
1
- import path from "node:path";
2
1
  import { ProjectConfigResolver, TemplatesManagerService, copy, ensureDir, log, pathExists, readFile, readJson, readdir, stat, writeFile } from "@agiflowai/aicode-utils";
2
+ import { z } from "zod";
3
+ import { Liquid } from "liquidjs";
4
+ import path from "node:path";
3
5
  import yaml from "js-yaml";
4
6
  import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
5
- import { z } from "zod";
6
7
  import { fileURLToPath } from "node:url";
7
- import { Liquid } from "liquidjs";
8
8
 
9
+ //#region src/services/TemplateService.ts
10
+ var TemplateService = class {
11
+ liquid;
12
+ constructor() {
13
+ this.liquid = new Liquid({
14
+ strictFilters: false,
15
+ strictVariables: false
16
+ });
17
+ this.setupCustomFilters();
18
+ log.info("TemplateService initialized");
19
+ }
20
+ toPascalCase(str) {
21
+ const camelCase = str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
22
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
23
+ }
24
+ setupCustomFilters() {
25
+ this.liquid.registerFilter("camelCase", (str) => {
26
+ return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
27
+ });
28
+ this.liquid.registerFilter("pascalCase", (str) => {
29
+ return this.toPascalCase(str);
30
+ });
31
+ this.liquid.registerFilter("titleCase", (str) => {
32
+ return this.toPascalCase(str);
33
+ });
34
+ this.liquid.registerFilter("kebabCase", (str) => {
35
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
36
+ });
37
+ this.liquid.registerFilter("snakeCase", (str) => {
38
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
39
+ });
40
+ this.liquid.registerFilter("upperCase", (str) => {
41
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toUpperCase();
42
+ });
43
+ this.liquid.registerFilter("lower", (str) => str.toLowerCase());
44
+ this.liquid.registerFilter("upper", (str) => str.toUpperCase());
45
+ this.liquid.registerFilter("pluralize", (str) => {
46
+ if (str.endsWith("y")) return `${str.slice(0, -1)}ies`;
47
+ else if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch") || str.endsWith("x") || str.endsWith("z")) return `${str}es`;
48
+ else return `${str}s`;
49
+ });
50
+ this.liquid.registerFilter("singularize", (str) => {
51
+ if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
52
+ else if (str.endsWith("es")) return str.slice(0, -2);
53
+ else if (str.endsWith("s") && !str.endsWith("ss")) return str.slice(0, -1);
54
+ else return str;
55
+ });
56
+ this.liquid.registerFilter("strip", (str) => {
57
+ return str.trim();
58
+ });
59
+ }
60
+ renderString(template, variables) {
61
+ try {
62
+ log.debug("Rendering template", {
63
+ variables,
64
+ templatePreview: template.substring(0, 100)
65
+ });
66
+ const result = this.liquid.parseAndRenderSync(template, variables);
67
+ log.debug("Rendered template", { resultPreview: result.substring(0, 100) });
68
+ return result;
69
+ } catch (error) {
70
+ log.error("LiquidJS rendering error", {
71
+ error: error instanceof Error ? error.message : String(error),
72
+ templatePreview: template.substring(0, 200),
73
+ variables
74
+ });
75
+ return template;
76
+ }
77
+ }
78
+ containsTemplateVariables(content) {
79
+ return [/\{\{.*?\}\}/, /\{%.*?%\}/].some((pattern) => pattern.test(content));
80
+ }
81
+ };
82
+
83
+ //#endregion
9
84
  //#region src/utils/pagination.ts
10
85
  var PaginationHelper = class PaginationHelper {
11
86
  /**
@@ -593,81 +668,6 @@ var ScaffoldService = class ScaffoldService {
593
668
  }
594
669
  };
595
670
 
596
- //#endregion
597
- //#region src/services/TemplateService.ts
598
- var TemplateService = class {
599
- liquid;
600
- constructor() {
601
- this.liquid = new Liquid({
602
- strictFilters: false,
603
- strictVariables: false
604
- });
605
- this.setupCustomFilters();
606
- log.info("TemplateService initialized");
607
- }
608
- toPascalCase(str) {
609
- const camelCase = str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
610
- return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
611
- }
612
- setupCustomFilters() {
613
- this.liquid.registerFilter("camelCase", (str) => {
614
- return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
615
- });
616
- this.liquid.registerFilter("pascalCase", (str) => {
617
- return this.toPascalCase(str);
618
- });
619
- this.liquid.registerFilter("titleCase", (str) => {
620
- return this.toPascalCase(str);
621
- });
622
- this.liquid.registerFilter("kebabCase", (str) => {
623
- return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
624
- });
625
- this.liquid.registerFilter("snakeCase", (str) => {
626
- return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
627
- });
628
- this.liquid.registerFilter("upperCase", (str) => {
629
- return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toUpperCase();
630
- });
631
- this.liquid.registerFilter("lower", (str) => str.toLowerCase());
632
- this.liquid.registerFilter("upper", (str) => str.toUpperCase());
633
- this.liquid.registerFilter("pluralize", (str) => {
634
- if (str.endsWith("y")) return `${str.slice(0, -1)}ies`;
635
- else if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch") || str.endsWith("x") || str.endsWith("z")) return `${str}es`;
636
- else return `${str}s`;
637
- });
638
- this.liquid.registerFilter("singularize", (str) => {
639
- if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
640
- else if (str.endsWith("es")) return str.slice(0, -2);
641
- else if (str.endsWith("s") && !str.endsWith("ss")) return str.slice(0, -1);
642
- else return str;
643
- });
644
- this.liquid.registerFilter("strip", (str) => {
645
- return str.trim();
646
- });
647
- }
648
- renderString(template, variables) {
649
- try {
650
- log.debug("Rendering template", {
651
- variables,
652
- templatePreview: template.substring(0, 100)
653
- });
654
- const result = this.liquid.parseAndRenderSync(template, variables);
655
- log.debug("Rendered template", { resultPreview: result.substring(0, 100) });
656
- return result;
657
- } catch (error) {
658
- log.error("LiquidJS rendering error", {
659
- error: error instanceof Error ? error.message : String(error),
660
- templatePreview: template.substring(0, 200),
661
- variables
662
- });
663
- return template;
664
- }
665
- }
666
- containsTemplateVariables(content) {
667
- return [/\{\{.*?\}\}/, /\{%.*?%\}/].some((pattern) => pattern.test(content));
668
- }
669
- };
670
-
671
671
  //#endregion
672
672
  //#region src/services/VariableReplacementService.ts
673
673
  var VariableReplacementService = class {
@@ -1054,4 +1054,4 @@ var ListScaffoldingMethodsTool = class ListScaffoldingMethodsTool {
1054
1054
  };
1055
1055
 
1056
1056
  //#endregion
1057
- export { ScaffoldService as a, FileSystemService as c, TemplateService as i, PaginationHelper as l, ScaffoldingMethodsService as n, ScaffoldProcessingService as o, VariableReplacementService as r, ScaffoldConfigLoader as s, ListScaffoldingMethodsTool as t };
1057
+ export { ScaffoldProcessingService as a, PaginationHelper as c, ScaffoldService as i, TemplateService as l, ScaffoldingMethodsService as n, ScaffoldConfigLoader as o, VariableReplacementService as r, FileSystemService as s, ListScaffoldingMethodsTool as t };
@@ -1,14 +1,154 @@
1
- const require_ListScaffoldingMethodsTool = require('./ListScaffoldingMethodsTool-Dnd3E5X_.cjs');
2
- require('./tools-S18iKO9I.cjs');
1
+ const require_ListScaffoldingMethodsTool = require('./ListScaffoldingMethodsTool-CkIkgP_u.cjs');
2
+ require('./tools-CC-lrhQ8.cjs');
3
+ let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
3
4
  let node_path = require("node:path");
4
5
  node_path = require_ListScaffoldingMethodsTool.__toESM(node_path);
5
- let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
6
6
  let node_fs_promises = require("node:fs/promises");
7
7
  node_fs_promises = require_ListScaffoldingMethodsTool.__toESM(node_fs_promises);
8
8
  let node_os = require("node:os");
9
9
  node_os = require_ListScaffoldingMethodsTool.__toESM(node_os);
10
10
  let __agiflowai_hooks_adapter = require("@agiflowai/hooks-adapter");
11
+ let node_child_process = require("node:child_process");
12
+
13
+ //#region src/hooks/claudeCode/phantomCodeCheck.ts
14
+ /**
15
+ * PhantomCodeCheck Hook for Claude Code
16
+ *
17
+ * DESIGN PATTERNS:
18
+ * - Class-based hook pattern: Encapsulates lifecycle hooks in a single class
19
+ * - Fail-open pattern: Errors allow operation to proceed (return DECISION_SKIP)
20
+ * - Marker-based detection: Scans for scaffold marker comments in code files
21
+ *
22
+ * CODING STANDARDS:
23
+ * - Export a class with stop, userPromptSubmit, taskCompleted methods
24
+ * - Handle all errors gracefully with fail-open behavior
25
+ * - Use execFileSync with args array to avoid shell injection
26
+ *
27
+ * AVOID:
28
+ * - Blocking operations on errors
29
+ * - Shell injection via marker parameter
30
+ * - Mutating context object
31
+ */
32
+ const EXCLUDED_DIRS = [
33
+ "node_modules",
34
+ "dist",
35
+ ".git",
36
+ ".next",
37
+ "build",
38
+ "coverage",
39
+ ".claude"
40
+ ];
41
+ /**
42
+ * PhantomCodeCheckHook — scans for unimplemented scaffold files containing marker comments.
43
+ *
44
+ * Checks at session boundaries (Stop, UserPromptSubmit, TaskCompleted) whether
45
+ * any generated files still carry the `// <marker>` comment, indicating they
46
+ * have not yet been implemented by the AI agent.
47
+ */
48
+ var PhantomCodeCheckHook = class {
49
+ markerComment;
50
+ constructor(marker = "@scaffold-generated") {
51
+ this.markerComment = `// ${marker}`;
52
+ }
53
+ /**
54
+ * Scans cwd for files containing the scaffold marker comment.
55
+ * Returns relative file paths. Returns empty array on any error (fail-open).
56
+ */
57
+ scanForPhantomFiles(cwd) {
58
+ try {
59
+ return (0, node_child_process.execFileSync)("grep", [
60
+ "-rl",
61
+ this.markerComment,
62
+ "--include=*.ts",
63
+ "--include=*.tsx",
64
+ "--include=*.js",
65
+ "--include=*.jsx",
66
+ ...EXCLUDED_DIRS.map((dir) => `--exclude-dir=${dir}`),
67
+ "."
68
+ ], {
69
+ cwd,
70
+ timeout: 1e4,
71
+ encoding: "utf8"
72
+ }).trim().split("\n").filter(Boolean).map((f) => f.replace(/^\.\//, ""));
73
+ } catch (error) {
74
+ if (error instanceof Error && "status" in error && error.status === 1) return [];
75
+ return [];
76
+ }
77
+ }
78
+ /**
79
+ * Stop hook — blocks session end if phantom files are found.
80
+ * Returns DECISION_DENY to prevent Claude from stopping with unimplemented files.
81
+ */
82
+ async stop(context) {
83
+ try {
84
+ const phantomFiles = this.scanForPhantomFiles(context.cwd);
85
+ if (phantomFiles.length === 0) return {
86
+ decision: __agiflowai_hooks_adapter.DECISION_SKIP,
87
+ message: "No phantom scaffold files found"
88
+ };
89
+ const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
90
+ return {
91
+ decision: __agiflowai_hooks_adapter.DECISION_DENY,
92
+ message: `⚠️ ${phantomFiles.length} scaffold file(s) still contain \`${this.markerComment}\` and have not been implemented:\n${fileList}\n\nPlease implement these files and remove the marker comment before ending the session.`
93
+ };
94
+ } catch {
95
+ return {
96
+ decision: __agiflowai_hooks_adapter.DECISION_SKIP,
97
+ message: "PhantomCodeCheckHook.stop error — skipping"
98
+ };
99
+ }
100
+ }
101
+ /**
102
+ * UserPromptSubmit hook — warns about phantom files without blocking.
103
+ * Returns DECISION_ALLOW with userMessage written to stderr (visible to user, not LLM).
104
+ */
105
+ async userPromptSubmit(context) {
106
+ try {
107
+ const phantomFiles = this.scanForPhantomFiles(context.cwd);
108
+ if (phantomFiles.length === 0) return {
109
+ decision: __agiflowai_hooks_adapter.DECISION_SKIP,
110
+ message: "No phantom scaffold files found"
111
+ };
112
+ const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
113
+ return {
114
+ decision: __agiflowai_hooks_adapter.DECISION_ALLOW,
115
+ message: "",
116
+ userMessage: `⚠️ Reminder: ${phantomFiles.length} scaffold file(s) still contain \`${this.markerComment}\`:\n${fileList}\n\nPlease implement these files and remove the marker comment.`
117
+ };
118
+ } catch {
119
+ return {
120
+ decision: __agiflowai_hooks_adapter.DECISION_SKIP,
121
+ message: "PhantomCodeCheckHook.userPromptSubmit error — skipping"
122
+ };
123
+ }
124
+ }
125
+ /**
126
+ * TaskCompleted hook — blocks task completion if phantom files are found.
127
+ * Returns DECISION_DENY with exitCode 2 to signal incomplete scaffolding.
128
+ */
129
+ async taskCompleted(context) {
130
+ try {
131
+ const phantomFiles = this.scanForPhantomFiles(context.cwd);
132
+ if (phantomFiles.length === 0) return {
133
+ decision: __agiflowai_hooks_adapter.DECISION_SKIP,
134
+ message: "No phantom scaffold files found"
135
+ };
136
+ const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
137
+ return {
138
+ decision: __agiflowai_hooks_adapter.DECISION_DENY,
139
+ exitCode: 2,
140
+ message: `⚠️ ${phantomFiles.length} scaffold file(s) still contain \`${this.markerComment}\` and have not been implemented:\n${fileList}\n\nTask cannot complete until all scaffold files are implemented.`
141
+ };
142
+ } catch {
143
+ return {
144
+ decision: __agiflowai_hooks_adapter.DECISION_SKIP,
145
+ message: "PhantomCodeCheckHook.taskCompleted error — skipping"
146
+ };
147
+ }
148
+ }
149
+ };
11
150
 
151
+ //#endregion
12
152
  //#region src/hooks/claudeCode/useScaffoldMethod.ts
13
153
  /**
14
154
  * Type guard for ScaffoldMethodsResponse
@@ -323,4 +463,5 @@ async function processPendingScaffoldLogs(sessionId, scaffoldId) {
323
463
  }
324
464
 
325
465
  //#endregion
466
+ exports.PhantomCodeCheckHook = PhantomCodeCheckHook;
326
467
  exports.UseScaffoldMethodHook = UseScaffoldMethodHook;