@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.
- package/README.md +14 -0
- package/dist/{ListScaffoldingMethodsTool-Dnd3E5X_.cjs → ListScaffoldingMethodsTool-CkIkgP_u.cjs} +78 -78
- package/dist/{ListScaffoldingMethodsTool-DjhhMWjh.mjs → ListScaffoldingMethodsTool-Cx-0gpV3.mjs} +79 -79
- package/dist/{useScaffoldMethod-BR3ESqor.cjs → claudeCode-DBoEGOeu.cjs} +223 -42
- package/dist/{useScaffoldMethod-DlrzH-3H.mjs → claudeCode-lHW7zQ5G.mjs} +223 -43
- package/dist/cli.cjs +152 -512
- package/dist/cli.mjs +149 -509
- package/dist/{useScaffoldMethod-CJG7ngkT.cjs → geminiCli-BHyWDBcF.cjs} +1 -1
- package/dist/{useScaffoldMethod-DaAZTyIM.mjs → geminiCli-DBdxdDMM.mjs} +1 -1
- package/dist/index.cjs +18 -15
- package/dist/index.d.cts +408 -398
- package/dist/index.d.mts +409 -399
- package/dist/index.mjs +4 -3
- package/dist/src-DXfJFUpv.mjs +808 -0
- package/dist/src-ppHF7rzK.cjs +844 -0
- package/dist/{stdio-Bw7Hyv3X.cjs → tools-CC-lrhQ8.cjs} +3 -351
- package/dist/{stdio-Boc4SGGT.mjs → tools-DtGTxmf-.mjs} +4 -333
- package/package.json +5 -5
- package/dist/phantomCodeCheck-BXQonrXo.mjs +0 -143
- package/dist/phantomCodeCheck-DNkWyMRE.cjs +0 -144
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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/
|
|
52
|
-
"@agiflowai/
|
|
53
|
-
"@agiflowai/
|
|
54
|
-
"@agiflowai/
|
|
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;
|