@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.
Files changed (78) 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/github-release.yml +56 -0
  9. package/.github/workflows/npm-publish.yml +50 -50
  10. package/.husky/commit-msg +0 -0
  11. package/CHANGELOG.md +759 -741
  12. package/CODE_OF_CONDUCT.md +79 -79
  13. package/CONTRIBUTING.md +60 -60
  14. package/LICENSE +21 -21
  15. package/README.md +632 -632
  16. package/SECURITY.md +20 -20
  17. package/db/index.js +204 -204
  18. package/docs/NETLIFY_DEPLOYMENT.md +125 -125
  19. package/docs/PLESK_DEPLOYMENT.md +170 -170
  20. package/docs/WORKFLOW-BEST-PRACTICES.md +58 -58
  21. package/docs/antigravity/EXAMPLES.md +535 -535
  22. package/docs/antigravity/GETTING-STARTED.md +292 -292
  23. package/docs/antigravity/TROUBLESHOOTING.md +742 -742
  24. package/docs/antigravity/WALKTHROUGH.md +25 -25
  25. package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
  26. package/docs/product/PRD.md +67 -67
  27. package/docs/release-notes/RELEASE_NOTES.md +233 -226
  28. package/docs/web-dashboard.md +175 -175
  29. package/exec.js +49 -49
  30. package/index.js +279 -279
  31. package/install.js +73 -73
  32. package/package.json +4 -4
  33. package/release-wrapper.js +192 -192
  34. package/scripts/build-docs.js +63 -63
  35. package/scripts/extract-release-notes.cjs +78 -0
  36. package/scripts/hello-world.js +1 -1
  37. package/scripts/release.cjs +252 -252
  38. package/scripts/release.js +252 -0
  39. package/scripts/update-release-notes.cjs +178 -178
  40. package/scripts/update-release-notes.js +182 -0
  41. package/server.json +23 -23
  42. package/tailwind.config.js +8 -8
  43. package/tests/build.test.js +83 -83
  44. package/tests/exec.test.js +35 -35
  45. package/tests/handlers.test.js +1058 -1058
  46. package/tests/hello-world.test.js +13 -13
  47. package/tests/mcpRegistry.test.js +65 -65
  48. package/tests/patch-release.test.js +7 -0
  49. package/tests/release-wrapper.test.js +107 -107
  50. package/tests/workflowState.test.js +505 -505
  51. package/tools/definitions.js +332 -332
  52. package/tools/handlers.js +1019 -1019
  53. package/vite.config.js +43 -43
  54. package/vite.web.config.js +23 -23
  55. package/web/README.md +36 -36
  56. package/web/app/docs/page.tsx +202 -202
  57. package/web/app/globals.css +90 -90
  58. package/web/app/layout.tsx +36 -36
  59. package/web/app/page.tsx +15 -15
  60. package/web/components/ApiReference.tsx +20 -20
  61. package/web/components/Features.tsx +61 -61
  62. package/web/components/Footer.tsx +9 -9
  63. package/web/components/Hero.tsx +96 -96
  64. package/web/components/History.tsx +143 -143
  65. package/web/components/Navbar.tsx +64 -64
  66. package/web/components/Terminal.tsx +114 -114
  67. package/web/eslint.config.mjs +18 -18
  68. package/web/lib/db.js +131 -131
  69. package/web/lib/docs.js +73 -73
  70. package/web/netlify.toml +3 -3
  71. package/web/next.config.ts +12 -12
  72. package/web/package-lock.json +6560 -6560
  73. package/web/package.json +28 -28
  74. package/web/postcss.config.mjs +9 -9
  75. package/web/server.js +31 -31
  76. package/web/tailwind.config.ts +19 -19
  77. package/web/tsconfig.json +42 -42
  78. 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.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.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();