forge-workflow 0.0.8 โ 0.0.9
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/premerge.md +2 -2
- package/.claude/commands/review.md +5 -2
- package/.claude/commands/ship.md +4 -3
- package/.claude/rules/greptile-review-process.md +4 -4
- package/.cline/workflows/premerge.md +2 -2
- package/.cline/workflows/review.md +5 -2
- package/.cline/workflows/ship.md +4 -3
- package/.codex/skills/premerge/SKILL.md +2 -2
- package/.codex/skills/review/SKILL.md +5 -2
- package/.codex/skills/ship/SKILL.md +4 -3
- package/.cursor/commands/premerge.md +2 -2
- package/.cursor/commands/review.md +5 -2
- package/.cursor/commands/ship.md +4 -3
- package/.github/prompts/premerge.prompt.md +2 -2
- package/.github/prompts/review.prompt.md +5 -2
- package/.github/prompts/ship.prompt.md +4 -3
- package/.github/workflows/beads-to-github.yml +1 -1
- package/.github/workflows/github-to-beads.yml +1 -1
- package/.kilocode/workflows/premerge.md +2 -2
- package/.kilocode/workflows/review.md +5 -2
- package/.kilocode/workflows/ship.md +4 -3
- package/.opencode/commands/premerge.md +2 -2
- package/.opencode/commands/review.md +5 -2
- package/.opencode/commands/ship.md +4 -3
- package/.roo/commands/premerge.md +2 -2
- package/.roo/commands/review.md +5 -2
- package/.roo/commands/ship.md +4 -3
- package/AGENTS.md +9 -9
- package/README.md +12 -6
- package/bin/forge.js +14 -3
- package/docs/BEADS_GITHUB_SYNC.md +6 -2
- package/docs/EXAMPLES.md +22 -22
- package/docs/ROADMAP.md +3 -3
- package/docs/TOOLCHAIN.md +60 -52
- package/lib/agents/codex.plugin.json +3 -0
- package/lib/agents-config.js +18 -12
- package/lib/codex-skills.js +54 -1
- package/lib/commands/plan.js +5 -2
- package/lib/commands/setup.js +231 -17
- package/lib/commands/ship.js +188 -5
- package/lib/commands/status.js +20 -33
- package/lib/commands/test.js +90 -25
- package/lib/commands/validate.js +218 -1
- package/lib/setup-action-log.js +2 -0
- package/lib/setup-summary-renderer.js +15 -11
- package/lib/workflow/enforce-stage.js +12 -8
- package/lib/workflow/state-manager.js +193 -0
- package/package.json +1 -1
- package/scripts/dep-guard.sh +11 -1
- package/scripts/forge-team/lib/hooks.sh +1 -1
- package/scripts/forge-team/lib/verify.sh +1 -1
- package/scripts/forge-team/lib/workload.sh +56 -27
- package/scripts/forge-team/tests/workload.test.sh +35 -4
- package/scripts/github-beads-sync/run-bd.mjs +4 -2
- package/scripts/smart-status.sh +10 -1
- package/scripts/sync-utils.sh +39 -0
- package/scripts/test.js +144 -38
package/scripts/test.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Cross-platform test runner for lefthook pre-push hook.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Cross-platform test runner for the lefthook pre-push hook.
|
|
4
|
+
*
|
|
5
|
+
* Runs only the tests affected by the changes being pushed when possible.
|
|
6
|
+
* Falls back to the full suite only for package-level changes.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
const { spawnSync } = require('node:child_process');
|
|
9
|
+
const { execFileSync: defaultExecFileSync, spawnSync: defaultSpawnSync } = require('node:child_process');
|
|
9
10
|
const fs = require('node:fs');
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const {
|
|
13
|
+
getAffectedTestFiles,
|
|
14
|
+
getChangedFiles,
|
|
15
|
+
} = require('../lib/commands/test');
|
|
16
|
+
|
|
17
|
+
const PACKAGE_LEVEL_PATHS = new Set([
|
|
18
|
+
'package.json',
|
|
19
|
+
'bun.lockb',
|
|
20
|
+
'pnpm-lock.yaml',
|
|
21
|
+
'yarn.lock',
|
|
22
|
+
'package-lock.json',
|
|
23
|
+
]);
|
|
24
|
+
|
|
12
25
|
const isWindows = process.platform === 'win32';
|
|
13
26
|
|
|
14
|
-
// Detect package manager from lock files (same priority as forge.js)
|
|
15
27
|
function detectPackageManager() {
|
|
16
28
|
if (fs.existsSync('bun.lockb') || fs.existsSync('bun.lock')) return 'bun';
|
|
17
29
|
if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
|
|
@@ -19,43 +31,137 @@ function detectPackageManager() {
|
|
|
19
31
|
return 'npm';
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// deletes the entire codebase.
|
|
31
|
-
const env = { ...process.env };
|
|
32
|
-
for (const key of Object.keys(env)) {
|
|
33
|
-
if (key === 'GIT_DIR' || key === 'GIT_WORK_TREE' || key === 'GIT_INDEX_FILE'
|
|
34
|
-
|| key === 'GIT_OBJECT_DIRECTORY' || key === 'GIT_ALTERNATE_OBJECT_DIRECTORIES'
|
|
35
|
-
|| key === 'GIT_QUARANTINE_PATH') {
|
|
36
|
-
delete env[key];
|
|
34
|
+
function stripGitHookEnv(sourceEnv = process.env) {
|
|
35
|
+
const env = { ...sourceEnv };
|
|
36
|
+
for (const key of Object.keys(env)) {
|
|
37
|
+
if (key === 'GIT_DIR' || key === 'GIT_WORK_TREE' || key === 'GIT_INDEX_FILE'
|
|
38
|
+
|| key === 'GIT_OBJECT_DIRECTORY' || key === 'GIT_ALTERNATE_OBJECT_DIRECTORIES'
|
|
39
|
+
|| key === 'GIT_QUARANTINE_PATH') {
|
|
40
|
+
delete env[key];
|
|
41
|
+
}
|
|
37
42
|
}
|
|
43
|
+
return env;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
46
|
+
function classifyPushTests(projectRoot, execFileSync = defaultExecFileSync) {
|
|
47
|
+
const changedFiles = getChangedFiles(execFileSync, { sinceUpstream: true });
|
|
48
|
+
const testTargets = getAffectedTestFiles(projectRoot, execFileSync, fs, { sinceUpstream: true });
|
|
49
|
+
|
|
50
|
+
let runFullSuite = false;
|
|
51
|
+
let runTestEnv = false;
|
|
52
|
+
let runE2E = false;
|
|
53
|
+
let hasUnmappedFiles = false;
|
|
54
|
+
const hasUnknownChangedFiles = changedFiles.length === 0 && testTargets.length === 0;
|
|
55
|
+
|
|
56
|
+
for (const file of changedFiles) {
|
|
57
|
+
if (PACKAGE_LEVEL_PATHS.has(file) || file.startsWith('packages/')) {
|
|
58
|
+
runFullSuite = true;
|
|
59
|
+
runTestEnv = true;
|
|
60
|
+
runE2E = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (file.startsWith('test-env/')) {
|
|
65
|
+
runTestEnv = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (file.startsWith('test/e2e/')) {
|
|
70
|
+
runE2E = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if ((file.startsWith('lib/') || file.startsWith('scripts/')) && file.endsWith('.js')) {
|
|
75
|
+
runTestEnv = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (file.startsWith('test/') || file.startsWith('.github/workflows/') || file.startsWith('.claude/commands/')) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
hasUnmappedFiles = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
changedFiles,
|
|
88
|
+
hasUnmappedFiles,
|
|
89
|
+
hasUnknownChangedFiles,
|
|
90
|
+
runE2E,
|
|
91
|
+
runFullSuite: runFullSuite || hasUnmappedFiles || hasUnknownChangedFiles,
|
|
92
|
+
runTestEnv,
|
|
93
|
+
testTargets,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
43
96
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
97
|
+
function runCommand(command, args, options = {}, spawnSync = defaultSpawnSync) {
|
|
98
|
+
const result = spawnSync(command, args, {
|
|
99
|
+
stdio: 'inherit',
|
|
100
|
+
shell: isWindows,
|
|
101
|
+
...options,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (result.error) {
|
|
105
|
+
throw result.error;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result.status ?? 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function runPrePushTests(projectRoot = process.cwd(), deps = {}) {
|
|
112
|
+
const spawnSync = deps.spawnSync || defaultSpawnSync;
|
|
113
|
+
const execFileSync = deps.execFileSync || defaultExecFileSync;
|
|
114
|
+
const pkgManager = deps.pkgManager || detectPackageManager();
|
|
115
|
+
const env = deps.env || stripGitHookEnv(process.env);
|
|
116
|
+
const plan = classifyPushTests(projectRoot, execFileSync);
|
|
117
|
+
|
|
118
|
+
console.log(`Running pre-push tests (${pkgManager})...`);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
if (plan.runFullSuite) {
|
|
122
|
+
const reason = plan.hasUnmappedFiles
|
|
123
|
+
? 'unmapped pushed files require full unit coverage'
|
|
124
|
+
: plan.hasUnknownChangedFiles
|
|
125
|
+
? 'changed files could not be resolved safely'
|
|
126
|
+
: 'package-level changes detected';
|
|
127
|
+
console.log(` Mode: full suite (${reason})`);
|
|
128
|
+
const status = runCommand(pkgManager, ['run', 'test'], { env }, spawnSync);
|
|
129
|
+
if (status !== 0) return status;
|
|
130
|
+
} else if (plan.testTargets.length > 0) {
|
|
131
|
+
console.log(` Mode: targeted (${plan.testTargets.length} test file${plan.testTargets.length === 1 ? '' : 's'})`);
|
|
132
|
+
const status = runCommand(pkgManager, ['run', 'test', ...plan.testTargets], { env }, spawnSync);
|
|
133
|
+
if (status !== 0) return status;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (plan.runE2E) {
|
|
137
|
+
console.log(' Extra: running affected e2e tests');
|
|
138
|
+
const status = runCommand('bun', ['test', 'test/e2e/'], { env }, spawnSync);
|
|
139
|
+
if (status !== 0) return status;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (plan.runTestEnv) {
|
|
143
|
+
console.log(' Extra: running affected edge-case tests');
|
|
144
|
+
const status = runCommand('bun', ['test', 'test-env/'], { env }, spawnSync);
|
|
145
|
+
if (status !== 0) return status;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log('Relevant tests passed');
|
|
149
|
+
return 0;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('');
|
|
152
|
+
console.error(`Failed to run tests: ${error.message}`);
|
|
153
|
+
console.error('');
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
50
156
|
}
|
|
51
157
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
console.error('โ Tests failed. Fix them before pushing.');
|
|
55
|
-
console.error('');
|
|
56
|
-
console.error('Fix the failing checks. Do not bypass hooks.');
|
|
57
|
-
console.error('');
|
|
58
|
-
process.exit(1);
|
|
158
|
+
if (require.main === module) {
|
|
159
|
+
process.exit(runPrePushTests());
|
|
59
160
|
}
|
|
60
161
|
|
|
61
|
-
|
|
162
|
+
module.exports = {
|
|
163
|
+
classifyPushTests,
|
|
164
|
+
detectPackageManager,
|
|
165
|
+
runPrePushTests,
|
|
166
|
+
stripGitHookEnv,
|
|
167
|
+
};
|