@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
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
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",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
+ "engines": {
10
+ "node": ">=20"
11
+ },
9
12
  "bin": {
10
13
  "dev-workflow-mcp": "index.js"
11
14
  },
@@ -17,16 +20,18 @@
17
20
  "dev": "node --watch index.js",
18
21
  "start": "node index.js",
19
22
  "local": "node index.js",
23
+ "setup": "node scripts/setup-stack.js",
20
24
  "test": "node --no-deprecation --test",
21
25
  "check": "node --check index.js",
22
- "prepare": "husky",
26
+ "prepare": "node scripts/install-husky.js",
23
27
  "postinstall": "node install.js",
24
28
  "release": "node scripts/release.cjs",
25
29
  "release:major": "node scripts/release.cjs major",
26
30
  "release:minor": "node scripts/release.cjs minor",
27
31
  "release:patch": "node scripts/release.cjs patch",
28
32
  "web": "npm start --prefix web",
29
- "release:first": "node scripts/release.cjs --first-release"
33
+ "release:first": "node scripts/release.cjs --first-release",
34
+ "verify:bundle": "node scripts/verify-bundle.mjs"
30
35
  },
31
36
  "keywords": [
32
37
  "mcp",
@@ -42,34 +47,30 @@
42
47
  },
43
48
  "dependencies": {
44
49
  "@modelcontextprotocol/sdk": "^1.0.0",
45
- "bcrypt": "^5.1.1",
46
- "better-sqlite3": "^11.8.0",
47
- "connect-pg-simple": "^9.0.1",
48
- "dotenv": "^17.2.3",
49
- "esbuild": "^0.25.0",
50
- "express": "^4.18.2",
51
- "express-session": "^1.18.0",
52
- "get-port": "^7.1.0",
53
- "marked": "^12.0.2",
50
+ "better-sqlite3": "^12.11.1",
51
+ "dotenv": "^18.0.9",
54
52
  "mysql2": "^3.15.3",
55
- "pg": "^8.16.3",
56
- "vite": "^7.1.12",
57
- "ws": "^8.16.0"
53
+ "pg": "^8.23.1",
54
+ "vite": "^7.1.12"
58
55
  },
59
56
  "publishConfig": {
60
57
  "access": "public"
61
58
  },
59
+ "overrides": {
60
+ "tar": "7.5.22"
61
+ },
62
62
  "devDependencies": {
63
- "@commitlint/cli": "^20.2.0",
64
- "@commitlint/config-conventional": "^20.2.0",
63
+ "@commitlint/cli": "^21.2.4",
64
+ "@commitlint/config-conventional": "^21.2.4",
65
65
  "@programinglive/commiter": "^1.2.17",
66
- "autoprefixer": "^10.4.22",
66
+ "@types/node": "^26.6.5",
67
+ "autoprefixer": "^10.6.1",
67
68
  "cross-env": "^10.1.0",
68
69
  "husky": "^9.1.7",
69
70
  "postcss": "^8.5.6",
70
71
  "standard-version": "^9.5.0",
71
- "supertest": "^7.1.4",
72
- "tailwindcss": "^3.4.18"
72
+ "supertest": "^7.3.1",
73
+ "typescript": "^7.0.2"
73
74
  },
74
75
  "standard-version": {
75
76
  "releaseCommitMessageFormat": "chore(release): {{currentTag}} 🚀",
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # ==============================================================================
3
+ # Programinglive Homelab Bootstrap Script for macOS (Mac)
4
+ # Installs: RTK (Token Killer), Superpowers (Skills), and Dev-Workflow MCP
5
+ #
6
+ # This script is a thin wrapper. All OpenCode configuration logic lives in
7
+ # scripts/setup-stack.js so Windows and macOS cannot drift apart.
8
+ # ==============================================================================
9
+
10
+ set -euo pipefail
11
+
12
+ echo "🚀 Starting Homelab Unix/macOS Bootstrap on Mac..."
13
+
14
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
15
+ SETUP_SCRIPT="$REPO_ROOT/scripts/setup-stack.js"
16
+
17
+ if [ ! -f "$SETUP_SCRIPT" ]; then
18
+ echo "✗ Could not find $SETUP_SCRIPT" >&2
19
+ echo " Run this script from inside a clone of dev-workflow-mcp-server." >&2
20
+ exit 1
21
+ fi
22
+
23
+ # 1. Ensure Node is available (setup-stack.js is a Node script)
24
+ if ! command -v node > /dev/null 2>&1; then
25
+ echo "✗ Node.js not found on PATH. Install Node 20+ and re-run." >&2
26
+ exit 1
27
+ fi
28
+
29
+ # 2. Check / install RTK
30
+ if ! command -v rtk > /dev/null 2>&1; then
31
+ echo "⚡ Installing RTK via Homebrew..."
32
+ brew install rtk || curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
33
+ else
34
+ echo "✓ RTK is already installed."
35
+ fi
36
+
37
+ # 3. Install dependencies BEFORE the MCP server is registered, otherwise the
38
+ # server crashes at startup with ERR_MODULE_NOT_FOUND.
39
+ if [ ! -d "$REPO_ROOT/node_modules" ]; then
40
+ echo "📥 Installing dependencies..."
41
+ ( cd "$REPO_ROOT" && npm install --no-audit --no-fund )
42
+ else
43
+ echo "✓ Dependencies already installed."
44
+ fi
45
+
46
+ # 4. Configure the stack (config path, plugins, MCP server, RTK hook, verification)
47
+ echo ""
48
+ echo "⚙️ Configuring OpenCode..."
49
+ node "$SETUP_SCRIPT"
50
+
51
+ echo ""
52
+ echo "============================================================="
53
+ echo "🎉 MAC HOMELAB BOOTSTRAP COMPLETE!"
54
+ echo "============================================================="
55
+ echo "Restart OpenCode / terminal and verify with: rtk gain"
@@ -0,0 +1,75 @@
1
+ # ==============================================================================
2
+ # Programinglive Homelab Bootstrap Script for Windows (HELLOWORLD)
3
+ # Installs: RTK (Token Killer), Superpowers (Skills), and Dev-Workflow MCP
4
+ #
5
+ # This script is a thin wrapper. All OpenCode configuration logic lives in
6
+ # scripts/setup-stack.js so Windows and macOS cannot drift apart.
7
+ # ==============================================================================
8
+
9
+ $ErrorActionPreference = 'Stop'
10
+
11
+ Write-Host "🚀 Starting Homelab Windows Bootstrap on HELLOWORLD..." -ForegroundColor Cyan
12
+
13
+ $repoRoot = Split-Path -Parent $PSScriptRoot
14
+ $setupScript = Join-Path $repoRoot 'scripts\setup-stack.js'
15
+
16
+ if (!(Test-Path $setupScript)) {
17
+ Write-Host "✗ Could not find $setupScript" -ForegroundColor Red
18
+ Write-Host " Run this script from inside a clone of dev-workflow-mcp-server." -ForegroundColor Red
19
+ exit 1
20
+ }
21
+
22
+ # 1. Ensure Node is available (setup-stack.js is a Node script)
23
+ if (!(Get-Command node -ErrorAction SilentlyContinue)) {
24
+ Write-Host "✗ Node.js not found on PATH. Install Node 20+ and re-run." -ForegroundColor Red
25
+ exit 1
26
+ }
27
+
28
+ # 2. Check / install RTK
29
+ if (!(Get-Command rtk -ErrorAction SilentlyContinue)) {
30
+ Write-Host "⚡ Installing RTK via winget..." -ForegroundColor Yellow
31
+ try {
32
+ winget install rtk-ai.rtk --accept-source-agreements --accept-package-agreements
33
+ } catch {
34
+ Write-Host "⚠️ winget install failed. Install RTK manually, then re-run." -ForegroundColor Yellow
35
+ }
36
+
37
+ # winget updates PATH for new shells only; refresh this process so rtk resolves.
38
+ $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' +
39
+ [System.Environment]::GetEnvironmentVariable('Path', 'User')
40
+ } else {
41
+ Write-Host "✓ RTK is already installed." -ForegroundColor Green
42
+ }
43
+
44
+ # 3. Install dependencies BEFORE the MCP server is registered, otherwise the
45
+ # server crashes at startup with ERR_MODULE_NOT_FOUND.
46
+ Write-Host "📥 Installing dependencies..." -ForegroundColor Yellow
47
+ Push-Location $repoRoot
48
+ try {
49
+ if (!(Test-Path (Join-Path $repoRoot 'node_modules'))) {
50
+ npm install --no-audit --no-fund
51
+ if ($LASTEXITCODE -ne 0) {
52
+ Write-Host "✗ npm install failed. The MCP server cannot start without dependencies." -ForegroundColor Red
53
+ exit 1
54
+ }
55
+ } else {
56
+ Write-Host "✓ Dependencies already installed." -ForegroundColor Green
57
+ }
58
+ } finally {
59
+ Pop-Location
60
+ }
61
+
62
+ # 4. Configure the stack (config path, plugins, MCP server, RTK hook, verification)
63
+ Write-Host "`n⚙️ Configuring OpenCode..." -ForegroundColor Yellow
64
+ node $setupScript
65
+ $setupExit = $LASTEXITCODE
66
+
67
+ if ($setupExit -ne 0) {
68
+ Write-Host "`n✗ Setup did not complete successfully (exit $setupExit)." -ForegroundColor Red
69
+ exit $setupExit
70
+ }
71
+
72
+ Write-Host "`n=============================================================" -ForegroundColor Cyan
73
+ Write-Host "🎉 HELLOWORLD HOMELAB BOOTSTRAP COMPLETE!" -ForegroundColor Green
74
+ Write-Host "=============================================================" -ForegroundColor Cyan
75
+ Write-Host "Restart OpenCode / terminal and verify with: rtk gain" -ForegroundColor Yellow
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Installs husky git hooks when running inside a git checkout, and does
5
+ * nothing otherwise.
6
+ *
7
+ * `prepare` runs on every `npm install`, including when the package is
8
+ * installed from the registry as a dependency. In that case there is no .git
9
+ * directory, and `husky` exits non-zero, which fails the consumer's install.
10
+ * The `husky || true` idiom does not help on Windows, where npm runs scripts
11
+ * through cmd.exe.
12
+ */
13
+
14
+ import { existsSync } from 'node:fs';
15
+ import { execSync } from 'node:child_process';
16
+ import path from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
20
+
21
+ if (!existsSync(path.join(projectRoot, '.git'))) {
22
+ console.log('Not a git checkout - skipping husky install.');
23
+ process.exit(0);
24
+ }
25
+
26
+ try {
27
+ // Resolve the local binary rather than relying on PATH: npm scripts get
28
+ // node_modules/.bin on PATH, but a direct `node scripts/...` invocation does
29
+ // not, and on Windows `husky` is a .cmd shim.
30
+ const huskyBin = path.join(
31
+ projectRoot,
32
+ 'node_modules',
33
+ '.bin',
34
+ process.platform === 'win32' ? 'husky.cmd' : 'husky',
35
+ );
36
+ const command = existsSync(huskyBin) ? `"${huskyBin}"` : 'husky';
37
+ execSync(command, { cwd: projectRoot, stdio: 'inherit', shell: true });
38
+ } catch (error) {
39
+ // Never fail an install because git hooks could not be set up.
40
+ console.warn(`Could not install husky hooks: ${error.message}`);
41
+ }
42
+
43
+ process.exit(0);
@@ -0,0 +1,436 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Unified Setup Script for AI Agent Development Stack:
5
+ * 1. dev-workflow-mcp-server (Workflow discipline & state tracking)
6
+ * 2. obra/superpowers (Agentic skills framework: brainstorming, TDD, planning)
7
+ * 3. rtk (Rust Token Killer - command output compression)
8
+ *
9
+ * Works against OpenCode V2, which reads its config from the path reported by
10
+ * `opencode debug paths` (~/.config/opencode on every platform) and uses the
11
+ * `plugins` key plus `mcp.servers`, not the V1 `plugin` key and flat `mcp`.
12
+ */
13
+
14
+ import fs from 'fs/promises';
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
16
+ import path from 'path';
17
+ import { fileURLToPath } from 'url';
18
+ import { execSync, spawn } from 'child_process';
19
+ import os from 'os';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+ const projectRoot = path.resolve(__dirname, '..');
24
+
25
+ export const SUPERPOWERS_SPEC = 'superpowers@git+https://github.com/obra/superpowers.git';
26
+ export const MCP_SERVER_NAME = 'dev-workflow';
27
+ export const SCHEMA_URL = 'https://opencode.ai/config.json';
28
+
29
+ /**
30
+ * Parses the `config` line out of `opencode debug paths`.
31
+ * Returns null when the output is unusable, so callers can fall back.
32
+ */
33
+ export function parseConfigPathOutput(stdout) {
34
+ if (typeof stdout !== 'string' || stdout.trim() === '') return null;
35
+
36
+ for (const line of stdout.split(/\r?\n/)) {
37
+ const match = line.match(/^\s*config\s+(.+?)\s*$/);
38
+ if (match && match[1]) return match[1];
39
+ }
40
+
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Last-resort config location. OpenCode V2 resolves its config directory to
46
+ * ~/.config/opencode on Windows as well - APPDATA is not consulted.
47
+ */
48
+ export function fallbackConfigDir({ home = os.homedir(), platform = process.platform } = {}) {
49
+ // Use the target platform's separator, not the host's: a POSIX home must not
50
+ // turn into a backslash path when this helper is exercised on Windows.
51
+ const join = platform === 'win32' ? path.win32.join : path.posix.join;
52
+ return join(home, '.config', 'opencode');
53
+ }
54
+
55
+ /**
56
+ * Resolves the OpenCode config directory, preferring what OpenCode itself
57
+ * reports over any platform guess.
58
+ */
59
+ export function resolveConfigDir({ probe, home = os.homedir() } = {}) {
60
+ const probed = typeof probe === 'function' ? probe() : null;
61
+ if (probed) return probed;
62
+
63
+ return fallbackConfigDir({ home });
64
+ }
65
+
66
+ export function ensureSchema(config) {
67
+ if (!config.$schema) config.$schema = SCHEMA_URL;
68
+ return config;
69
+ }
70
+
71
+ /**
72
+ * Registers Superpowers under the V2 `plugins` key.
73
+ * The V1 `plugin` key is deprecated and ignored by OpenCode V2.
74
+ */
75
+ export function mergePluginEntry(config) {
76
+ if (!Array.isArray(config.plugins)) config.plugins = [];
77
+ if (!config.plugins.includes(SUPERPOWERS_SPEC)) config.plugins.push(SUPERPOWERS_SPEC);
78
+ return config;
79
+ }
80
+
81
+ /**
82
+ * Registers a local MCP server under `mcp.servers`, matching
83
+ * Mcp.LocalConfigEncoded. `enabled` is not part of that schema.
84
+ */
85
+ export function mergeMcpServer(config, { name = MCP_SERVER_NAME, command }) {
86
+ if (!config.mcp || typeof config.mcp !== 'object' || Array.isArray(config.mcp)) {
87
+ config.mcp = {};
88
+ }
89
+ if (!config.mcp.servers || typeof config.mcp.servers !== 'object') {
90
+ config.mcp.servers = {};
91
+ }
92
+
93
+ config.mcp.servers[name] = { type: 'local', command };
94
+ return config;
95
+ }
96
+
97
+ export function backupPathFor(configDir, filename, stamp) {
98
+ return path.join(configDir, 'secrets', `opencode-backup-${stamp}.json`);
99
+ }
100
+
101
+ function timestamp() {
102
+ const d = new Date();
103
+ const pad = (n) => String(n).padStart(2, '0');
104
+ return [
105
+ d.getFullYear(),
106
+ pad(d.getMonth() + 1),
107
+ pad(d.getDate()),
108
+ ].join('') + '-' + [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join('');
109
+ }
110
+
111
+ function run(command, options = {}) {
112
+ return execSync(command, { encoding: 'utf8', stdio: 'pipe', ...options });
113
+ }
114
+
115
+ function hasCommand(name) {
116
+ try {
117
+ execSync(process.platform === 'win32' ? `where ${name}` : `command -v ${name}`, {
118
+ stdio: 'ignore',
119
+ });
120
+ return true;
121
+ } catch {
122
+ return false;
123
+ }
124
+ }
125
+
126
+ function probeConfigDir() {
127
+ try {
128
+ return parseConfigPathOutput(run('opencode debug paths'));
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ function readConfig(configPath) {
135
+ if (!existsSync(configPath)) return ensureSchema({});
136
+ const raw = readFileSync(configPath, 'utf8').replace(/^\uFEFF/, '');
137
+ return ensureSchema(JSON.parse(raw));
138
+ }
139
+
140
+ function writeConfig(configPath, config) {
141
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
142
+ }
143
+
144
+ function backupConfig(configPath, configDir) {
145
+ if (!existsSync(configPath)) return null;
146
+
147
+ const dir = path.join(configDir, 'secrets');
148
+ mkdirSync(dir, { recursive: true });
149
+ const target = backupPathFor(configDir, path.basename(configPath), timestamp());
150
+ copyFileSync(configPath, target);
151
+ return target;
152
+ }
153
+
154
+ function ensureDependencies() {
155
+ const nodeModules = path.join(projectRoot, 'node_modules');
156
+ if (existsSync(nodeModules)) {
157
+ console.log('✓ Dependencies already installed.');
158
+ return true;
159
+ }
160
+
161
+ console.log('📥 Installing dependencies (required: the MCP server imports dotenv and others)...');
162
+ try {
163
+ execSync('npm install --no-audit --no-fund', { cwd: projectRoot, stdio: 'inherit' });
164
+ console.log('✓ Dependencies installed.');
165
+ return true;
166
+ } catch {
167
+ console.error('✗ npm install failed. The MCP server cannot start without dependencies.');
168
+ return false;
169
+ }
170
+ }
171
+
172
+ function smokeTestServer(timeoutMs = 4000) {
173
+ return new Promise((resolve) => {
174
+ const child = spawn(process.execPath, [path.join(projectRoot, 'index.js')], {
175
+ cwd: projectRoot,
176
+ // stdin must stay OPEN: a stdio MCP server treats stdin EOF as "shut down"
177
+ // and exits 0 immediately, which would look like a crash.
178
+ stdio: ['pipe', 'pipe', 'pipe'],
179
+ });
180
+
181
+ let stdout = '';
182
+ let stderr = '';
183
+ let settled = false;
184
+ let exitedEarly = false;
185
+ let exitCode = null;
186
+
187
+ const finish = (result) => {
188
+ if (settled) return;
189
+ settled = true;
190
+ clearTimeout(timer);
191
+ try {
192
+ child.kill();
193
+ } catch {
194
+ // already gone
195
+ }
196
+ resolve(result);
197
+ };
198
+
199
+ child.stdout.on('data', (chunk) => {
200
+ stdout += chunk.toString();
201
+ });
202
+ child.stderr.on('data', (chunk) => {
203
+ stderr += chunk.toString();
204
+ });
205
+
206
+ child.on('error', (error) => {
207
+ stderr += String(error.message || error);
208
+ exitedEarly = true;
209
+ exitCode = -1;
210
+ finish(smokeTestVerdict({ exitedEarly, exitCode, stdout, stderr }));
211
+ });
212
+ child.on('exit', (code) => {
213
+ if (settled) return;
214
+ exitedEarly = true;
215
+ exitCode = code;
216
+ finish(smokeTestVerdict({ exitedEarly, exitCode, stdout, stderr }));
217
+ });
218
+
219
+ const timer = setTimeout(
220
+ () => finish(smokeTestVerdict({ exitedEarly: false, exitCode: null, stdout, stderr })),
221
+ timeoutMs,
222
+ );
223
+ });
224
+ }
225
+
226
+ /**
227
+ * Decides whether a smoke-test run counts as healthy.
228
+ *
229
+ * A stdio MCP server exits cleanly when stdin closes, so "exited with 0" is not
230
+ * a failure on its own - only an early exit *without* the startup banner, or a
231
+ * non-zero exit, is. Exported so the rule is unit-testable without spawning.
232
+ */
233
+ export function smokeTestVerdict({ exitedEarly, exitCode, stdout = '', stderr = '' }) {
234
+ if (!exitedEarly) return { ok: true, reason: 'server stayed alive' };
235
+
236
+ const started = /running on stdio|MCP Server starting/i.test(`${stdout}${stderr}`);
237
+ if (started) return { ok: true, reason: 'server started then exited cleanly' };
238
+
239
+ return {
240
+ ok: false,
241
+ reason: `exited with code ${exitCode} before starting: ${stderr.trim().split('\n')[0] || 'no output'}`,
242
+ };
243
+ }
244
+
245
+ function verifyInstall(configPath) {
246
+ const results = [];
247
+
248
+ try {
249
+ const output = run('opencode mcp list');
250
+ const connected = /dev-workflow\s+connected/i.test(output) || /dev-workflow/.test(output);
251
+ results.push({ check: 'mcp dev-workflow registered', ok: connected, detail: output.trim() });
252
+ } catch (error) {
253
+ results.push({ check: 'mcp dev-workflow registered', ok: false, detail: error.message });
254
+ }
255
+
256
+ try {
257
+ const output = run('opencode plugin list');
258
+ const ok = /superpowers/.test(output);
259
+ results.push({ check: 'superpowers plugin loaded', ok, detail: output.trim() });
260
+ } catch (error) {
261
+ results.push({ check: 'superpowers plugin loaded', ok: false, detail: error.message });
262
+ }
263
+
264
+ try {
265
+ const config = JSON.parse(readFileSync(configPath, 'utf8').replace(/^\uFEFF/, ''));
266
+ const ok = Array.isArray(config.plugins) && config.plugins.includes(SUPERPOWERS_SPEC);
267
+ results.push({
268
+ check: 'opencode.json uses V2 plugins key',
269
+ ok,
270
+ detail: `plugins=${JSON.stringify(config.plugins ?? null)}`,
271
+ });
272
+ } catch (error) {
273
+ results.push({ check: 'opencode.json readable', ok: false, detail: error.message });
274
+ }
275
+
276
+ return results;
277
+ }
278
+
279
+ function ensureAgentsMd(targetProjectRoot) {
280
+ const agentsMdPath = path.join(targetProjectRoot, 'AGENTS.md');
281
+ if (existsSync(agentsMdPath)) {
282
+ console.log('✓ AGENTS.md already exists in project root.');
283
+ return;
284
+ }
285
+
286
+ const defaultAgentsMd = `# Dev Workflow Enforcement Rules
287
+
288
+ ## 🛑 MANDATORY MCP WORKFLOW DISCIPLINE
289
+
290
+ You are connected to the \`dev-workflow\` MCP server. **You MUST NOT perform any coding task, bug fix, or feature implementation without going through the strict workflow enforced by the dev-workflow MCP tools.**
291
+
292
+ ### The Rule of Operation
293
+ Before taking any action or answering user requests regarding code changes, check your workflow status and follow these exact steps in sequence:
294
+
295
+ 1. **Start Task**: Call \`start_task\` with a clear description and type (\`feature\`, \`bugfix\`, \`refactor\`, or \`other\`).
296
+ 2. **Implement**: Write your code changes.
297
+ 3. **Mark Fixed**: Call \`mark_bug_fixed\` with a summary of the implementation.
298
+ 4. **Create Tests**: Create appropriate unit/integration tests covering your changes.
299
+ 5. **Run Tests**: Execute your test suite and call \`run_tests\` with \`passed: true\` and the test command. **Never commit if tests fail!**
300
+ 6. **Document**: Update documentation and call \`create_documentation\`.
301
+ 7. **Ready Check**: Call \`check_ready_to_commit\` to verify all steps.
302
+ 8. **Commit & Push**: Call \`commit_and_push\` with a conventional commit message.
303
+ 9. **Release**: Call \`perform_release\` with the release command (or \`skip_release\` if docs-only).
304
+ 10. **Complete**: Call \`complete_task\` to finish the session and reset the workflow.
305
+
306
+ **Do not bypass these steps. The MCP server will block commits and releases if any required step is skipped.**
307
+ `;
308
+
309
+ try {
310
+ writeFileSync(agentsMdPath, defaultAgentsMd, 'utf8');
311
+ console.log(`✓ Created AGENTS.md rule enforcement at ${agentsMdPath}`);
312
+ } catch (error) {
313
+ console.warn('⚠️ Could not create AGENTS.md:', error.message);
314
+ }
315
+ }
316
+
317
+ export async function setupStack() {
318
+ console.log('🚀 Setting up Unified AI Agent Development Stack...\n');
319
+
320
+ const configDir = resolveConfigDir({ probe: probeConfigDir });
321
+ const opencodeJsonPath = path.join(configDir, 'opencode.json');
322
+ const serverIndexPath = path.join(projectRoot, 'index.js');
323
+
324
+ console.log(`✓ OpenCode config directory: ${configDir}`);
325
+
326
+ mkdirSync(path.join(configDir, 'plugins'), { recursive: true });
327
+
328
+ const backup = backupConfig(opencodeJsonPath, configDir);
329
+ if (backup) console.log(`✓ Backed up opencode.json → ${backup}`);
330
+
331
+ const hasOpencodeCli = hasCommand('opencode');
332
+ const command = ['node', serverIndexPath];
333
+ let usedCli = false;
334
+
335
+ if (hasOpencodeCli) {
336
+ try {
337
+ console.log('\n🔌 Registering Superpowers via OpenCode CLI...');
338
+ execSync(`opencode plugin add "${SUPERPOWERS_SPEC}"`, { stdio: 'inherit' });
339
+ console.log('✓ Superpowers plugin registered.');
340
+
341
+ console.log('\n🔌 Registering dev-workflow MCP server via OpenCode CLI...');
342
+ execSync(`opencode mcp add ${MCP_SERVER_NAME} --global -- node "${serverIndexPath}"`, {
343
+ stdio: 'inherit',
344
+ });
345
+ console.log('✓ dev-workflow MCP server registered.');
346
+ usedCli = true;
347
+ } catch (error) {
348
+ console.warn(`⚠️ OpenCode CLI registration failed (${error.message}). Falling back to direct config edit.`);
349
+ }
350
+ } else {
351
+ console.warn('⚠️ `opencode` CLI not found on PATH. Falling back to direct config edit.');
352
+ }
353
+
354
+ if (!usedCli) {
355
+ const config = readConfig(opencodeJsonPath);
356
+ mergePluginEntry(config);
357
+ mergeMcpServer(config, { name: MCP_SERVER_NAME, command });
358
+ writeConfig(opencodeJsonPath, config);
359
+ console.log(`✓ opencode.json updated directly at ${opencodeJsonPath}`);
360
+ }
361
+
362
+ // Whatever path we took, confirm the on-disk config is V2-shaped.
363
+ const finalConfig = readConfig(opencodeJsonPath);
364
+ mergePluginEntry(finalConfig);
365
+ mergeMcpServer(finalConfig, { name: MCP_SERVER_NAME, command });
366
+ writeConfig(opencodeJsonPath, finalConfig);
367
+ console.log('✓ opencode.json verified (V2 `plugins` + `mcp.servers`).');
368
+
369
+ console.log('\n⚡ Checking RTK (Rust Token Killer)...');
370
+ if (hasCommand('rtk')) {
371
+ try {
372
+ const version = run('rtk --version').trim();
373
+ console.log(`✓ Found ${version}`);
374
+ execSync('rtk init -g --opencode --auto-patch --no-trust-filters', { stdio: 'inherit' });
375
+ console.log('✓ RTK OpenCode plugin registered.');
376
+ } catch {
377
+ console.warn('⚠️ Could not run `rtk init -g --opencode`. Run it manually.');
378
+ }
379
+ } else {
380
+ console.warn('⚠️ `rtk` not found on PATH. Install it, then re-run this script:');
381
+ console.log(process.platform === 'win32' ? ' winget install rtk-ai.rtk' : ' brew install rtk');
382
+ }
383
+
384
+ const depsOk = ensureDependencies();
385
+
386
+ console.log('\n🔎 Smoke testing the MCP server...');
387
+ const smoke = depsOk
388
+ ? await smokeTestServer()
389
+ : { ok: false, reason: 'skipped: dependencies missing', stderr: '' };
390
+ if (smoke.ok) {
391
+ console.log(`✓ MCP server healthy (${smoke.reason}).`);
392
+ } else {
393
+ console.error(`✗ MCP server failed to start (${smoke.reason}).`);
394
+ if (smoke.stderr) console.error(smoke.stderr.trim());
395
+ }
396
+
397
+ ensureAgentsMd(process.env.INIT_CWD || process.cwd());
398
+
399
+ console.log('\n🧪 Verifying installation...');
400
+ const checks = verifyInstall(opencodeJsonPath);
401
+ let allOk = smoke.ok;
402
+
403
+ for (const check of checks) {
404
+ console.log(` ${check.ok ? '✓' : '✗'} ${check.check}`);
405
+ if (!check.ok) {
406
+ allOk = false;
407
+ if (check.detail) console.log(` ${check.detail.split('\n')[0]}`);
408
+ }
409
+ }
410
+
411
+ console.log('\n============================================================');
412
+ if (allOk) {
413
+ console.log('🎉 UNIFIED AI AGENT DEVELOPMENT STACK INSTALLED SUCCESSFULLY!');
414
+ console.log('============================================================');
415
+ console.log('• Workflow Discipline (dev-workflow MCP): Active');
416
+ console.log('• Agentic Skills (Superpowers): Registered');
417
+ console.log('• Token Compression (RTK): Configured');
418
+ console.log('\nRestart OpenCode to start developing with superpowers! 🚀');
419
+ } else {
420
+ console.log('⚠️ SETUP INCOMPLETE - see the failed checks above.');
421
+ console.log('============================================================');
422
+ }
423
+
424
+ return allOk;
425
+ }
426
+
427
+ const isDirectRun = process.argv[1] && path.resolve(process.argv[1]) === __filename;
428
+
429
+ if (isDirectRun) {
430
+ setupStack()
431
+ .then((ok) => process.exit(ok ? 0 : 1))
432
+ .catch((error) => {
433
+ console.error('Setup encountered an error:', error);
434
+ process.exit(1);
435
+ });
436
+ }