@agiflowai/scaffold-mcp 1.0.21 → 1.0.23

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.
@@ -1,19 +1,13 @@
1
- import { a as ScaffoldService, c as FileSystemService, i as TemplateService, l as PaginationHelper, n as ScaffoldingMethodsService, r as VariableReplacementService, s as ScaffoldConfigLoader } from "./ListScaffoldingMethodsTool-DjhhMWjh.mjs";
1
+ import { c as PaginationHelper, i as ScaffoldService, l as TemplateService, n as ScaffoldingMethodsService, o as ScaffoldConfigLoader, r as VariableReplacementService, s as FileSystemService } from "./ListScaffoldingMethodsTool-Cx-0gpV3.mjs";
2
+ import { ProjectConfigResolver, ensureDir, generateStableId, log, pathExists, pathExistsSync, readFile, readFileSync, readdir, statSync, writeFile } from "@agiflowai/aicode-utils";
3
+ import { z } from "zod";
2
4
  import * as path$1 from "node:path";
3
5
  import path from "node:path";
4
- import { ProjectConfigResolver, ensureDir, generateStableId, log, pathExists, pathExistsSync, readFile, readFileSync, readdir, statSync, writeFile } from "@agiflowai/aicode-utils";
5
6
  import * as yaml$1 from "js-yaml";
6
7
  import { readdirSync } from "node:fs";
7
8
  import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
8
- import { z } from "zod";
9
9
  import * as fs$1 from "node:fs/promises";
10
10
  import * as os$1 from "node:os";
11
- import { randomUUID } from "node:crypto";
12
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
14
- import express from "express";
15
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
16
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
11
 
18
12
  //#region src/services/BoilerplateGeneratorService.ts
19
13
  /**
@@ -1524,327 +1518,4 @@ Parameters:
1524
1518
  };
1525
1519
 
1526
1520
  //#endregion
1527
- //#region src/transports/http.ts
1528
- /**
1529
- * HTTP session manager
1530
- */
1531
- var HttpFullSessionManager = class {
1532
- sessions = /* @__PURE__ */ new Map();
1533
- getSession(sessionId) {
1534
- return this.sessions.get(sessionId);
1535
- }
1536
- setSession(sessionId, transport, server) {
1537
- this.sessions.set(sessionId, {
1538
- transport,
1539
- server
1540
- });
1541
- }
1542
- deleteSession(sessionId) {
1543
- const session = this.sessions.get(sessionId);
1544
- if (session) session.server.close();
1545
- this.sessions.delete(sessionId);
1546
- }
1547
- hasSession(sessionId) {
1548
- return this.sessions.has(sessionId);
1549
- }
1550
- clear() {
1551
- for (const session of this.sessions.values()) session.server.close();
1552
- this.sessions.clear();
1553
- }
1554
- };
1555
- /**
1556
- * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
1557
- * Provides stateful session management with resumability support
1558
- */
1559
- var HttpTransportHandler = class {
1560
- serverFactory;
1561
- app;
1562
- server = null;
1563
- sessionManager;
1564
- config;
1565
- constructor(serverFactory, config) {
1566
- this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
1567
- this.app = express();
1568
- this.sessionManager = new HttpFullSessionManager();
1569
- this.config = {
1570
- mode: config.mode,
1571
- port: config.port ?? 3e3,
1572
- host: config.host ?? "localhost"
1573
- };
1574
- this.setupMiddleware();
1575
- this.setupRoutes();
1576
- }
1577
- setupMiddleware() {
1578
- this.app.use(express.json());
1579
- }
1580
- setupRoutes() {
1581
- this.app.post("/mcp", async (req, res) => {
1582
- await this.handlePostRequest(req, res);
1583
- });
1584
- this.app.get("/mcp", async (req, res) => {
1585
- await this.handleGetRequest(req, res);
1586
- });
1587
- this.app.delete("/mcp", async (req, res) => {
1588
- await this.handleDeleteRequest(req, res);
1589
- });
1590
- this.app.get("/health", (_req, res) => {
1591
- res.json({
1592
- status: "ok",
1593
- transport: "http"
1594
- });
1595
- });
1596
- }
1597
- async handlePostRequest(req, res) {
1598
- const sessionId = req.headers["mcp-session-id"];
1599
- let transport;
1600
- if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
1601
- else if (!sessionId && isInitializeRequest(req.body)) {
1602
- const mcpServer = this.serverFactory();
1603
- transport = new StreamableHTTPServerTransport({
1604
- sessionIdGenerator: () => randomUUID(),
1605
- enableJsonResponse: true,
1606
- onsessioninitialized: (sessionId$1) => {
1607
- this.sessionManager.setSession(sessionId$1, transport, mcpServer);
1608
- }
1609
- });
1610
- transport.onclose = () => {
1611
- if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
1612
- };
1613
- await mcpServer.connect(transport);
1614
- } else {
1615
- res.status(400).json({
1616
- jsonrpc: "2.0",
1617
- error: {
1618
- code: -32e3,
1619
- message: "Bad Request: No valid session ID provided"
1620
- },
1621
- id: null
1622
- });
1623
- return;
1624
- }
1625
- await transport.handleRequest(req, res, req.body);
1626
- }
1627
- async handleGetRequest(req, res) {
1628
- const sessionId = req.headers["mcp-session-id"];
1629
- if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
1630
- res.status(400).send("Invalid or missing session ID");
1631
- return;
1632
- }
1633
- await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
1634
- }
1635
- async handleDeleteRequest(req, res) {
1636
- const sessionId = req.headers["mcp-session-id"];
1637
- if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
1638
- res.status(400).send("Invalid or missing session ID");
1639
- return;
1640
- }
1641
- await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
1642
- this.sessionManager.deleteSession(sessionId);
1643
- }
1644
- async start() {
1645
- return new Promise((resolve, reject) => {
1646
- try {
1647
- this.server = this.app.listen(this.config.port, this.config.host, () => {
1648
- console.error(`Scaffolding MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
1649
- console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
1650
- resolve();
1651
- });
1652
- this.server.on("error", (error) => {
1653
- reject(error);
1654
- });
1655
- } catch (error) {
1656
- reject(error);
1657
- }
1658
- });
1659
- }
1660
- async stop() {
1661
- return new Promise((resolve, reject) => {
1662
- if (this.server) {
1663
- this.sessionManager.clear();
1664
- this.server.close((err) => {
1665
- if (err) reject(err);
1666
- else {
1667
- this.server = null;
1668
- resolve();
1669
- }
1670
- });
1671
- } else resolve();
1672
- });
1673
- }
1674
- getPort() {
1675
- return this.config.port;
1676
- }
1677
- getHost() {
1678
- return this.config.host;
1679
- }
1680
- };
1681
-
1682
- //#endregion
1683
- //#region src/transports/sse.ts
1684
- /**
1685
- * Session manager for SSE transports
1686
- */
1687
- var SseSessionManager = class {
1688
- sessions = /* @__PURE__ */ new Map();
1689
- getSession(sessionId) {
1690
- return this.sessions.get(sessionId)?.transport;
1691
- }
1692
- setSession(sessionId, transport, server) {
1693
- this.sessions.set(sessionId, {
1694
- transport,
1695
- server
1696
- });
1697
- }
1698
- deleteSession(sessionId) {
1699
- const session = this.sessions.get(sessionId);
1700
- if (session) session.server.close();
1701
- this.sessions.delete(sessionId);
1702
- }
1703
- hasSession(sessionId) {
1704
- return this.sessions.has(sessionId);
1705
- }
1706
- clear() {
1707
- for (const session of this.sessions.values()) session.server.close();
1708
- this.sessions.clear();
1709
- }
1710
- };
1711
- /**
1712
- * SSE (Server-Sent Events) transport handler
1713
- * Legacy transport for backwards compatibility (protocol version 2024-11-05)
1714
- * Uses separate endpoints: /sse for SSE stream (GET) and /messages for client messages (POST)
1715
- */
1716
- var SseTransportHandler = class {
1717
- serverFactory;
1718
- app;
1719
- server = null;
1720
- sessionManager;
1721
- config;
1722
- constructor(serverFactory, config) {
1723
- this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
1724
- this.app = express();
1725
- this.sessionManager = new SseSessionManager();
1726
- this.config = {
1727
- mode: config.mode,
1728
- port: config.port ?? 3e3,
1729
- host: config.host ?? "localhost"
1730
- };
1731
- this.setupMiddleware();
1732
- this.setupRoutes();
1733
- }
1734
- setupMiddleware() {
1735
- this.app.use(express.json());
1736
- }
1737
- setupRoutes() {
1738
- this.app.get("/sse", async (req, res) => {
1739
- await this.handleSseConnection(req, res);
1740
- });
1741
- this.app.post("/messages", async (req, res) => {
1742
- await this.handlePostMessage(req, res);
1743
- });
1744
- this.app.get("/health", (_req, res) => {
1745
- res.json({
1746
- status: "ok",
1747
- transport: "sse"
1748
- });
1749
- });
1750
- }
1751
- async handleSseConnection(_req, res) {
1752
- try {
1753
- const mcpServer = this.serverFactory();
1754
- const transport = new SSEServerTransport("/messages", res);
1755
- this.sessionManager.setSession(transport.sessionId, transport, mcpServer);
1756
- res.on("close", () => {
1757
- this.sessionManager.deleteSession(transport.sessionId);
1758
- });
1759
- await mcpServer.connect(transport);
1760
- console.error(`SSE session established: ${transport.sessionId}`);
1761
- } catch (error) {
1762
- console.error("Error handling SSE connection:", error);
1763
- if (!res.headersSent) res.status(500).send("Internal Server Error");
1764
- }
1765
- }
1766
- async handlePostMessage(req, res) {
1767
- const sessionId = req.query.sessionId;
1768
- if (!sessionId) {
1769
- res.status(400).send("Missing sessionId query parameter");
1770
- return;
1771
- }
1772
- const transport = this.sessionManager.getSession(sessionId);
1773
- if (!transport) {
1774
- res.status(404).send("No transport found for sessionId");
1775
- return;
1776
- }
1777
- try {
1778
- await transport.handlePostMessage(req, res, req.body);
1779
- } catch (error) {
1780
- console.error("Error handling post message:", error);
1781
- if (!res.headersSent) res.status(500).send("Internal Server Error");
1782
- }
1783
- }
1784
- async start() {
1785
- return new Promise((resolve, reject) => {
1786
- try {
1787
- this.server = this.app.listen(this.config.port, this.config.host, () => {
1788
- console.error(`Scaffolding MCP server started with SSE transport on http://${this.config.host}:${this.config.port}`);
1789
- console.error(`SSE endpoint: http://${this.config.host}:${this.config.port}/sse`);
1790
- console.error(`Messages endpoint: http://${this.config.host}:${this.config.port}/messages`);
1791
- console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
1792
- resolve();
1793
- });
1794
- this.server.on("error", (error) => {
1795
- reject(error);
1796
- });
1797
- } catch (error) {
1798
- reject(error);
1799
- }
1800
- });
1801
- }
1802
- async stop() {
1803
- return new Promise((resolve, reject) => {
1804
- if (this.server) {
1805
- this.sessionManager.clear();
1806
- this.server.close((err) => {
1807
- if (err) reject(err);
1808
- else {
1809
- this.server = null;
1810
- resolve();
1811
- }
1812
- });
1813
- } else resolve();
1814
- });
1815
- }
1816
- getPort() {
1817
- return this.config.port;
1818
- }
1819
- getHost() {
1820
- return this.config.host;
1821
- }
1822
- };
1823
-
1824
- //#endregion
1825
- //#region src/transports/stdio.ts
1826
- /**
1827
- * Stdio transport handler for MCP server
1828
- * Used for command-line and direct integrations
1829
- */
1830
- var StdioTransportHandler = class {
1831
- server;
1832
- transport = null;
1833
- constructor(server) {
1834
- this.server = server;
1835
- }
1836
- async start() {
1837
- this.transport = new StdioServerTransport();
1838
- await this.server.connect(this.transport);
1839
- console.error("Scaffolding MCP server started on stdio");
1840
- }
1841
- async stop() {
1842
- if (this.transport) {
1843
- await this.transport.close();
1844
- this.transport = null;
1845
- }
1846
- }
1847
- };
1848
-
1849
- //#endregion
1850
- export { UseScaffoldMethodTool as a, GenerateFeatureScaffoldTool as c, ScaffoldGeneratorService as d, BoilerplateService as f, WriteToFileTool as i, GenerateBoilerplateTool as l, SseTransportHandler as n, UseBoilerplateTool as o, BoilerplateGeneratorService as p, HttpTransportHandler as r, ListBoilerplatesTool as s, StdioTransportHandler as t, GenerateBoilerplateFileTool as u };
1521
+ export { GenerateFeatureScaffoldTool as a, ScaffoldGeneratorService as c, ListBoilerplatesTool as i, BoilerplateService as l, UseScaffoldMethodTool as n, GenerateBoilerplateTool as o, UseBoilerplateTool as r, GenerateBoilerplateFileTool as s, WriteToFileTool as t, BoilerplateGeneratorService as u };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agiflowai/scaffold-mcp",
3
3
  "description": "MCP server for scaffolding applications with boilerplate templates",
4
- "version": "1.0.21",
4
+ "version": "1.0.23",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "AgiflowIO",
7
7
  "repository": {
@@ -48,10 +48,10 @@
48
48
  "pino": "^10.0.0",
49
49
  "pino-pretty": "^13.1.1",
50
50
  "zod": "3.25.76",
51
- "@agiflowai/architect-mcp": "1.0.19",
52
- "@agiflowai/aicode-utils": "1.0.14",
53
- "@agiflowai/hooks-adapter": "0.0.15",
54
- "@agiflowai/coding-agent-bridge": "1.0.17"
51
+ "@agiflowai/aicode-utils": "1.0.16",
52
+ "@agiflowai/coding-agent-bridge": "1.0.19",
53
+ "@agiflowai/architect-mcp": "1.0.21",
54
+ "@agiflowai/hooks-adapter": "0.0.17"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/express": "^5.0.0",
@@ -1,143 +0,0 @@
1
- import { DECISION_ALLOW, DECISION_DENY, DECISION_SKIP } from "@agiflowai/hooks-adapter";
2
- import { execFileSync } from "node:child_process";
3
-
4
- //#region src/hooks/claudeCode/phantomCodeCheck.ts
5
- /**
6
- * PhantomCodeCheck Hook for Claude Code
7
- *
8
- * DESIGN PATTERNS:
9
- * - Class-based hook pattern: Encapsulates lifecycle hooks in a single class
10
- * - Fail-open pattern: Errors allow operation to proceed (return DECISION_SKIP)
11
- * - Marker-based detection: Scans for scaffold marker comments in code files
12
- *
13
- * CODING STANDARDS:
14
- * - Export a class with stop, userPromptSubmit, taskCompleted methods
15
- * - Handle all errors gracefully with fail-open behavior
16
- * - Use execFileSync with args array to avoid shell injection
17
- *
18
- * AVOID:
19
- * - Blocking operations on errors
20
- * - Shell injection via marker parameter
21
- * - Mutating context object
22
- */
23
- const EXCLUDED_DIRS = [
24
- "node_modules",
25
- "dist",
26
- ".git",
27
- ".next",
28
- "build",
29
- "coverage",
30
- ".claude"
31
- ];
32
- /**
33
- * PhantomCodeCheckHook — scans for unimplemented scaffold files containing marker comments.
34
- *
35
- * Checks at session boundaries (Stop, UserPromptSubmit, TaskCompleted) whether
36
- * any generated files still carry the `// <marker>` comment, indicating they
37
- * have not yet been implemented by the AI agent.
38
- */
39
- var PhantomCodeCheckHook = class {
40
- markerComment;
41
- constructor(marker = "@scaffold-generated") {
42
- this.markerComment = `// ${marker}`;
43
- }
44
- /**
45
- * Scans cwd for files containing the scaffold marker comment.
46
- * Returns relative file paths. Returns empty array on any error (fail-open).
47
- */
48
- scanForPhantomFiles(cwd) {
49
- try {
50
- return execFileSync("grep", [
51
- "-rl",
52
- this.markerComment,
53
- "--include=*.ts",
54
- "--include=*.tsx",
55
- "--include=*.js",
56
- "--include=*.jsx",
57
- ...EXCLUDED_DIRS.map((dir) => `--exclude-dir=${dir}`),
58
- "."
59
- ], {
60
- cwd,
61
- timeout: 1e4,
62
- encoding: "utf8"
63
- }).trim().split("\n").filter(Boolean).map((f) => f.replace(/^\.\//, ""));
64
- } catch (error) {
65
- if (error instanceof Error && "status" in error && error.status === 1) return [];
66
- return [];
67
- }
68
- }
69
- /**
70
- * Stop hook — blocks session end if phantom files are found.
71
- * Returns DECISION_DENY to prevent Claude from stopping with unimplemented files.
72
- */
73
- async stop(context) {
74
- try {
75
- const phantomFiles = this.scanForPhantomFiles(context.cwd);
76
- if (phantomFiles.length === 0) return {
77
- decision: DECISION_SKIP,
78
- message: "No phantom scaffold files found"
79
- };
80
- const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
81
- return {
82
- decision: DECISION_DENY,
83
- 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.`
84
- };
85
- } catch {
86
- return {
87
- decision: DECISION_SKIP,
88
- message: "PhantomCodeCheckHook.stop error — skipping"
89
- };
90
- }
91
- }
92
- /**
93
- * UserPromptSubmit hook — warns about phantom files without blocking.
94
- * Returns DECISION_ALLOW with userMessage written to stderr (visible to user, not LLM).
95
- */
96
- async userPromptSubmit(context) {
97
- try {
98
- const phantomFiles = this.scanForPhantomFiles(context.cwd);
99
- if (phantomFiles.length === 0) return {
100
- decision: DECISION_SKIP,
101
- message: "No phantom scaffold files found"
102
- };
103
- const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
104
- return {
105
- decision: DECISION_ALLOW,
106
- message: "",
107
- userMessage: `⚠️ Reminder: ${phantomFiles.length} scaffold file(s) still contain \`${this.markerComment}\`:\n${fileList}\n\nPlease implement these files and remove the marker comment.`
108
- };
109
- } catch {
110
- return {
111
- decision: DECISION_SKIP,
112
- message: "PhantomCodeCheckHook.userPromptSubmit error — skipping"
113
- };
114
- }
115
- }
116
- /**
117
- * TaskCompleted hook — blocks task completion if phantom files are found.
118
- * Returns DECISION_DENY with exitCode 2 to signal incomplete scaffolding.
119
- */
120
- async taskCompleted(context) {
121
- try {
122
- const phantomFiles = this.scanForPhantomFiles(context.cwd);
123
- if (phantomFiles.length === 0) return {
124
- decision: DECISION_SKIP,
125
- message: "No phantom scaffold files found"
126
- };
127
- const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
128
- return {
129
- decision: DECISION_DENY,
130
- exitCode: 2,
131
- 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.`
132
- };
133
- } catch {
134
- return {
135
- decision: DECISION_SKIP,
136
- message: "PhantomCodeCheckHook.taskCompleted error — skipping"
137
- };
138
- }
139
- }
140
- };
141
-
142
- //#endregion
143
- export { PhantomCodeCheckHook };
@@ -1,144 +0,0 @@
1
- const require_ListScaffoldingMethodsTool = require('./ListScaffoldingMethodsTool-Dnd3E5X_.cjs');
2
- let __agiflowai_hooks_adapter = require("@agiflowai/hooks-adapter");
3
- let node_child_process = require("node:child_process");
4
-
5
- //#region src/hooks/claudeCode/phantomCodeCheck.ts
6
- /**
7
- * PhantomCodeCheck Hook for Claude Code
8
- *
9
- * DESIGN PATTERNS:
10
- * - Class-based hook pattern: Encapsulates lifecycle hooks in a single class
11
- * - Fail-open pattern: Errors allow operation to proceed (return DECISION_SKIP)
12
- * - Marker-based detection: Scans for scaffold marker comments in code files
13
- *
14
- * CODING STANDARDS:
15
- * - Export a class with stop, userPromptSubmit, taskCompleted methods
16
- * - Handle all errors gracefully with fail-open behavior
17
- * - Use execFileSync with args array to avoid shell injection
18
- *
19
- * AVOID:
20
- * - Blocking operations on errors
21
- * - Shell injection via marker parameter
22
- * - Mutating context object
23
- */
24
- const EXCLUDED_DIRS = [
25
- "node_modules",
26
- "dist",
27
- ".git",
28
- ".next",
29
- "build",
30
- "coverage",
31
- ".claude"
32
- ];
33
- /**
34
- * PhantomCodeCheckHook — scans for unimplemented scaffold files containing marker comments.
35
- *
36
- * Checks at session boundaries (Stop, UserPromptSubmit, TaskCompleted) whether
37
- * any generated files still carry the `// <marker>` comment, indicating they
38
- * have not yet been implemented by the AI agent.
39
- */
40
- var PhantomCodeCheckHook = class {
41
- markerComment;
42
- constructor(marker = "@scaffold-generated") {
43
- this.markerComment = `// ${marker}`;
44
- }
45
- /**
46
- * Scans cwd for files containing the scaffold marker comment.
47
- * Returns relative file paths. Returns empty array on any error (fail-open).
48
- */
49
- scanForPhantomFiles(cwd) {
50
- try {
51
- return (0, node_child_process.execFileSync)("grep", [
52
- "-rl",
53
- this.markerComment,
54
- "--include=*.ts",
55
- "--include=*.tsx",
56
- "--include=*.js",
57
- "--include=*.jsx",
58
- ...EXCLUDED_DIRS.map((dir) => `--exclude-dir=${dir}`),
59
- "."
60
- ], {
61
- cwd,
62
- timeout: 1e4,
63
- encoding: "utf8"
64
- }).trim().split("\n").filter(Boolean).map((f) => f.replace(/^\.\//, ""));
65
- } catch (error) {
66
- if (error instanceof Error && "status" in error && error.status === 1) return [];
67
- return [];
68
- }
69
- }
70
- /**
71
- * Stop hook — blocks session end if phantom files are found.
72
- * Returns DECISION_DENY to prevent Claude from stopping with unimplemented files.
73
- */
74
- async stop(context) {
75
- try {
76
- const phantomFiles = this.scanForPhantomFiles(context.cwd);
77
- if (phantomFiles.length === 0) return {
78
- decision: __agiflowai_hooks_adapter.DECISION_SKIP,
79
- message: "No phantom scaffold files found"
80
- };
81
- const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
82
- return {
83
- decision: __agiflowai_hooks_adapter.DECISION_DENY,
84
- 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.`
85
- };
86
- } catch {
87
- return {
88
- decision: __agiflowai_hooks_adapter.DECISION_SKIP,
89
- message: "PhantomCodeCheckHook.stop error — skipping"
90
- };
91
- }
92
- }
93
- /**
94
- * UserPromptSubmit hook — warns about phantom files without blocking.
95
- * Returns DECISION_ALLOW with userMessage written to stderr (visible to user, not LLM).
96
- */
97
- async userPromptSubmit(context) {
98
- try {
99
- const phantomFiles = this.scanForPhantomFiles(context.cwd);
100
- if (phantomFiles.length === 0) return {
101
- decision: __agiflowai_hooks_adapter.DECISION_SKIP,
102
- message: "No phantom scaffold files found"
103
- };
104
- const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
105
- return {
106
- decision: __agiflowai_hooks_adapter.DECISION_ALLOW,
107
- message: "",
108
- userMessage: `⚠️ Reminder: ${phantomFiles.length} scaffold file(s) still contain \`${this.markerComment}\`:\n${fileList}\n\nPlease implement these files and remove the marker comment.`
109
- };
110
- } catch {
111
- return {
112
- decision: __agiflowai_hooks_adapter.DECISION_SKIP,
113
- message: "PhantomCodeCheckHook.userPromptSubmit error — skipping"
114
- };
115
- }
116
- }
117
- /**
118
- * TaskCompleted hook — blocks task completion if phantom files are found.
119
- * Returns DECISION_DENY with exitCode 2 to signal incomplete scaffolding.
120
- */
121
- async taskCompleted(context) {
122
- try {
123
- const phantomFiles = this.scanForPhantomFiles(context.cwd);
124
- if (phantomFiles.length === 0) return {
125
- decision: __agiflowai_hooks_adapter.DECISION_SKIP,
126
- message: "No phantom scaffold files found"
127
- };
128
- const fileList = phantomFiles.map((f) => ` - ${f}`).join("\n");
129
- return {
130
- decision: __agiflowai_hooks_adapter.DECISION_DENY,
131
- exitCode: 2,
132
- 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.`
133
- };
134
- } catch {
135
- return {
136
- decision: __agiflowai_hooks_adapter.DECISION_SKIP,
137
- message: "PhantomCodeCheckHook.taskCompleted error — skipping"
138
- };
139
- }
140
- }
141
- };
142
-
143
- //#endregion
144
- exports.PhantomCodeCheckHook = PhantomCodeCheckHook;