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

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 (76) 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 -50
  9. package/.husky/commit-msg +0 -0
  10. package/CHANGELOG.md +748 -741
  11. package/CODE_OF_CONDUCT.md +79 -79
  12. package/CONTRIBUTING.md +60 -60
  13. package/LICENSE +21 -21
  14. package/README.md +632 -632
  15. package/SECURITY.md +20 -20
  16. package/db/index.js +204 -204
  17. package/docs/NETLIFY_DEPLOYMENT.md +125 -125
  18. package/docs/PLESK_DEPLOYMENT.md +170 -170
  19. package/docs/WORKFLOW-BEST-PRACTICES.md +58 -58
  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 -67
  26. package/docs/release-notes/RELEASE_NOTES.md +226 -226
  27. package/docs/web-dashboard.md +175 -175
  28. package/exec.js +49 -49
  29. package/index.js +279 -279
  30. package/install.js +73 -73
  31. package/package.json +8 -8
  32. package/release-wrapper.js +192 -192
  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/release.js +252 -0
  37. package/scripts/update-release-notes.cjs +178 -178
  38. package/scripts/update-release-notes.js +182 -0
  39. package/server.json +23 -23
  40. package/tailwind.config.js +8 -8
  41. package/tests/build.test.js +83 -83
  42. package/tests/exec.test.js +35 -35
  43. package/tests/handlers.test.js +1058 -1058
  44. package/tests/hello-world.test.js +13 -13
  45. package/tests/mcpRegistry.test.js +65 -65
  46. package/tests/patch-release.test.js +7 -0
  47. package/tests/release-wrapper.test.js +107 -107
  48. package/tests/workflowState.test.js +505 -505
  49. package/tools/definitions.js +332 -332
  50. package/tools/handlers.js +1019 -1019
  51. package/vite.config.js +43 -43
  52. package/vite.web.config.js +23 -23
  53. package/web/README.md +36 -36
  54. package/web/app/docs/page.tsx +202 -202
  55. package/web/app/globals.css +90 -90
  56. package/web/app/layout.tsx +36 -36
  57. package/web/app/page.tsx +15 -15
  58. package/web/components/ApiReference.tsx +20 -20
  59. package/web/components/Features.tsx +61 -61
  60. package/web/components/Footer.tsx +9 -9
  61. package/web/components/Hero.tsx +96 -96
  62. package/web/components/History.tsx +143 -143
  63. package/web/components/Navbar.tsx +64 -64
  64. package/web/components/Terminal.tsx +114 -114
  65. package/web/eslint.config.mjs +18 -18
  66. package/web/lib/db.js +131 -131
  67. package/web/lib/docs.js +73 -73
  68. package/web/netlify.toml +3 -3
  69. package/web/next.config.ts +12 -12
  70. package/web/package-lock.json +6560 -6560
  71. package/web/package.json +28 -28
  72. package/web/postcss.config.mjs +9 -9
  73. package/web/server.js +31 -31
  74. package/web/tailwind.config.ts +19 -19
  75. package/web/tsconfig.json +42 -42
  76. 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.2",
3
+ "version": "1.5.3",
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",
@@ -22,10 +22,10 @@
22
22
  "check": "node --check index.js",
23
23
  "prepare": "husky",
24
24
  "postinstall": "node install.js",
25
- "release": "node scripts/release.cjs",
26
- "release:major": "node scripts/release.cjs major",
27
- "release:minor": "node scripts/release.cjs minor",
28
- "release:patch": "node scripts/release.cjs patch",
25
+ "release": "node scripts/release.js",
26
+ "release:major": "node scripts/release.js major",
27
+ "release:minor": "node scripts/release.js minor",
28
+ "release:patch": "node scripts/release.js patch",
29
29
  "web": "npm start --prefix web"
30
30
  },
31
31
  "keywords": [
@@ -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.1.0",
56
- "@commitlint/config-conventional": "^20.0.0",
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",
@@ -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();