@programinglive/dev-workflow-mcp-server 1.8.1 → 1.10.0

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 (114) hide show
  1. package/.env.example +21 -1
  2. package/AGENTS.md +25 -0
  3. package/README.md +372 -649
  4. package/factory-client.js +554 -0
  5. package/git-facade.js +35 -0
  6. package/index.js +9 -34
  7. package/package.json +21 -20
  8. package/scripts/bootstrap-unix.sh +55 -0
  9. package/scripts/bootstrap-windows.ps1 +75 -0
  10. package/scripts/install-husky.js +43 -0
  11. package/scripts/setup-stack.js +436 -0
  12. package/scripts/setup-test-dbs.sh +61 -0
  13. package/scripts/test-db-adapters.sh +71 -0
  14. package/scripts/verify-bundle.mjs +92 -0
  15. package/tools/definitions.js +143 -0
  16. package/tools/handler-modules/commit-handlers.js +2 -1
  17. package/tools/handler-modules/factory-handlers.js +276 -0
  18. package/tools/handler-modules/index.js +24 -7
  19. package/tools/handler-modules/shared.js +7 -41
  20. package/tools/handler-modules/status-handlers.js +25 -3
  21. package/tools/handler-modules/workflow-handlers.js +57 -10
  22. package/vite.config.js +6 -0
  23. package/workflow-state.js +176 -2
  24. package/.agent/workflows/dev-workflow-bugfix.md +0 -250
  25. package/.agent/workflows/dev-workflow-feature.md +0 -201
  26. package/.agent/workflows/dev-workflow-full-cycle.md +0 -400
  27. package/.agent/workflows/dev-workflow-refactor.md +0 -238
  28. package/.env.production +0 -16
  29. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -34
  30. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
  31. package/.github/pull_request_template.md +0 -22
  32. package/.github/workflows/github-release.yml +0 -131
  33. package/.husky/commit-msg +0 -3
  34. package/.husky/pre-commit +0 -2
  35. package/CHANGELOG.md +0 -1145
  36. package/docs/GCP_DEPLOYMENT.md +0 -298
  37. package/docs/NETLIFY_DEPLOYMENT.md +0 -125
  38. package/docs/PLESK_DEPLOYMENT.md +0 -170
  39. package/docs/PRD.md +0 -86
  40. package/docs/WORKFLOW-BEST-PRACTICES.md +0 -58
  41. package/docs/antigravity/EXAMPLES.md +0 -535
  42. package/docs/antigravity/GETTING-STARTED.md +0 -292
  43. package/docs/antigravity/TROUBLESHOOTING.md +0 -742
  44. package/docs/antigravity/WALKTHROUGH.md +0 -25
  45. package/docs/antigravity/WORKFLOW-TEMPLATES.md +0 -670
  46. package/docs/release-notes/RELEASE_NOTES.md +0 -524
  47. package/docs/web-dashboard.md +0 -175
  48. package/scripts/test-auth-flow.js +0 -75
  49. package/tailwind.config.js +0 -8
  50. package/tests/build.test.js +0 -74
  51. package/tests/db-adapters.test.js +0 -108
  52. package/tests/env-support.test.js +0 -46
  53. package/tests/exec.test.js +0 -35
  54. package/tests/handlers-commit.test.js +0 -174
  55. package/tests/handlers-release.test.js +0 -212
  56. package/tests/handlers-workflow.test.js +0 -68
  57. package/tests/handlers.test.js +0 -1142
  58. package/tests/hello-world.test.js +0 -13
  59. package/tests/mcpRegistry.test.js +0 -65
  60. package/tests/patch-release.test.js +0 -7
  61. package/tests/release-wrapper.test.js +0 -107
  62. package/tests/startup-resilience.test.js +0 -73
  63. package/tests/test-helpers.js +0 -50
  64. package/tests/workflowState.test.js +0 -520
  65. package/web/Dockerfile.api +0 -25
  66. package/web/README.md +0 -133
  67. package/web/__tests__/api/workflow-history.test.js +0 -63
  68. package/web/api-server.js +0 -211
  69. package/web/app/docs/page.tsx +0 -202
  70. package/web/app/favicon.ico +0 -0
  71. package/web/app/globals.css +0 -91
  72. package/web/app/history/page.tsx +0 -220
  73. package/web/app/layout.tsx +0 -39
  74. package/web/app/login/page.tsx +0 -142
  75. package/web/app/page.tsx +0 -13
  76. package/web/components/ApiReference.tsx +0 -20
  77. package/web/components/Features.tsx +0 -61
  78. package/web/components/Footer.tsx +0 -9
  79. package/web/components/Hero.tsx +0 -96
  80. package/web/components/History.tsx +0 -175
  81. package/web/components/Navbar.tsx +0 -208
  82. package/web/components/Terminal.tsx +0 -114
  83. package/web/components/ThemeProvider.tsx +0 -58
  84. package/web/eslint.config.mjs +0 -18
  85. package/web/lib/api-client.ts +0 -123
  86. package/web/lib/auth.js +0 -75
  87. package/web/lib/db.js +0 -131
  88. package/web/lib/docs.js +0 -73
  89. package/web/middleware/auth.js +0 -26
  90. package/web/netlify.toml +0 -8
  91. package/web/next.config.ts +0 -19
  92. package/web/package-lock.json +0 -10911
  93. package/web/package.json +0 -42
  94. package/web/postcss.config.mjs +0 -9
  95. package/web/public/assets/android-chrome-192x192.png +0 -0
  96. package/web/public/assets/android-chrome-512x512.png +0 -0
  97. package/web/public/assets/apple-touch-icon.png +0 -0
  98. package/web/public/assets/favicon-16x16.png +0 -0
  99. package/web/public/assets/favicon-32x32.png +0 -0
  100. package/web/public/assets/favicon.ico +0 -0
  101. package/web/public/assets/favicon.png +0 -0
  102. package/web/public/assets/logo.png +0 -0
  103. package/web/public/assets/og-preview.png +0 -0
  104. package/web/public/assets/site.webmanifest +0 -1
  105. package/web/public/file.svg +0 -1
  106. package/web/public/globe.svg +0 -1
  107. package/web/public/next.svg +0 -1
  108. package/web/public/vercel.svg +0 -1
  109. package/web/public/window.svg +0 -1
  110. package/web/scripts/db-query-history.js +0 -120
  111. package/web/scripts/smoke-test.js +0 -30
  112. package/web/server.js +0 -184
  113. package/web/tailwind.config.ts +0 -20
  114. package/web/tsconfig.json +0 -42
@@ -1,520 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import {
4
- mkdtemp,
5
- rm,
6
- mkdir,
7
- realpath,
8
- readFile,
9
- writeFile,
10
- access,
11
- copyFile,
12
- } from "node:fs/promises";
13
- import { constants as fsConstants } from "node:fs";
14
- import { tmpdir } from "node:os";
15
- import path from "node:path";
16
- import { fileURLToPath, pathToFileURL } from "node:url";
17
-
18
- // Isolate these tests from the real database configuration
19
- delete process.env.DEV_WORKFLOW_DB_TYPE;
20
- delete process.env.DEV_WORKFLOW_DB_URL;
21
- delete process.env.TEST_MYSQL_URL;
22
- delete process.env.TEST_POSTGRES_URL;
23
-
24
- import {
25
- WorkflowState,
26
- getNextStep,
27
- containsTestFilesInStatus,
28
- createCommitMessageParts,
29
- determineReleaseTypeFromCommit,
30
- workingTreeSummary,
31
- } from "../index.js";
32
-
33
- async function withWorkflowState(callback) {
34
- const tempDir = await mkdtemp(path.join(tmpdir(), "workflow-state-"));
35
- const stateFile = path.join(tempDir, "state.json");
36
- const state = new WorkflowState(stateFile);
37
- await state.load();
38
-
39
- try {
40
- await callback(state);
41
- } finally {
42
- await rm(tempDir, { recursive: true, force: true });
43
- }
44
- }
45
-
46
- test("resolveStateFile prefers current working directory when multiple project markers exist", async () => {
47
- const tempRoot = await mkdtemp(path.join(tmpdir(), "workflow-prefers-cwd-"));
48
- const moduleDir = path.join(tempRoot, "module");
49
- const projectA = path.join(tempRoot, "project-a");
50
- const projectB = path.join(tempRoot, "project-b");
51
- const projectBNested = path.join(projectB, "sub", "app");
52
-
53
- await mkdir(moduleDir, { recursive: true });
54
- await mkdir(projectA, { recursive: true });
55
- await mkdir(projectBNested, { recursive: true });
56
-
57
- const sourceModulePath = fileURLToPath(new URL("../workflow-state.js", import.meta.url));
58
- await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
59
-
60
- await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module", type: "module" }));
61
- await mkdir(path.join(moduleDir, "db"), { recursive: true });
62
- await writeFile(path.join(moduleDir, "db", "index.js"), 'export function getDbAdapter() { return { connect: async () => {}, saveState: async () => {}, getState: async () => null }; } export async function insertHistoryEntry() {} export async function updateSummaryForUser() {} export async function saveState() {} export async function getState() {} export async function getSummaryForUser() { return { totalTasks: 0, taskTypes: {} }; } export async function getHistoryForUser() { return { entries: [], total: 0 }; }');
63
- await writeFile(path.join(projectA, "package.json"), JSON.stringify({ name: "project-a" }));
64
- await writeFile(path.join(projectB, "package.json"), JSON.stringify({ name: "project-b" }));
65
-
66
- const moduleUrl = pathToFileURL(path.join(moduleDir, "workflow-state.js"));
67
- const { WorkflowState: TempWorkflowState } = await import(`${moduleUrl.href}?preferCwd=${Date.now()}`);
68
-
69
- const originalCwd = process.cwd();
70
- const initWasSet = Object.prototype.hasOwnProperty.call(process.env, "INIT_CWD");
71
- const originalInitCwd = process.env.INIT_CWD;
72
-
73
- try {
74
- process.chdir(projectBNested);
75
- process.env.INIT_CWD = projectA;
76
-
77
- const state = new TempWorkflowState();
78
- const resolvedStateFile = state.stateFile;
79
- const resolvedProjectA = await realpath(projectA);
80
- const resolvedProjectB = await realpath(projectB);
81
-
82
- assert.ok(
83
- resolvedStateFile.startsWith(path.join(resolvedProjectB, ".state", "users")),
84
- "State file should resolve under the current working directory's project"
85
- );
86
-
87
- assert.ok(
88
- !resolvedStateFile.startsWith(path.join(resolvedProjectA, ".state")),
89
- "State file should not use INIT_CWD project when cwd has its own markers"
90
- );
91
- } finally {
92
- process.chdir(originalCwd);
93
- if (initWasSet) {
94
- process.env.INIT_CWD = originalInitCwd;
95
- } else {
96
- delete process.env.INIT_CWD;
97
- }
98
- await rm(tempRoot, { recursive: true, force: true });
99
- }
100
- });
101
-
102
- test("WorkflowState mirrors state file to compatibility locations", async () => {
103
- const tempRoot = await mkdtemp(path.join(tmpdir(), "workflow-compat-"));
104
- const moduleDir = path.join(tempRoot, "module");
105
- const projectRoot = path.join(tempRoot, "project");
106
- const stateDir = path.join(projectRoot, ".state");
107
- const sourceModulePath = fileURLToPath(new URL("../workflow-state.js", import.meta.url));
108
- await mkdir(moduleDir, { recursive: true });
109
- await mkdir(path.join(moduleDir, "dist"), { recursive: true });
110
- await mkdir(path.join(tempRoot, "dist"), { recursive: true });
111
- await mkdir(stateDir, { recursive: true });
112
- await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
113
- await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module", type: "module" }));
114
- await mkdir(path.join(moduleDir, "db"), { recursive: true });
115
- await writeFile(path.join(moduleDir, "db", "index.js"), 'export function getDbAdapter() { return { connect: async () => {}, saveState: async () => {}, getState: async () => null }; } export async function insertHistoryEntry() {} export async function updateSummaryForUser() {} export async function saveState() {} export async function getState() {} export async function getSummaryForUser() { return { totalTasks: 0, taskTypes: {} }; } export async function getHistoryForUser() { return { entries: [], total: 0 }; }');
116
- await writeFile(path.join(projectRoot, "package.json"), JSON.stringify({ name: "temp" }));
117
-
118
- const stateFile = path.join(stateDir, "workflow-state.json");
119
- await writeFile(stateFile, JSON.stringify({ currentPhase: "testing" }, null, 2));
120
-
121
- const moduleUrl = pathToFileURL(path.join(moduleDir, "workflow-state.js"));
122
- const { WorkflowState: TempWorkflowState } = await import(moduleUrl.href);
123
-
124
- const state = new TempWorkflowState(stateFile);
125
- await state.load();
126
- state.state.currentPhase = "commit";
127
- await state.save();
128
-
129
- // Ensure primary state file was written correctly
130
- const primaryContent = await readFile(stateFile, "utf-8");
131
- const primaryParsed = JSON.parse(primaryContent);
132
- assert.equal(primaryParsed.currentPhase, "commit", "primary state file should be updated");
133
-
134
- await rm(tempRoot, { recursive: true, force: true });
135
- });
136
-
137
- test("WorkflowState avoids filesystem root when no project markers found", async () => {
138
- const tempRoot = await mkdtemp(path.join(tmpdir(), "workflow-rootless-"));
139
- const moduleDir = path.join(tempRoot, "module");
140
- await mkdir(moduleDir, { recursive: true });
141
-
142
- const sourceModulePath = fileURLToPath(new URL("../workflow-state.js", import.meta.url));
143
- await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
144
- await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module", type: "module" }));
145
- await mkdir(path.join(moduleDir, "db"), { recursive: true });
146
- await writeFile(path.join(moduleDir, "db", "index.js"), 'export function getDbAdapter() { return { connect: async () => {}, saveState: async () => {}, getState: async () => null }; } export async function insertHistoryEntry() {} export async function updateSummaryForUser() {} export async function saveState() {} export async function getState() {} export async function getSummaryForUser() { return { totalTasks: 0, taskTypes: {} }; } export async function getHistoryForUser() { return { entries: [], total: 0 }; }');
147
-
148
- const moduleUrl = pathToFileURL(path.join(moduleDir, "workflow-state.js"));
149
- const { WorkflowState: TempWorkflowState } = await import(moduleUrl.href);
150
-
151
- const originalCwd = process.cwd();
152
- const initWasSet = Object.prototype.hasOwnProperty.call(process.env, "INIT_CWD");
153
- const originalInitCwd = process.env.INIT_CWD;
154
-
155
- try {
156
- const cwdWithoutMarkers = path.join(tempRoot, "random", "deep");
157
- await mkdir(cwdWithoutMarkers, { recursive: true });
158
-
159
- process.chdir(cwdWithoutMarkers);
160
- process.env.INIT_CWD = cwdWithoutMarkers;
161
-
162
- const state = new TempWorkflowState();
163
- const resolvedTempRoot = await realpath(tempRoot);
164
-
165
- assert.ok(
166
- state.stateFile.startsWith(resolvedTempRoot),
167
- "state file should remain inside the temporary root when no project markers exist"
168
- );
169
- assert.ok(
170
- !state.stateFile.startsWith(path.join(path.sep, ".state")),
171
- "state file should not resolve to filesystem root"
172
- );
173
- } finally {
174
- process.chdir(originalCwd);
175
- if (initWasSet) {
176
- process.env.INIT_CWD = originalInitCwd;
177
- } else {
178
- delete process.env.INIT_CWD;
179
- }
180
-
181
- await rm(tempRoot, { recursive: true, force: true });
182
- }
183
- });
184
-
185
- test("resolveUserId persists generated ID", async () => {
186
- const projectRoot = await mkdtemp(path.join(tmpdir(), "workflow-userid-"));
187
- await writeFile(path.join(projectRoot, "package.json"), JSON.stringify({ name: "temp" }));
188
-
189
- const originalCwd = process.cwd();
190
- const initWasSet = Object.prototype.hasOwnProperty.call(process.env, "INIT_CWD");
191
- const originalInitCwd = process.env.INIT_CWD;
192
- const userEnvWasSet = Object.prototype.hasOwnProperty.call(process.env, "DEV_WORKFLOW_USER_ID");
193
- const originalUserEnv = process.env.DEV_WORKFLOW_USER_ID;
194
-
195
- try {
196
- process.chdir(projectRoot);
197
- process.env.INIT_CWD = projectRoot;
198
- if (userEnvWasSet) {
199
- delete process.env.DEV_WORKFLOW_USER_ID;
200
- }
201
-
202
- const firstModuleHref = new URL("../workflow-state.js?resolveUserId1", import.meta.url).href;
203
- const { WorkflowState: FirstWorkflowState } = await import(firstModuleHref);
204
-
205
- const state = new FirstWorkflowState();
206
- await state.ensurePrimaryFile();
207
-
208
- const userDir = path.dirname(state.stateFile);
209
- const userId = path.basename(userDir);
210
- assert.ok(userId.startsWith("user-"), "Generated user ID should use user- prefix");
211
-
212
- const userIdFile = path.join(projectRoot, ".state", "user-id");
213
- const persistedId = (await readFile(userIdFile, "utf-8")).trim();
214
- assert.equal(persistedId, userId, "user-id file should match generated user ID");
215
-
216
- const secondModuleHref = new URL("../workflow-state.js?resolveUserId2", import.meta.url).href;
217
- const { WorkflowState: SecondWorkflowState } = await import(secondModuleHref);
218
-
219
- const reloadedState = new SecondWorkflowState();
220
- await reloadedState.ensurePrimaryFile();
221
-
222
- assert.equal(reloadedState.stateFile, state.stateFile, "State file path should persist across reloads");
223
- const reloadedUserId = path.basename(path.dirname(reloadedState.stateFile));
224
- assert.equal(reloadedUserId, userId, "Reloaded module should reuse persisted user ID");
225
- } finally {
226
- process.chdir(originalCwd);
227
- if (initWasSet) {
228
- process.env.INIT_CWD = originalInitCwd;
229
- } else {
230
- delete process.env.INIT_CWD;
231
- }
232
- if (userEnvWasSet) {
233
- process.env.DEV_WORKFLOW_USER_ID = originalUserEnv;
234
- } else {
235
- delete process.env.DEV_WORKFLOW_USER_ID;
236
- }
237
- await rm(projectRoot, { recursive: true, force: true });
238
- }
239
- });
240
-
241
- test("WorkflowState generates project summary on load and updates on save", async () => {
242
- await withWorkflowState(async (state) => {
243
- const summaryPath = path.join(path.dirname(state.stateFile), "project-summary.json");
244
-
245
- const initialSummary = JSON.parse(await readFile(summaryPath, "utf-8"));
246
- assert.equal(initialSummary.totalTasks, 0);
247
- assert.deepEqual(initialSummary.taskTypes, {});
248
-
249
- state.addToHistory({
250
- taskDescription: "Seed summary",
251
- taskType: "feature",
252
- commitMessage: "feat: seed",
253
- });
254
- await state.save();
255
-
256
- const updatedSummary = JSON.parse(await readFile(summaryPath, "utf-8"));
257
- assert.equal(updatedSummary.totalTasks, 1);
258
- assert.equal(updatedSummary.taskTypes.feature, 1);
259
- assert.equal(updatedSummary.recentTasks[0].description, "Seed summary");
260
- });
261
- });
262
-
263
- test("WorkflowState persists data across save/load", async () => {
264
- await withWorkflowState(async (state) => {
265
- state.state.currentPhase = "testing";
266
- state.state.taskDescription = "Test persistence";
267
- state.state.bugFixed = true;
268
- state.state.testsCreated = true;
269
- state.state.readyCheckCompleted = true;
270
- state.state.released = true;
271
- state.state.releaseCommand = "npm run release";
272
- state.state.releaseNotes = "Automated release";
273
- state.state.commitAndPushCompleted = true;
274
- state.state.lastCommitMessage = "test: persistence";
275
- state.state.lastPushBranch = "main";
276
- state.state.testsSkipped = true;
277
- state.state.testsSkippedReason = "Legacy app uses manual QA";
278
- await state.save();
279
-
280
- const reloaded = new WorkflowState(state.stateFile);
281
- await reloaded.load();
282
-
283
- assert.equal(reloaded.state.currentPhase, "testing");
284
- assert.equal(reloaded.state.taskDescription, "Test persistence");
285
- assert.equal(reloaded.state.bugFixed, true);
286
- assert.equal(reloaded.state.testsCreated, true);
287
- assert.equal(reloaded.state.readyCheckCompleted, true);
288
- assert.equal(reloaded.state.released, true);
289
- assert.equal(reloaded.state.releaseCommand, "npm run release");
290
- assert.equal(reloaded.state.releaseNotes, "Automated release");
291
- assert.equal(reloaded.state.commitAndPushCompleted, true);
292
- assert.equal(reloaded.state.lastCommitMessage, "test: persistence");
293
- assert.equal(reloaded.state.lastPushBranch, "main");
294
- assert.equal(reloaded.state.testsSkipped, true);
295
- assert.equal(
296
- reloaded.state.testsSkippedReason,
297
- "Legacy app uses manual QA"
298
- );
299
- });
300
- });
301
-
302
- test("WorkflowState resolves state file relative to project root", async () => {
303
- const projectRoot = await mkdtemp(path.join(tmpdir(), "workflow-project-"));
304
- const originalCwd = process.cwd();
305
- const initWasSet = Object.prototype.hasOwnProperty.call(process.env, "INIT_CWD");
306
- const originalInitCwd = process.env.INIT_CWD;
307
- const devStateWasSet = Object.prototype.hasOwnProperty.call(
308
- process.env,
309
- "DEV_WORKFLOW_STATE_FILE"
310
- );
311
- const originalDevState = process.env.DEV_WORKFLOW_STATE_FILE;
312
-
313
- try {
314
- await mkdir(path.join(projectRoot, ".git"));
315
- const nestedDir = path.join(projectRoot, "dist", "server");
316
- await mkdir(nestedDir, { recursive: true });
317
-
318
- process.chdir(projectRoot);
319
- process.env.INIT_CWD = projectRoot;
320
- if (devStateWasSet) {
321
- delete process.env.DEV_WORKFLOW_STATE_FILE;
322
- }
323
- const resolvedProjectRoot = await realpath(projectRoot);
324
-
325
- const state = new WorkflowState();
326
- assert.ok(
327
- state.stateFile.startsWith(path.join(resolvedProjectRoot, ".state", "users")),
328
- "state file should resolve under project .state/users directory"
329
- );
330
-
331
- process.chdir(nestedDir);
332
- delete process.env.INIT_CWD;
333
- delete process.env.DEV_WORKFLOW_STATE_FILE;
334
-
335
- const state2 = new WorkflowState();
336
- assert.ok(
337
- state2.stateFile.startsWith(path.join(resolvedProjectRoot, ".state", "users")),
338
- "state file should resolve to project root"
339
- );
340
- } finally {
341
- process.chdir(originalCwd);
342
- if (initWasSet) {
343
- process.env.INIT_CWD = originalInitCwd;
344
- } else {
345
- delete process.env.INIT_CWD;
346
- }
347
- if (devStateWasSet) {
348
- process.env.DEV_WORKFLOW_STATE_FILE = originalDevState;
349
- } else {
350
- delete process.env.DEV_WORKFLOW_STATE_FILE;
351
- }
352
- await rm(projectRoot, { recursive: true, force: true });
353
- }
354
- });
355
-
356
- test("WorkflowState reset keeps history but clears progress", async () => {
357
- await withWorkflowState(async (state) => {
358
- state.state.currentPhase = "ready";
359
- state.state.testsCreated = true;
360
- state.state.testsPassed = true;
361
- state.state.documentationCreated = true;
362
- state.state.readyCheckCompleted = true;
363
- state.state.released = true;
364
- state.state.commitAndPushCompleted = true;
365
- state.state.testsSkipped = true;
366
- state.state.testsSkippedReason = "Manual QA";
367
- state.addToHistory({ taskDescription: "Sample task" });
368
-
369
- state.reset();
370
-
371
- assert.equal(state.state.currentPhase, "idle");
372
- assert.equal(state.state.testsCreated, false);
373
- assert.equal(state.state.testsPassed, false);
374
- assert.equal(state.state.documentationCreated, false);
375
- assert.equal(state.state.readyCheckCompleted, false);
376
- assert.equal(state.state.released, false);
377
- assert.equal(state.state.commitAndPushCompleted, false);
378
- assert.equal(state.state.testsSkipped, false);
379
- assert.equal(state.state.testsSkippedReason, "");
380
- assert.equal(state.state.history.length, 1);
381
- assert.equal(state.state.history[0].taskDescription, "Sample task");
382
- });
383
- });
384
-
385
- test("getNextStep treats skipped tests as satisfied", () => {
386
- const skippedState = {
387
- bugFixed: true,
388
- testsCreated: true,
389
- testsPassed: true,
390
- testsSkipped: true,
391
- testsSkippedReason: "Legacy system",
392
- documentationCreated: false,
393
- readyCheckCompleted: false,
394
- commitAndPushCompleted: false,
395
- released: false,
396
- };
397
-
398
- assert.equal(getNextStep(skippedState), "Create documentation");
399
- });
400
-
401
- test("getNextStep guides workflow progression", () => {
402
- const baseState = {
403
- bugFixed: false,
404
- testsCreated: false,
405
- testsPassed: false,
406
- documentationCreated: false,
407
- readyCheckCompleted: false,
408
- commitAndPushCompleted: false,
409
- released: false,
410
- };
411
-
412
- assert.equal(
413
- getNextStep(baseState),
414
- "Mark feature/bug as fixed"
415
- );
416
-
417
- const afterFix = { ...baseState, bugFixed: true };
418
- assert.equal(
419
- getNextStep(afterFix),
420
- "Create tests"
421
- );
422
-
423
- const afterTestsCreated = { ...afterFix, testsCreated: true };
424
- assert.equal(getNextStep(afterTestsCreated), "Run tests");
425
-
426
- const afterTests = { ...afterTestsCreated, testsPassed: true };
427
- assert.equal(
428
- getNextStep(afterTests),
429
- "Create documentation"
430
- );
431
-
432
- const afterDocs = { ...afterTests, documentationCreated: true };
433
- assert.equal(getNextStep(afterDocs), "Run 'check_ready_to_commit'");
434
-
435
- const afterReadyCheck = { ...afterDocs, readyCheckCompleted: true };
436
- assert.equal(getNextStep(afterReadyCheck), "Run 'commit_and_push'");
437
-
438
- const afterCommit = { ...afterReadyCheck, commitAndPushCompleted: true };
439
- assert.equal(getNextStep(afterCommit), "Run 'perform_release'");
440
-
441
- const afterRelease = { ...afterCommit, released: true };
442
- assert.equal(getNextStep(afterRelease), "Complete the task");
443
- });
444
-
445
- test("containsTestFilesInStatus detects modified test files", () => {
446
- const statusOutput = [
447
- " M src/app.test.js",
448
- "A tests/helpers/util.js",
449
- "R src/old.js -> src/new.spec.ts",
450
- ].join("\n");
451
-
452
- assert.equal(containsTestFilesInStatus(statusOutput), true);
453
- });
454
-
455
- test("containsTestFilesInStatus returns false when no test files present", () => {
456
- const statusOutput = [
457
- " M src/app.js",
458
- "A docs/README.md",
459
- "?? assets/logo.png",
460
- ].join("\n");
461
-
462
- assert.equal(containsTestFilesInStatus(statusOutput), false);
463
- });
464
-
465
- test("createCommitMessageParts generates test prefixed summary with details", () => {
466
- const changes = [
467
- { status: "M", path: "src/app.test.js" },
468
- { status: "A", path: "tests/helpers/util.js" },
469
- ];
470
-
471
- const { summary, body } = createCommitMessageParts(changes);
472
-
473
- assert.equal(summary.startsWith("test:"), true);
474
- assert.equal(body.includes("- modified src/app.test.js"), true);
475
- assert.equal(body.includes("- added tests/helpers/util.js"), true);
476
- });
477
-
478
- test("createCommitMessageParts respects provided summary when present", () => {
479
- const changes = [{ status: "M", path: "docs/guide.md" }];
480
- const provided = "docs: update guide";
481
-
482
- const { summary, body } = createCommitMessageParts(changes, provided);
483
-
484
- assert.equal(summary, provided);
485
- assert.equal(body.includes("- modified docs/guide.md"), true);
486
- });
487
-
488
- test("determineReleaseTypeFromCommit returns major for breaking change", () => {
489
- const message = "feat!: overhaul API\n\nBREAKING CHANGE: new behavior";
490
- assert.equal(determineReleaseTypeFromCommit(message), "major");
491
- });
492
-
493
- test("determineReleaseTypeFromCommit returns minor for feat", () => {
494
- const message = "feat: add dashboard";
495
- assert.equal(determineReleaseTypeFromCommit(message), "minor");
496
- });
497
-
498
- test("determineReleaseTypeFromCommit returns patch for fix", () => {
499
- const message = "fix: resolve login issue";
500
- assert.equal(determineReleaseTypeFromCommit(message), "patch");
501
- });
502
-
503
- test("determineReleaseTypeFromCommit defaults to patch for other commit types", () => {
504
- const message = "chore: update dependencies";
505
- assert.equal(determineReleaseTypeFromCommit(message), "patch");
506
- });
507
-
508
- test("workingTreeSummary reports no changes for empty output", () => {
509
- const summary = workingTreeSummary("\n \n");
510
- assert.equal(summary.hasChanges, false);
511
- assert.deepEqual(summary.lines, []);
512
- });
513
-
514
- test("workingTreeSummary lists trimmed status lines", () => {
515
- const statusOutput = " M src/app.js\nA tests/new.test.js\n";
516
- const summary = workingTreeSummary(statusOutput);
517
-
518
- assert.equal(summary.hasChanges, true);
519
- assert.deepEqual(summary.lines, ["M src/app.js", "A tests/new.test.js"]);
520
- });
@@ -1,25 +0,0 @@
1
- FROM node:20-alpine
2
-
3
- # Install dependencies for native modules
4
- RUN apk add --no-cache python3 make g++ postgresql-client
5
-
6
- WORKDIR /app
7
-
8
- # Copy package files
9
- COPY package*.json ./
10
-
11
- # Install dependencies
12
- RUN npm install --only=production
13
-
14
- # Copy application code
15
- COPY . .
16
-
17
- # Expose API port
18
- EXPOSE 8080
19
-
20
- # Set environment
21
- ENV NODE_ENV=production
22
- ENV API_PORT=8080
23
-
24
- # Run API server
25
- CMD ["node", "api-server.js"]
package/web/README.md DELETED
@@ -1,133 +0,0 @@
1
- # Dev Workflow Website
2
-
3
- A modern, responsive website for the Dev Workflow MCP Server built with Next.js, React, and Tailwind CSS.
4
-
5
- ## Features
6
-
7
- - ✅ **Responsive Design** - Optimized for all screen sizes including vertical/portrait monitors
8
- - 🌓 **Dark/Light Mode Toggle** - User preference persisted to localStorage with system preference detection
9
- - 📱 **Mobile First** - Fully responsive with adaptive layouts for sm, md, lg, and xl breakpoints
10
- - ⚡ **Static Export** - Pre-rendered static HTML for fast Netlify deployment
11
- - 🎨 **Modern UI** - Built with Tailwind CSS and Lucide React icons
12
- - ♿ **Accessible** - Semantic HTML and proper ARIA labels
13
-
14
- ## Getting Started
15
-
16
- ### Development
17
-
18
- ```bash
19
- npm run dev
20
- ```
21
-
22
- Open [http://localhost:3000](http://localhost:3000) to see the site.
23
-
24
- ### Build
25
-
26
- ```bash
27
- npm run build
28
- ```
29
-
30
- Generates static HTML in the `out/` directory.
31
-
32
- ## Features Overview
33
-
34
- ### Responsive Vertical Monitor Support
35
- - Grid layout switches from 2-column to single-column on smaller screens
36
- - Terminal visualization hidden on screens smaller than xl breakpoint
37
- - Responsive padding and text sizes scale appropriately
38
-
39
- ### Dark/Light Mode
40
- - Toggle button in navbar (Sun/Moon icons)
41
- - Theme preference stored in localStorage
42
- - Respects system color scheme preference on first visit
43
- - Smooth transitions between modes
44
-
45
- ## Deployment
46
-
47
- ### Netlify
48
-
49
- 1. **Prerequisites**: Install Netlify CLI
50
- ```bash
51
- npm install -g netlify-cli
52
- ```
53
-
54
- 2. **Build the site**
55
- ```bash
56
- npm run build
57
- ```
58
-
59
- 3. **Deploy**
60
- ```bash
61
- netlify deploy --prod --dir=out
62
- ```
63
-
64
- 4. **Or connect GitHub for automatic deployments**
65
- - Push to GitHub
66
- - Connect repo to Netlify
67
- - Netlify automatically builds and deploys on push
68
-
69
- ### Environment Setup
70
-
71
- Create a `.env.local` file if needed for environment variables (currently not required).
72
-
73
- ## Testing
74
-
75
- Run the build verification tests:
76
-
77
- ```bash
78
- node verify-build.js
79
- ```
80
-
81
- Tests verify:
82
- - Build completes successfully
83
- - All pages generate correctly
84
- - Dark mode classes are included
85
- - Responsive design classes present
86
- - Theme toggle functionality
87
- - Viewport meta tags for mobile
88
- - Vertical monitor optimization
89
-
90
- ## Project Structure
91
-
92
- ```
93
- web/
94
- ├── app/
95
- │ ├── layout.tsx # Root layout with ThemeProvider
96
- │ ├── page.tsx # Homepage
97
- │ ├── globals.css # Global styles
98
- │ └── docs/ # Documentation pages
99
- ├── components/
100
- │ ├── Navbar.tsx # Navigation with theme toggle
101
- │ ├── Hero.tsx # Hero section
102
- │ ├── Features.tsx # Features grid
103
- │ ├── History.tsx # Workflow history section
104
- │ ├── Terminal.tsx # Animated terminal demo
105
- │ ├── Footer.tsx # Footer
106
- │ └── ThemeProvider.tsx # Theme context provider
107
- ├── public/ # Static assets
108
- ├── tailwind.config.ts # Tailwind configuration
109
- ├── next.config.ts # Next.js configuration
110
- └── package.json
111
- ```
112
-
113
- ## Technologies
114
-
115
- - **Framework**: Next.js 16 with Turbopack
116
- - **Styling**: Tailwind CSS 3
117
- - **Icons**: Lucide React
118
- - **Language**: TypeScript
119
- - **Deployment**: Netlify (static export)
120
-
121
- ## Browser Support
122
-
123
- - Chrome/Edge (latest)
124
- - Firefox (latest)
125
- - Safari (latest)
126
- - Mobile browsers (iOS Safari, Chrome Mobile)
127
-
128
- ## Performance
129
-
130
- - Static HTML export for instant page loads
131
- - Optimized images with Next.js Image component
132
- - Minimal JavaScript bundle
133
- - CSS-in-JS with Tailwind for efficient styling