@programinglive/dev-workflow-mcp-server 1.5.2 → 1.5.4
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/.agent/workflows/dev-workflow-bugfix.md +250 -250
- package/.agent/workflows/dev-workflow-feature.md +201 -201
- package/.agent/workflows/dev-workflow-full-cycle.md +400 -400
- package/.agent/workflows/dev-workflow-refactor.md +238 -238
- package/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
- package/.github/pull_request_template.md +22 -22
- package/.github/workflows/github-release.yml +56 -0
- package/.github/workflows/npm-publish.yml +50 -50
- package/.husky/commit-msg +0 -0
- package/CHANGELOG.md +759 -741
- package/CODE_OF_CONDUCT.md +79 -79
- package/CONTRIBUTING.md +60 -60
- package/LICENSE +21 -21
- package/README.md +632 -632
- package/SECURITY.md +20 -20
- package/db/index.js +204 -204
- package/docs/NETLIFY_DEPLOYMENT.md +125 -125
- package/docs/PLESK_DEPLOYMENT.md +170 -170
- package/docs/WORKFLOW-BEST-PRACTICES.md +58 -58
- package/docs/antigravity/EXAMPLES.md +535 -535
- package/docs/antigravity/GETTING-STARTED.md +292 -292
- package/docs/antigravity/TROUBLESHOOTING.md +742 -742
- package/docs/antigravity/WALKTHROUGH.md +25 -25
- package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
- package/docs/product/PRD.md +67 -67
- package/docs/release-notes/RELEASE_NOTES.md +233 -226
- package/docs/web-dashboard.md +175 -175
- package/exec.js +49 -49
- package/index.js +279 -279
- package/install.js +73 -73
- package/package.json +4 -4
- package/release-wrapper.js +192 -192
- package/scripts/build-docs.js +63 -63
- package/scripts/extract-release-notes.cjs +78 -0
- package/scripts/hello-world.js +1 -1
- package/scripts/release.cjs +252 -252
- package/scripts/release.js +252 -0
- package/scripts/update-release-notes.cjs +178 -178
- package/scripts/update-release-notes.js +182 -0
- package/server.json +23 -23
- package/tailwind.config.js +8 -8
- package/tests/build.test.js +83 -83
- package/tests/exec.test.js +35 -35
- package/tests/handlers.test.js +1058 -1058
- package/tests/hello-world.test.js +13 -13
- package/tests/mcpRegistry.test.js +65 -65
- package/tests/patch-release.test.js +7 -0
- package/tests/release-wrapper.test.js +107 -107
- package/tests/workflowState.test.js +505 -505
- package/tools/definitions.js +332 -332
- package/tools/handlers.js +1019 -1019
- package/vite.config.js +43 -43
- package/vite.web.config.js +23 -23
- package/web/README.md +36 -36
- package/web/app/docs/page.tsx +202 -202
- package/web/app/globals.css +90 -90
- package/web/app/layout.tsx +36 -36
- package/web/app/page.tsx +15 -15
- package/web/components/ApiReference.tsx +20 -20
- package/web/components/Features.tsx +61 -61
- package/web/components/Footer.tsx +9 -9
- package/web/components/Hero.tsx +96 -96
- package/web/components/History.tsx +143 -143
- package/web/components/Navbar.tsx +64 -64
- package/web/components/Terminal.tsx +114 -114
- package/web/eslint.config.mjs +18 -18
- package/web/lib/db.js +131 -131
- package/web/lib/docs.js +73 -73
- package/web/netlify.toml +3 -3
- package/web/next.config.ts +12 -12
- package/web/package-lock.json +6560 -6560
- package/web/package.json +28 -28
- package/web/postcss.config.mjs +9 -9
- package/web/server.js +31 -31
- package/web/tailwind.config.ts +19 -19
- package/web/tsconfig.json +42 -42
- package/workflow-state.js +612 -612
package/install.js
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Installation script for dev-workflow MCP server
|
|
5
|
-
* Sets up per-project workflow state file in the target project directory
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = path.dirname(__filename);
|
|
14
|
-
|
|
15
|
-
async function install() {
|
|
16
|
-
try {
|
|
17
|
-
// npm sets INIT_CWD to the directory where `npm install` was originally invoked.
|
|
18
|
-
// Fallback to process.cwd() if it's not present (e.g., older npm versions).
|
|
19
|
-
const projectRoot = process.env.INIT_CWD || process.cwd();
|
|
20
|
-
|
|
21
|
-
// If INIT_CWD points inside node_modules, this script is being executed
|
|
22
|
-
// while installing the package itself. In that case, skip creating a state file.
|
|
23
|
-
if (projectRoot.includes(`node_modules${path.sep}`)) {
|
|
24
|
-
console.log('ℹ️ Skipping workflow state creation inside node_modules.');
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const stateDir = path.join(projectRoot, '.state');
|
|
29
|
-
const stateFile = path.join(stateDir, 'workflow-state.json');
|
|
30
|
-
|
|
31
|
-
// Create .state directory if it doesn't exist
|
|
32
|
-
await fs.mkdir(stateDir, { recursive: true });
|
|
33
|
-
|
|
34
|
-
// Check if state file already exists
|
|
35
|
-
try {
|
|
36
|
-
await fs.access(stateFile);
|
|
37
|
-
console.log(`✓ Workflow state file already exists at ${stateFile}`);
|
|
38
|
-
return;
|
|
39
|
-
} catch {
|
|
40
|
-
// File doesn't exist, create it
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Create initial workflow state
|
|
44
|
-
const initialState = {
|
|
45
|
-
currentPhase: 'idle',
|
|
46
|
-
taskDescription: '',
|
|
47
|
-
bugFixed: false,
|
|
48
|
-
testsCreated: false,
|
|
49
|
-
testsPassed: false,
|
|
50
|
-
testsSkipped: false,
|
|
51
|
-
testsSkippedReason: '',
|
|
52
|
-
documentationCreated: false,
|
|
53
|
-
readyToCommit: false,
|
|
54
|
-
readyCheckCompleted: false,
|
|
55
|
-
released: false,
|
|
56
|
-
releaseCommand: '',
|
|
57
|
-
releaseNotes: '',
|
|
58
|
-
commitAndPushCompleted: false,
|
|
59
|
-
lastCommitMessage: '',
|
|
60
|
-
lastPushBranch: '',
|
|
61
|
-
history: [],
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
await fs.writeFile(stateFile, JSON.stringify(initialState, null, 2));
|
|
65
|
-
console.log(`✓ Created workflow state file at ${stateFile}`);
|
|
66
|
-
console.log(`✓ Each project now has its own isolated workflow history`);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error('✗ Installation failed:', error.message);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
install();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Installation script for dev-workflow MCP server
|
|
5
|
+
* Sets up per-project workflow state file in the target project directory
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
async function install() {
|
|
16
|
+
try {
|
|
17
|
+
// npm sets INIT_CWD to the directory where `npm install` was originally invoked.
|
|
18
|
+
// Fallback to process.cwd() if it's not present (e.g., older npm versions).
|
|
19
|
+
const projectRoot = process.env.INIT_CWD || process.cwd();
|
|
20
|
+
|
|
21
|
+
// If INIT_CWD points inside node_modules, this script is being executed
|
|
22
|
+
// while installing the package itself. In that case, skip creating a state file.
|
|
23
|
+
if (projectRoot.includes(`node_modules${path.sep}`)) {
|
|
24
|
+
console.log('ℹ️ Skipping workflow state creation inside node_modules.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const stateDir = path.join(projectRoot, '.state');
|
|
29
|
+
const stateFile = path.join(stateDir, 'workflow-state.json');
|
|
30
|
+
|
|
31
|
+
// Create .state directory if it doesn't exist
|
|
32
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
33
|
+
|
|
34
|
+
// Check if state file already exists
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(stateFile);
|
|
37
|
+
console.log(`✓ Workflow state file already exists at ${stateFile}`);
|
|
38
|
+
return;
|
|
39
|
+
} catch {
|
|
40
|
+
// File doesn't exist, create it
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create initial workflow state
|
|
44
|
+
const initialState = {
|
|
45
|
+
currentPhase: 'idle',
|
|
46
|
+
taskDescription: '',
|
|
47
|
+
bugFixed: false,
|
|
48
|
+
testsCreated: false,
|
|
49
|
+
testsPassed: false,
|
|
50
|
+
testsSkipped: false,
|
|
51
|
+
testsSkippedReason: '',
|
|
52
|
+
documentationCreated: false,
|
|
53
|
+
readyToCommit: false,
|
|
54
|
+
readyCheckCompleted: false,
|
|
55
|
+
released: false,
|
|
56
|
+
releaseCommand: '',
|
|
57
|
+
releaseNotes: '',
|
|
58
|
+
commitAndPushCompleted: false,
|
|
59
|
+
lastCommitMessage: '',
|
|
60
|
+
lastPushBranch: '',
|
|
61
|
+
history: [],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
await fs.writeFile(stateFile, JSON.stringify(initialState, null, 2));
|
|
65
|
+
console.log(`✓ Created workflow state file at ${stateFile}`);
|
|
66
|
+
console.log(`✓ Each project now has its own isolated workflow history`);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('✗ Installation failed:', error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
install();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
41
|
-
"url": "https://github.com/programinglive/dev-workflow-mcp-server.git"
|
|
41
|
+
"url": "git+https://github.com/programinglive/dev-workflow-mcp-server.git"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@commitlint/cli": "^20.
|
|
56
|
-
"@commitlint/config-conventional": "^20.
|
|
55
|
+
"@commitlint/cli": "^20.2.0",
|
|
56
|
+
"@commitlint/config-conventional": "^20.2.0",
|
|
57
57
|
"@programinglive/commiter": "^1.1.8",
|
|
58
58
|
"autoprefixer": "^10.4.22",
|
|
59
59
|
"husky": "^9.1.7",
|
package/release-wrapper.js
CHANGED
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import fs from 'node:fs/promises';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { resolveStateFile } from './workflow-state.js';
|
|
7
|
-
|
|
8
|
-
const RELEASE_TYPES = new Set(['major', 'minor', 'patch']);
|
|
9
|
-
|
|
10
|
-
async function readStateCandidate(filePath) {
|
|
11
|
-
if (!filePath) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const [raw, stats] = await Promise.all([fs.readFile(filePath, 'utf8'), fs.stat(filePath)]);
|
|
17
|
-
return {
|
|
18
|
-
state: JSON.parse(raw),
|
|
19
|
-
path: filePath,
|
|
20
|
-
mtimeMs: stats.mtimeMs,
|
|
21
|
-
};
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function loadWorkflowStateCandidates(projectRoot) {
|
|
28
|
-
const candidates = [];
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
candidates.push(resolveStateFile());
|
|
32
|
-
} catch {
|
|
33
|
-
// resolver may throw before state exists; fall through to other candidates
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const legacyStatePath = path.join(projectRoot, '.state', 'workflow-state.json');
|
|
37
|
-
candidates.push(legacyStatePath);
|
|
38
|
-
|
|
39
|
-
// Add explicit DEV_WORKFLOW_STATE_FILE if provided (resolver would have used it, but keep explicit just in case)
|
|
40
|
-
if (process.env.DEV_WORKFLOW_STATE_FILE) {
|
|
41
|
-
candidates.push(path.resolve(process.env.DEV_WORKFLOW_STATE_FILE));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const seen = new Set();
|
|
45
|
-
let freshest = null;
|
|
46
|
-
|
|
47
|
-
for (const candidate of candidates) {
|
|
48
|
-
if (!candidate) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const resolved = path.resolve(candidate);
|
|
53
|
-
if (seen.has(resolved)) {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
seen.add(resolved);
|
|
57
|
-
|
|
58
|
-
const result = await readStateCandidate(resolved);
|
|
59
|
-
if (!result) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!freshest || result.mtimeMs > freshest.mtimeMs) {
|
|
64
|
-
freshest = result;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return freshest;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function main() {
|
|
72
|
-
const releaseTypeArg = (process.argv[2] || '').toLowerCase();
|
|
73
|
-
const forceFlag = process.argv.includes('--force') || process.env.DEV_WORKFLOW_FORCE_RELEASE === '1';
|
|
74
|
-
const additionalArgs = process.argv.slice(3).filter((arg) => arg !== '--force');
|
|
75
|
-
|
|
76
|
-
if (!RELEASE_TYPES.has(releaseTypeArg)) {
|
|
77
|
-
console.error(
|
|
78
|
-
'❌ Release guard: expected release type argument (major | minor | patch). Example: npm run release:patch'
|
|
79
|
-
);
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const repoRoot = process.cwd();
|
|
84
|
-
const projectRootCandidate = process.env.INIT_CWD || repoRoot;
|
|
85
|
-
if (projectRootCandidate.includes(`node_modules${path.sep}`)) {
|
|
86
|
-
console.error('❌ Release guard: refusing to run inside node_modules.');
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
const projectRoot = projectRootCandidate;
|
|
90
|
-
|
|
91
|
-
const result = await loadWorkflowStateCandidates(projectRoot);
|
|
92
|
-
if (!result) {
|
|
93
|
-
console.error(
|
|
94
|
-
`❌ Release guard: cannot locate workflow state under ${path.join(projectRoot, '.state')}.
|
|
95
|
-
Make sure the dev-workflow MCP is installed and you have started a task.`
|
|
96
|
-
);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const { state, path: statePath } = result;
|
|
101
|
-
|
|
102
|
-
const errors = [];
|
|
103
|
-
if (state.currentPhase !== 'release') {
|
|
104
|
-
errors.push(
|
|
105
|
-
"Current workflow phase isn't 'release'. Complete documentation, commit, and push steps first."
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
if (!state.readyCheckCompleted) {
|
|
109
|
-
errors.push("'check_ready_to_commit' has not been completed.");
|
|
110
|
-
}
|
|
111
|
-
if (!state.commitAndPushCompleted) {
|
|
112
|
-
errors.push("'commit_and_push' has not been completed.");
|
|
113
|
-
}
|
|
114
|
-
if (state.released) {
|
|
115
|
-
errors.push('A release has already been recorded for this task.');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (errors.length && !forceFlag) {
|
|
119
|
-
console.error('❌ Release guard blocked the release:');
|
|
120
|
-
for (const message of errors) {
|
|
121
|
-
console.error(` • ${message}`);
|
|
122
|
-
}
|
|
123
|
-
console.error("\nUse 'perform_release' through the dev-workflow MCP after completing the missing steps.");
|
|
124
|
-
console.error("Or use: npm run release:patch -- --force (to bypass guard)");
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (forceFlag && errors.length) {
|
|
129
|
-
console.warn('⚠️ --force flag detected. Bypassing workflow guard.');
|
|
130
|
-
console.warn('Errors that were skipped:');
|
|
131
|
-
for (const message of errors) {
|
|
132
|
-
console.warn(` • ${message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const commiterReleaseScript = path.join(repoRoot, 'scripts', 'release.cjs');
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
await fs.access(commiterReleaseScript);
|
|
140
|
-
} catch {
|
|
141
|
-
console.error('❌ Release guard: @programinglive/commiter release script not found.');
|
|
142
|
-
console.error(' Ensure @programinglive/commiter is installed as a dev dependency.');
|
|
143
|
-
console.error(' Try: npm install --save-dev @programinglive/commiter');
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const runRelease = async () => {
|
|
148
|
-
if (process.env.DEV_WORKFLOW_SKIP_RELEASE === '1') {
|
|
149
|
-
console.log('ℹ️ DEV_WORKFLOW_SKIP_RELEASE=1 detected. Skipping actual release command.');
|
|
150
|
-
return 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return await new Promise((resolve, reject) => {
|
|
154
|
-
const child = spawn(
|
|
155
|
-
process.execPath,
|
|
156
|
-
[commiterReleaseScript, releaseTypeArg, ...additionalArgs],
|
|
157
|
-
{ stdio: 'inherit', cwd: repoRoot }
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
child.on('error', (error) => reject(error));
|
|
161
|
-
child.on('close', (code) => resolve(code));
|
|
162
|
-
});
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const exitCode = await runRelease();
|
|
167
|
-
if (exitCode !== 0) {
|
|
168
|
-
console.error(`❌ Release command exited with code ${exitCode}.`);
|
|
169
|
-
process.exit(exitCode);
|
|
170
|
-
}
|
|
171
|
-
} catch (error) {
|
|
172
|
-
console.error(`❌ Failed to run release command: ${error.message}`);
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const recordedReleaseCommand = `node node_modules/@programinglive/commiter/scripts/release.js ${releaseTypeArg}`;
|
|
177
|
-
|
|
178
|
-
state.released = true;
|
|
179
|
-
state.releaseCommand = recordedReleaseCommand;
|
|
180
|
-
state.currentPhase = 'ready_to_complete';
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.error(`❌ Release guard: failed to update workflow state. ${error.message}`);
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log('✅ Release recorded in workflow state. You can now run complete_task.');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { resolveStateFile } from './workflow-state.js';
|
|
7
|
+
|
|
8
|
+
const RELEASE_TYPES = new Set(['major', 'minor', 'patch']);
|
|
9
|
+
|
|
10
|
+
async function readStateCandidate(filePath) {
|
|
11
|
+
if (!filePath) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const [raw, stats] = await Promise.all([fs.readFile(filePath, 'utf8'), fs.stat(filePath)]);
|
|
17
|
+
return {
|
|
18
|
+
state: JSON.parse(raw),
|
|
19
|
+
path: filePath,
|
|
20
|
+
mtimeMs: stats.mtimeMs,
|
|
21
|
+
};
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function loadWorkflowStateCandidates(projectRoot) {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
candidates.push(resolveStateFile());
|
|
32
|
+
} catch {
|
|
33
|
+
// resolver may throw before state exists; fall through to other candidates
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const legacyStatePath = path.join(projectRoot, '.state', 'workflow-state.json');
|
|
37
|
+
candidates.push(legacyStatePath);
|
|
38
|
+
|
|
39
|
+
// Add explicit DEV_WORKFLOW_STATE_FILE if provided (resolver would have used it, but keep explicit just in case)
|
|
40
|
+
if (process.env.DEV_WORKFLOW_STATE_FILE) {
|
|
41
|
+
candidates.push(path.resolve(process.env.DEV_WORKFLOW_STATE_FILE));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
let freshest = null;
|
|
46
|
+
|
|
47
|
+
for (const candidate of candidates) {
|
|
48
|
+
if (!candidate) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const resolved = path.resolve(candidate);
|
|
53
|
+
if (seen.has(resolved)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
seen.add(resolved);
|
|
57
|
+
|
|
58
|
+
const result = await readStateCandidate(resolved);
|
|
59
|
+
if (!result) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!freshest || result.mtimeMs > freshest.mtimeMs) {
|
|
64
|
+
freshest = result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return freshest;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main() {
|
|
72
|
+
const releaseTypeArg = (process.argv[2] || '').toLowerCase();
|
|
73
|
+
const forceFlag = process.argv.includes('--force') || process.env.DEV_WORKFLOW_FORCE_RELEASE === '1';
|
|
74
|
+
const additionalArgs = process.argv.slice(3).filter((arg) => arg !== '--force');
|
|
75
|
+
|
|
76
|
+
if (!RELEASE_TYPES.has(releaseTypeArg)) {
|
|
77
|
+
console.error(
|
|
78
|
+
'❌ Release guard: expected release type argument (major | minor | patch). Example: npm run release:patch'
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const repoRoot = process.cwd();
|
|
84
|
+
const projectRootCandidate = process.env.INIT_CWD || repoRoot;
|
|
85
|
+
if (projectRootCandidate.includes(`node_modules${path.sep}`)) {
|
|
86
|
+
console.error('❌ Release guard: refusing to run inside node_modules.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const projectRoot = projectRootCandidate;
|
|
90
|
+
|
|
91
|
+
const result = await loadWorkflowStateCandidates(projectRoot);
|
|
92
|
+
if (!result) {
|
|
93
|
+
console.error(
|
|
94
|
+
`❌ Release guard: cannot locate workflow state under ${path.join(projectRoot, '.state')}.
|
|
95
|
+
Make sure the dev-workflow MCP is installed and you have started a task.`
|
|
96
|
+
);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { state, path: statePath } = result;
|
|
101
|
+
|
|
102
|
+
const errors = [];
|
|
103
|
+
if (state.currentPhase !== 'release') {
|
|
104
|
+
errors.push(
|
|
105
|
+
"Current workflow phase isn't 'release'. Complete documentation, commit, and push steps first."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (!state.readyCheckCompleted) {
|
|
109
|
+
errors.push("'check_ready_to_commit' has not been completed.");
|
|
110
|
+
}
|
|
111
|
+
if (!state.commitAndPushCompleted) {
|
|
112
|
+
errors.push("'commit_and_push' has not been completed.");
|
|
113
|
+
}
|
|
114
|
+
if (state.released) {
|
|
115
|
+
errors.push('A release has already been recorded for this task.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (errors.length && !forceFlag) {
|
|
119
|
+
console.error('❌ Release guard blocked the release:');
|
|
120
|
+
for (const message of errors) {
|
|
121
|
+
console.error(` • ${message}`);
|
|
122
|
+
}
|
|
123
|
+
console.error("\nUse 'perform_release' through the dev-workflow MCP after completing the missing steps.");
|
|
124
|
+
console.error("Or use: npm run release:patch -- --force (to bypass guard)");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (forceFlag && errors.length) {
|
|
129
|
+
console.warn('⚠️ --force flag detected. Bypassing workflow guard.');
|
|
130
|
+
console.warn('Errors that were skipped:');
|
|
131
|
+
for (const message of errors) {
|
|
132
|
+
console.warn(` • ${message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const commiterReleaseScript = path.join(repoRoot, 'scripts', 'release.cjs');
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await fs.access(commiterReleaseScript);
|
|
140
|
+
} catch {
|
|
141
|
+
console.error('❌ Release guard: @programinglive/commiter release script not found.');
|
|
142
|
+
console.error(' Ensure @programinglive/commiter is installed as a dev dependency.');
|
|
143
|
+
console.error(' Try: npm install --save-dev @programinglive/commiter');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const runRelease = async () => {
|
|
148
|
+
if (process.env.DEV_WORKFLOW_SKIP_RELEASE === '1') {
|
|
149
|
+
console.log('ℹ️ DEV_WORKFLOW_SKIP_RELEASE=1 detected. Skipping actual release command.');
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return await new Promise((resolve, reject) => {
|
|
154
|
+
const child = spawn(
|
|
155
|
+
process.execPath,
|
|
156
|
+
[commiterReleaseScript, releaseTypeArg, ...additionalArgs],
|
|
157
|
+
{ stdio: 'inherit', cwd: repoRoot }
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
child.on('error', (error) => reject(error));
|
|
161
|
+
child.on('close', (code) => resolve(code));
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const exitCode = await runRelease();
|
|
167
|
+
if (exitCode !== 0) {
|
|
168
|
+
console.error(`❌ Release command exited with code ${exitCode}.`);
|
|
169
|
+
process.exit(exitCode);
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error(`❌ Failed to run release command: ${error.message}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const recordedReleaseCommand = `node node_modules/@programinglive/commiter/scripts/release.js ${releaseTypeArg}`;
|
|
177
|
+
|
|
178
|
+
state.released = true;
|
|
179
|
+
state.releaseCommand = recordedReleaseCommand;
|
|
180
|
+
state.currentPhase = 'ready_to_complete';
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(`❌ Release guard: failed to update workflow state. ${error.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('✅ Release recorded in workflow state. You can now run complete_task.');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
main();
|