forgecraft-mcp 1.3.2 → 1.4.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/dist/artifacts/commit-hooks.d.ts +1 -1
- package/dist/artifacts/commit-hooks.d.ts.map +1 -1
- package/dist/artifacts/commit-hooks.js +2 -0
- package/dist/artifacts/commit-hooks.js.map +1 -1
- package/dist/cli/commands.d.ts +35 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +109 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +7 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -1
- package/dist/cli.js.map +1 -1
- package/dist/shared/result-utils.d.ts +27 -0
- package/dist/shared/result-utils.d.ts.map +1 -0
- package/dist/shared/result-utils.js +41 -0
- package/dist/shared/result-utils.js.map +1 -0
- package/dist/tools/close-cycle-helpers.d.ts +21 -2
- package/dist/tools/close-cycle-helpers.d.ts.map +1 -1
- package/dist/tools/close-cycle-helpers.js +66 -10
- package/dist/tools/close-cycle-helpers.js.map +1 -1
- package/dist/tools/close-cycle.d.ts +2 -2
- package/dist/tools/close-cycle.d.ts.map +1 -1
- package/dist/tools/close-cycle.js +1 -1
- package/dist/tools/close-cycle.js.map +1 -1
- package/dist/tools/consolidate-status.d.ts +81 -0
- package/dist/tools/consolidate-status.d.ts.map +1 -0
- package/dist/tools/consolidate-status.js +251 -0
- package/dist/tools/consolidate-status.js.map +1 -0
- package/dist/tools/forgecraft-dispatch.d.ts.map +1 -1
- package/dist/tools/forgecraft-dispatch.js +10 -0
- package/dist/tools/forgecraft-dispatch.js.map +1 -1
- package/dist/tools/forgecraft-router.d.ts +8 -0
- package/dist/tools/forgecraft-router.d.ts.map +1 -1
- package/dist/tools/forgecraft-router.js +21 -1
- package/dist/tools/forgecraft-router.js.map +1 -1
- package/dist/tools/forgecraft-schema-params.d.ts +4 -4
- package/dist/tools/forgecraft-schema.d.ts +5 -5
- package/dist/tools/forgecraft-schema.d.ts.map +1 -1
- package/dist/tools/forgecraft-schema.js +3 -0
- package/dist/tools/forgecraft-schema.js.map +1 -1
- package/dist/tools/gate-violations.d.ts +59 -0
- package/dist/tools/gate-violations.d.ts.map +1 -0
- package/dist/tools/gate-violations.js +152 -0
- package/dist/tools/gate-violations.js.map +1 -0
- package/dist/tools/generate-session-prompt.d.ts +3 -3
- package/dist/tools/generate-session-prompt.d.ts.map +1 -1
- package/dist/tools/generate-session-prompt.js +57 -15
- package/dist/tools/generate-session-prompt.js.map +1 -1
- package/dist/tools/roadmap-builder.d.ts.map +1 -1
- package/dist/tools/roadmap-builder.js +19 -9
- package/dist/tools/roadmap-builder.js.map +1 -1
- package/dist/tools/session-prompt-builders.d.ts.map +1 -1
- package/dist/tools/session-prompt-builders.js +34 -10
- package/dist/tools/session-prompt-builders.js.map +1 -1
- package/package.json +1 -1
- package/templates/universal/hooks.yaml +212 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-prompt-builders.d.ts","sourceRoot":"","sources":["../../src/tools/session-prompt-builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAWxD,yCAAyC;AACzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAID,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"session-prompt-builders.d.ts","sourceRoot":"","sources":["../../src/tools/session-prompt-builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAWxD,yCAAyC;AACzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAID,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,kDACc,CAAC;AAelD;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAY3E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAqC/D;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAyCrE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtE;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,MAAM,GACtB,aAAa,GAAG,IAAI,CActB;AAID;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAuC3D"}
|
|
@@ -13,8 +13,12 @@ import { buildContextLoadBlock, buildTddGateSection, buildContextRetrievalSectio
|
|
|
13
13
|
/** Primary-use text shown for the forgecraft server entry. */
|
|
14
14
|
export const FORGECRAFT_PRIMARY_USE = "check_cascade, generate_session_prompt, audit";
|
|
15
15
|
const CONSTITUTION_PATHS = [
|
|
16
|
-
"CLAUDE.md",
|
|
17
|
-
".
|
|
16
|
+
"CLAUDE.md",
|
|
17
|
+
"AGENTS.md",
|
|
18
|
+
".github/copilot-instructions.md",
|
|
19
|
+
".cursor/rules",
|
|
20
|
+
".windsurfrules",
|
|
21
|
+
".clinerules",
|
|
18
22
|
];
|
|
19
23
|
const ADR_DIRS = ["docs/adrs", "docs/adr"];
|
|
20
24
|
// ── MCP Tools Section ────────────────────────────────────────────────
|
|
@@ -34,7 +38,9 @@ export function loadMcpServerDescriptions() {
|
|
|
34
38
|
for (const server of parsed.servers ?? [])
|
|
35
39
|
map.set(server.name, server);
|
|
36
40
|
}
|
|
37
|
-
catch {
|
|
41
|
+
catch {
|
|
42
|
+
/* Return empty map */
|
|
43
|
+
}
|
|
38
44
|
return map;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
@@ -66,7 +72,9 @@ export function buildMcpToolsSection(projectDir) {
|
|
|
66
72
|
: (descriptions.get(name)?.description ?? "MCP server");
|
|
67
73
|
lines.push(`- **${name}** — ${primaryUse}`);
|
|
68
74
|
}
|
|
69
|
-
return `## Active MCP Tools\n\nThese tools are available in this session. Use them:\n` +
|
|
75
|
+
return (`## Active MCP Tools\n\nThese tools are available in this session. Use them:\n` +
|
|
76
|
+
lines.join("\n") +
|
|
77
|
+
"\n\n");
|
|
70
78
|
}
|
|
71
79
|
// ── Artifact Discovery ───────────────────────────────────────────────
|
|
72
80
|
/**
|
|
@@ -97,8 +105,17 @@ export function discoverArtifacts(projectDir) {
|
|
|
97
105
|
existsSync(join(projectDir, "docs/UseCases.md"));
|
|
98
106
|
const activeGateDir = join(projectDir, ".forgecraft/gates/project/active");
|
|
99
107
|
const activeGateCount = existsSync(activeGateDir)
|
|
100
|
-
? readdirSync(activeGateDir).filter((f) => f.endsWith(".yaml")).length
|
|
101
|
-
|
|
108
|
+
? readdirSync(activeGateDir).filter((f) => f.endsWith(".yaml")).length
|
|
109
|
+
: 0;
|
|
110
|
+
return {
|
|
111
|
+
constitutionPath,
|
|
112
|
+
statusExists,
|
|
113
|
+
adrCount,
|
|
114
|
+
adrDir,
|
|
115
|
+
diagramsExist,
|
|
116
|
+
useCasesExist,
|
|
117
|
+
activeGateCount,
|
|
118
|
+
};
|
|
102
119
|
}
|
|
103
120
|
/**
|
|
104
121
|
* Extract the last meaningful section of Status.md for context.
|
|
@@ -141,7 +158,12 @@ export function buildRoadmapItemAmbiguity(itemDescription) {
|
|
|
141
158
|
field: "roadmap_item",
|
|
142
159
|
understood_as: `Interpreting '${itemDescription}' as its most common literal meaning`,
|
|
143
160
|
understood_example: `I scoped the session prompt to: ${itemDescription} (minimal implementation)`,
|
|
144
|
-
alternatives: [
|
|
161
|
+
alternatives: [
|
|
162
|
+
{
|
|
163
|
+
label: "If broader scope intended",
|
|
164
|
+
action: `Include related concerns: error handling, integration tests, ADR for architectural decisions`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
145
167
|
resolution_hint: `Pass a more specific item_description, e.g. 'Add JWT authentication with email/password login, bcrypt hashing, and a /auth/refresh endpoint'`,
|
|
146
168
|
};
|
|
147
169
|
}
|
|
@@ -153,15 +175,17 @@ export function buildRoadmapItemAmbiguity(itemDescription) {
|
|
|
153
175
|
* @returns Complete, ready-to-paste session prompt
|
|
154
176
|
*/
|
|
155
177
|
export function buildPrompt(input) {
|
|
156
|
-
const { projectDir, itemDescription, sessionType, scopeNote, acceptanceCriteria, artifacts, statusSummary } = input;
|
|
178
|
+
const { projectDir, itemDescription, sessionType, scopeNote, acceptanceCriteria, artifacts, statusSummary, } = input;
|
|
157
179
|
const contextLoadBlock = buildContextLoadBlock(artifacts);
|
|
158
|
-
const scopeBlock = scopeNote
|
|
180
|
+
const scopeBlock = scopeNote
|
|
181
|
+
? `\n## Out of Scope\nDo NOT touch: ${scopeNote}\n`
|
|
182
|
+
: "";
|
|
159
183
|
const criteriaLines = acceptanceCriteria.map((c) => `- [ ] ${c}`).join("\n");
|
|
160
184
|
const conventionalType = sessionType === "fix" ? "fix" : sessionType;
|
|
161
185
|
let prompt = `# Session Prompt — Bound\n\n> Generated by ForgeCraft \`generate_session_prompt\`. Load context, then issue this prompt.\n\n---\n\n`;
|
|
162
186
|
prompt += `## Context Load Order\n\nLoad these artifacts **before** issuing the implementation prompt:\n\n${contextLoadBlock}\n`;
|
|
163
187
|
if (statusSummary) {
|
|
164
|
-
prompt +=
|
|
188
|
+
prompt += statusSummary + "\n\n";
|
|
165
189
|
}
|
|
166
190
|
prompt += `---\n\n## Implementation Prompt\n\n*(Paste everything below this line to the AI assistant)*\n\n---\n\n`;
|
|
167
191
|
prompt += `### Task\n\n${itemDescription}\n\n`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-prompt-builders.js","sourceRoot":"","sources":["../../src/tools/session-prompt-builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AAmCtC,wEAAwE;AAExE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"session-prompt-builders.js","sourceRoot":"","sources":["../../src/tools/session-prompt-builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AAmCtC,wEAAwE;AAExE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,sBAAsB,GACjC,+CAA+C,CAAC;AAElD,MAAM,kBAAkB,GAAG;IACzB,WAAW;IACX,WAAW;IACX,iCAAiC;IACjC,eAAe;IACf,gBAAgB;IAChB,aAAa;CACL,CAAC;AAEX,MAAM,QAAQ,GAAG,CAAC,WAAW,EAAE,UAAU,CAAU,CAAC;AAEpD,wEAAwE;AAExE;;;;GAIG;AACH,MAAM,UAAU,yBAAyB;IACvC,MAAM,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,mBAAmB,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC;QACrE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,GAAG,CAAC;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAmB,CAAC;QAC5E,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAClE,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,OAAO,sFAAsF,CAAC;IAChG,CAAC;IAED,IAAI,eAAe,GAAa,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAG9D,CAAC;QACF,MAAM,UAAU,GAAG,QAAQ,CAAC,YAAY,CAE3B,CAAC;QACd,eAAe,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,sFAAsF,CAAC;IAChG,CAAC;IAED,MAAM,YAAY,GAAG,yBAAyB,EAAE,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,UAAU,GACd,IAAI,KAAK,YAAY;YACnB,CAAC,CAAC,sBAAsB;YACxB,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,IAAI,YAAY,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,QAAQ,UAAU,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,CACL,+EAA+E;QAC/E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAChB,MAAM,CACP,CAAC;AACJ,CAAC;AAED,wEAAwE;AAExE;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,MAAM,gBAAgB,GACpB,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC1E,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAE/D,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YACnE,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;gBAC3B,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;gBACvB,MAAM,GAAG,GAAG,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IACtD,MAAM,aAAa,GACjB,UAAU,CAAC,WAAW,CAAC;QACvB,WAAW,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,aAAa,GACjB,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;QACjD,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEnD,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,kCAAkC,CAAC,CAAC;IAC3E,MAAM,eAAe,GAAG,UAAU,CAAC,aAAa,CAAC;QAC/C,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;QACtE,CAAC,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,gBAAgB;QAChB,YAAY;QACZ,QAAQ;QACR,MAAM;QACN,aAAa;QACb,aAAa;QACb,eAAe;KAChB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,gBAAgB,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAChF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,eAAuB;IAC1D,OAAO;QACL,mCAAmC,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK;QAC3E,gDAAgD;QAChD,gDAAgD;QAChD,yCAAyC;QACzC,6CAA6C;KAC9C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,eAAuB;IAEvB,IAAI,eAAe,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO;QACL,KAAK,EAAE,cAAc;QACrB,aAAa,EAAE,iBAAiB,eAAe,sCAAsC;QACrF,kBAAkB,EAAE,mCAAmC,eAAe,2BAA2B;QACjG,YAAY,EAAE;YACZ;gBACE,KAAK,EAAE,2BAA2B;gBAClC,MAAM,EAAE,8FAA8F;aACvG;SACF;QACD,eAAe,EAAE,8IAA8I;KAChK,CAAC;AACJ,CAAC;AAED,wEAAwE;AAExE;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,EACJ,UAAU,EACV,eAAe,EACf,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,SAAS,EACT,aAAa,GACd,GAAG,KAAK,CAAC;IAEV,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,SAAS;QAC1B,CAAC,CAAC,oCAAoC,SAAS,IAAI;QACnD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,gBAAgB,GAAG,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC;IAErE,IAAI,MAAM,GAAG,qIAAqI,CAAC;IACnJ,MAAM,IAAI,kGAAkG,gBAAgB,IAAI,CAAC;IAEjI,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,aAAa,GAAG,MAAM,CAAC;IACnC,CAAC;IAED,MAAM,IAAI,wGAAwG,CAAC;IACnH,MAAM,IAAI,eAAe,eAAe,MAAM,CAAC;IAC/C,IAAI,UAAU;QAAE,MAAM,IAAI,UAAU,GAAG,IAAI,CAAC;IAE5C,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAChD,MAAM,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,IAAI,4BAA4B,CAAC,UAAU,CAAC,CAAC;IACnD,MAAM,IAAI,yBAAyB,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC;IACnE,MAAM,IAAI,qFAAqF,CAAC;IAChG,MAAM,IAAI,kGAAkG,aAAa,MAAM,CAAC;IAChI,MAAM,IAAI,qPAAqP,SAAS,CAAC,MAAM,IAAI,YAAY,MAAM,CAAC;IACtS,MAAM,IAAI,4HAA4H,CAAC;IAEvI,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgecraft-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"mcpName": "io.github.jghiringhelli/forgecraft",
|
|
5
5
|
"description": "CLI + MCP sentinel for engineering standards — SOLID, testing, architecture, CI/CD — auto-tailored to your stack. Minimal MCP footprint (~200 tokens) via CLI-first design.",
|
|
6
6
|
"type": "module",
|
|
@@ -90,6 +90,10 @@ hooks:
|
|
|
90
90
|
echo "$STAGED" | grep '\.py$' | xargs -r isort --quiet 2>/dev/null
|
|
91
91
|
# TypeScript/JavaScript
|
|
92
92
|
echo "$STAGED" | grep '\.\(ts\|tsx\|js\|jsx\)$' | xargs -r npx prettier --write 2>/dev/null
|
|
93
|
+
# Rust
|
|
94
|
+
if echo "$STAGED" | grep -q '\.rs$' && [ -f "Cargo.toml" ]; then
|
|
95
|
+
cargo fmt 2>/dev/null
|
|
96
|
+
fi
|
|
93
97
|
# Re-stage formatted files
|
|
94
98
|
echo "$STAGED" | xargs -r git add
|
|
95
99
|
|
|
@@ -99,6 +103,20 @@ hooks:
|
|
|
99
103
|
filename: pre-commit-secrets.sh
|
|
100
104
|
script: |
|
|
101
105
|
#!/bin/bash
|
|
106
|
+
_fc_write_violation() {
|
|
107
|
+
local hook_name="$1" severity="${2:-error}" message="$3"
|
|
108
|
+
local repo_root
|
|
109
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
110
|
+
local dir="$repo_root/.forgecraft"
|
|
111
|
+
mkdir -p "$dir" 2>/dev/null || return 0
|
|
112
|
+
local ts
|
|
113
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || printf "unknown")"
|
|
114
|
+
local esc_msg
|
|
115
|
+
esc_msg="$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
116
|
+
printf '{"hook":"%s","severity":"%s","message":"%s","timestamp":"%s"}\n' \
|
|
117
|
+
"$hook_name" "$severity" "$esc_msg" "$ts" \
|
|
118
|
+
>> "$dir/gate-violations.jsonl" 2>/dev/null || true
|
|
119
|
+
}
|
|
102
120
|
PATTERNS=(
|
|
103
121
|
'AKIA[0-9A-Z]{16}'
|
|
104
122
|
'password\s*=\s*["\x27][^"\x27]+'
|
|
@@ -111,6 +129,7 @@ hooks:
|
|
|
111
129
|
for pattern in "${PATTERNS[@]}"; do
|
|
112
130
|
if grep -qE "$pattern" "$file" 2>/dev/null; then
|
|
113
131
|
echo "❌ Potential secret found in $file matching pattern"
|
|
132
|
+
_fc_write_violation "pre-commit-secrets" "error" "Potential secrets detected in staged files — review output above"
|
|
114
133
|
exit 1
|
|
115
134
|
fi
|
|
116
135
|
done
|
|
@@ -122,6 +141,20 @@ hooks:
|
|
|
122
141
|
filename: pre-commit-compile.sh
|
|
123
142
|
script: |
|
|
124
143
|
#!/bin/bash
|
|
144
|
+
_fc_write_violation() {
|
|
145
|
+
local hook_name="$1" severity="${2:-error}" message="$3"
|
|
146
|
+
local repo_root
|
|
147
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
148
|
+
local dir="$repo_root/.forgecraft"
|
|
149
|
+
mkdir -p "$dir" 2>/dev/null || return 0
|
|
150
|
+
local ts
|
|
151
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || printf "unknown")"
|
|
152
|
+
local esc_msg
|
|
153
|
+
esc_msg="$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
154
|
+
printf '{"hook":"%s","severity":"%s","message":"%s","timestamp":"%s"}\n' \
|
|
155
|
+
"$hook_name" "$severity" "$esc_msg" "$ts" \
|
|
156
|
+
>> "$dir/gate-violations.jsonl" 2>/dev/null || true
|
|
157
|
+
}
|
|
125
158
|
echo "🔨 Running build check..."
|
|
126
159
|
if [ -f "pyproject.toml" ] || [ -f "setup.py" ] || [ -f "requirements.txt" ]; then
|
|
127
160
|
STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$')
|
|
@@ -130,6 +163,7 @@ hooks:
|
|
|
130
163
|
python -m py_compile "$file" 2>&1
|
|
131
164
|
if [ $? -ne 0 ]; then
|
|
132
165
|
echo "❌ Syntax error in $file"
|
|
166
|
+
_fc_write_violation "pre-commit-compile" "error" "TypeScript compilation failed — run tsc --noEmit to see errors"
|
|
133
167
|
exit 1
|
|
134
168
|
fi
|
|
135
169
|
done
|
|
@@ -140,12 +174,62 @@ hooks:
|
|
|
140
174
|
npx tsc --noEmit 2>&1
|
|
141
175
|
if [ $? -ne 0 ]; then
|
|
142
176
|
echo "❌ TypeScript compilation failed."
|
|
177
|
+
_fc_write_violation "pre-commit-compile" "error" "TypeScript compilation failed — run tsc --noEmit to see errors"
|
|
143
178
|
exit 1
|
|
144
179
|
fi
|
|
145
180
|
echo " ✅ TypeScript compilation OK"
|
|
146
181
|
fi
|
|
182
|
+
if [ -f "Cargo.toml" ]; then
|
|
183
|
+
STAGED_RS=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rs$')
|
|
184
|
+
if [ -n "$STAGED_RS" ]; then
|
|
185
|
+
cargo check --quiet 2>&1
|
|
186
|
+
if [ $? -ne 0 ]; then
|
|
187
|
+
echo "❌ Rust cargo check failed."
|
|
188
|
+
_fc_write_violation "pre-commit-compile" "error" "Rust cargo check failed — run cargo check to see errors"
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
echo " ✅ Rust cargo check OK"
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
147
194
|
echo "🔨 Build check passed"
|
|
148
195
|
|
|
196
|
+
- name: cargo-clippy
|
|
197
|
+
trigger: pre-commit
|
|
198
|
+
description: "Run cargo clippy on Rust projects — catches bugs, style issues, and common mistakes beyond cargo check"
|
|
199
|
+
filename: pre-commit-clippy.sh
|
|
200
|
+
script: |
|
|
201
|
+
#!/bin/bash
|
|
202
|
+
_fc_write_violation() {
|
|
203
|
+
local hook_name="$1" severity="${2:-error}" message="$3"
|
|
204
|
+
local repo_root
|
|
205
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
206
|
+
local dir="$repo_root/.forgecraft"
|
|
207
|
+
mkdir -p "$dir" 2>/dev/null || return 0
|
|
208
|
+
local ts
|
|
209
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || printf "unknown")"
|
|
210
|
+
local esc_msg
|
|
211
|
+
esc_msg="$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
212
|
+
printf '{"hook":"%s","severity":"%s","message":"%s","timestamp":"%s"}\n' \
|
|
213
|
+
"$hook_name" "$severity" "$esc_msg" "$ts" \
|
|
214
|
+
>> "$dir/gate-violations.jsonl" 2>/dev/null || true
|
|
215
|
+
}
|
|
216
|
+
if [ ! -f "Cargo.toml" ]; then
|
|
217
|
+
exit 0
|
|
218
|
+
fi
|
|
219
|
+
STAGED_RS=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rs$')
|
|
220
|
+
if [ -z "$STAGED_RS" ]; then
|
|
221
|
+
exit 0
|
|
222
|
+
fi
|
|
223
|
+
echo "🦀 Running cargo clippy..."
|
|
224
|
+
cargo clippy --all-targets --all-features 2>&1
|
|
225
|
+
if [ $? -ne 0 ]; then
|
|
226
|
+
echo "❌ cargo clippy failed — fix lint errors before committing."
|
|
227
|
+
echo " Run: cargo clippy --all-targets --all-features"
|
|
228
|
+
_fc_write_violation "pre-commit-clippy" "error" "Cargo clippy violations found — run cargo clippy to see details"
|
|
229
|
+
exit 1
|
|
230
|
+
fi
|
|
231
|
+
echo " ✅ cargo clippy passed"
|
|
232
|
+
|
|
149
233
|
- name: tdd-phase-gate
|
|
150
234
|
trigger: pre-commit
|
|
151
235
|
description: "RED gate: test-only commits must have failing tests; warn on implementation without tests"
|
|
@@ -180,6 +264,8 @@ hooks:
|
|
|
180
264
|
elif grep -q '"jest"' package.json 2>/dev/null; then
|
|
181
265
|
RUN_CMD="npx jest --passWithNoTests"
|
|
182
266
|
fi
|
|
267
|
+
elif [ -f "Cargo.toml" ]; then
|
|
268
|
+
RUN_CMD="cargo test"
|
|
183
269
|
elif [ -f "pytest.ini" ] || [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
|
|
184
270
|
RUN_CMD="python -m pytest"
|
|
185
271
|
fi
|
|
@@ -270,6 +356,14 @@ hooks:
|
|
|
270
356
|
echo " ✅ Python tests passed"
|
|
271
357
|
fi
|
|
272
358
|
fi
|
|
359
|
+
if [ -f "Cargo.toml" ]; then
|
|
360
|
+
cargo test --quiet 2>&1
|
|
361
|
+
if [ $? -ne 0 ]; then
|
|
362
|
+
echo "❌ Rust tests failed."
|
|
363
|
+
exit 1
|
|
364
|
+
fi
|
|
365
|
+
echo " ✅ Rust tests passed"
|
|
366
|
+
fi
|
|
273
367
|
echo "🧪 All tests passed"
|
|
274
368
|
|
|
275
369
|
- name: coverage-gate
|
|
@@ -278,6 +372,20 @@ hooks:
|
|
|
278
372
|
filename: pre-commit-coverage.sh
|
|
279
373
|
script: |
|
|
280
374
|
#!/bin/bash
|
|
375
|
+
_fc_write_violation() {
|
|
376
|
+
local hook_name="$1" severity="${2:-error}" message="$3"
|
|
377
|
+
local repo_root
|
|
378
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
379
|
+
local dir="$repo_root/.forgecraft"
|
|
380
|
+
mkdir -p "$dir" 2>/dev/null || return 0
|
|
381
|
+
local ts
|
|
382
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || printf "unknown")"
|
|
383
|
+
local esc_msg
|
|
384
|
+
esc_msg="$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
385
|
+
printf '{"hook":"%s","severity":"%s","message":"%s","timestamp":"%s"}\n' \
|
|
386
|
+
"$hook_name" "$severity" "$esc_msg" "$ts" \
|
|
387
|
+
>> "$dir/gate-violations.jsonl" 2>/dev/null || true
|
|
388
|
+
}
|
|
281
389
|
# ──────────────────────────────────────────────────────────────────────
|
|
282
390
|
# Pre-Commit Hook: Coverage Gate
|
|
283
391
|
#
|
|
@@ -317,8 +425,30 @@ hooks:
|
|
|
317
425
|
# ── Run coverage ───────────────────────────────────────────────────────
|
|
318
426
|
echo "📊 Running coverage gate (src/ files staged)..."
|
|
319
427
|
|
|
320
|
-
if [ ! -f "package.json" ]; then
|
|
321
|
-
|
|
428
|
+
if [ ! -f "package.json" ] && [ ! -f "pyproject.toml" ] && [ ! -f "setup.py" ]; then
|
|
429
|
+
if [ -f "Cargo.toml" ]; then
|
|
430
|
+
# Rust: run tests; use cargo-tarpaulin for coverage if available
|
|
431
|
+
if command -v cargo-tarpaulin &> /dev/null; then
|
|
432
|
+
cargo tarpaulin --out Stdout --fail-under {{coverage_minimum | default: 80}} 2>&1
|
|
433
|
+
if [ $? -ne 0 ]; then
|
|
434
|
+
echo "❌ Coverage gate failed — below {{coverage_minimum | default: 80}}%."
|
|
435
|
+
echo " Run: cargo tarpaulin --out Html"
|
|
436
|
+
_fc_write_violation "pre-commit-coverage" "error" "Coverage gate failed — run npm test --coverage to see report"
|
|
437
|
+
exit 1
|
|
438
|
+
fi
|
|
439
|
+
echo " ✅ Rust coverage gate passed"
|
|
440
|
+
else
|
|
441
|
+
cargo test --quiet 2>&1
|
|
442
|
+
if [ $? -ne 0 ]; then
|
|
443
|
+
echo "❌ Rust tests failed."
|
|
444
|
+
_fc_write_violation "pre-commit-coverage" "error" "Coverage gate failed — run npm test --coverage to see report"
|
|
445
|
+
exit 1
|
|
446
|
+
fi
|
|
447
|
+
echo " ✅ Rust tests passed (install cargo-tarpaulin for coverage enforcement)"
|
|
448
|
+
fi
|
|
449
|
+
else
|
|
450
|
+
echo " ⚠️ No supported build system found — skipping coverage check."
|
|
451
|
+
fi
|
|
322
452
|
exit 0
|
|
323
453
|
fi
|
|
324
454
|
|
|
@@ -332,6 +462,7 @@ hooks:
|
|
|
332
462
|
echo "❌ Coverage gate failed — thresholds not met."
|
|
333
463
|
echo " Run 'npx vitest run --coverage' locally to see the full report."
|
|
334
464
|
echo " Add tests until coverage meets the configured minimums."
|
|
465
|
+
_fc_write_violation "pre-commit-coverage" "error" "Coverage gate failed — run npm test --coverage to see report"
|
|
335
466
|
exit 1
|
|
336
467
|
fi
|
|
337
468
|
echo " ✅ Coverage gate passed"
|
|
@@ -345,17 +476,19 @@ hooks:
|
|
|
345
476
|
--silent 2>&1
|
|
346
477
|
if [ $? -ne 0 ]; then
|
|
347
478
|
echo "❌ Coverage gate failed — thresholds not met."
|
|
479
|
+
_fc_write_violation "pre-commit-coverage" "error" "Coverage gate failed — run npm test --coverage to see report"
|
|
348
480
|
exit 1
|
|
349
481
|
fi
|
|
350
482
|
echo " ✅ Coverage gate passed"
|
|
351
483
|
exit 0
|
|
352
484
|
fi
|
|
353
485
|
|
|
354
|
-
if [ -f "pyproject.toml" ]
|
|
486
|
+
if [ -f "pyproject.toml" ]|| [ -f "setup.py" ]; then
|
|
355
487
|
if command -v pytest &> /dev/null; then
|
|
356
488
|
pytest --tb=no --quiet --cov=src --cov-fail-under={{coverage_minimum | default: 80}} 2>&1
|
|
357
489
|
if [ $? -ne 0 ]; then
|
|
358
490
|
echo "❌ Coverage gate failed — below {{coverage_minimum | default: 80}}%."
|
|
491
|
+
_fc_write_violation "pre-commit-coverage" "error" "Coverage gate failed — run npm test --coverage to see report"
|
|
359
492
|
exit 1
|
|
360
493
|
fi
|
|
361
494
|
echo " ✅ Coverage gate passed"
|
|
@@ -376,8 +509,22 @@ hooks:
|
|
|
376
509
|
- .env vs .env.example drift (warns on missing variables)
|
|
377
510
|
script: |
|
|
378
511
|
#!/bin/bash
|
|
512
|
+
_fc_write_violation() {
|
|
513
|
+
local hook_name="$1" severity="${2:-error}" message="$3"
|
|
514
|
+
local repo_root
|
|
515
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
516
|
+
local dir="$repo_root/.forgecraft"
|
|
517
|
+
mkdir -p "$dir" 2>/dev/null || return 0
|
|
518
|
+
local ts
|
|
519
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || printf "unknown")"
|
|
520
|
+
local esc_msg
|
|
521
|
+
esc_msg="$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
522
|
+
printf '{"hook":"%s","severity":"%s","message":"%s","timestamp":"%s"}\n' \
|
|
523
|
+
"$hook_name" "$severity" "$esc_msg" "$ts" \
|
|
524
|
+
>> "$dir/gate-violations.jsonl" 2>/dev/null || true
|
|
525
|
+
}
|
|
379
526
|
STAGED=$(git diff --cached --name-only --diff-filter=ACM)
|
|
380
|
-
SOURCE_FILES=$(echo "$STAGED" | grep -E '\.(py|ts|tsx|js|jsx)$' | grep -vE '(test_|\.test\.|\.spec\.|__tests__|tests/|fixtures/|mock|conftest)')
|
|
527
|
+
SOURCE_FILES=$(echo "$STAGED" | grep -E '\.(py|ts|tsx|js|jsx|rs)$' | grep -vE '(test_|\.test\.|\.spec\.|__tests__|tests/|fixtures/|mock|conftest|_test\.rs)')
|
|
381
528
|
if [ -z "$SOURCE_FILES" ]; then exit 0; fi
|
|
382
529
|
VIOLATIONS=0
|
|
383
530
|
WARNINGS=0
|
|
@@ -443,10 +590,39 @@ hooks:
|
|
|
443
590
|
echo " ⚠️ $file — $LINE_COUNT lines (max {{max_file_length | default: 300}})"
|
|
444
591
|
WARNINGS=$((WARNINGS + 1))
|
|
445
592
|
fi
|
|
446
|
-
|
|
593
|
+
# Rust-specific anti-patterns
|
|
594
|
+
if echo "$file" | grep -q '\.rs$'; then
|
|
595
|
+
if ! is_excepted "rust/unwrap" "$file"; then
|
|
596
|
+
if grep -nE '\.unwrap\(\)' "$file" > /tmp/violations 2>/dev/null; then
|
|
597
|
+
if [ -s /tmp/violations ]; then
|
|
598
|
+
echo " ⚠️ $file — .unwrap() in production code — use ? or explicit error handling"
|
|
599
|
+
WARNINGS=$((WARNINGS + 1))
|
|
600
|
+
fi
|
|
601
|
+
fi
|
|
602
|
+
fi
|
|
603
|
+
if grep -nE '\btodo!\(|\bunimplemented!\(' "$file" > /tmp/violations 2>/dev/null; then
|
|
604
|
+
if [ -s /tmp/violations ]; then
|
|
605
|
+
echo " ❌ $file — todo!/unimplemented! in production code"
|
|
606
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
607
|
+
fi
|
|
608
|
+
fi
|
|
609
|
+
if grep -nE '^[[:space:]]*#\[allow\(dead_code\)\]' "$file" > /tmp/violations 2>/dev/null; then
|
|
610
|
+
if [ -s /tmp/violations ]; then
|
|
611
|
+
echo " ⚠️ $file — #[allow(dead_code)] suppression — delete orphaned code instead"
|
|
612
|
+
WARNINGS=$((WARNINGS + 1))
|
|
613
|
+
fi
|
|
614
|
+
fi
|
|
615
|
+
if grep -nE '^[[:space:]]*unsafe[[:space:]]*\{' "$file" > /tmp/violations 2>/dev/null; then
|
|
616
|
+
if [ -s /tmp/violations ]; then
|
|
617
|
+
echo " ⚠️ $file — unsafe block present — requires explicit justification comment"
|
|
618
|
+
WARNINGS=$((WARNINGS + 1))
|
|
619
|
+
fi
|
|
620
|
+
fi
|
|
621
|
+
fi
|
|
447
622
|
rm -f /tmp/violations
|
|
448
623
|
if [ $VIOLATIONS -gt 0 ]; then
|
|
449
624
|
echo "❌ $VIOLATIONS violation(s) found — commit blocked."
|
|
625
|
+
_fc_write_violation "pre-commit-anti-patterns" "error" "Anti-patterns detected in staged files — see output above"
|
|
450
626
|
exit 1
|
|
451
627
|
fi
|
|
452
628
|
if [ $WARNINGS -gt 0 ]; then
|
|
@@ -462,26 +638,42 @@ hooks:
|
|
|
462
638
|
#!/bin/bash
|
|
463
639
|
MAX_LENGTH={{max_function_length | default: 50}}
|
|
464
640
|
STAGED=$(git diff --cached --name-only --diff-filter=ACM)
|
|
465
|
-
SOURCE_FILES=$(echo "$STAGED" | grep -E '\.(ts|tsx|js|jsx)$' | grep -vE '(\.test\.|\.spec\.|__tests__|tests
|
|
641
|
+
SOURCE_FILES=$(echo "$STAGED" | grep -E '\.(ts|tsx|js|jsx|rs)$' | grep -vE '(\.test\.|\.spec\.|__tests__|tests/|_test\.rs)')
|
|
466
642
|
if [ -z "$SOURCE_FILES" ]; then exit 0; fi
|
|
467
643
|
WARNINGS=0
|
|
468
644
|
for file in $SOURCE_FILES; do
|
|
469
|
-
# Heuristic: find function/method declarations and count lines to next declaration
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
645
|
+
# Heuristic: find function/method declarations and count lines to next declaration
|
|
646
|
+
if echo "$file" | grep -q '\.rs$'; then
|
|
647
|
+
awk -v max="$MAX_LENGTH" -v fname="$file" '
|
|
648
|
+
/^[[:space:]]*(pub )?(async )?fn [a-zA-Z_]/ {
|
|
649
|
+
if (start > 0 && NR - start > max) {
|
|
650
|
+
printf " ⚠️ %s:%d — fn starting here is %d lines (max %d)\n", fname, start, NR - start, max
|
|
651
|
+
}
|
|
652
|
+
start = NR
|
|
475
653
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
printf " ⚠️ %s:%d — function starting here is %d lines (max %d)\n", fname, start, NR - start, max
|
|
481
|
-
warnings++
|
|
654
|
+
END {
|
|
655
|
+
if (start > 0 && NR - start > max) {
|
|
656
|
+
printf " ⚠️ %s:%d — fn starting here is %d lines (max %d)\n", fname, start, NR - start, max
|
|
657
|
+
}
|
|
482
658
|
}
|
|
483
|
-
|
|
484
|
-
|
|
659
|
+
' "$file"
|
|
660
|
+
else
|
|
661
|
+
awk -v max="$MAX_LENGTH" -v fname="$file" '
|
|
662
|
+
/^[[:space:]]*(export )?(async )?(function |const [a-zA-Z]+ = (async )?\(|[a-zA-Z]+\(.*\) \{|[a-zA-Z]+\(.*\): )/ {
|
|
663
|
+
if (start > 0 && NR - start > max) {
|
|
664
|
+
printf " ⚠️ %s:%d — function starting here is %d lines (max %d)\n", fname, start, NR - start, max
|
|
665
|
+
warnings++
|
|
666
|
+
}
|
|
667
|
+
start = NR
|
|
668
|
+
}
|
|
669
|
+
END {
|
|
670
|
+
if (start > 0 && NR - start > max) {
|
|
671
|
+
printf " ⚠️ %s:%d — function starting here is %d lines (max %d)\n", fname, start, NR - start, max
|
|
672
|
+
warnings++
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
' "$file"
|
|
676
|
+
fi
|
|
485
677
|
WARNINGS=$((WARNINGS + $?))
|
|
486
678
|
done
|
|
487
679
|
# Warning only — does not block commit since bash heuristics aren't perfect
|