@programinglive/dev-workflow-mcp-server 1.5.0 → 1.5.2

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.
Files changed (99) hide show
  1. package/.agent/workflows/dev-workflow-bugfix.md +250 -250
  2. package/.agent/workflows/dev-workflow-feature.md +201 -201
  3. package/.agent/workflows/dev-workflow-full-cycle.md +400 -400
  4. package/.agent/workflows/dev-workflow-refactor.md +238 -238
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
  7. package/.github/pull_request_template.md +22 -22
  8. package/.github/workflows/npm-publish.yml +50 -47
  9. package/.husky/commit-msg +0 -0
  10. package/CHANGELOG.md +750 -491
  11. package/CODE_OF_CONDUCT.md +79 -79
  12. package/CONTRIBUTING.md +60 -60
  13. package/LICENSE +21 -21
  14. package/README.md +632 -609
  15. package/SECURITY.md +20 -20
  16. package/db/index.js +204 -204
  17. package/docs/NETLIFY_DEPLOYMENT.md +125 -0
  18. package/docs/PLESK_DEPLOYMENT.md +170 -0
  19. package/docs/WORKFLOW-BEST-PRACTICES.md +58 -0
  20. package/docs/antigravity/EXAMPLES.md +535 -535
  21. package/docs/antigravity/GETTING-STARTED.md +292 -292
  22. package/docs/antigravity/TROUBLESHOOTING.md +742 -742
  23. package/docs/antigravity/WALKTHROUGH.md +25 -25
  24. package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
  25. package/docs/product/PRD.md +67 -64
  26. package/docs/release-notes/RELEASE_NOTES.md +226 -132
  27. package/docs/web-dashboard.md +175 -163
  28. package/exec.js +49 -49
  29. package/index.js +279 -279
  30. package/install.js +73 -73
  31. package/package.json +11 -9
  32. package/release-wrapper.js +192 -157
  33. package/scripts/build-docs.js +63 -63
  34. package/scripts/hello-world.js +1 -1
  35. package/scripts/release.cjs +252 -252
  36. package/scripts/update-release-notes.cjs +178 -182
  37. package/server.json +23 -0
  38. package/tailwind.config.js +8 -10
  39. package/tests/build.test.js +83 -48
  40. package/tests/exec.test.js +35 -35
  41. package/tests/handlers.test.js +1058 -980
  42. package/tests/hello-world.test.js +13 -13
  43. package/tests/mcpRegistry.test.js +65 -0
  44. package/tests/release-wrapper.test.js +107 -105
  45. package/tests/workflowState.test.js +505 -502
  46. package/tools/definitions.js +332 -332
  47. package/tools/handlers.js +1019 -1019
  48. package/vite.config.js +43 -43
  49. package/vite.web.config.js +23 -0
  50. package/web/README.md +36 -0
  51. package/web/app/docs/page.tsx +202 -0
  52. package/web/app/favicon.ico +0 -0
  53. package/web/app/globals.css +91 -0
  54. package/web/app/layout.tsx +36 -0
  55. package/web/app/page.tsx +15 -0
  56. package/web/components/ApiReference.tsx +20 -0
  57. package/web/components/Features.tsx +61 -0
  58. package/web/components/Footer.tsx +9 -0
  59. package/web/components/Hero.tsx +96 -0
  60. package/web/components/History.tsx +143 -0
  61. package/web/components/Navbar.tsx +64 -0
  62. package/web/components/Terminal.tsx +114 -0
  63. package/web/eslint.config.mjs +18 -0
  64. package/web/lib/db.js +131 -0
  65. package/web/lib/docs.js +73 -0
  66. package/web/netlify.toml +3 -0
  67. package/web/next.config.ts +12 -0
  68. package/web/package-lock.json +6560 -0
  69. package/web/package.json +28 -0
  70. package/web/postcss.config.mjs +9 -0
  71. package/web/public/assets/android-chrome-192x192.png +0 -0
  72. package/web/public/assets/android-chrome-512x512.png +0 -0
  73. package/web/public/assets/apple-touch-icon.png +0 -0
  74. package/web/public/assets/favicon-16x16.png +0 -0
  75. package/web/public/assets/favicon-32x32.png +0 -0
  76. package/web/public/assets/favicon.ico +0 -0
  77. package/web/public/assets/favicon.png +0 -0
  78. package/web/public/assets/logo.png +0 -0
  79. package/web/public/assets/og-preview.png +0 -0
  80. package/web/public/assets/site.webmanifest +1 -0
  81. package/web/public/file.svg +1 -0
  82. package/web/public/globe.svg +1 -0
  83. package/web/public/next.svg +1 -0
  84. package/web/public/vercel.svg +1 -0
  85. package/web/public/window.svg +1 -0
  86. package/web/server.js +31 -159
  87. package/web/tailwind.config.ts +19 -0
  88. package/web/tsconfig.json +42 -0
  89. package/workflow-state.js +612 -626
  90. package/dist/docs/EXAMPLES.html +0 -411
  91. package/dist/docs/GETTING-STARTED.html +0 -233
  92. package/dist/docs/README.html +0 -535
  93. package/dist/docs/RELEASE_NOTES.html +0 -167
  94. package/dist/docs/TROUBLESHOOTING.html +0 -542
  95. package/dist/docs/WORKFLOW-TEMPLATES.html +0 -591
  96. package/dist/docs/web-dashboard.html +0 -158
  97. package/web/public/index.html +0 -1046
  98. package/web/public/input.css +0 -3
  99. package/web/public/output.css +0 -1
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,18 +1,20 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "MCP server to enforce development workflow discipline",
5
5
  "homepage": "https://devworkflow.programinglive.com/",
6
+ "mcpName": "io.github.programinglive/dev-workflow-mcp-server",
6
7
  "type": "module",
7
8
  "main": "index.js",
8
9
  "bin": {
9
10
  "dev-workflow-mcp": "index.js"
10
11
  },
11
12
  "scripts": {
12
- "build": "vite build",
13
- "build:web": "npx tailwindcss -i ./web/public/input.css -o ./web/public/output.css --minify",
14
- "watch:web": "npx tailwindcss -i ./web/public/input.css -o ./web/public/output.css --watch",
15
- "postbuild": "npm run build:web && node scripts/build-docs.js",
13
+ "build": "npm run build:server && npm run build:web",
14
+ "build:server": "vite build",
15
+ "build:web": "npm run build --prefix web",
16
+ "watch:web": "npm run dev --prefix web",
17
+ "postbuild": "node scripts/build-docs.js",
16
18
  "dev": "node --watch index.js",
17
19
  "start": "node index.js",
18
20
  "local": "node index.js",
@@ -21,10 +23,10 @@
21
23
  "prepare": "husky",
22
24
  "postinstall": "node install.js",
23
25
  "release": "node scripts/release.cjs",
24
- "release:major": "node release-wrapper.js major",
25
- "release:minor": "node release-wrapper.js minor",
26
- "release:patch": "node release-wrapper.js patch",
27
- "web": "node web/server.js"
26
+ "release:major": "node scripts/release.cjs major",
27
+ "release:minor": "node scripts/release.cjs minor",
28
+ "release:patch": "node scripts/release.cjs patch",
29
+ "web": "npm start --prefix web"
28
30
  },
29
31
  "keywords": [
30
32
  "mcp",
@@ -1,157 +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
-
7
- const RELEASE_TYPES = new Set(['major', 'minor', 'patch']);
8
-
9
- async function main() {
10
- const releaseTypeArg = (process.argv[2] || '').toLowerCase();
11
- const forceFlag = process.argv.includes('--force') || process.env.DEV_WORKFLOW_FORCE_RELEASE === '1';
12
- const additionalArgs = process.argv.slice(3).filter((arg) => arg !== '--force');
13
-
14
- if (!RELEASE_TYPES.has(releaseTypeArg)) {
15
- console.error(
16
- '❌ Release guard: expected release type argument (major | minor | patch). Example: npm run release:patch'
17
- );
18
- process.exit(1);
19
- }
20
-
21
- const repoRoot = process.cwd();
22
- const projectRootCandidate = process.env.INIT_CWD || repoRoot;
23
- if (projectRootCandidate.includes(`node_modules${path.sep}`)) {
24
- console.error('❌ Release guard: refusing to run inside node_modules.');
25
- process.exit(1);
26
- }
27
- const projectRoot = projectRootCandidate;
28
-
29
- // Prefer state under INIT_CWD to support per-project runs and tests.
30
- // If DEV_WORKFLOW_USER_ID is set and a user-scoped file exists, use it; otherwise fall back to legacy path.
31
- const baseStateDir = path.join(projectRoot, '.state');
32
- const legacyStatePath = path.join(baseStateDir, 'workflow-state.json');
33
- let resolvedUserId = (process.env.DEV_WORKFLOW_USER_ID || '').trim();
34
- if (!resolvedUserId) {
35
- try {
36
- const fileUserId = (await fs.readFile(path.join(baseStateDir, 'user-id'), 'utf8')).trim();
37
- if (fileUserId) {
38
- resolvedUserId = fileUserId;
39
- }
40
- } catch {
41
- // no user-id file; legacy layout likely
42
- }
43
- }
44
- const userStatePath = resolvedUserId ? path.join(baseStateDir, 'users', resolvedUserId, 'workflow-state.json') : null;
45
-
46
- let statePath = legacyStatePath;
47
- if (userStatePath) {
48
- try {
49
- await fs.access(userStatePath);
50
- statePath = userStatePath;
51
- } catch {
52
- // fall back to legacy path
53
- }
54
- }
55
- let state;
56
-
57
- try {
58
- const raw = await fs.readFile(statePath, 'utf8');
59
- state = JSON.parse(raw);
60
- } catch (error) {
61
- console.error(
62
- `❌ Release guard: cannot locate workflow state at ${statePath}. Make sure the dev-workflow MCP is installed and you have started a task.`
63
- );
64
- process.exit(1);
65
- }
66
-
67
- const errors = [];
68
- if (state.currentPhase !== 'release') {
69
- errors.push(
70
- "Current workflow phase isn't 'release'. Complete documentation, commit, and push steps first."
71
- );
72
- }
73
- if (!state.readyCheckCompleted) {
74
- errors.push("'check_ready_to_commit' has not been completed.");
75
- }
76
- if (!state.commitAndPushCompleted) {
77
- errors.push("'commit_and_push' has not been completed.");
78
- }
79
- if (state.released) {
80
- errors.push('A release has already been recorded for this task.');
81
- }
82
-
83
- if (errors.length && !forceFlag) {
84
- console.error('❌ Release guard blocked the release:');
85
- for (const message of errors) {
86
- console.error(` • ${message}`);
87
- }
88
- console.error("\nUse 'perform_release' through the dev-workflow MCP after completing the missing steps.");
89
- console.error("Or use: npm run release:patch -- --force (to bypass guard)");
90
- process.exit(1);
91
- }
92
-
93
- if (forceFlag && errors.length) {
94
- console.warn('⚠️ --force flag detected. Bypassing workflow guard.');
95
- console.warn('Errors that were skipped:');
96
- for (const message of errors) {
97
- console.warn(` • ${message}`);
98
- }
99
- }
100
-
101
- const commiterReleaseScript = path.join(repoRoot, 'scripts', 'release.cjs');
102
-
103
- try {
104
- await fs.access(commiterReleaseScript);
105
- } catch {
106
- console.error('❌ Release guard: @programinglive/commiter release script not found.');
107
- console.error(' Ensure @programinglive/commiter is installed as a dev dependency.');
108
- console.error(' Try: npm install --save-dev @programinglive/commiter');
109
- process.exit(1);
110
- }
111
-
112
- const runRelease = async () => {
113
- if (process.env.DEV_WORKFLOW_SKIP_RELEASE === '1') {
114
- console.log('ℹ️ DEV_WORKFLOW_SKIP_RELEASE=1 detected. Skipping actual release command.');
115
- return 0;
116
- }
117
-
118
- return await new Promise((resolve, reject) => {
119
- const child = spawn(
120
- process.execPath,
121
- [commiterReleaseScript, releaseTypeArg, ...additionalArgs],
122
- { stdio: 'inherit', cwd: repoRoot }
123
- );
124
-
125
- child.on('error', (error) => reject(error));
126
- child.on('close', (code) => resolve(code));
127
- });
128
- };
129
-
130
- try {
131
- const exitCode = await runRelease();
132
- if (exitCode !== 0) {
133
- console.error(`❌ Release command exited with code ${exitCode}.`);
134
- process.exit(exitCode);
135
- }
136
- } catch (error) {
137
- console.error(`❌ Failed to run release command: ${error.message}`);
138
- process.exit(1);
139
- }
140
-
141
- const recordedReleaseCommand = `node node_modules/@programinglive/commiter/scripts/release.js ${releaseTypeArg}`;
142
-
143
- state.released = true;
144
- state.releaseCommand = recordedReleaseCommand;
145
- state.currentPhase = 'ready_to_complete';
146
-
147
- try {
148
- await fs.writeFile(statePath, JSON.stringify(state, null, 2));
149
- } catch (error) {
150
- console.error(`❌ Release guard: failed to update workflow state. ${error.message}`);
151
- process.exit(1);
152
- }
153
-
154
- console.log('✅ Release recorded in workflow state. You can now run complete_task.');
155
- }
156
-
157
- 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();