@avesta-hq/prevention 0.5.0 → 0.6.0-pre.10

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/CLAUDE.md CHANGED
@@ -68,7 +68,7 @@
68
68
 
69
69
  ## MCP Server Integration
70
70
 
71
- This project uses Prevention as an MCP server with 15 tools, 26 agents, and 34 skills that provide all methodology knowledge (TDD, Clean Architecture, test pyramid, CI/CD, etc.) on demand.
71
+ This project uses Prevention as two MCP servers: a local server (12 tools for workflow orchestration) and a remote server (3 tools for content delivery skills, prompts, catalog).
72
72
 
73
73
  **When starting work:**
74
74
 
@@ -76,10 +76,13 @@ This project uses Prevention as an MCP server with 15 tools, 26 agents, and 34 s
76
76
  2. The workflow tracks your phase (vision → plan → atdd → tdd → review → ship)
77
77
  3. Gates enforce that prerequisites are met before advancing
78
78
 
79
- **Key tools:**
79
+ **Key tools (local):**
80
80
 
81
81
  - `avesta_get_status` — Current phase, gates, next action
82
82
  - `avesta_dispatch` — Route tasks to the right workflow step
83
+
84
+ **Key tools (remote — `prevention-content`):**
85
+
83
86
  - `avesta_get_catalog` — All available commands and skills
84
87
  - `avesta_get_skill` — Deep knowledge on any practice (testing, architecture, CI/CD)
85
88
 
package/bin/cli.js CHANGED
@@ -47,7 +47,7 @@ switch (command) {
47
47
  require('./lib/init').serve();
48
48
  break;
49
49
  case 'test-local':
50
- require('./lib/test-local').testLocal(args[1]);
50
+ require('../scripts/lib/test-local').testLocal(args[1]);
51
51
  break;
52
52
  case 'session-start':
53
53
  require('./lib/session-start').sessionStart();
package/bin/lib/init.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { COMMANDS_DIR, AGENTS_DIR, AVESTA_DIR, GITHUB_RELEASES_REPO, copyDir, downloadBinary, getVersion } = require('./utils');
4
- const { configureMcpServer, configureSessionStartHook, configureEnforcementHooks, configureStatusLine, ensureClaudeMdSection } = require('./settings');
4
+ const { configureMcpServer, configureSessionStartHook, configureEnforcementHooks, configurePermissions, configureStatusLine, ensureClaudeMdSection } = require('./settings');
5
5
 
6
6
  function getInstalledVersion(targetDir) {
7
7
  try {
@@ -49,6 +49,7 @@ function init() {
49
49
  configureMcpServer(targetDir, binaryPath);
50
50
  configureSessionStartHook(targetDir);
51
51
  configureEnforcementHooks(targetDir);
52
+ configurePermissions(targetDir);
52
53
  configureStatusLine(targetDir);
53
54
  ensureClaudeMdSection(targetDir);
54
55
 
@@ -110,8 +111,9 @@ function update() {
110
111
  process.exit(1);
111
112
  }
112
113
 
113
- // Reconfigure hooks (picks up any new hook patterns)
114
+ // Reconfigure hooks and permissions (picks up any new patterns)
114
115
  configureEnforcementHooks(targetDir);
116
+ configurePermissions(targetDir);
115
117
 
116
118
  writeInstalledVersion(targetDir);
117
119
 
@@ -137,7 +139,7 @@ function serve() {
137
139
  return;
138
140
  }
139
141
 
140
- const mcpServerPath = path.join(PACKAGE_ROOT, 'src', 'mcp-server.ts');
142
+ const mcpServerPath = path.join(PACKAGE_ROOT, 'package', 'mcp-server.ts');
141
143
  if (fs.existsSync(mcpServerPath)) {
142
144
  const { execFileSync } = require('child_process');
143
145
  try { execFileSync('npx', ['tsx', mcpServerPath], { stdio: 'inherit' }); } catch (e) { process.exit(e.status || 1); }
@@ -122,6 +122,19 @@ function configureEnforcementHooks(targetDir) {
122
122
  console.log(' ✓ Configured workflow enforcement hooks (PreToolUse, UserPromptSubmit, SubagentStart)');
123
123
  }
124
124
 
125
+ function configurePermissions(targetDir) {
126
+ const settings = readSettings(targetDir);
127
+ if (!settings.permissions) settings.permissions = {};
128
+ if (!settings.permissions.allow) settings.permissions.allow = [];
129
+
130
+ const rule = 'mcp__prevention__*';
131
+ if (!settings.permissions.allow.includes(rule)) {
132
+ settings.permissions.allow.push(rule);
133
+ }
134
+
135
+ writeSettings(targetDir, settings);
136
+ }
137
+
125
138
  function configureStatusLine(targetDir) {
126
139
  const statusLineSrc = path.join(targetDir, '.avesta', 'statusLine.js');
127
140
  if (!fs.existsSync(statusLineSrc)) return;
@@ -131,7 +144,7 @@ function configureStatusLine(targetDir) {
131
144
  writeSettings(targetDir, settings);
132
145
  }
133
146
 
134
- function configureMcpServer(targetDir, binaryPath) {
147
+ function configureMcpServer(targetDir, binaryPath, options = {}) {
135
148
  const mcpJsonPath = path.join(targetDir, '.mcp.json');
136
149
  let mcpConfig = {};
137
150
  if (fs.existsSync(mcpJsonPath)) {
@@ -140,11 +153,51 @@ function configureMcpServer(targetDir, binaryPath) {
140
153
 
141
154
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
142
155
 
143
- if (binaryPath) {
144
- mcpConfig.mcpServers['prevention'] = { command: path.resolve(binaryPath) };
156
+ // Local MCP server: workflow orchestration tools
157
+ // Pass through API/MCP URLs so the server process can reach them
158
+ const env = {};
159
+ if (process.env.AVESTA_API_URL) env.AVESTA_API_URL = process.env.AVESTA_API_URL;
160
+ if (process.env.AVESTA_MCP_URL) env.AVESTA_MCP_URL = process.env.AVESTA_MCP_URL;
161
+ const hasEnv = Object.keys(env).length > 0;
162
+
163
+ if (options.localSource) {
164
+ // Dev mode: run from source via tsx
165
+ const entry = { command: 'npx', args: ['tsx', options.localSource] };
166
+ if (hasEnv) entry.env = env;
167
+ mcpConfig.mcpServers['prevention'] = entry;
168
+ } else if (binaryPath) {
169
+ const entry = { command: path.resolve(binaryPath) };
170
+ if (hasEnv) entry.env = env;
171
+ mcpConfig.mcpServers['prevention'] = entry;
145
172
  } else {
146
- mcpConfig.mcpServers['prevention'] = { command: 'npx', args: ['@avesta-hq/prevention', 'serve'] };
173
+ const entry = { command: 'npx', args: ['@avesta-hq/prevention', 'serve'] };
174
+ if (hasEnv) entry.env = env;
175
+ mcpConfig.mcpServers['prevention'] = entry;
176
+ }
177
+
178
+ // Remote MCP server: content delivery (skills, prompts, catalog)
179
+ // Pre-populate auth token from existing session if available
180
+ const remoteUrl = process.env.AVESTA_MCP_URL || 'https://prevention-production.up.railway.app/mcp';
181
+ const existingRemote = mcpConfig.mcpServers['prevention-content'];
182
+ const existingToken = existingRemote?.headers?.Authorization;
183
+ let authToken = existingToken || '';
184
+ if (!authToken) {
185
+ try {
186
+ const sessionPath = path.join(require('os').homedir(), '.avesta', 'session.json');
187
+ if (fs.existsSync(sessionPath)) {
188
+ const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
189
+ if (session.access_token) {
190
+ authToken = `Bearer ${session.access_token}`;
191
+ }
192
+ }
193
+ } catch {}
147
194
  }
195
+ const headers = authToken ? { Authorization: authToken } : {};
196
+ mcpConfig.mcpServers['prevention-content'] = {
197
+ type: 'http',
198
+ url: remoteUrl,
199
+ headers,
200
+ };
148
201
 
149
202
  fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
150
203
  }
@@ -177,6 +230,7 @@ function ensureClaudeMdSection(targetDir) {
177
230
  module.exports = {
178
231
  configureSessionStartHook,
179
232
  configureEnforcementHooks,
233
+ configurePermissions,
180
234
  configureStatusLine,
181
235
  configureMcpServer,
182
236
  ensureClaudeMdSection,
package/bin/lib/utils.js CHANGED
@@ -70,16 +70,23 @@ function getPlatformBinaryName() {
70
70
  }
71
71
 
72
72
  function getLatestVersion() {
73
- try {
74
- const result = execSync(
75
- `curl -sI "https://github.com/${GITHUB_RELEASES_REPO}/releases/latest" | grep -i ^location:`,
76
- { encoding: 'utf8', timeout: 10000 }
77
- );
78
- const match = result.match(/\/tag\/(v[^\s\r\n]+)/);
79
- return match ? match[1] : null;
80
- } catch {
81
- return null;
73
+ // Use package version to match the corresponding GitHub Release
74
+ const pkgVersion = `v${getVersion()}`;
75
+
76
+ // For stable releases, verify the release exists via GitHub latest redirect
77
+ // (handles partial release failures where npm published but binary upload failed)
78
+ if (!pkgVersion.includes('-pre')) {
79
+ try {
80
+ const result = execSync(
81
+ `curl -sI "https://github.com/${GITHUB_RELEASES_REPO}/releases/latest" | grep -i ^location:`,
82
+ { encoding: 'utf8', timeout: 10000 }
83
+ );
84
+ const match = result.match(/\/tag\/(v[^\s\r\n]+)/);
85
+ if (match) return match[1];
86
+ } catch {}
82
87
  }
88
+
89
+ return pkgVersion;
83
90
  }
84
91
 
85
92
  function downloadBinary(targetDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avesta-hq/prevention",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-pre.10",
4
4
  "description": "XP/CD development agent commands for Claude Code - achieve Elite DORA metrics through disciplined TDD and Continuous Delivery practices",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -16,14 +16,7 @@
16
16
  ],
17
17
  "author": "Avesta Technologies",
18
18
  "license": "UNLICENSED",
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/avesta-hq/prevention.git"
22
- },
23
- "homepage": "https://github.com/avesta-hq/prevention#readme",
24
- "bugs": {
25
- "url": "https://github.com/avesta-hq/prevention/issues"
26
- },
19
+ "homepage": "https://avestahq.com/prevention",
27
20
  "bin": {
28
21
  "prevention": "./bin/cli.js"
29
22
  },
@@ -37,13 +30,12 @@
37
30
  ],
38
31
  "scripts": {
39
32
  "build": "tsc",
40
- "dev": "tsx src/mcp-server.ts",
33
+ "dev": "tsx package/mcp-server.ts",
41
34
  "test": "jest",
42
- "test:evals": "cd src/_deprecated/evals && python -m pytest test_cases/orchestrator/ -v",
43
35
  "generate:catalog": "tsx scripts/generate-catalog-data.ts",
44
- "prebuild:assets": "tsx scripts/generate-catalog-data.ts && tsx scripts/embed-assets.ts",
45
- "build:binary": "npm run prebuild:assets && bun build src/mcp-server.ts --compile --outfile dist/prevention",
46
- "build:all": "npm run prebuild:assets && bun build src/mcp-server.ts --compile --target=bun-linux-x64 --outfile dist/prevention-linux-x64 && bun build src/mcp-server.ts --compile --target=bun-darwin-arm64 --outfile dist/prevention-darwin-arm64 && bun build src/mcp-server.ts --compile --target=bun-darwin-x64 --outfile dist/prevention-darwin-x64"
36
+ "prebuild": "tsx scripts/generate-catalog-data.ts",
37
+ "build:binary": "pnpm prebuild && bun build package/mcp-server.ts --compile --outfile dist/prevention",
38
+ "build:all": "pnpm prebuild && bun build package/mcp-server.ts --compile --target=bun-linux-x64 --outfile dist/prevention-linux-x64 && bun build package/mcp-server.ts --compile --target=bun-darwin-arm64 --outfile dist/prevention-darwin-arm64 && bun build package/mcp-server.ts --compile --target=bun-darwin-x64 --outfile dist/prevention-darwin-x64"
47
39
  },
48
40
  "engines": {
49
41
  "node": ">=18.0.0"
@@ -1,121 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Local Testing Setup
4
- *
5
- * Sets up a target project to use the local Prevention source code
6
- * instead of the published npm package. Copies agents, commands,
7
- * configures hooks, and points MCP server at local source.
8
- *
9
- * Usage:
10
- * cd ~/Projects/my-test-project
11
- * node ~/Projects/cd-agent/bin/lib/test-local.js
12
- *
13
- * Or via CLI:
14
- * node ~/Projects/cd-agent/bin/cli.js test-local [target-dir]
15
- */
16
-
17
- const fs = require('fs');
18
- const path = require('path');
19
- const { execSync } = require('child_process');
20
- const { PACKAGE_ROOT, COMMANDS_DIR, AGENTS_DIR, AVESTA_DIR, copyDir, getVersion } = require('./utils');
21
- const {
22
- configureSessionStartHook,
23
- configureEnforcementHooks,
24
- configureStatusLine,
25
- ensureClaudeMdSection,
26
- } = require('./settings');
27
-
28
- function testLocal(targetDir) {
29
- targetDir = targetDir || process.cwd();
30
- const cdAgentRoot = PACKAGE_ROOT;
31
-
32
- console.log(`\n Setting up local Prevention test environment...`);
33
- console.log(` Source: ${cdAgentRoot}`);
34
- console.log(` Target: ${targetDir}\n`);
35
-
36
- // Step 1: Rebuild embedded assets
37
- console.log(' [1/5] Rebuilding catalog + embedded assets...');
38
- try {
39
- execSync('pnpm run prebuild:assets', { cwd: cdAgentRoot, stdio: 'pipe' });
40
- console.log(' ✓ Assets rebuilt');
41
- } catch (e) {
42
- console.error(' ✗ Asset rebuild failed:', e.message);
43
- console.error(' Try running manually: cd ' + cdAgentRoot + ' && pnpm run prebuild:assets');
44
- process.exit(1);
45
- }
46
-
47
- // Step 2: Copy agents + commands
48
- console.log(' [2/5] Copying agents and commands...');
49
- const claudeDir = path.join(targetDir, '.claude');
50
- if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
51
-
52
- if (fs.existsSync(COMMANDS_DIR)) {
53
- copyDir(COMMANDS_DIR, path.join(claudeDir, 'commands'));
54
- }
55
- if (fs.existsSync(AGENTS_DIR)) {
56
- copyDir(AGENTS_DIR, path.join(claudeDir, 'agents'));
57
- }
58
- if (fs.existsSync(AVESTA_DIR)) {
59
- copyDir(AVESTA_DIR, path.join(targetDir, '.avesta'));
60
- }
61
- const commandCount = fs.existsSync(COMMANDS_DIR) ? fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md')).length : 0;
62
- const agentCount = fs.existsSync(AGENTS_DIR) ? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md')).length : 0;
63
- console.log(` ✓ Copied ${commandCount} commands, ${agentCount} agents`);
64
-
65
- // Step 3: Configure MCP server to use local source via tsx
66
- console.log(' [3/5] Configuring MCP server (local source)...');
67
- const mcpJsonPath = path.join(targetDir, '.mcp.json');
68
- let mcpConfig = {};
69
- if (fs.existsSync(mcpJsonPath)) {
70
- try { mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8')); } catch {}
71
- }
72
- if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
73
-
74
- const mcpServerPath = path.join(cdAgentRoot, 'src', 'mcp-server.ts');
75
- mcpConfig.mcpServers['prevention'] = {
76
- command: 'npx',
77
- args: ['tsx', mcpServerPath],
78
- };
79
- fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
80
- console.log(` ✓ MCP server → npx tsx ${mcpServerPath}`);
81
-
82
- // Step 4: Configure hooks (pointing at local CLI)
83
- console.log(' [4/5] Configuring hooks...');
84
- configureSessionStartHook(targetDir);
85
- configureEnforcementHooks(targetDir);
86
- configureStatusLine(targetDir);
87
- ensureClaudeMdSection(targetDir);
88
-
89
- // Step 5: Show result
90
- console.log(' [5/5] Verifying setup...');
91
- const settings = JSON.parse(fs.readFileSync(path.join(claudeDir, 'settings.json'), 'utf8'));
92
- const hookTypes = Object.keys(settings.hooks || {});
93
- console.log(` ✓ Hooks configured: ${hookTypes.join(', ')}`);
94
-
95
- const mcpFinal = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
96
- const mcpCmd = mcpFinal.mcpServers?.prevention?.command || '?';
97
- const mcpArgs = (mcpFinal.mcpServers?.prevention?.args || []).join(' ');
98
- console.log(` ✓ MCP server: ${mcpCmd} ${mcpArgs}`);
99
-
100
- console.log(`
101
- ✅ Local test environment ready! (v${getVersion()})
102
-
103
- Changes from source at ${cdAgentRoot} are active.
104
- No npm publish needed — MCP server runs from source via tsx.
105
-
106
- To test:
107
- 1. Open Claude Code in ${targetDir}
108
- 2. Run /avesta-init backend (or frontend)
109
- 3. Test skill enforcement: /avesta-red should block writes until skills loaded
110
- 4. Test gate enforcement: git commit should be blocked without review gate
111
-
112
- To revert to published version:
113
- node ${path.join(cdAgentRoot, 'bin', 'cli.js')} update
114
- `);
115
- }
116
-
117
- if (require.main === module) {
118
- testLocal(process.argv[2]);
119
- }
120
-
121
- module.exports = { testLocal };