@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.
- package/.env.example +21 -1
- package/AGENTS.md +25 -0
- package/README.md +372 -649
- package/factory-client.js +554 -0
- package/git-facade.js +35 -0
- package/index.js +9 -34
- package/package.json +21 -20
- package/scripts/bootstrap-unix.sh +55 -0
- package/scripts/bootstrap-windows.ps1 +75 -0
- package/scripts/install-husky.js +43 -0
- package/scripts/setup-stack.js +436 -0
- package/scripts/setup-test-dbs.sh +61 -0
- package/scripts/test-db-adapters.sh +71 -0
- package/scripts/verify-bundle.mjs +92 -0
- package/tools/definitions.js +143 -0
- package/tools/handler-modules/commit-handlers.js +2 -1
- package/tools/handler-modules/factory-handlers.js +276 -0
- package/tools/handler-modules/index.js +24 -7
- package/tools/handler-modules/shared.js +7 -41
- package/tools/handler-modules/status-handlers.js +25 -3
- package/tools/handler-modules/workflow-handlers.js +57 -10
- package/vite.config.js +6 -0
- package/workflow-state.js +176 -2
- package/.agent/workflows/dev-workflow-bugfix.md +0 -250
- package/.agent/workflows/dev-workflow-feature.md +0 -201
- package/.agent/workflows/dev-workflow-full-cycle.md +0 -400
- package/.agent/workflows/dev-workflow-refactor.md +0 -238
- package/.env.production +0 -16
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/github-release.yml +0 -131
- package/.husky/commit-msg +0 -3
- package/.husky/pre-commit +0 -2
- package/CHANGELOG.md +0 -1145
- package/docs/GCP_DEPLOYMENT.md +0 -298
- package/docs/NETLIFY_DEPLOYMENT.md +0 -125
- package/docs/PLESK_DEPLOYMENT.md +0 -170
- package/docs/PRD.md +0 -86
- package/docs/WORKFLOW-BEST-PRACTICES.md +0 -58
- package/docs/antigravity/EXAMPLES.md +0 -535
- package/docs/antigravity/GETTING-STARTED.md +0 -292
- package/docs/antigravity/TROUBLESHOOTING.md +0 -742
- package/docs/antigravity/WALKTHROUGH.md +0 -25
- package/docs/antigravity/WORKFLOW-TEMPLATES.md +0 -670
- package/docs/release-notes/RELEASE_NOTES.md +0 -524
- package/docs/web-dashboard.md +0 -175
- package/scripts/test-auth-flow.js +0 -75
- package/tailwind.config.js +0 -8
- package/tests/build.test.js +0 -74
- package/tests/db-adapters.test.js +0 -108
- package/tests/env-support.test.js +0 -46
- package/tests/exec.test.js +0 -35
- package/tests/handlers-commit.test.js +0 -174
- package/tests/handlers-release.test.js +0 -212
- package/tests/handlers-workflow.test.js +0 -68
- package/tests/handlers.test.js +0 -1142
- package/tests/hello-world.test.js +0 -13
- package/tests/mcpRegistry.test.js +0 -65
- package/tests/patch-release.test.js +0 -7
- package/tests/release-wrapper.test.js +0 -107
- package/tests/startup-resilience.test.js +0 -73
- package/tests/test-helpers.js +0 -50
- package/tests/workflowState.test.js +0 -520
- package/web/Dockerfile.api +0 -25
- package/web/README.md +0 -133
- package/web/__tests__/api/workflow-history.test.js +0 -63
- package/web/api-server.js +0 -211
- package/web/app/docs/page.tsx +0 -202
- package/web/app/favicon.ico +0 -0
- package/web/app/globals.css +0 -91
- package/web/app/history/page.tsx +0 -220
- package/web/app/layout.tsx +0 -39
- package/web/app/login/page.tsx +0 -142
- package/web/app/page.tsx +0 -13
- package/web/components/ApiReference.tsx +0 -20
- package/web/components/Features.tsx +0 -61
- package/web/components/Footer.tsx +0 -9
- package/web/components/Hero.tsx +0 -96
- package/web/components/History.tsx +0 -175
- package/web/components/Navbar.tsx +0 -208
- package/web/components/Terminal.tsx +0 -114
- package/web/components/ThemeProvider.tsx +0 -58
- package/web/eslint.config.mjs +0 -18
- package/web/lib/api-client.ts +0 -123
- package/web/lib/auth.js +0 -75
- package/web/lib/db.js +0 -131
- package/web/lib/docs.js +0 -73
- package/web/middleware/auth.js +0 -26
- package/web/netlify.toml +0 -8
- package/web/next.config.ts +0 -19
- package/web/package-lock.json +0 -10911
- package/web/package.json +0 -42
- package/web/postcss.config.mjs +0 -9
- package/web/public/assets/android-chrome-192x192.png +0 -0
- package/web/public/assets/android-chrome-512x512.png +0 -0
- package/web/public/assets/apple-touch-icon.png +0 -0
- package/web/public/assets/favicon-16x16.png +0 -0
- package/web/public/assets/favicon-32x32.png +0 -0
- package/web/public/assets/favicon.ico +0 -0
- package/web/public/assets/favicon.png +0 -0
- package/web/public/assets/logo.png +0 -0
- package/web/public/assets/og-preview.png +0 -0
- package/web/public/assets/site.webmanifest +0 -1
- package/web/public/file.svg +0 -1
- package/web/public/globe.svg +0 -1
- package/web/public/next.svg +0 -1
- package/web/public/vercel.svg +0 -1
- package/web/public/window.svg +0 -1
- package/web/scripts/db-query-history.js +0 -120
- package/web/scripts/smoke-test.js +0 -30
- package/web/server.js +0 -184
- package/web/tailwind.config.ts +0 -20
- 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.
|
|
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
|
-
"
|
|
46
|
-
"
|
|
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.
|
|
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": "^
|
|
64
|
-
"@commitlint/config-conventional": "^
|
|
63
|
+
"@commitlint/cli": "^21.2.4",
|
|
64
|
+
"@commitlint/config-conventional": "^21.2.4",
|
|
65
65
|
"@programinglive/commiter": "^1.2.17",
|
|
66
|
-
"
|
|
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
|
|
72
|
-
"
|
|
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
|
+
}
|