@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 +5 -2
- package/bin/cli.js +1 -1
- package/bin/lib/init.js +5 -3
- package/bin/lib/settings.js +58 -4
- package/bin/lib/utils.js +16 -9
- package/package.json +6 -14
- package/bin/lib/test-local.js +0 -121
package/CLAUDE.md
CHANGED
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
## MCP Server Integration
|
|
70
70
|
|
|
71
|
-
This project uses Prevention as
|
|
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('
|
|
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
|
|
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, '
|
|
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); }
|
package/bin/lib/settings.js
CHANGED
|
@@ -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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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
|
|
45
|
-
"build:binary": "
|
|
46
|
-
"build:all": "
|
|
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"
|
package/bin/lib/test-local.js
DELETED
|
@@ -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 };
|