forge-workflow 0.0.4 ā 0.0.6
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/.claude/commands/dev.md +345 -340
- package/.claude/commands/plan.md +566 -521
- package/.claude/commands/premerge.md +186 -176
- package/.claude/commands/research.md +42 -42
- package/.claude/commands/review.md +448 -442
- package/.claude/commands/rollback.md +721 -721
- package/.claude/commands/ship.md +212 -164
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +90 -48
- package/.claude/commands/validate.md +288 -282
- package/.claude/commands/verify.md +269 -221
- package/.claude/rules/greptile-review-process.md +285 -285
- package/.claude/rules/workflow.md +121 -105
- package/.claude/scripts/greptile-resolve.sh +558 -526
- package/.claude/scripts/load-env.sh +32 -32
- package/.cline/workflows/dev.md +342 -337
- package/.cline/workflows/plan.md +563 -518
- package/.cline/workflows/premerge.md +183 -173
- package/.cline/workflows/research.md +39 -39
- package/.cline/workflows/review.md +445 -439
- package/.cline/workflows/rollback.md +718 -718
- package/.cline/workflows/ship.md +209 -161
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +87 -45
- package/.cline/workflows/validate.md +285 -279
- package/.cline/workflows/verify.md +266 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +345 -340
- package/.codex/skills/plan/SKILL.md +566 -521
- package/.codex/skills/premerge/SKILL.md +186 -176
- package/.codex/skills/research/SKILL.md +42 -42
- package/.codex/skills/review/SKILL.md +448 -442
- package/.codex/skills/rollback/SKILL.md +721 -721
- package/.codex/skills/ship/SKILL.md +212 -164
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +90 -48
- package/.codex/skills/validate/SKILL.md +288 -282
- package/.codex/skills/verify/SKILL.md +269 -221
- package/.cursor/commands/dev.md +342 -337
- package/.cursor/commands/plan.md +563 -518
- package/.cursor/commands/premerge.md +183 -173
- package/.cursor/commands/research.md +39 -39
- package/.cursor/commands/review.md +445 -439
- package/.cursor/commands/rollback.md +718 -718
- package/.cursor/commands/ship.md +209 -161
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +87 -45
- package/.cursor/commands/validate.md +285 -279
- package/.cursor/commands/verify.md +266 -218
- package/.cursor/rules/permissions-guidance.mdc +37 -37
- package/.forge/hooks/check-tdd.js +240 -240
- package/.github/PLUGIN_TEMPLATE.json +32 -32
- package/.github/prompts/dev.prompt.md +347 -342
- package/.github/prompts/plan.prompt.md +568 -523
- package/.github/prompts/premerge.prompt.md +188 -178
- package/.github/prompts/research.prompt.md +44 -44
- package/.github/prompts/review.prompt.md +450 -444
- package/.github/prompts/rollback.prompt.md +723 -723
- package/.github/prompts/ship.prompt.md +214 -166
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +92 -50
- package/.github/prompts/validate.prompt.md +290 -284
- package/.github/prompts/verify.prompt.md +271 -223
- package/.github/workflows/beads-to-github.yml +56 -0
- package/.github/workflows/github-to-beads.yml +97 -0
- package/.kilocode/workflows/dev.md +346 -341
- package/.kilocode/workflows/plan.md +567 -522
- package/.kilocode/workflows/premerge.md +187 -177
- package/.kilocode/workflows/research.md +43 -43
- package/.kilocode/workflows/review.md +449 -443
- package/.kilocode/workflows/rollback.md +722 -722
- package/.kilocode/workflows/ship.md +213 -165
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +91 -49
- package/.kilocode/workflows/validate.md +289 -283
- package/.kilocode/workflows/verify.md +270 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +345 -340
- package/.opencode/commands/plan.md +566 -521
- package/.opencode/commands/premerge.md +186 -176
- package/.opencode/commands/research.md +42 -42
- package/.opencode/commands/review.md +448 -442
- package/.opencode/commands/rollback.md +721 -721
- package/.opencode/commands/ship.md +212 -164
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +90 -48
- package/.opencode/commands/validate.md +288 -282
- package/.opencode/commands/verify.md +269 -221
- package/.roo/commands/dev.md +346 -341
- package/.roo/commands/plan.md +567 -522
- package/.roo/commands/premerge.md +187 -177
- package/.roo/commands/research.md +43 -43
- package/.roo/commands/review.md +449 -443
- package/.roo/commands/rollback.md +722 -722
- package/.roo/commands/ship.md +213 -165
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +91 -49
- package/.roo/commands/validate.md +289 -283
- package/.roo/commands/verify.md +270 -222
- package/AGENTS.md +272 -175
- package/CLAUDE.md +110 -100
- package/README.md +429 -416
- package/bin/forge-cmd.js +317 -313
- package/bin/forge-preflight.js +322 -309
- package/bin/forge.js +4765 -4303
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -251
- package/docs/ENHANCED_ONBOARDING.md +612 -602
- package/docs/EXAMPLES.md +482 -482
- package/docs/GREPTILE_SETUP.md +400 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
- package/docs/ROADMAP.md +359 -359
- package/docs/SETUP.md +663 -631
- package/docs/TOOLCHAIN.md +653 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1056
- package/lefthook.yml +50 -39
- package/lib/agents/README.md +198 -198
- package/lib/agents/claude.plugin.json +28 -28
- package/lib/agents/cline.plugin.json +22 -22
- package/lib/agents/codex.plugin.json +19 -19
- package/lib/agents/copilot.plugin.json +24 -24
- package/lib/agents/cursor.plugin.json +25 -25
- package/lib/agents/kilocode.plugin.json +22 -22
- package/lib/agents/opencode.plugin.json +20 -20
- package/lib/agents/roo.plugin.json +23 -23
- package/lib/agents-config.js +2112 -2112
- package/lib/beads-health-check.js +143 -0
- package/lib/beads-setup.js +341 -0
- package/lib/beads-sync-scaffold.js +260 -0
- package/lib/commands/_registry.js +134 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/dev.js +571 -513
- package/lib/commands/plan.js +692 -692
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +119 -119
- package/lib/commands/ship.js +377 -377
- package/lib/commands/status.js +378 -378
- package/lib/commands/sync.js +55 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/validate.js +602 -602
- package/lib/commands/worktree.js +310 -0
- package/lib/context-merge.js +359 -359
- package/lib/dep-guard/analyzer.js +294 -294
- package/lib/dep-guard/behavior-detector.js +98 -98
- package/lib/dep-guard/contract-detector.js +162 -162
- package/lib/dep-guard/import-detector.js +498 -498
- package/lib/dep-guard/path-utils.js +13 -13
- package/lib/dep-guard/rubric.js +120 -120
- package/lib/dep-guard/task-parser.js +318 -318
- package/lib/detect-agent.js +191 -191
- package/lib/detect-worktree.js +47 -47
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/file-hash.js +26 -26
- package/lib/freshness-token.js +148 -0
- package/lib/greptile-match.js +80 -0
- package/lib/husky-migration.js +450 -0
- package/lib/lefthook-check.js +65 -0
- package/lib/pat-setup.js +207 -0
- package/lib/plugin-catalog.js +350 -350
- package/lib/plugin-manager.js +166 -166
- package/lib/plugin-recommender.js +141 -141
- package/lib/project-discovery.js +491 -491
- package/lib/reset.js +309 -0
- package/lib/setup-action-log.js +139 -139
- package/lib/setup-summary-renderer.js +106 -106
- package/lib/setup-utils.js +96 -0
- package/lib/setup.js +192 -192
- package/lib/smart-merge.js +64 -0
- package/lib/symlink-utils.js +81 -0
- package/lib/task-ownership.js +117 -0
- package/lib/workflow-profiles.js +197 -197
- package/package.json +131 -128
- package/scripts/beads-context.sh +426 -0
- package/scripts/beads-context.test.js +567 -0
- package/scripts/behavioral-judge.sh +378 -0
- package/scripts/benchmark.js +85 -0
- package/scripts/branch-protection.js +183 -0
- package/scripts/check-agents.js +172 -0
- package/scripts/check-forge-token.js +98 -0
- package/scripts/commitlint.js +42 -0
- package/scripts/conflict-detect.sh +323 -0
- package/scripts/dep-guard-analyze.js +71 -0
- package/scripts/dep-guard.sh +789 -0
- package/scripts/eval_win.py +249 -0
- package/scripts/file-index.sh +493 -0
- package/scripts/forge-team/index.sh +86 -0
- package/scripts/forge-team/lib/agent-prompt.sh +52 -0
- package/scripts/forge-team/lib/claim.sh +256 -0
- package/scripts/forge-team/lib/dashboard.sh +341 -0
- package/scripts/forge-team/lib/epic.sh +332 -0
- package/scripts/forge-team/lib/hooks.sh +253 -0
- package/scripts/forge-team/lib/identity.sh +235 -0
- package/scripts/forge-team/lib/sync-github.sh +317 -0
- package/scripts/forge-team/lib/verify.sh +284 -0
- package/scripts/forge-team/lib/workload.sh +296 -0
- package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
- package/scripts/forge-team/tests/claim.test.sh +179 -0
- package/scripts/forge-team/tests/dashboard.test.sh +170 -0
- package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
- package/scripts/forge-team/tests/epic.test.sh +176 -0
- package/scripts/forge-team/tests/hooks.test.sh +239 -0
- package/scripts/forge-team/tests/identity.test.sh +176 -0
- package/scripts/forge-team/tests/integration.test.sh +371 -0
- package/scripts/forge-team/tests/sync-github.test.sh +209 -0
- package/scripts/forge-team/tests/verify.test.sh +314 -0
- package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
- package/scripts/forge-team/tests/workload.test.sh +209 -0
- package/scripts/github-beads-sync/comment.mjs +64 -0
- package/scripts/github-beads-sync/config.mjs +148 -0
- package/scripts/github-beads-sync/github-api.mjs +131 -0
- package/scripts/github-beads-sync/index.mjs +332 -0
- package/scripts/github-beads-sync/label-mapper.mjs +54 -0
- package/scripts/github-beads-sync/mapping.mjs +78 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
- package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
- package/scripts/github-beads-sync/run-bd.mjs +159 -0
- package/scripts/github-beads-sync/sanitize.mjs +121 -0
- package/scripts/github-beads-sync.config.json +26 -0
- package/scripts/improve-command.js +375 -0
- package/scripts/lib/eval-runner.js +268 -0
- package/scripts/lib/eval-schema.js +135 -0
- package/scripts/lib/eval-storage.js +78 -0
- package/scripts/lib/grading.js +203 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/lib/transcript-parser.js +63 -0
- package/scripts/lint.js +47 -0
- package/scripts/migrate-to-bun-test.js +412 -0
- package/scripts/pr-coordinator.sh +706 -0
- package/scripts/run-command-eval.js +236 -0
- package/scripts/smart-status.sh +809 -0
- package/scripts/sync-commands.js +571 -0
- package/scripts/sync-utils.sh +455 -0
- package/scripts/test-dashboard.js +123 -0
- package/scripts/test.js +46 -0
- package/scripts/validate.sh +94 -0
- package/skills/parallel-deep-research/SKILL.md +108 -108
- package/skills/parallel-deep-research/evals/README.md +27 -27
- package/skills/parallel-deep-research/evals/evals.json +62 -62
- package/skills/sonarcloud-analysis/SKILL.md +171 -171
- package/skills/sonarcloud-analysis/evals/README.md +27 -27
- package/skills/sonarcloud-analysis/evals/evals.json +50 -50
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
- package/.cursor/hooks/state/continual-learning-index.json +0 -19
- package/.cursor/hooks/state/continual-learning.json +0 -8
|
@@ -1,240 +1,240 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TDD Enforcement Hook
|
|
5
|
-
*
|
|
6
|
-
* Checks if source code changes have corresponding test files.
|
|
7
|
-
* Offers guided recovery options when violations are found.
|
|
8
|
-
*
|
|
9
|
-
* Security: Uses execFileSync to prevent command injection.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { execFileSync } = require("node:child_process");
|
|
13
|
-
const fs = require("node:fs");
|
|
14
|
-
const path = require("node:path");
|
|
15
|
-
const readline = require("node:readline");
|
|
16
|
-
|
|
17
|
-
// Get staged files using git diff --cached
|
|
18
|
-
function getStagedFiles() {
|
|
19
|
-
try {
|
|
20
|
-
const output = execFileSync(
|
|
21
|
-
"git",
|
|
22
|
-
["diff", "--cached", "--name-only", "--diff-filter=ACM"],
|
|
23
|
-
{
|
|
24
|
-
encoding: "utf8",
|
|
25
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
return output.trim().split("\n").filter(Boolean);
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error("Error getting staged files:", error.message);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Check if a file is a source file (not test, not config)
|
|
36
|
-
function isSourceFile(file) {
|
|
37
|
-
const sourceExtensions = [
|
|
38
|
-
".js",
|
|
39
|
-
".ts",
|
|
40
|
-
".jsx",
|
|
41
|
-
".tsx",
|
|
42
|
-
".py",
|
|
43
|
-
".go",
|
|
44
|
-
".java",
|
|
45
|
-
".rb",
|
|
46
|
-
];
|
|
47
|
-
const ext = path.extname(file);
|
|
48
|
-
|
|
49
|
-
if (!sourceExtensions.includes(ext)) return false;
|
|
50
|
-
|
|
51
|
-
// Exclude test files
|
|
52
|
-
if (file.includes(".test.") || file.includes(".spec.")) return false;
|
|
53
|
-
|
|
54
|
-
// Exclude common config files
|
|
55
|
-
const excludePatterns = [
|
|
56
|
-
/^\./, // dot files
|
|
57
|
-
/config\./,
|
|
58
|
-
/\.config\./,
|
|
59
|
-
/setup\./,
|
|
60
|
-
/^test\//,
|
|
61
|
-
/^tests\//,
|
|
62
|
-
/__tests__\//,
|
|
63
|
-
/^scripts\//, // build/utility scripts (benchmarks, CI tools)
|
|
64
|
-
/package\.json/,
|
|
65
|
-
/tsconfig/,
|
|
66
|
-
/jest\.config/,
|
|
67
|
-
/vite\.config/,
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
return !excludePatterns.some((pattern) => pattern.test(file));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Find corresponding test file for a source file
|
|
74
|
-
function hasTestFile(sourceFile, stagedFiles) {
|
|
75
|
-
const dir = path.dirname(sourceFile);
|
|
76
|
-
const basename = path.basename(sourceFile, path.extname(sourceFile));
|
|
77
|
-
const ext = path.extname(sourceFile);
|
|
78
|
-
|
|
79
|
-
// Common test patterns (including nested directories)
|
|
80
|
-
const testPatterns = [
|
|
81
|
-
// Colocated tests
|
|
82
|
-
`${basename}.test${ext}`,
|
|
83
|
-
`${basename}.spec${ext}`,
|
|
84
|
-
`${dir}/${basename}.test${ext}`,
|
|
85
|
-
`${dir}/${basename}.spec${ext}`,
|
|
86
|
-
`${dir}/__tests__/${basename}.test${ext}`,
|
|
87
|
-
`${dir}/__tests__/${basename}.spec${ext}`,
|
|
88
|
-
// Top-level test directories
|
|
89
|
-
`test/${basename}.test${ext}`,
|
|
90
|
-
`test/${basename}.spec${ext}`,
|
|
91
|
-
`tests/${basename}.test${ext}`,
|
|
92
|
-
`tests/${basename}.spec${ext}`,
|
|
93
|
-
`__tests__/${basename}.test${ext}`,
|
|
94
|
-
`__tests__/${basename}.spec${ext}`,
|
|
95
|
-
// Nested test directories (unit/integration)
|
|
96
|
-
`test/unit/${basename}.test${ext}`,
|
|
97
|
-
`test/unit/${basename}.spec${ext}`,
|
|
98
|
-
`test/integration/${basename}.test${ext}`,
|
|
99
|
-
`test/integration/${basename}.spec${ext}`,
|
|
100
|
-
`tests/unit/${basename}.test${ext}`,
|
|
101
|
-
`tests/unit/${basename}.spec${ext}`,
|
|
102
|
-
`tests/integration/${basename}.test${ext}`,
|
|
103
|
-
`tests/integration/${basename}.spec${ext}`,
|
|
104
|
-
// Mirror source directory structure (lib/commands/foo.js ā test/commands/foo.test.js)
|
|
105
|
-
sourceFile.replace(/^(lib|src)\//, 'test/').replace(ext, `.test${ext}`),
|
|
106
|
-
sourceFile.replace(/^(lib|src)\//, 'test/').replace(ext, `.spec${ext}`),
|
|
107
|
-
sourceFile.replace(/^(lib|src)\//, 'tests/').replace(ext, `.test${ext}`),
|
|
108
|
-
sourceFile.replace(/^(lib|src)\//, 'tests/').replace(ext, `.spec${ext}`),
|
|
109
|
-
// bin/ directory (bin/forge-cmd.js ā test/cli/forge-cmd.test.js)
|
|
110
|
-
sourceFile.replace(/^bin\//, 'test/cli/').replace(ext, `.test${ext}`),
|
|
111
|
-
sourceFile.replace(/^bin\//, 'test/cli/').replace(ext, `.spec${ext}`),
|
|
112
|
-
sourceFile.replace(/^bin\//, 'test/bin/').replace(ext, `.test${ext}`),
|
|
113
|
-
sourceFile.replace(/^bin\//, 'test/bin/').replace(ext, `.spec${ext}`),
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
// Check if test file exists in staged files or filesystem
|
|
117
|
-
for (const pattern of testPatterns) {
|
|
118
|
-
if (stagedFiles.includes(pattern)) return true;
|
|
119
|
-
if (fs.existsSync(pattern)) return true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Prompt user for action (handles non-TTY environments like CI/CD)
|
|
126
|
-
function promptUser(question, options) {
|
|
127
|
-
return new Promise((resolve) => {
|
|
128
|
-
// In non-interactive environments (CI/CD), auto-abort to enforce TDD
|
|
129
|
-
if (!process.stdin.isTTY) {
|
|
130
|
-
console.log("\nā ļø Non-interactive environment detected (CI/CD).");
|
|
131
|
-
console.log("Aborting commit - source files must have tests.");
|
|
132
|
-
console.log("š” Tip: Use --no-verify to skip this check if needed.");
|
|
133
|
-
resolve("abort");
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const rl = readline.createInterface({
|
|
138
|
-
input: process.stdin,
|
|
139
|
-
output: process.stdout,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
console.log("\n" + question);
|
|
143
|
-
options.forEach((opt, i) => {
|
|
144
|
-
console.log(` ${i + 1}. ${opt.label}`);
|
|
145
|
-
});
|
|
146
|
-
console.log();
|
|
147
|
-
|
|
148
|
-
rl.question("Your choice (1-" + options.length + "): ", (answer) => {
|
|
149
|
-
rl.close();
|
|
150
|
-
const choice = parseInt(answer, 10) - 1;
|
|
151
|
-
if (choice >= 0 && choice < options.length) {
|
|
152
|
-
resolve(options[choice].value);
|
|
153
|
-
} else {
|
|
154
|
-
console.log("Invalid choice. Aborting commit.");
|
|
155
|
-
resolve("abort");
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Main hook logic
|
|
162
|
-
async function main() {
|
|
163
|
-
console.log("š TDD Check: Verifying test coverage for staged files...\n");
|
|
164
|
-
|
|
165
|
-
const stagedFiles = getStagedFiles();
|
|
166
|
-
|
|
167
|
-
if (stagedFiles.length === 0) {
|
|
168
|
-
console.log("ā No staged files to check.");
|
|
169
|
-
process.exit(0);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const sourceFiles = stagedFiles.filter(isSourceFile);
|
|
173
|
-
|
|
174
|
-
if (sourceFiles.length === 0) {
|
|
175
|
-
console.log("ā No source files staged (only tests/config/docs).");
|
|
176
|
-
process.exit(0);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const filesWithoutTests = sourceFiles.filter(
|
|
180
|
-
(file) => !hasTestFile(file, stagedFiles),
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
if (filesWithoutTests.length === 0) {
|
|
184
|
-
console.log("ā All source files have corresponding tests!");
|
|
185
|
-
process.exit(0);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Found violations
|
|
189
|
-
console.log("ā ļø Looks like you're committing source code without tests:\n");
|
|
190
|
-
filesWithoutTests.forEach((file) => {
|
|
191
|
-
console.log(` - ${file}`);
|
|
192
|
-
});
|
|
193
|
-
console.log();
|
|
194
|
-
|
|
195
|
-
console.log("š TDD Reminder:");
|
|
196
|
-
console.log(" Write tests BEFORE implementation (RED-GREEN-REFACTOR)");
|
|
197
|
-
console.log();
|
|
198
|
-
|
|
199
|
-
const action = await promptUser("What would you like to do?", [
|
|
200
|
-
{ label: "Unstage source files (keep tests staged)", value: "unstage" },
|
|
201
|
-
{ label: "Continue anyway (I have a good reason)", value: "continue" },
|
|
202
|
-
{ label: "Abort commit (let me add tests)", value: "abort" },
|
|
203
|
-
]);
|
|
204
|
-
|
|
205
|
-
switch (action) {
|
|
206
|
-
case "unstage":
|
|
207
|
-
console.log("\nš Unstaging source files without tests...");
|
|
208
|
-
filesWithoutTests.forEach((file) => {
|
|
209
|
-
try {
|
|
210
|
-
execFileSync("git", ["reset", "HEAD", file], { stdio: "inherit" });
|
|
211
|
-
console.log(` ā Unstaged: ${file}`);
|
|
212
|
-
} catch (_error) {
|
|
213
|
-
console.error(` ā Failed to unstage: ${file}`);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
console.log(
|
|
217
|
-
"\nYou can now commit your tests and add source files later.",
|
|
218
|
-
);
|
|
219
|
-
process.exit(0);
|
|
220
|
-
break;
|
|
221
|
-
|
|
222
|
-
case "continue":
|
|
223
|
-
console.log("\nā Continuing with commit...");
|
|
224
|
-
console.log("š” Tip: Use --no-verify to skip this check in emergencies.");
|
|
225
|
-
process.exit(0);
|
|
226
|
-
break;
|
|
227
|
-
|
|
228
|
-
case "abort":
|
|
229
|
-
default:
|
|
230
|
-
console.log("\nā Commit aborted. Add tests and try again!");
|
|
231
|
-
console.log("š” Tip: Use --no-verify to skip this check if needed.");
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Run with error handling
|
|
237
|
-
main().catch((error) => {
|
|
238
|
-
console.error("Error in TDD check hook:", error.message);
|
|
239
|
-
process.exit(1);
|
|
240
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TDD Enforcement Hook
|
|
5
|
+
*
|
|
6
|
+
* Checks if source code changes have corresponding test files.
|
|
7
|
+
* Offers guided recovery options when violations are found.
|
|
8
|
+
*
|
|
9
|
+
* Security: Uses execFileSync to prevent command injection.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execFileSync } = require("node:child_process");
|
|
13
|
+
const fs = require("node:fs");
|
|
14
|
+
const path = require("node:path");
|
|
15
|
+
const readline = require("node:readline");
|
|
16
|
+
|
|
17
|
+
// Get staged files using git diff --cached
|
|
18
|
+
function getStagedFiles() {
|
|
19
|
+
try {
|
|
20
|
+
const output = execFileSync(
|
|
21
|
+
"git",
|
|
22
|
+
["diff", "--cached", "--name-only", "--diff-filter=ACM"],
|
|
23
|
+
{
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
return output.trim().split("\n").filter(Boolean);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("Error getting staged files:", error.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if a file is a source file (not test, not config)
|
|
36
|
+
function isSourceFile(file) {
|
|
37
|
+
const sourceExtensions = [
|
|
38
|
+
".js",
|
|
39
|
+
".ts",
|
|
40
|
+
".jsx",
|
|
41
|
+
".tsx",
|
|
42
|
+
".py",
|
|
43
|
+
".go",
|
|
44
|
+
".java",
|
|
45
|
+
".rb",
|
|
46
|
+
];
|
|
47
|
+
const ext = path.extname(file);
|
|
48
|
+
|
|
49
|
+
if (!sourceExtensions.includes(ext)) return false;
|
|
50
|
+
|
|
51
|
+
// Exclude test files
|
|
52
|
+
if (file.includes(".test.") || file.includes(".spec.")) return false;
|
|
53
|
+
|
|
54
|
+
// Exclude common config files
|
|
55
|
+
const excludePatterns = [
|
|
56
|
+
/^\./, // dot files
|
|
57
|
+
/config\./,
|
|
58
|
+
/\.config\./,
|
|
59
|
+
/setup\./,
|
|
60
|
+
/^test\//,
|
|
61
|
+
/^tests\//,
|
|
62
|
+
/__tests__\//,
|
|
63
|
+
/^scripts\//, // build/utility scripts (benchmarks, CI tools)
|
|
64
|
+
/package\.json/,
|
|
65
|
+
/tsconfig/,
|
|
66
|
+
/jest\.config/,
|
|
67
|
+
/vite\.config/,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
return !excludePatterns.some((pattern) => pattern.test(file));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Find corresponding test file for a source file
|
|
74
|
+
function hasTestFile(sourceFile, stagedFiles) {
|
|
75
|
+
const dir = path.dirname(sourceFile);
|
|
76
|
+
const basename = path.basename(sourceFile, path.extname(sourceFile));
|
|
77
|
+
const ext = path.extname(sourceFile);
|
|
78
|
+
|
|
79
|
+
// Common test patterns (including nested directories)
|
|
80
|
+
const testPatterns = [
|
|
81
|
+
// Colocated tests
|
|
82
|
+
`${basename}.test${ext}`,
|
|
83
|
+
`${basename}.spec${ext}`,
|
|
84
|
+
`${dir}/${basename}.test${ext}`,
|
|
85
|
+
`${dir}/${basename}.spec${ext}`,
|
|
86
|
+
`${dir}/__tests__/${basename}.test${ext}`,
|
|
87
|
+
`${dir}/__tests__/${basename}.spec${ext}`,
|
|
88
|
+
// Top-level test directories
|
|
89
|
+
`test/${basename}.test${ext}`,
|
|
90
|
+
`test/${basename}.spec${ext}`,
|
|
91
|
+
`tests/${basename}.test${ext}`,
|
|
92
|
+
`tests/${basename}.spec${ext}`,
|
|
93
|
+
`__tests__/${basename}.test${ext}`,
|
|
94
|
+
`__tests__/${basename}.spec${ext}`,
|
|
95
|
+
// Nested test directories (unit/integration)
|
|
96
|
+
`test/unit/${basename}.test${ext}`,
|
|
97
|
+
`test/unit/${basename}.spec${ext}`,
|
|
98
|
+
`test/integration/${basename}.test${ext}`,
|
|
99
|
+
`test/integration/${basename}.spec${ext}`,
|
|
100
|
+
`tests/unit/${basename}.test${ext}`,
|
|
101
|
+
`tests/unit/${basename}.spec${ext}`,
|
|
102
|
+
`tests/integration/${basename}.test${ext}`,
|
|
103
|
+
`tests/integration/${basename}.spec${ext}`,
|
|
104
|
+
// Mirror source directory structure (lib/commands/foo.js ā test/commands/foo.test.js)
|
|
105
|
+
sourceFile.replace(/^(lib|src)\//, 'test/').replace(ext, `.test${ext}`),
|
|
106
|
+
sourceFile.replace(/^(lib|src)\//, 'test/').replace(ext, `.spec${ext}`),
|
|
107
|
+
sourceFile.replace(/^(lib|src)\//, 'tests/').replace(ext, `.test${ext}`),
|
|
108
|
+
sourceFile.replace(/^(lib|src)\//, 'tests/').replace(ext, `.spec${ext}`),
|
|
109
|
+
// bin/ directory (bin/forge-cmd.js ā test/cli/forge-cmd.test.js)
|
|
110
|
+
sourceFile.replace(/^bin\//, 'test/cli/').replace(ext, `.test${ext}`),
|
|
111
|
+
sourceFile.replace(/^bin\//, 'test/cli/').replace(ext, `.spec${ext}`),
|
|
112
|
+
sourceFile.replace(/^bin\//, 'test/bin/').replace(ext, `.test${ext}`),
|
|
113
|
+
sourceFile.replace(/^bin\//, 'test/bin/').replace(ext, `.spec${ext}`),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// Check if test file exists in staged files or filesystem
|
|
117
|
+
for (const pattern of testPatterns) {
|
|
118
|
+
if (stagedFiles.includes(pattern)) return true;
|
|
119
|
+
if (fs.existsSync(pattern)) return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Prompt user for action (handles non-TTY environments like CI/CD)
|
|
126
|
+
function promptUser(question, options) {
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
// In non-interactive environments (CI/CD), auto-abort to enforce TDD
|
|
129
|
+
if (!process.stdin.isTTY) {
|
|
130
|
+
console.log("\nā ļø Non-interactive environment detected (CI/CD).");
|
|
131
|
+
console.log("Aborting commit - source files must have tests.");
|
|
132
|
+
console.log("š” Tip: Use --no-verify to skip this check if needed.");
|
|
133
|
+
resolve("abort");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const rl = readline.createInterface({
|
|
138
|
+
input: process.stdin,
|
|
139
|
+
output: process.stdout,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
console.log("\n" + question);
|
|
143
|
+
options.forEach((opt, i) => {
|
|
144
|
+
console.log(` ${i + 1}. ${opt.label}`);
|
|
145
|
+
});
|
|
146
|
+
console.log();
|
|
147
|
+
|
|
148
|
+
rl.question("Your choice (1-" + options.length + "): ", (answer) => {
|
|
149
|
+
rl.close();
|
|
150
|
+
const choice = parseInt(answer, 10) - 1;
|
|
151
|
+
if (choice >= 0 && choice < options.length) {
|
|
152
|
+
resolve(options[choice].value);
|
|
153
|
+
} else {
|
|
154
|
+
console.log("Invalid choice. Aborting commit.");
|
|
155
|
+
resolve("abort");
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Main hook logic
|
|
162
|
+
async function main() {
|
|
163
|
+
console.log("š TDD Check: Verifying test coverage for staged files...\n");
|
|
164
|
+
|
|
165
|
+
const stagedFiles = getStagedFiles();
|
|
166
|
+
|
|
167
|
+
if (stagedFiles.length === 0) {
|
|
168
|
+
console.log("ā No staged files to check.");
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const sourceFiles = stagedFiles.filter(isSourceFile);
|
|
173
|
+
|
|
174
|
+
if (sourceFiles.length === 0) {
|
|
175
|
+
console.log("ā No source files staged (only tests/config/docs).");
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const filesWithoutTests = sourceFiles.filter(
|
|
180
|
+
(file) => !hasTestFile(file, stagedFiles),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (filesWithoutTests.length === 0) {
|
|
184
|
+
console.log("ā All source files have corresponding tests!");
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Found violations
|
|
189
|
+
console.log("ā ļø Looks like you're committing source code without tests:\n");
|
|
190
|
+
filesWithoutTests.forEach((file) => {
|
|
191
|
+
console.log(` - ${file}`);
|
|
192
|
+
});
|
|
193
|
+
console.log();
|
|
194
|
+
|
|
195
|
+
console.log("š TDD Reminder:");
|
|
196
|
+
console.log(" Write tests BEFORE implementation (RED-GREEN-REFACTOR)");
|
|
197
|
+
console.log();
|
|
198
|
+
|
|
199
|
+
const action = await promptUser("What would you like to do?", [
|
|
200
|
+
{ label: "Unstage source files (keep tests staged)", value: "unstage" },
|
|
201
|
+
{ label: "Continue anyway (I have a good reason)", value: "continue" },
|
|
202
|
+
{ label: "Abort commit (let me add tests)", value: "abort" },
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
switch (action) {
|
|
206
|
+
case "unstage":
|
|
207
|
+
console.log("\nš Unstaging source files without tests...");
|
|
208
|
+
filesWithoutTests.forEach((file) => {
|
|
209
|
+
try {
|
|
210
|
+
execFileSync("git", ["reset", "HEAD", file], { stdio: "inherit" });
|
|
211
|
+
console.log(` ā Unstaged: ${file}`);
|
|
212
|
+
} catch (_error) {
|
|
213
|
+
console.error(` ā Failed to unstage: ${file}`);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
console.log(
|
|
217
|
+
"\nYou can now commit your tests and add source files later.",
|
|
218
|
+
);
|
|
219
|
+
process.exit(0);
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case "continue":
|
|
223
|
+
console.log("\nā Continuing with commit...");
|
|
224
|
+
console.log("š” Tip: Use --no-verify to skip this check in emergencies.");
|
|
225
|
+
process.exit(0);
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case "abort":
|
|
229
|
+
default:
|
|
230
|
+
console.log("\nā Commit aborted. Add tests and try again!");
|
|
231
|
+
console.log("š” Tip: Use --no-verify to skip this check if needed.");
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Run with error handling
|
|
237
|
+
main().catch((error) => {
|
|
238
|
+
console.error("Error in TDD check hook:", error.message);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "your-agent",
|
|
3
|
-
"name": "Your Agent Name",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Brief description of your AI coding agent",
|
|
6
|
-
"homepage": "https://your-agent.example.com",
|
|
7
|
-
"capabilities": {
|
|
8
|
-
"commands": true,
|
|
9
|
-
"skills": true,
|
|
10
|
-
"hooks": false
|
|
11
|
-
},
|
|
12
|
-
"directories": {
|
|
13
|
-
"commands": ".your-agent/commands",
|
|
14
|
-
"rules": ".your-agent/rules",
|
|
15
|
-
"skills": ".your-agent/skills/forge-workflow",
|
|
16
|
-
"scripts": ".your-agent/scripts"
|
|
17
|
-
},
|
|
18
|
-
"files": {
|
|
19
|
-
"rootConfig": ".your-agent-rules",
|
|
20
|
-
"skillDefinition": ".your-agent/skills/forge-workflow/SKILL.md"
|
|
21
|
-
},
|
|
22
|
-
"setup": {
|
|
23
|
-
"copyCommands": true,
|
|
24
|
-
"copyRules": true,
|
|
25
|
-
"copyScripts": false,
|
|
26
|
-
"createSkill": true,
|
|
27
|
-
"customSetup": "",
|
|
28
|
-
"needsConversion": false,
|
|
29
|
-
"promptFormat": false,
|
|
30
|
-
"continueFormat": false
|
|
31
|
-
}
|
|
32
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "your-agent",
|
|
3
|
+
"name": "Your Agent Name",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Brief description of your AI coding agent",
|
|
6
|
+
"homepage": "https://your-agent.example.com",
|
|
7
|
+
"capabilities": {
|
|
8
|
+
"commands": true,
|
|
9
|
+
"skills": true,
|
|
10
|
+
"hooks": false
|
|
11
|
+
},
|
|
12
|
+
"directories": {
|
|
13
|
+
"commands": ".your-agent/commands",
|
|
14
|
+
"rules": ".your-agent/rules",
|
|
15
|
+
"skills": ".your-agent/skills/forge-workflow",
|
|
16
|
+
"scripts": ".your-agent/scripts"
|
|
17
|
+
},
|
|
18
|
+
"files": {
|
|
19
|
+
"rootConfig": ".your-agent-rules",
|
|
20
|
+
"skillDefinition": ".your-agent/skills/forge-workflow/SKILL.md"
|
|
21
|
+
},
|
|
22
|
+
"setup": {
|
|
23
|
+
"copyCommands": true,
|
|
24
|
+
"copyRules": true,
|
|
25
|
+
"copyScripts": false,
|
|
26
|
+
"createSkill": true,
|
|
27
|
+
"customSetup": "",
|
|
28
|
+
"needsConversion": false,
|
|
29
|
+
"promptFormat": false,
|
|
30
|
+
"continueFormat": false
|
|
31
|
+
}
|
|
32
|
+
}
|